Merge "Added highlighted background support" into main
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/OWNERS b/OWNERS
index eb2bfcf..d0a634e 100644
--- a/OWNERS
+++ b/OWNERS
@@ -47,3 +47,4 @@
 per-file BROADCASTS_OWNERS = file:/BROADCASTS_OWNERS
 per-file ADPF_OWNERS = file:/ADPF_OWNERS
 per-file GAME_MANAGER_OWNERS = file:/GAME_MANAGER_OWNERS
+per-file SDK_OWNERS = file:/SDK_OWNERS
diff --git a/SDK_OWNERS b/SDK_OWNERS
new file mode 100644
index 0000000..c9ca47a
--- /dev/null
+++ b/SDK_OWNERS
@@ -0,0 +1,6 @@
+amhk@google.com
+kimalexander@google.com
+lus@google.com
+michaelwr@google.com
+nanaasiedu@google.com
+paulduffin@google.com
diff --git a/android-sdk-flags/OWNERS b/android-sdk-flags/OWNERS
new file mode 100644
index 0000000..01f45dd
--- /dev/null
+++ b/android-sdk-flags/OWNERS
@@ -0,0 +1 @@
+include /SDK_OWNERS
diff --git a/android-sdk-flags/flags.aconfig b/android-sdk-flags/flags.aconfig
index cfe298e..19c7bf6 100644
--- a/android-sdk-flags/flags.aconfig
+++ b/android-sdk-flags/flags.aconfig
@@ -6,6 +6,7 @@
     namespace: "android_sdk"
     description: "Use the new SDK major.minor versioning scheme (e.g. Android 40.1) which replaces the old single-integer scheme (e.g. Android 15)."
     bug: "350458259"
+    is_exported: true
 
     # Use is_fixed_read_only because DeviceConfig may not be available when Build.VERSION_CODES is first accessed
     is_fixed_read_only: true
diff --git a/apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java b/apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java
index 8d2d044..8160ca2 100644
--- a/apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java
@@ -159,9 +159,7 @@
         LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         long time = 1000;
-        LongArrayMultiStateCounter.LongArrayContainer timeInFreq =
-                new LongArrayMultiStateCounter.LongArrayContainer(4);
-        timeInFreq.setValues(new long[]{100, 200, 300, 400});
+        long[] timeInFreq = {100, 200, 300, 400};
         while (state.keepRunning()) {
             counter.setState(1, time);
             counter.setState(0, time + 1000);
diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig
index debd850..47a85498f 100644
--- a/apex/jobscheduler/framework/aconfig/job.aconfig
+++ b/apex/jobscheduler/framework/aconfig/job.aconfig
@@ -37,3 +37,19 @@
     description: "Ignore the important_while_foreground flag and change the related APIs to be not effective"
     bug: "374175032"
 }
+
+flag {
+    name: "get_pending_job_reasons_api"
+    is_exported: true
+    namespace: "backstage_power"
+    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/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 790fc6b..909a9b3 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -618,6 +618,26 @@
         return mRunningJob;
     }
 
+    @VisibleForTesting
+    void setRunningJobLockedForTest(JobStatus job) {
+        mRunningJob = job;
+    }
+
+    @VisibleForTesting
+    void setJobParamsLockedForTest(JobParameters params) {
+        mParams = params;
+    }
+
+    @VisibleForTesting
+    void setRunningCallbackLockedForTest(JobCallback callback) {
+        mRunningCallback = callback;
+    }
+
+    @VisibleForTesting
+    void setPendingStopReasonLockedForTest(int stopReason) {
+        mPendingStopReason = stopReason;
+    }
+
     @JobConcurrencyManager.WorkType
     int getRunningJobWorkType() {
         return mRunningJobWorkType;
@@ -786,6 +806,7 @@
                 executing = getRunningJobLocked();
             }
             if (executing != null && jobId == executing.getJobId()) {
+                executing.setAbandoned(true);
                 final StringBuilder stateSuffix = new StringBuilder();
                 stateSuffix.append(TRACE_ABANDONED_JOB);
                 stateSuffix.append(executing.getBatteryName());
@@ -1364,8 +1385,9 @@
     }
 
     /** Process MSG_TIMEOUT here. */
+    @VisibleForTesting
     @GuardedBy("mLock")
-    private void handleOpTimeoutLocked() {
+    void handleOpTimeoutLocked() {
         switch (mVerb) {
             case VERB_BINDING:
                 // The system may have been too busy. Don't drop the job or trigger an ANR.
@@ -1427,9 +1449,25 @@
                     // Not an error - client ran out of time.
                     Slog.i(TAG, "Client timed out while executing (no jobFinished received)."
                             + " Sending onStop: " + getRunningJobNameLocked());
-                    mParams.setStopReason(JobParameters.STOP_REASON_TIMEOUT,
-                            JobParameters.INTERNAL_STOP_REASON_TIMEOUT, "client timed out");
-                    sendStopMessageLocked("timeout while executing");
+
+                    final JobStatus executing = getRunningJobLocked();
+                    int stopReason = JobParameters.STOP_REASON_TIMEOUT;
+                    int internalStopReason = JobParameters.INTERNAL_STOP_REASON_TIMEOUT;
+                    final StringBuilder stopMessage = new StringBuilder("timeout while executing");
+                    final StringBuilder debugStopReason = new StringBuilder("client timed out");
+
+                    if (android.app.job.Flags.handleAbandonedJobs()
+                            && executing != null && executing.isAbandoned()) {
+                        final String abandonedMessage = " and maybe abandoned";
+                        stopReason = JobParameters.STOP_REASON_TIMEOUT_ABANDONED;
+                        internalStopReason = JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED;
+                        stopMessage.append(abandonedMessage);
+                        debugStopReason.append(abandonedMessage);
+                    }
+
+                    mParams.setStopReason(stopReason,
+                                    internalStopReason, debugStopReason.toString());
+                    sendStopMessageLocked(stopMessage.toString());
                 } else if (nowElapsed >= earliestStopTimeElapsed) {
                     // We've given the app the minimum execution time. See if we should stop it or
                     // let it continue running
@@ -1479,8 +1517,9 @@
      * Already running, need to stop. Will switch {@link #mVerb} from VERB_EXECUTING ->
      * VERB_STOPPING.
      */
+    @VisibleForTesting
     @GuardedBy("mLock")
-    private void sendStopMessageLocked(@Nullable String reason) {
+    void sendStopMessageLocked(@Nullable String reason) {
         removeOpTimeOutLocked();
         if (mVerb != VERB_EXECUTING) {
             Slog.e(TAG, "Sending onStopJob for a job that isn't started. " + mRunningJob);
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 e3af1d8..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
@@ -576,6 +576,13 @@
     private String mSystemTraceTag;
 
     /**
+     * Job maybe abandoned by not calling
+     * {@link android.app.job.JobService#jobFinished(JobParameters, boolean)} while
+     * the strong reference to {@link android.app.job.JobParameters} is lost
+     */
+    private boolean mIsAbandoned;
+
+    /**
      * Core constructor for JobStatus instances.  All other ctors funnel down to this one.
      *
      * @param job The actual requested parameters for the job
@@ -725,6 +732,8 @@
         updateNetworkBytesLocked();
 
         updateMediaBackupExemptionStatus();
+
+        mIsAbandoned = false;
     }
 
     /** Copy constructor: used specifically when cloning JobStatus objects for persistence,
@@ -1061,6 +1070,16 @@
         return job.getTraceTag();
     }
 
+    /** Returns if the job maybe abandoned */
+    public boolean isAbandoned() {
+        return mIsAbandoned;
+    }
+
+    /** Set the job maybe abandoned state*/
+    public void setAbandoned(boolean abandoned) {
+        mIsAbandoned = abandoned;
+    }
+
     /** Returns a trace tag using debug information provided by job scheduler service. */
     @NonNull
     public String computeSystemTraceTag() {
@@ -2048,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) {
@@ -2065,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/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index ff4af69..8bd3ef4 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -121,6 +121,9 @@
     private static final String ALARM_TAG_CLEANUP = "*job.cleanup*";
     private static final String ALARM_TAG_QUOTA_CHECK = "*job.quota_check*";
 
+    private static final String TRACE_QUOTA_STATE_CHANGED_TAG = "QuotaStateChanged:";
+    private static final String TRACE_QUOTA_STATE_CHANGED_DELIMITER = "#";
+
     private static final int SYSTEM_APP_CHECK_FLAGS =
             PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                     | PackageManager.GET_PERMISSIONS | PackageManager.MATCH_KNOWN_PACKAGES;
@@ -2657,11 +2660,12 @@
                         if (timeRemainingMs <= 50) {
                             // Less than 50 milliseconds left. Start process of shutting down jobs.
                             if (DEBUG) Slog.d(TAG, pkg + " has reached its quota.");
-                            if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
-                                Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER,
-                                        JobSchedulerService.TRACE_TRACK_NAME,
-                                        pkg + "#" + MSG_REACHED_TIME_QUOTA);
-                            }
+                            final StringBuilder traceMsg = new StringBuilder();
+                            traceMsg.append(TRACE_QUOTA_STATE_CHANGED_TAG)
+                                    .append(pkg)
+                                    .append(TRACE_QUOTA_STATE_CHANGED_DELIMITER)
+                                    .append(MSG_REACHED_TIME_QUOTA);
+                            Trace.instant(Trace.TRACE_TAG_POWER, traceMsg.toString());
                             mStateChangedListener.onControllerStateChanged(
                                     maybeUpdateConstraintForPkgLocked(
                                             sElapsedRealtimeClock.millis(),
@@ -2690,11 +2694,12 @@
                                 pkg.userId, pkg.packageName);
                         if (timeRemainingMs <= 0) {
                             if (DEBUG) Slog.d(TAG, pkg + " has reached its EJ quota.");
-                            if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
-                                Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER,
-                                        JobSchedulerService.TRACE_TRACK_NAME,
-                                        pkg + "#" + MSG_REACHED_EJ_TIME_QUOTA);
-                            }
+                            final StringBuilder traceMsg = new StringBuilder();
+                            traceMsg.append(TRACE_QUOTA_STATE_CHANGED_TAG)
+                                    .append(pkg)
+                                    .append(TRACE_QUOTA_STATE_CHANGED_DELIMITER)
+                                    .append(MSG_REACHED_EJ_TIME_QUOTA);
+                            Trace.instant(Trace.TRACE_TAG_POWER, traceMsg.toString());
                             mStateChangedListener.onControllerStateChanged(
                                     maybeUpdateConstraintForPkgLocked(
                                             sElapsedRealtimeClock.millis(),
@@ -2719,11 +2724,12 @@
                             Slog.d(TAG, pkg + " has reached its count quota.");
                         }
 
-                        if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
-                            Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER,
-                                    JobSchedulerService.TRACE_TRACK_NAME,
-                                    pkg + "#" + MSG_REACHED_COUNT_QUOTA);
-                        }
+                        final StringBuilder traceMsg = new StringBuilder();
+                        traceMsg.append(TRACE_QUOTA_STATE_CHANGED_TAG)
+                                .append(pkg)
+                                .append(TRACE_QUOTA_STATE_CHANGED_DELIMITER)
+                                .append(MSG_REACHED_COUNT_QUOTA);
+                        Trace.instant(Trace.TRACE_TAG_POWER, traceMsg.toString());
 
                         mStateChangedListener.onControllerStateChanged(
                                 maybeUpdateConstraintForPkgLocked(
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/boot/boot-image-profile-extra.txt b/boot/boot-image-profile-extra.txt
index 11ca1dc..e31eb3a 100644
--- a/boot/boot-image-profile-extra.txt
+++ b/boot/boot-image-profile-extra.txt
@@ -23,3 +23,4 @@
 # For now, compile all methods in MessageQueue to avoid performance cliffs for
 # flagged/evolving hot code paths. See: b/338098106
 HSPLandroid/os/MessageQueue;->*
+HSPLandroid/os/MessageQueue$*;->*
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 0404b79..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";
@@ -1006,6 +1007,7 @@
     field public static final int insetRight = 16843192; // 0x10101b8
     field public static final int insetTop = 16843193; // 0x10101b9
     field public static final int installLocation = 16843447; // 0x10102b7
+    field @FlaggedApi("android.security.enable_intent_matching_flags") public static final int intentMatchingFlags;
     field public static final int interactiveUiTimeout = 16844181; // 0x1010595
     field public static final int interpolator = 16843073; // 0x1010141
     field public static final int intro = 16844395; // 0x101066b
@@ -1073,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
@@ -1609,6 +1612,7 @@
     field public static final int summaryColumn = 16843426; // 0x10102a2
     field public static final int summaryOff = 16843248; // 0x10101f0
     field public static final int summaryOn = 16843247; // 0x10101ef
+    field @FlaggedApi("android.view.accessibility.supplemental_description") public static final int supplementalDescription;
     field public static final int supportedTypes = 16844369; // 0x1010651
     field public static final int supportsAssist = 16844016; // 0x10104f0
     field public static final int supportsBatteryGameMode = 16844374; // 0x1010656
@@ -4616,6 +4620,7 @@
     method public void setInheritShowWhenLocked(boolean);
     method public void setIntent(android.content.Intent);
     method @FlaggedApi("android.security.content_uri_permission_apis") public void setIntent(@Nullable android.content.Intent, @Nullable android.app.ComponentCaller);
+    method @FlaggedApi("com.android.window.flags.enable_desktop_windowing_app_to_web_education") public final void setLimitSystemEducationDialogs(boolean);
     method public void setLocusContext(@Nullable android.content.LocusId, @Nullable android.os.Bundle);
     method public final void setMediaController(android.media.session.MediaController);
     method public void setPictureInPictureParams(@NonNull android.app.PictureInPictureParams);
@@ -8020,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";
@@ -8783,7 +8789,8 @@
 
   @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager {
     method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
-    method public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
+    method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
+    method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
     method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
     field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0
     field public static final int APP_FUNCTION_STATE_DISABLED = 2; // 0x2
@@ -8816,6 +8823,7 @@
 
   @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class ExecuteAppFunctionResponse implements android.os.Parcelable {
     method public int describeContents();
+    method public int getErrorCategory();
     method @Nullable public String getErrorMessage();
     method @NonNull public android.os.Bundle getExtras();
     method public int getResultCode();
@@ -8825,14 +8833,19 @@
     method @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") @NonNull public static android.app.appfunctions.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.ExecuteAppFunctionResponse> CREATOR;
+    field public static final int ERROR_CATEGORY_APP = 3; // 0x3
+    field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1
+    field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2
+    field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
     field public static final String PROPERTY_RETURN_VALUE = "returnValue";
-    field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2
-    field public static final int RESULT_CANCELLED = 6; // 0x6
-    field public static final int RESULT_DENIED = 1; // 0x1
-    field public static final int RESULT_DISABLED = 5; // 0x5
-    field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
-    field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
+    field public static final int RESULT_APP_UNKNOWN_ERROR = 3000; // 0xbb8
+    field public static final int RESULT_CANCELLED = 2001; // 0x7d1
+    field public static final int RESULT_DENIED = 1000; // 0x3e8
+    field public static final int RESULT_DISABLED = 1002; // 0x3ea
+    field public static final int RESULT_FUNCTION_NOT_FOUND = 1003; // 0x3eb
+    field public static final int RESULT_INVALID_ARGUMENT = 1001; // 0x3e9
     field public static final int RESULT_OK = 0; // 0x0
+    field public static final int RESULT_SYSTEM_ERROR = 2000; // 0x7d0
   }
 
 }
@@ -9235,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
@@ -9244,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
@@ -9679,7 +9694,7 @@
     method @Nullable public CharSequence getTitle();
     method @NonNull public android.app.wallpaper.WallpaperDescription.Builder toBuilder();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @Nullable public static final android.os.Parcelable.Creator<android.app.wallpaper.WallpaperDescription> CREATOR;
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.wallpaper.WallpaperDescription> CREATOR;
   }
 
   public static final class WallpaperDescription.Builder {
@@ -9832,6 +9847,7 @@
     field public static final int RESIZE_VERTICAL = 2; // 0x2
     field public static final int WIDGET_CATEGORY_HOME_SCREEN = 1; // 0x1
     field public static final int WIDGET_CATEGORY_KEYGUARD = 2; // 0x2
+    field @FlaggedApi("android.appwidget.flags.not_keyguard_category") public static final int WIDGET_CATEGORY_NOT_KEYGUARD = 8; // 0x8
     field public static final int WIDGET_CATEGORY_SEARCHBOX = 4; // 0x4
     field public static final int WIDGET_FEATURE_CONFIGURATION_OPTIONAL = 4; // 0x4
     field public static final int WIDGET_FEATURE_HIDE_FROM_PICKER = 2; // 0x2
@@ -10941,6 +10957,7 @@
     field public static final int RECEIVER_VISIBLE_TO_INSTANT_APPS = 1; // 0x1
     field public static final String RESTRICTIONS_SERVICE = "restrictions";
     field public static final String ROLE_SERVICE = "role";
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") public static final String SATELLITE_SERVICE = "satellite";
     field public static final String SEARCH_SERVICE = "search";
     field @FlaggedApi("android.os.security_state_service") public static final String SECURITY_STATE_SERVICE = "security_state";
     field public static final String SENSOR_SERVICE = "sensor";
@@ -12718,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 {
@@ -12753,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 {
@@ -13687,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
@@ -16404,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
@@ -16830,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 {
@@ -18694,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
@@ -19333,6 +19356,8 @@
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> AUTOMOTIVE_LENS_FACING;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> AUTOMOTIVE_LOCATION;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES;
+    field @FlaggedApi("com.android.internal.camera.flags.color_temperature") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> COLOR_CORRECTION_AVAILABLE_MODES;
+    field @FlaggedApi("com.android.internal.camera.flags.color_temperature") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Integer>> COLOR_CORRECTION_COLOR_TEMPERATURE_RANGE;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AE_AVAILABLE_ANTIBANDING_MODES;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AE_AVAILABLE_MODES;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Integer>[]> CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES;
@@ -19634,6 +19659,7 @@
     field public static final int COLOR_CORRECTION_ABERRATION_MODE_FAST = 1; // 0x1
     field public static final int COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY = 2; // 0x2
     field public static final int COLOR_CORRECTION_ABERRATION_MODE_OFF = 0; // 0x0
+    field @FlaggedApi("com.android.internal.camera.flags.color_temperature") public static final int COLOR_CORRECTION_MODE_CCT = 3; // 0x3
     field public static final int COLOR_CORRECTION_MODE_FAST = 1; // 0x1
     field public static final int COLOR_CORRECTION_MODE_HIGH_QUALITY = 2; // 0x2
     field public static final int COLOR_CORRECTION_MODE_TRANSFORM_MATRIX = 0; // 0x0
@@ -19920,6 +19946,8 @@
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> BLACK_LEVEL_LOCK;
     field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> COLOR_CORRECTION_ABERRATION_MODE;
+    field @FlaggedApi("com.android.internal.camera.flags.color_temperature") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> COLOR_CORRECTION_COLOR_TEMPERATURE;
+    field @FlaggedApi("com.android.internal.camera.flags.color_temperature") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> COLOR_CORRECTION_COLOR_TINT;
     field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.hardware.camera2.params.RggbChannelVector> COLOR_CORRECTION_GAINS;
     field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> COLOR_CORRECTION_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.hardware.camera2.params.ColorSpaceTransform> COLOR_CORRECTION_TRANSFORM;
@@ -20010,6 +20038,8 @@
     method public int getSequenceId();
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> BLACK_LEVEL_LOCK;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> COLOR_CORRECTION_ABERRATION_MODE;
+    field @FlaggedApi("com.android.internal.camera.flags.color_temperature") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> COLOR_CORRECTION_COLOR_TEMPERATURE;
+    field @FlaggedApi("com.android.internal.camera.flags.color_temperature") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> COLOR_CORRECTION_COLOR_TINT;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.RggbChannelVector> COLOR_CORRECTION_GAINS;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> COLOR_CORRECTION_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.ColorSpaceTransform> COLOR_CORRECTION_TRANSFORM;
@@ -21391,6 +21421,7 @@
     field public static final int TYPE_IP = 20; // 0x14
     field public static final int TYPE_LINE_ANALOG = 5; // 0x5
     field public static final int TYPE_LINE_DIGITAL = 6; // 0x6
+    field @FlaggedApi("android.media.audio.enable_multichannel_group_device") public static final int TYPE_MULTICHANNEL_GROUP = 32; // 0x20
     field public static final int TYPE_REMOTE_SUBMIX = 25; // 0x19
     field public static final int TYPE_TELEPHONY = 18; // 0x12
     field public static final int TYPE_TV_TUNER = 17; // 0x11
@@ -23108,6 +23139,65 @@
     field public static final int AC4Profile11 = 514; // 0x202
     field public static final int AC4Profile21 = 1026; // 0x402
     field public static final int AC4Profile22 = 1028; // 0x404
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel11Band0 = 513; // 0x201
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel11Band1 = 514; // 0x202
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel11Band2 = 516; // 0x204
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel11Band3 = 520; // 0x208
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel1Band0 = 257; // 0x101
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel1Band1 = 258; // 0x102
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel1Band2 = 260; // 0x104
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel1Band3 = 264; // 0x108
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel21Band0 = 2049; // 0x801
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel21Band1 = 2050; // 0x802
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel21Band2 = 2052; // 0x804
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel21Band3 = 2056; // 0x808
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel2Band0 = 1025; // 0x401
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel2Band1 = 1026; // 0x402
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel2Band2 = 1028; // 0x404
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel2Band3 = 1032; // 0x408
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel31Band0 = 8193; // 0x2001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel31Band1 = 8194; // 0x2002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel31Band2 = 8196; // 0x2004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel31Band3 = 8200; // 0x2008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel3Band0 = 4097; // 0x1001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel3Band1 = 4098; // 0x1002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel3Band2 = 4100; // 0x1004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel3Band3 = 4104; // 0x1008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel41Band0 = 32769; // 0x8001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel41Band1 = 32770; // 0x8002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel41Band2 = 32772; // 0x8004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel41Band3 = 32776; // 0x8008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel4Band0 = 16385; // 0x4001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel4Band1 = 16386; // 0x4002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel4Band2 = 16388; // 0x4004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel4Band3 = 16392; // 0x4008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel51Band0 = 131073; // 0x20001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel51Band1 = 131074; // 0x20002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel51Band2 = 131076; // 0x20004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel51Band3 = 131080; // 0x20008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel5Band0 = 65537; // 0x10001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel5Band1 = 65538; // 0x10002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel5Band2 = 65540; // 0x10004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel5Band3 = 65544; // 0x10008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel61Band0 = 524289; // 0x80001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel61Band1 = 524290; // 0x80002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel61Band2 = 524292; // 0x80004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel61Band3 = 524296; // 0x80008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel6Band0 = 262145; // 0x40001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel6Band1 = 262146; // 0x40002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel6Band2 = 262148; // 0x40004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel6Band3 = 262152; // 0x40008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel71Band0 = 2097153; // 0x200001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel71Band1 = 2097154; // 0x200002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel71Band2 = 2097156; // 0x200004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel71Band3 = 2097160; // 0x200008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel7Band0 = 1048577; // 0x100001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel7Band1 = 1048578; // 0x100002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel7Band2 = 1048580; // 0x100004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel7Band3 = 1048584; // 0x100008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVProfile422_10 = 1; // 0x1
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVProfile422_10HDR10 = 4096; // 0x1000
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVProfile422_10HDR10Plus = 8192; // 0x2000
     field public static final int AV1Level2 = 1; // 0x1
     field public static final int AV1Level21 = 2; // 0x2
     field public static final int AV1Level22 = 4; // 0x4
@@ -23958,6 +24048,7 @@
     field public static final String MIMETYPE_TEXT_CEA_708 = "text/cea-708";
     field public static final String MIMETYPE_TEXT_SUBRIP = "application/x-subrip";
     field public static final String MIMETYPE_TEXT_VTT = "text/vtt";
+    field @FlaggedApi("android.media.codec.apv_support") public static final String MIMETYPE_VIDEO_APV = "video/apv";
     field public static final String MIMETYPE_VIDEO_AV1 = "video/av01";
     field public static final String MIMETYPE_VIDEO_AVC = "video/avc";
     field public static final String MIMETYPE_VIDEO_DOLBY_VISION = "video/dolby-vision";
@@ -24587,6 +24678,7 @@
     field @FlaggedApi("com.android.media.flags.enable_audio_policies_device_and_bluetooth_controller") public static final int TYPE_HDMI_ARC = 10; // 0xa
     field @FlaggedApi("com.android.media.flags.enable_audio_policies_device_and_bluetooth_controller") public static final int TYPE_HDMI_EARC = 29; // 0x1d
     field public static final int TYPE_HEARING_AID = 23; // 0x17
+    field @FlaggedApi("android.media.audio.enable_multichannel_group_device") public static final int TYPE_MULTICHANNEL_SPEAKER_GROUP = 32; // 0x20
     field public static final int TYPE_REMOTE_AUDIO_VIDEO_RECEIVER = 1003; // 0x3eb
     field @FlaggedApi("com.android.media.flags.enable_new_media_route_2_info_types") public static final int TYPE_REMOTE_CAR = 1008; // 0x3f0
     field @FlaggedApi("com.android.media.flags.enable_new_media_route_2_info_types") public static final int TYPE_REMOTE_COMPUTER = 1006; // 0x3ee
@@ -32861,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;
@@ -33882,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);
@@ -33905,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";
@@ -33937,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);
   }
@@ -34454,6 +34558,7 @@
     method public int describeContents();
     method @NonNull public static android.os.VibrationEffect.Composition startComposition();
     method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public static android.os.VibrationEffect.WaveformEnvelopeBuilder startWaveformEnvelope();
+    method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public static android.os.VibrationEffect.WaveformEnvelopeBuilder startWaveformEnvelope(@FloatRange(from=0) float);
     field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
     field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
     field public static final int EFFECT_CLICK = 0; // 0x0
@@ -37218,7 +37323,7 @@
   }
 
   public static final class ContactsContract.Settings implements android.provider.ContactsContract.SettingsColumns {
-    method @Nullable public static android.accounts.Account getDefaultAccount(@NonNull android.content.ContentResolver);
+    method @Deprecated @FlaggedApi("android.provider.new_default_account_api_enabled") @Nullable public static android.accounts.Account getDefaultAccount(@NonNull android.content.ContentResolver);
     field public static final String ACTION_SET_DEFAULT_ACCOUNT = "android.provider.action.SET_DEFAULT_ACCOUNT";
     field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/setting";
     field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/setting";
@@ -42105,6 +42210,7 @@
     ctor public WallpaperService();
     method public final android.os.IBinder onBind(android.content.Intent);
     method @MainThread public abstract android.service.wallpaper.WallpaperService.Engine onCreateEngine();
+    method @FlaggedApi("android.app.live_wallpaper_content_handling") @MainThread @Nullable public android.service.wallpaper.WallpaperService.Engine onCreateEngine(@NonNull android.app.wallpaper.WallpaperDescription);
     field public static final String SERVICE_INTERFACE = "android.service.wallpaper.WallpaperService";
     field public static final String SERVICE_META_DATA = "android.service.wallpaper";
   }
@@ -42120,6 +42226,7 @@
     method public boolean isPreview();
     method public boolean isVisible();
     method public void notifyColorsChanged();
+    method @FlaggedApi("android.app.live_wallpaper_content_handling") @Nullable public android.app.wallpaper.WallpaperDescription onApplyWallpaper(int);
     method @MainThread public void onApplyWindowInsets(android.view.WindowInsets);
     method @MainThread public android.os.Bundle onCommand(String, int, int, int, android.os.Bundle, boolean);
     method @MainThread @Nullable public android.app.WallpaperColors onComputeColors();
@@ -47790,6 +47897,19 @@
 
 }
 
+package android.telephony.satellite {
+
+  @FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") public final class SatelliteManager {
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") @RequiresPermission(anyOf={android.Manifest.permission.READ_BASIC_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE, "carrier privileges"}) public void registerStateChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateChangeListener);
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") @RequiresPermission(anyOf={android.Manifest.permission.READ_BASIC_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE, "carrier privileges"}) public void unregisterStateChangeListener(@NonNull android.telephony.satellite.SatelliteStateChangeListener);
+  }
+
+  @FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") public interface SatelliteStateChangeListener {
+    method public void onEnabledStateChanged(boolean);
+  }
+
+}
+
 package android.text {
 
   @Deprecated public class AlteredCharSequence implements java.lang.CharSequence android.text.GetChars {
@@ -52898,7 +53018,7 @@
     method public void addOnUnhandledKeyEventListener(android.view.View.OnUnhandledKeyEventListener);
     method public void addTouchables(java.util.ArrayList<android.view.View>);
     method public android.view.ViewPropertyAnimator animate();
-    method public void announceForAccessibility(CharSequence);
+    method @Deprecated @FlaggedApi("android.view.accessibility.deprecate_accessibility_announcement_apis") public void announceForAccessibility(CharSequence);
     method public void autofill(android.view.autofill.AutofillValue);
     method public void autofill(@NonNull android.util.SparseArray<android.view.autofill.AutofillValue>);
     method protected boolean awakenScrollBars();
@@ -53148,6 +53268,7 @@
     method public android.animation.StateListAnimator getStateListAnimator();
     method protected int getSuggestedMinimumHeight();
     method protected int getSuggestedMinimumWidth();
+    method @FlaggedApi("android.view.accessibility.supplemental_description") @Nullable public CharSequence getSupplementalDescription();
     method @NonNull public java.util.List<android.graphics.Rect> getSystemGestureExclusionRects();
     method @Deprecated public int getSystemUiVisibility();
     method public Object getTag();
@@ -53528,6 +53649,7 @@
     method public void setSoundEffectsEnabled(boolean);
     method public void setStateDescription(@Nullable CharSequence);
     method public void setStateListAnimator(android.animation.StateListAnimator);
+    method @FlaggedApi("android.view.accessibility.supplemental_description") public void setSupplementalDescription(@Nullable CharSequence);
     method public void setSystemGestureExclusionRects(@NonNull java.util.List<android.graphics.Rect>);
     method @Deprecated public void setSystemUiVisibility(int);
     method public void setTag(Object);
@@ -54082,6 +54204,7 @@
     method public void onStopNestedScroll(android.view.View);
     method public void onViewAdded(android.view.View);
     method public void onViewRemoved(android.view.View);
+    method @FlaggedApi("android.view.flags.toolkit_viewgroup_set_requested_frame_rate_api") public void propagateRequestedFrameRate(float, boolean);
     method public void recomputeViewAttributes(android.view.View);
     method public void removeAllViews();
     method public void removeAllViewsInLayout();
@@ -55134,6 +55257,7 @@
     field public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 8; // 0x8
     field public static final int CONTENT_CHANGE_TYPE_STATE_DESCRIPTION = 64; // 0x40
     field public static final int CONTENT_CHANGE_TYPE_SUBTREE = 1; // 0x1
+    field @FlaggedApi("android.view.accessibility.supplemental_description") public static final int CONTENT_CHANGE_TYPE_SUPPLEMENTAL_DESCRIPTION = 32768; // 0x8000
     field public static final int CONTENT_CHANGE_TYPE_TEXT = 2; // 0x2
     field public static final int CONTENT_CHANGE_TYPE_UNDEFINED = 0; // 0x0
     field @NonNull public static final android.os.Parcelable.Creator<android.view.accessibility.AccessibilityEvent> CREATOR;
@@ -55144,7 +55268,7 @@
     field public static final int SPEECH_STATE_SPEAKING_END = 2; // 0x2
     field public static final int SPEECH_STATE_SPEAKING_START = 1; // 0x1
     field public static final int TYPES_ALL_MASK = -1; // 0xffffffff
-    field public static final int TYPE_ANNOUNCEMENT = 16384; // 0x4000
+    field @Deprecated @FlaggedApi("android.view.accessibility.deprecate_accessibility_announcement_apis") public static final int TYPE_ANNOUNCEMENT = 16384; // 0x4000
     field public static final int TYPE_ASSIST_READING_CONTEXT = 16777216; // 0x1000000
     field public static final int TYPE_GESTURE_DETECTION_END = 524288; // 0x80000
     field public static final int TYPE_GESTURE_DETECTION_START = 262144; // 0x40000
@@ -55296,6 +55420,7 @@
     method @Nullable public android.view.accessibility.AccessibilityNodeInfo getParent(int);
     method public android.view.accessibility.AccessibilityNodeInfo.RangeInfo getRangeInfo();
     method @Nullable public CharSequence getStateDescription();
+    method @FlaggedApi("android.view.accessibility.supplemental_description") @Nullable public CharSequence getSupplementalDescription();
     method public CharSequence getText();
     method public int getTextSelectionEnd();
     method public int getTextSelectionStart();
@@ -55318,6 +55443,7 @@
     method public boolean isDismissable();
     method public boolean isEditable();
     method public boolean isEnabled();
+    method @FlaggedApi("android.view.accessibility.a11y_is_required_api") public boolean isFieldRequired();
     method public boolean isFocusable();
     method public boolean isFocused();
     method @FlaggedApi("android.view.accessibility.granular_scrolling") public boolean isGranularScrollingSupported();
@@ -55372,6 +55498,7 @@
     method public void setEnabled(boolean);
     method public void setError(CharSequence);
     method @FlaggedApi("android.view.accessibility.a11y_expansion_state_api") public void setExpandedState(int);
+    method @FlaggedApi("android.view.accessibility.a11y_is_required_api") public void setFieldRequired(boolean);
     method public void setFocusable(boolean);
     method public void setFocused(boolean);
     method @FlaggedApi("android.view.accessibility.granular_scrolling") public void setGranularScrollingSupported(boolean);
@@ -55404,6 +55531,7 @@
     method public void setSource(android.view.View);
     method public void setSource(android.view.View, int);
     method public void setStateDescription(@Nullable CharSequence);
+    method @FlaggedApi("android.view.accessibility.supplemental_description") public void setSupplementalDescription(@Nullable CharSequence);
     method public void setText(CharSequence);
     method public void setTextEntryKey(boolean);
     method public void setTextSelectable(boolean);
@@ -55466,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
@@ -55606,6 +55735,7 @@
     method public int getType();
     method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo.RangeInfo obtain(int, float, float, float);
     field public static final int RANGE_TYPE_FLOAT = 1; // 0x1
+    field @FlaggedApi("android.view.accessibility.indeterminate_range_info") public static final int RANGE_TYPE_INDETERMINATE = 3; // 0x3
     field public static final int RANGE_TYPE_INT = 0; // 0x0
     field public static final int RANGE_TYPE_PERCENT = 2; // 0x2
   }
@@ -56867,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();
@@ -56886,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);
@@ -61940,6 +62075,11 @@
     method public void markSyncReady();
   }
 
+  @FlaggedApi("com.android.window.flags.predictive_back_system_override_callback") public final class SystemOnBackInvokedCallbacks {
+    method @FlaggedApi("com.android.window.flags.predictive_back_system_override_callback") @NonNull public static android.window.OnBackInvokedCallback finishAndRemoveTaskCallback(@NonNull android.app.Activity);
+    method @FlaggedApi("com.android.window.flags.predictive_back_system_override_callback") @NonNull public static android.window.OnBackInvokedCallback moveTaskToBackCallback(@NonNull android.app.Activity);
+  }
+
   @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public final class TrustedPresentationThresholds implements android.os.Parcelable {
     ctor @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public TrustedPresentationThresholds(@FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @IntRange(from=1) int);
     method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public int describeContents();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b130f00..2a01ca0 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -382,6 +382,7 @@
     field public static final String SHOW_KEYGUARD_MESSAGE = "android.permission.SHOW_KEYGUARD_MESSAGE";
     field public static final String SHUTDOWN = "android.permission.SHUTDOWN";
     field public static final String SIGNAL_REBOOT_READINESS = "android.permission.SIGNAL_REBOOT_READINESS";
+    field @FlaggedApi("android.media.tv.flags.kids_mode_tvdb_sharing") public static final String SINGLE_USER_TIS_ACCESS = "android.permission.SINGLE_USER_TIS_ACCESS";
     field public static final String SOUND_TRIGGER_RUN_IN_BATTERY_SAVER = "android.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER";
     field public static final String STAGE_HEALTH_CONNECT_REMOTE_DATA = "android.permission.STAGE_HEALTH_CONNECT_REMOTE_DATA";
     field public static final String START_ACTIVITIES_FROM_BACKGROUND = "android.permission.START_ACTIVITIES_FROM_BACKGROUND";
@@ -695,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";
@@ -703,6 +705,7 @@
     field public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images";
     field public static final String OPSTR_READ_MEDIA_VIDEO = "android:read_media_video";
     field public static final String OPSTR_READ_MEDIA_VISUAL_USER_SELECTED = "android:read_media_visual_user_selected";
+    field @FlaggedApi("android.permission.flags.platform_skin_temperature_enabled") public static final String OPSTR_READ_SKIN_TEMPERATURE = "android:read_skin_temperature";
     field public static final String OPSTR_READ_WRITE_HEALTH_DATA = "android:read_write_health_data";
     field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio";
     field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast";
@@ -1265,8 +1268,10 @@
   public class WallpaperManager {
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void clearWallpaper(int, int);
     method @FloatRange(from=0.0f, to=1.0f) @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public float getWallpaperDimAmount();
+    method @FlaggedApi("android.app.live_wallpaper_content_handling") @Nullable @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public android.app.wallpaper.WallpaperInstance getWallpaperInstance(int);
     method public void setDisplayOffset(android.os.IBinder, int, int);
     method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponent(android.content.ComponentName);
+    method @FlaggedApi("android.app.live_wallpaper_content_handling") @RequiresPermission(allOf={android.Manifest.permission.SET_WALLPAPER_COMPONENT, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional=true) public boolean setWallpaperComponentWithDescription(@NonNull android.app.wallpaper.WallpaperDescription, int);
     method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponentWithFlags(@NonNull android.content.ComponentName, int);
     method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public void setWallpaperDimAmount(@FloatRange(from=0.0f, to=1.0f) float);
   }
@@ -3554,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);
@@ -3575,6 +3582,7 @@
     field @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public static final int POLICY_TYPE_BLOCKED_ACTIVITY = 6; // 0x6
     field @FlaggedApi("android.companion.virtual.flags.virtual_camera") public static final int POLICY_TYPE_CAMERA = 5; // 0x5
     field @FlaggedApi("android.companion.virtual.flags.cross_device_clipboard") public static final int POLICY_TYPE_CLIPBOARD = 4; // 0x4
+    field @FlaggedApi("android.companion.virtualdevice.flags.default_device_camera_access_policy") public static final int POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS = 7; // 0x7
     field public static final int POLICY_TYPE_RECENTS = 2; // 0x2
     field public static final int POLICY_TYPE_SENSORS = 0; // 0x0
   }
@@ -3590,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);
@@ -5284,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);
@@ -7018,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>);
   }
 
@@ -7230,6 +7246,7 @@
     field @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public static final int USAGE_CALL_ASSISTANT = 17; // 0x11
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_EMERGENCY = 1000; // 0x3e8
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_SAFETY = 1001; // 0x3e9
+    field @FlaggedApi("android.media.audio.speaker_cleanup_usage") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_SPEAKER_CLEANUP = 1004; // 0x3ec
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int USAGE_VEHICLE_STATUS = 1002; // 0x3ea
   }
 
@@ -7387,7 +7404,7 @@
     field public static final String EXTRA_VOLUME_STREAM_VALUE = "android.media.EXTRA_VOLUME_STREAM_VALUE";
     field public static final int FLAG_BLUETOOTH_ABS_VOLUME = 64; // 0x40
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int STREAM_ASSISTANT = 11; // 0xb
-    field public static final int STREAM_BLUETOOTH_SCO = 6; // 0x6
+    field @Deprecated @FlaggedApi("android.media.audio.deprecate_stream_bt_sco") public static final int STREAM_BLUETOOTH_SCO = 6; // 0x6
     field public static final int SUCCESS = 0; // 0x0
   }
 
@@ -7604,7 +7621,7 @@
   public final class MediaRecorder.AudioSource {
     field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public static final int ECHO_REFERENCE = 1997; // 0x7cd
     field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public static final int HOTWORD = 1999; // 0x7cf
-    field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public static final int RADIO_TUNER = 1998; // 0x7ce
+    field @RequiresPermission(android.Manifest.permission.CAPTURE_TUNER_AUDIO_INPUT) public static final int RADIO_TUNER = 1998; // 0x7ce
     field @RequiresPermission(android.Manifest.permission.ACCESS_ULTRASOUND) public static final int ULTRASOUND = 2000; // 0x7d0
   }
 
@@ -8161,6 +8178,7 @@
     method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPid(@NonNull String);
     method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPriority(int, @NonNull String);
     method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPriority(int);
+    method @FlaggedApi("android.media.tv.flags.kids_mode_tvdb_sharing") @RequiresPermission(android.Manifest.permission.SINGLE_USER_TIS_ACCESS) public int getClientUserId(@NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TUNED_INFO) public java.util.List<android.media.tv.TunedInfo> getCurrentTunedInfos();
     method @NonNull @RequiresPermission("android.permission.DVB_DEVICE") public java.util.List<android.media.tv.DvbDeviceInfo> getDvbDeviceList();
     method @Nullable @RequiresPermission(android.Manifest.permission.TIS_EXTENSION_INTERFACE) public android.os.IBinder getExtensionInterface(@NonNull String, @NonNull String);
@@ -12109,7 +12127,7 @@
   }
 
   public static final class ContactsContract.Settings implements android.provider.ContactsContract.SettingsColumns {
-    method @RequiresPermission(android.Manifest.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS) public static void setDefaultAccount(@NonNull android.content.ContentResolver, @Nullable android.accounts.Account);
+    method @Deprecated @FlaggedApi("android.provider.new_default_account_api_enabled") @RequiresPermission(android.Manifest.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS) public static void setDefaultAccount(@NonNull android.content.ContentResolver, @Nullable android.accounts.Account);
   }
 
   public static final class ContactsContract.SimContacts {
@@ -15602,9 +15620,9 @@
   }
 
   @FlaggedApi("com.android.internal.telephony.flags.emergency_callback_mode_notification") public static interface TelephonyCallback.EmergencyCallbackModeListener {
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onCallbackModeRestarted(int, @NonNull java.time.Duration, int);
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onCallbackModeStarted(int, @NonNull java.time.Duration, int);
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onCallbackModeStopped(int, int, int);
+    method public void onCallbackModeRestarted(int, @NonNull java.time.Duration, int);
+    method public void onCallbackModeStarted(int, @NonNull java.time.Duration, int);
+    method public void onCallbackModeStopped(int, int, int);
   }
 
   public static interface TelephonyCallback.LinkCapacityEstimateChangedListener {
@@ -18253,7 +18271,7 @@
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteDatagramReceived(long, @NonNull android.telephony.satellite.SatelliteDatagram, int, @NonNull java.util.function.Consumer<java.lang.Void>);
   }
 
-  public final class SatelliteManager {
+  @FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") public final class SatelliteManager {
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void addAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getAttachRestrictionReasonsForCarrier(int);
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/Activity.java b/core/java/android/app/Activity.java
index 4fb35c3..7a1c759 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1262,6 +1262,33 @@
         }
     }
 
+    /**
+     * To make users aware of system features such as the app header menu and its various
+     * functionalities, educational dialogs are shown to demonstrate how to find and utilize these
+     * features. Using this method, an activity can specify if it wants these educational dialogs to
+     * be shown. When set to {@code true}, these dialogs are not completely blocked; however, the
+     * system will be notified that they should not be shown unless necessary. If this API is not
+     * called, the system's educational dialogs are not limited by default.
+     *
+     * <p>This method can be utilized when activities have states where showing an
+     * educational dialog would be disruptive to the user. For example, if a game application is
+     * expecting prompt user input, this method can be used to limit educational dialogs such as the
+     * dialogs that showcase the app header's features which, in this instance, would disrupt the
+     * user's experience if shown.</p>
+     *
+     * <p>Note that educational dialogs may be shown soon after this activity is launched, so
+     * this method must be called early if the intent is to limit the dialogs from the start.</p>
+     */
+    @FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION)
+    public final void setLimitSystemEducationDialogs(boolean limitSystemEducationDialogs) {
+        try {
+            ActivityTaskManager
+                  .getService().setLimitSystemEducationDialogs(mToken, limitSystemEducationDialogs);
+        } catch (RemoteException e) {
+            // Empty
+        }
+    }
+
     /** Return the application that owns this activity. */
     public final Application getApplication() {
         return mApplication;
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 56538d9..38c8583 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -63,6 +63,7 @@
 import android.os.HandlerExecutor;
 import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.IpcDataCache;
 import android.os.Looper;
 import android.os.PackageTagsList;
 import android.os.Parcel;
@@ -78,12 +79,14 @@
 import android.permission.PermissionUsageHelper;
 import android.permission.flags.Flags;
 import android.provider.DeviceConfig;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.LongSparseArray;
 import android.util.LongSparseLongArray;
 import android.util.Pools;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.Immutable;
@@ -910,159 +913,157 @@
 
     /** @hide No operation specified. */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int OP_NONE = AppProtoEnums.APP_OP_NONE;
+    public static final int OP_NONE = AppOpEnums.APP_OP_NONE;
     /** @hide Access to coarse location information. */
     @UnsupportedAppUsage
     @TestApi
-    public static final int OP_COARSE_LOCATION = AppProtoEnums.APP_OP_COARSE_LOCATION;
+    public static final int OP_COARSE_LOCATION = AppOpEnums.APP_OP_COARSE_LOCATION;
     /** @hide Access to fine location information. */
     @UnsupportedAppUsage
-    public static final int OP_FINE_LOCATION = AppProtoEnums.APP_OP_FINE_LOCATION;
+    public static final int OP_FINE_LOCATION = AppOpEnums.APP_OP_FINE_LOCATION;
     /** @hide Causing GPS to run. */
     @UnsupportedAppUsage
-    public static final int OP_GPS = AppProtoEnums.APP_OP_GPS;
+    public static final int OP_GPS = AppOpEnums.APP_OP_GPS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_VIBRATE = AppProtoEnums.APP_OP_VIBRATE;
+    public static final int OP_VIBRATE = AppOpEnums.APP_OP_VIBRATE;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_READ_CONTACTS = AppProtoEnums.APP_OP_READ_CONTACTS;
+    public static final int OP_READ_CONTACTS = AppOpEnums.APP_OP_READ_CONTACTS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_WRITE_CONTACTS = AppProtoEnums.APP_OP_WRITE_CONTACTS;
+    public static final int OP_WRITE_CONTACTS = AppOpEnums.APP_OP_WRITE_CONTACTS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_READ_CALL_LOG = AppProtoEnums.APP_OP_READ_CALL_LOG;
+    public static final int OP_READ_CALL_LOG = AppOpEnums.APP_OP_READ_CALL_LOG;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_WRITE_CALL_LOG = AppProtoEnums.APP_OP_WRITE_CALL_LOG;
+    public static final int OP_WRITE_CALL_LOG = AppOpEnums.APP_OP_WRITE_CALL_LOG;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_READ_CALENDAR = AppProtoEnums.APP_OP_READ_CALENDAR;
+    public static final int OP_READ_CALENDAR = AppOpEnums.APP_OP_READ_CALENDAR;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_WRITE_CALENDAR = AppProtoEnums.APP_OP_WRITE_CALENDAR;
+    public static final int OP_WRITE_CALENDAR = AppOpEnums.APP_OP_WRITE_CALENDAR;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_WIFI_SCAN = AppProtoEnums.APP_OP_WIFI_SCAN;
+    public static final int OP_WIFI_SCAN = AppOpEnums.APP_OP_WIFI_SCAN;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_POST_NOTIFICATION = AppProtoEnums.APP_OP_POST_NOTIFICATION;
+    public static final int OP_POST_NOTIFICATION = AppOpEnums.APP_OP_POST_NOTIFICATION;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_NEIGHBORING_CELLS = AppProtoEnums.APP_OP_NEIGHBORING_CELLS;
+    public static final int OP_NEIGHBORING_CELLS = AppOpEnums.APP_OP_NEIGHBORING_CELLS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_CALL_PHONE = AppProtoEnums.APP_OP_CALL_PHONE;
+    public static final int OP_CALL_PHONE = AppOpEnums.APP_OP_CALL_PHONE;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_READ_SMS = AppProtoEnums.APP_OP_READ_SMS;
+    public static final int OP_READ_SMS = AppOpEnums.APP_OP_READ_SMS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_WRITE_SMS = AppProtoEnums.APP_OP_WRITE_SMS;
+    public static final int OP_WRITE_SMS = AppOpEnums.APP_OP_WRITE_SMS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_RECEIVE_SMS = AppProtoEnums.APP_OP_RECEIVE_SMS;
+    public static final int OP_RECEIVE_SMS = AppOpEnums.APP_OP_RECEIVE_SMS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_RECEIVE_EMERGECY_SMS =
-            AppProtoEnums.APP_OP_RECEIVE_EMERGENCY_SMS;
+    public static final int OP_RECEIVE_EMERGECY_SMS = AppOpEnums.APP_OP_RECEIVE_EMERGENCY_SMS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_RECEIVE_MMS = AppProtoEnums.APP_OP_RECEIVE_MMS;
+    public static final int OP_RECEIVE_MMS = AppOpEnums.APP_OP_RECEIVE_MMS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_RECEIVE_WAP_PUSH = AppProtoEnums.APP_OP_RECEIVE_WAP_PUSH;
+    public static final int OP_RECEIVE_WAP_PUSH = AppOpEnums.APP_OP_RECEIVE_WAP_PUSH;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_SEND_SMS = AppProtoEnums.APP_OP_SEND_SMS;
+    public static final int OP_SEND_SMS = AppOpEnums.APP_OP_SEND_SMS;
     /** @hide */
-    public static final int OP_MANAGE_ONGOING_CALLS = AppProtoEnums.APP_OP_MANAGE_ONGOING_CALLS;
+    public static final int OP_MANAGE_ONGOING_CALLS = AppOpEnums.APP_OP_MANAGE_ONGOING_CALLS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_READ_ICC_SMS = AppProtoEnums.APP_OP_READ_ICC_SMS;
+    public static final int OP_READ_ICC_SMS = AppOpEnums.APP_OP_READ_ICC_SMS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_WRITE_ICC_SMS = AppProtoEnums.APP_OP_WRITE_ICC_SMS;
+    public static final int OP_WRITE_ICC_SMS = AppOpEnums.APP_OP_WRITE_ICC_SMS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_WRITE_SETTINGS = AppProtoEnums.APP_OP_WRITE_SETTINGS;
+    public static final int OP_WRITE_SETTINGS = AppOpEnums.APP_OP_WRITE_SETTINGS;
     /** @hide Required to draw on top of other apps. */
     @UnsupportedAppUsage
     @TestApi
-    public static final int OP_SYSTEM_ALERT_WINDOW = AppProtoEnums.APP_OP_SYSTEM_ALERT_WINDOW;
+    public static final int OP_SYSTEM_ALERT_WINDOW = AppOpEnums.APP_OP_SYSTEM_ALERT_WINDOW;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_ACCESS_NOTIFICATIONS =
-            AppProtoEnums.APP_OP_ACCESS_NOTIFICATIONS;
+    public static final int OP_ACCESS_NOTIFICATIONS = AppOpEnums.APP_OP_ACCESS_NOTIFICATIONS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_CAMERA = AppProtoEnums.APP_OP_CAMERA;
+    public static final int OP_CAMERA = AppOpEnums.APP_OP_CAMERA;
     /** @hide */
     @UnsupportedAppUsage
     @TestApi
-    public static final int OP_RECORD_AUDIO = AppProtoEnums.APP_OP_RECORD_AUDIO;
+    public static final int OP_RECORD_AUDIO = AppOpEnums.APP_OP_RECORD_AUDIO;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_PLAY_AUDIO = AppProtoEnums.APP_OP_PLAY_AUDIO;
+    public static final int OP_PLAY_AUDIO = AppOpEnums.APP_OP_PLAY_AUDIO;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_READ_CLIPBOARD = AppProtoEnums.APP_OP_READ_CLIPBOARD;
+    public static final int OP_READ_CLIPBOARD = AppOpEnums.APP_OP_READ_CLIPBOARD;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_WRITE_CLIPBOARD = AppProtoEnums.APP_OP_WRITE_CLIPBOARD;
+    public static final int OP_WRITE_CLIPBOARD = AppOpEnums.APP_OP_WRITE_CLIPBOARD;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_TAKE_MEDIA_BUTTONS = AppProtoEnums.APP_OP_TAKE_MEDIA_BUTTONS;
+    public static final int OP_TAKE_MEDIA_BUTTONS = AppOpEnums.APP_OP_TAKE_MEDIA_BUTTONS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_TAKE_AUDIO_FOCUS = AppProtoEnums.APP_OP_TAKE_AUDIO_FOCUS;
+    public static final int OP_TAKE_AUDIO_FOCUS = AppOpEnums.APP_OP_TAKE_AUDIO_FOCUS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_AUDIO_MASTER_VOLUME = AppProtoEnums.APP_OP_AUDIO_MASTER_VOLUME;
+    public static final int OP_AUDIO_MASTER_VOLUME = AppOpEnums.APP_OP_AUDIO_MASTER_VOLUME;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_AUDIO_VOICE_VOLUME = AppProtoEnums.APP_OP_AUDIO_VOICE_VOLUME;
+    public static final int OP_AUDIO_VOICE_VOLUME = AppOpEnums.APP_OP_AUDIO_VOICE_VOLUME;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_AUDIO_RING_VOLUME = AppProtoEnums.APP_OP_AUDIO_RING_VOLUME;
+    public static final int OP_AUDIO_RING_VOLUME = AppOpEnums.APP_OP_AUDIO_RING_VOLUME;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_AUDIO_MEDIA_VOLUME = AppProtoEnums.APP_OP_AUDIO_MEDIA_VOLUME;
+    public static final int OP_AUDIO_MEDIA_VOLUME = AppOpEnums.APP_OP_AUDIO_MEDIA_VOLUME;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_AUDIO_ALARM_VOLUME = AppProtoEnums.APP_OP_AUDIO_ALARM_VOLUME;
+    public static final int OP_AUDIO_ALARM_VOLUME = AppOpEnums.APP_OP_AUDIO_ALARM_VOLUME;
     /** @hide */
     @UnsupportedAppUsage
     public static final int OP_AUDIO_NOTIFICATION_VOLUME =
-            AppProtoEnums.APP_OP_AUDIO_NOTIFICATION_VOLUME;
+            AppOpEnums.APP_OP_AUDIO_NOTIFICATION_VOLUME;
     /** @hide */
     @UnsupportedAppUsage
     public static final int OP_AUDIO_BLUETOOTH_VOLUME =
-            AppProtoEnums.APP_OP_AUDIO_BLUETOOTH_VOLUME;
+            AppOpEnums.APP_OP_AUDIO_BLUETOOTH_VOLUME;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_WAKE_LOCK = AppProtoEnums.APP_OP_WAKE_LOCK;
+    public static final int OP_WAKE_LOCK = AppOpEnums.APP_OP_WAKE_LOCK;
     /** @hide Continually monitoring location data. */
     @UnsupportedAppUsage
     public static final int OP_MONITOR_LOCATION =
-            AppProtoEnums.APP_OP_MONITOR_LOCATION;
+            AppOpEnums.APP_OP_MONITOR_LOCATION;
     /** @hide Continually monitoring location data with a relatively high power request. */
     @UnsupportedAppUsage
     public static final int OP_MONITOR_HIGH_POWER_LOCATION =
-            AppProtoEnums.APP_OP_MONITOR_HIGH_POWER_LOCATION;
+            AppOpEnums.APP_OP_MONITOR_HIGH_POWER_LOCATION;
     /** @hide Retrieve current usage stats via {@link UsageStatsManager}. */
     @UnsupportedAppUsage
-    public static final int OP_GET_USAGE_STATS = AppProtoEnums.APP_OP_GET_USAGE_STATS;
+    public static final int OP_GET_USAGE_STATS = AppOpEnums.APP_OP_GET_USAGE_STATS;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_MUTE_MICROPHONE = AppProtoEnums.APP_OP_MUTE_MICROPHONE;
+    public static final int OP_MUTE_MICROPHONE = AppOpEnums.APP_OP_MUTE_MICROPHONE;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_TOAST_WINDOW = AppProtoEnums.APP_OP_TOAST_WINDOW;
+    public static final int OP_TOAST_WINDOW = AppOpEnums.APP_OP_TOAST_WINDOW;
     /** @hide Capture the device's display contents and/or audio */
     @UnsupportedAppUsage
-    public static final int OP_PROJECT_MEDIA = AppProtoEnums.APP_OP_PROJECT_MEDIA;
+    public static final int OP_PROJECT_MEDIA = AppOpEnums.APP_OP_PROJECT_MEDIA;
     /**
      * Start (without additional user intervention) a VPN connection, as used by {@link
      * android.net.VpnService} along with as Platform VPN connections, as used by {@link
@@ -1075,146 +1076,141 @@
      * @hide
      */
     @UnsupportedAppUsage
-    public static final int OP_ACTIVATE_VPN = AppProtoEnums.APP_OP_ACTIVATE_VPN;
+    public static final int OP_ACTIVATE_VPN = AppOpEnums.APP_OP_ACTIVATE_VPN;
     /** @hide Access the WallpaperManagerAPI to write wallpapers. */
     @UnsupportedAppUsage
-    public static final int OP_WRITE_WALLPAPER = AppProtoEnums.APP_OP_WRITE_WALLPAPER;
+    public static final int OP_WRITE_WALLPAPER = AppOpEnums.APP_OP_WRITE_WALLPAPER;
     /** @hide Received the assist structure from an app. */
     @UnsupportedAppUsage
-    public static final int OP_ASSIST_STRUCTURE = AppProtoEnums.APP_OP_ASSIST_STRUCTURE;
+    public static final int OP_ASSIST_STRUCTURE = AppOpEnums.APP_OP_ASSIST_STRUCTURE;
     /** @hide Received a screenshot from assist. */
     @UnsupportedAppUsage
-    public static final int OP_ASSIST_SCREENSHOT = AppProtoEnums.APP_OP_ASSIST_SCREENSHOT;
+    public static final int OP_ASSIST_SCREENSHOT = AppOpEnums.APP_OP_ASSIST_SCREENSHOT;
     /** @hide Read the phone state. */
     @UnsupportedAppUsage
-    public static final int OP_READ_PHONE_STATE = AppProtoEnums.APP_OP_READ_PHONE_STATE;
+    public static final int OP_READ_PHONE_STATE = AppOpEnums.APP_OP_READ_PHONE_STATE;
     /** @hide Add voicemail messages to the voicemail content provider. */
     @UnsupportedAppUsage
-    public static final int OP_ADD_VOICEMAIL = AppProtoEnums.APP_OP_ADD_VOICEMAIL;
+    public static final int OP_ADD_VOICEMAIL = AppOpEnums.APP_OP_ADD_VOICEMAIL;
     /** @hide Access APIs for SIP calling over VOIP or WiFi. */
     @UnsupportedAppUsage
-    public static final int OP_USE_SIP = AppProtoEnums.APP_OP_USE_SIP;
+    public static final int OP_USE_SIP = AppOpEnums.APP_OP_USE_SIP;
     /** @hide Intercept outgoing calls. */
     @UnsupportedAppUsage
-    public static final int OP_PROCESS_OUTGOING_CALLS =
-            AppProtoEnums.APP_OP_PROCESS_OUTGOING_CALLS;
+    public static final int OP_PROCESS_OUTGOING_CALLS = AppOpEnums.APP_OP_PROCESS_OUTGOING_CALLS;
     /** @hide User the fingerprint API. */
     @UnsupportedAppUsage
-    public static final int OP_USE_FINGERPRINT = AppProtoEnums.APP_OP_USE_FINGERPRINT;
+    public static final int OP_USE_FINGERPRINT = AppOpEnums.APP_OP_USE_FINGERPRINT;
     /** @hide Access to body sensors such as heart rate, etc. */
     @UnsupportedAppUsage
-    public static final int OP_BODY_SENSORS = AppProtoEnums.APP_OP_BODY_SENSORS;
+    public static final int OP_BODY_SENSORS = AppOpEnums.APP_OP_BODY_SENSORS;
     /** @hide Read previously received cell broadcast messages. */
     @UnsupportedAppUsage
-    public static final int OP_READ_CELL_BROADCASTS = AppProtoEnums.APP_OP_READ_CELL_BROADCASTS;
+    public static final int OP_READ_CELL_BROADCASTS = AppOpEnums.APP_OP_READ_CELL_BROADCASTS;
     /** @hide Inject mock location into the system. */
     @UnsupportedAppUsage
-    public static final int OP_MOCK_LOCATION = AppProtoEnums.APP_OP_MOCK_LOCATION;
+    public static final int OP_MOCK_LOCATION = AppOpEnums.APP_OP_MOCK_LOCATION;
     /** @hide Read external storage. */
     @UnsupportedAppUsage
-    public static final int OP_READ_EXTERNAL_STORAGE = AppProtoEnums.APP_OP_READ_EXTERNAL_STORAGE;
+    public static final int OP_READ_EXTERNAL_STORAGE = AppOpEnums.APP_OP_READ_EXTERNAL_STORAGE;
     /** @hide Write external storage. */
     @UnsupportedAppUsage
-    public static final int OP_WRITE_EXTERNAL_STORAGE =
-            AppProtoEnums.APP_OP_WRITE_EXTERNAL_STORAGE;
+    public static final int OP_WRITE_EXTERNAL_STORAGE = AppOpEnums.APP_OP_WRITE_EXTERNAL_STORAGE;
     /** @hide Turned on the screen. */
     @UnsupportedAppUsage
-    public static final int OP_TURN_SCREEN_ON = AppProtoEnums.APP_OP_TURN_SCREEN_ON;
+    public static final int OP_TURN_SCREEN_ON = AppOpEnums.APP_OP_TURN_SCREEN_ON;
     /** @hide Get device accounts. */
     @UnsupportedAppUsage
-    public static final int OP_GET_ACCOUNTS = AppProtoEnums.APP_OP_GET_ACCOUNTS;
+    public static final int OP_GET_ACCOUNTS = AppOpEnums.APP_OP_GET_ACCOUNTS;
     /** @hide Control whether an application is allowed to run in the background. */
     @UnsupportedAppUsage
-    public static final int OP_RUN_IN_BACKGROUND =
-            AppProtoEnums.APP_OP_RUN_IN_BACKGROUND;
+    public static final int OP_RUN_IN_BACKGROUND = AppOpEnums.APP_OP_RUN_IN_BACKGROUND;
     /** @hide */
     @UnsupportedAppUsage
     public static final int OP_AUDIO_ACCESSIBILITY_VOLUME =
-            AppProtoEnums.APP_OP_AUDIO_ACCESSIBILITY_VOLUME;
+             AppOpEnums.APP_OP_AUDIO_ACCESSIBILITY_VOLUME;
     /** @hide Read the phone number. */
     @UnsupportedAppUsage
-    public static final int OP_READ_PHONE_NUMBERS = AppProtoEnums.APP_OP_READ_PHONE_NUMBERS;
+    public static final int OP_READ_PHONE_NUMBERS = AppOpEnums.APP_OP_READ_PHONE_NUMBERS;
     /** @hide Request package installs through package installer */
     @UnsupportedAppUsage
     public static final int OP_REQUEST_INSTALL_PACKAGES =
-            AppProtoEnums.APP_OP_REQUEST_INSTALL_PACKAGES;
+            AppOpEnums.APP_OP_REQUEST_INSTALL_PACKAGES;
     /** @hide Enter picture-in-picture. */
     @UnsupportedAppUsage
-    public static final int OP_PICTURE_IN_PICTURE = AppProtoEnums.APP_OP_PICTURE_IN_PICTURE;
+    public static final int OP_PICTURE_IN_PICTURE = AppOpEnums.APP_OP_PICTURE_IN_PICTURE;
     /** @hide Instant app start foreground service. */
     @UnsupportedAppUsage
     public static final int OP_INSTANT_APP_START_FOREGROUND =
-            AppProtoEnums.APP_OP_INSTANT_APP_START_FOREGROUND;
+            AppOpEnums.APP_OP_INSTANT_APP_START_FOREGROUND;
     /** @hide Answer incoming phone calls */
     @UnsupportedAppUsage
-    public static final int OP_ANSWER_PHONE_CALLS = AppProtoEnums.APP_OP_ANSWER_PHONE_CALLS;
+    public static final int OP_ANSWER_PHONE_CALLS = AppOpEnums.APP_OP_ANSWER_PHONE_CALLS;
     /** @hide Run jobs when in background */
     @UnsupportedAppUsage
-    public static final int OP_RUN_ANY_IN_BACKGROUND = AppProtoEnums.APP_OP_RUN_ANY_IN_BACKGROUND;
+    public static final int OP_RUN_ANY_IN_BACKGROUND = AppOpEnums.APP_OP_RUN_ANY_IN_BACKGROUND;
     /** @hide Change Wi-Fi connectivity state */
     @UnsupportedAppUsage
-    public static final int OP_CHANGE_WIFI_STATE = AppProtoEnums.APP_OP_CHANGE_WIFI_STATE;
+    public static final int OP_CHANGE_WIFI_STATE = AppOpEnums.APP_OP_CHANGE_WIFI_STATE;
     /** @hide Request package deletion through package installer */
     @UnsupportedAppUsage
-    public static final int OP_REQUEST_DELETE_PACKAGES =
-            AppProtoEnums.APP_OP_REQUEST_DELETE_PACKAGES;
+    public static final int OP_REQUEST_DELETE_PACKAGES = AppOpEnums.APP_OP_REQUEST_DELETE_PACKAGES;
     /** @hide Bind an accessibility service. */
     @UnsupportedAppUsage
     public static final int OP_BIND_ACCESSIBILITY_SERVICE =
-            AppProtoEnums.APP_OP_BIND_ACCESSIBILITY_SERVICE;
+            AppOpEnums.APP_OP_BIND_ACCESSIBILITY_SERVICE;
     /** @hide Continue handover of a call from another app */
     @UnsupportedAppUsage
-    public static final int OP_ACCEPT_HANDOVER = AppProtoEnums.APP_OP_ACCEPT_HANDOVER;
+    public static final int OP_ACCEPT_HANDOVER = AppOpEnums.APP_OP_ACCEPT_HANDOVER;
     /** @hide Create and Manage IPsec Tunnels */
     @UnsupportedAppUsage
-    public static final int OP_MANAGE_IPSEC_TUNNELS = AppProtoEnums.APP_OP_MANAGE_IPSEC_TUNNELS;
+    public static final int OP_MANAGE_IPSEC_TUNNELS = AppOpEnums.APP_OP_MANAGE_IPSEC_TUNNELS;
     /** @hide Any app start foreground service. */
     @UnsupportedAppUsage
     @TestApi
-    public static final int OP_START_FOREGROUND = AppProtoEnums.APP_OP_START_FOREGROUND;
+    public static final int OP_START_FOREGROUND = AppOpEnums.APP_OP_START_FOREGROUND;
     /** @hide */
     @UnsupportedAppUsage
-    public static final int OP_BLUETOOTH_SCAN = AppProtoEnums.APP_OP_BLUETOOTH_SCAN;
+    public static final int OP_BLUETOOTH_SCAN = AppOpEnums.APP_OP_BLUETOOTH_SCAN;
     /** @hide */
-    public static final int OP_BLUETOOTH_CONNECT = AppProtoEnums.APP_OP_BLUETOOTH_CONNECT;
+    public static final int OP_BLUETOOTH_CONNECT = AppOpEnums.APP_OP_BLUETOOTH_CONNECT;
     /** @hide */
-    public static final int OP_BLUETOOTH_ADVERTISE = AppProtoEnums.APP_OP_BLUETOOTH_ADVERTISE;
+    public static final int OP_BLUETOOTH_ADVERTISE = AppOpEnums.APP_OP_BLUETOOTH_ADVERTISE;
     /** @hide Use the BiometricPrompt/BiometricManager APIs. */
-    public static final int OP_USE_BIOMETRIC = AppProtoEnums.APP_OP_USE_BIOMETRIC;
+    public static final int OP_USE_BIOMETRIC = AppOpEnums.APP_OP_USE_BIOMETRIC;
     /** @hide Physical activity recognition. */
-    public static final int OP_ACTIVITY_RECOGNITION = AppProtoEnums.APP_OP_ACTIVITY_RECOGNITION;
+    public static final int OP_ACTIVITY_RECOGNITION = AppOpEnums.APP_OP_ACTIVITY_RECOGNITION;
     /** @hide Financial app sms read. */
     public static final int OP_SMS_FINANCIAL_TRANSACTIONS =
-            AppProtoEnums.APP_OP_SMS_FINANCIAL_TRANSACTIONS;
+            AppOpEnums.APP_OP_SMS_FINANCIAL_TRANSACTIONS;
     /** @hide Read media of audio type. */
-    public static final int OP_READ_MEDIA_AUDIO = AppProtoEnums.APP_OP_READ_MEDIA_AUDIO;
+    public static final int OP_READ_MEDIA_AUDIO = AppOpEnums.APP_OP_READ_MEDIA_AUDIO;
     /** @hide Write media of audio type. */
-    public static final int OP_WRITE_MEDIA_AUDIO = AppProtoEnums.APP_OP_WRITE_MEDIA_AUDIO;
+    public static final int OP_WRITE_MEDIA_AUDIO = AppOpEnums.APP_OP_WRITE_MEDIA_AUDIO;
     /** @hide Read media of video type. */
-    public static final int OP_READ_MEDIA_VIDEO = AppProtoEnums.APP_OP_READ_MEDIA_VIDEO;
+    public static final int OP_READ_MEDIA_VIDEO = AppOpEnums.APP_OP_READ_MEDIA_VIDEO;
     /** @hide Write media of video type. */
-    public static final int OP_WRITE_MEDIA_VIDEO = AppProtoEnums.APP_OP_WRITE_MEDIA_VIDEO;
+    public static final int OP_WRITE_MEDIA_VIDEO = AppOpEnums.APP_OP_WRITE_MEDIA_VIDEO;
     /** @hide Read media of image type. */
-    public static final int OP_READ_MEDIA_IMAGES = AppProtoEnums.APP_OP_READ_MEDIA_IMAGES;
+    public static final int OP_READ_MEDIA_IMAGES = AppOpEnums.APP_OP_READ_MEDIA_IMAGES;
     /** @hide Write media of image type. */
-    public static final int OP_WRITE_MEDIA_IMAGES = AppProtoEnums.APP_OP_WRITE_MEDIA_IMAGES;
+    public static final int OP_WRITE_MEDIA_IMAGES = AppOpEnums.APP_OP_WRITE_MEDIA_IMAGES;
     /** @hide Has a legacy (non-isolated) view of storage. */
-    public static final int OP_LEGACY_STORAGE = AppProtoEnums.APP_OP_LEGACY_STORAGE;
+    public static final int OP_LEGACY_STORAGE = AppOpEnums.APP_OP_LEGACY_STORAGE;
     /** @hide Accessing accessibility features */
-    public static final int OP_ACCESS_ACCESSIBILITY = AppProtoEnums.APP_OP_ACCESS_ACCESSIBILITY;
+    public static final int OP_ACCESS_ACCESSIBILITY = AppOpEnums.APP_OP_ACCESS_ACCESSIBILITY;
     /** @hide Read the device identifiers (IMEI / MEID, IMSI, SIM / Build serial) */
     public static final int OP_READ_DEVICE_IDENTIFIERS =
-            AppProtoEnums.APP_OP_READ_DEVICE_IDENTIFIERS;
+            AppOpEnums.APP_OP_READ_DEVICE_IDENTIFIERS;
     /** @hide Read location metadata from media */
-    public static final int OP_ACCESS_MEDIA_LOCATION = AppProtoEnums.APP_OP_ACCESS_MEDIA_LOCATION;
+    public static final int OP_ACCESS_MEDIA_LOCATION = AppOpEnums.APP_OP_ACCESS_MEDIA_LOCATION;
     /** @hide Query all apps on device, regardless of declarations in the calling app manifest */
-    public static final int OP_QUERY_ALL_PACKAGES = AppProtoEnums.APP_OP_QUERY_ALL_PACKAGES;
+    public static final int OP_QUERY_ALL_PACKAGES = AppOpEnums.APP_OP_QUERY_ALL_PACKAGES;
     /** @hide Access all external storage */
-    public static final int OP_MANAGE_EXTERNAL_STORAGE =
-            AppProtoEnums.APP_OP_MANAGE_EXTERNAL_STORAGE;
+    public static final int OP_MANAGE_EXTERNAL_STORAGE = AppOpEnums.APP_OP_MANAGE_EXTERNAL_STORAGE;
     /** @hide Communicate cross-profile within the same profile group. */
     public static final int OP_INTERACT_ACROSS_PROFILES =
-            AppProtoEnums.APP_OP_INTERACT_ACROSS_PROFILES;
+            AppOpEnums.APP_OP_INTERACT_ACROSS_PROFILES;
     /**
      * Start (without additional user intervention) a Platform VPN connection, as used by {@link
      * android.net.VpnManager}
@@ -1225,16 +1221,16 @@
      *
      * @hide
      */
-    public static final int OP_ACTIVATE_PLATFORM_VPN = AppProtoEnums.APP_OP_ACTIVATE_PLATFORM_VPN;
+    public static final int OP_ACTIVATE_PLATFORM_VPN = AppOpEnums.APP_OP_ACTIVATE_PLATFORM_VPN;
     /** @hide Controls whether or not read logs are available for incremental installations. */
-    public static final int OP_LOADER_USAGE_STATS = AppProtoEnums.APP_OP_LOADER_USAGE_STATS;
+    public static final int OP_LOADER_USAGE_STATS = AppOpEnums.APP_OP_LOADER_USAGE_STATS;
 
     // App op deprecated/removed.
-    private static final int OP_DEPRECATED_1 = AppProtoEnums.APP_OP_DEPRECATED_1;
+    private static final int OP_DEPRECATED_1 = AppOpEnums.APP_OP_DEPRECATED_1;
 
     /** @hide Auto-revoke app permissions if app is unused for an extended period */
     public static final int OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED =
-            AppProtoEnums.APP_OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED;
+            AppOpEnums.APP_OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED;
 
     /**
      * Whether {@link #OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED} is allowed to be changed by
@@ -1243,55 +1239,55 @@
      * @hide
      */
     public static final int OP_AUTO_REVOKE_MANAGED_BY_INSTALLER =
-            AppProtoEnums.APP_OP_AUTO_REVOKE_MANAGED_BY_INSTALLER;
+            AppOpEnums.APP_OP_AUTO_REVOKE_MANAGED_BY_INSTALLER;
 
     /** @hide */
-    public static final int OP_NO_ISOLATED_STORAGE = AppProtoEnums.APP_OP_NO_ISOLATED_STORAGE;
+    public static final int OP_NO_ISOLATED_STORAGE = AppOpEnums.APP_OP_NO_ISOLATED_STORAGE;
 
     /**
      * Phone call is using microphone
      *
      * @hide
      */
-    public static final int OP_PHONE_CALL_MICROPHONE = AppProtoEnums.APP_OP_PHONE_CALL_MICROPHONE;
+    public static final int OP_PHONE_CALL_MICROPHONE = AppOpEnums.APP_OP_PHONE_CALL_MICROPHONE;
     /**
      * Phone call is using camera
      *
      * @hide
      */
-    public static final int OP_PHONE_CALL_CAMERA = AppProtoEnums.APP_OP_PHONE_CALL_CAMERA;
+    public static final int OP_PHONE_CALL_CAMERA = AppOpEnums.APP_OP_PHONE_CALL_CAMERA;
 
     /**
      * Audio is being recorded for hotword detection.
      *
      * @hide
      */
-    public static final int OP_RECORD_AUDIO_HOTWORD = AppProtoEnums.APP_OP_RECORD_AUDIO_HOTWORD;
+    public static final int OP_RECORD_AUDIO_HOTWORD = AppOpEnums.APP_OP_RECORD_AUDIO_HOTWORD;
 
     /**
      * Manage credentials in the system KeyChain.
      *
      * @hide
      */
-    public static final int OP_MANAGE_CREDENTIALS = AppProtoEnums.APP_OP_MANAGE_CREDENTIALS;
+    public static final int OP_MANAGE_CREDENTIALS = AppOpEnums.APP_OP_MANAGE_CREDENTIALS;
 
     /** @hide */
     public static final int OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER =
-            AppProtoEnums.APP_OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER;
+            AppOpEnums.APP_OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER;
 
     /**
      * App output audio is being recorded
      *
      * @hide
      */
-    public static final int OP_RECORD_AUDIO_OUTPUT = AppProtoEnums.APP_OP_RECORD_AUDIO_OUTPUT;
+    public static final int OP_RECORD_AUDIO_OUTPUT = AppOpEnums.APP_OP_RECORD_AUDIO_OUTPUT;
 
     /**
      * App can schedule exact alarm to perform timing based background work
      *
      * @hide
      */
-    public static final int OP_SCHEDULE_EXACT_ALARM = AppProtoEnums.APP_OP_SCHEDULE_EXACT_ALARM;
+    public static final int OP_SCHEDULE_EXACT_ALARM = AppOpEnums.APP_OP_SCHEDULE_EXACT_ALARM;
 
     /**
      * Fine location being accessed by a location source, which is
@@ -1301,7 +1297,7 @@
      *
      * @hide
      */
-    public static final int OP_FINE_LOCATION_SOURCE = AppProtoEnums.APP_OP_FINE_LOCATION_SOURCE;
+    public static final int OP_FINE_LOCATION_SOURCE = AppOpEnums.APP_OP_FINE_LOCATION_SOURCE;
 
     /**
      * Coarse location being accessed by a location source, which is
@@ -1311,7 +1307,7 @@
      *
      * @hide
      */
-    public static final int OP_COARSE_LOCATION_SOURCE = AppProtoEnums.APP_OP_COARSE_LOCATION_SOURCE;
+    public static final int OP_COARSE_LOCATION_SOURCE = AppOpEnums.APP_OP_COARSE_LOCATION_SOURCE;
 
     /**
      * Allow apps to create the requests to manage the media files without user confirmation.
@@ -1323,13 +1319,13 @@
      *
      * @hide
      */
-    public static final int OP_MANAGE_MEDIA = AppProtoEnums.APP_OP_MANAGE_MEDIA;
+    public static final int OP_MANAGE_MEDIA = AppOpEnums.APP_OP_MANAGE_MEDIA;
 
     /** @hide */
-    public static final int OP_UWB_RANGING = AppProtoEnums.APP_OP_UWB_RANGING;
+    public static final int OP_UWB_RANGING = AppOpEnums.APP_OP_UWB_RANGING;
 
     /** @hide */
-    public static final int OP_NEARBY_WIFI_DEVICES = AppProtoEnums.APP_OP_NEARBY_WIFI_DEVICES;
+    public static final int OP_NEARBY_WIFI_DEVICES = AppOpEnums.APP_OP_NEARBY_WIFI_DEVICES;
 
     /**
      * Activity recognition being accessed by an activity recognition source, which
@@ -1339,7 +1335,7 @@
      * @hide
      */
     public static final int OP_ACTIVITY_RECOGNITION_SOURCE =
-            AppProtoEnums.APP_OP_ACTIVITY_RECOGNITION_SOURCE;
+            AppOpEnums.APP_OP_ACTIVITY_RECOGNITION_SOURCE;
 
     /**
      * Incoming phone audio is being recorded
@@ -1347,21 +1343,21 @@
      * @hide
      */
     public static final int OP_RECORD_INCOMING_PHONE_AUDIO =
-            AppProtoEnums.APP_OP_RECORD_INCOMING_PHONE_AUDIO;
+            AppOpEnums.APP_OP_RECORD_INCOMING_PHONE_AUDIO;
 
     /**
      * VPN app establishes a connection through the VpnService API.
      *
      * @hide
      */
-    public static final int OP_ESTABLISH_VPN_SERVICE = AppProtoEnums.APP_OP_ESTABLISH_VPN_SERVICE;
+    public static final int OP_ESTABLISH_VPN_SERVICE = AppOpEnums.APP_OP_ESTABLISH_VPN_SERVICE;
 
     /**
      * VPN app establishes a connection through the VpnManager API.
      *
      * @hide
      */
-    public static final int OP_ESTABLISH_VPN_MANAGER = AppProtoEnums.APP_OP_ESTABLISH_VPN_MANAGER;
+    public static final int OP_ESTABLISH_VPN_MANAGER = AppOpEnums.APP_OP_ESTABLISH_VPN_MANAGER;
 
     /**
      * Access restricted settings.
@@ -1369,7 +1365,7 @@
      * @hide
      */
     public static final int OP_ACCESS_RESTRICTED_SETTINGS =
-            AppProtoEnums.APP_OP_ACCESS_RESTRICTED_SETTINGS;
+            AppOpEnums.APP_OP_ACCESS_RESTRICTED_SETTINGS;
 
     /**
      * Receive microphone audio from an ambient sound detection event
@@ -1377,7 +1373,7 @@
      * @hide
      */
     public static final int OP_RECEIVE_AMBIENT_TRIGGER_AUDIO =
-            AppProtoEnums.APP_OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
+            AppOpEnums.APP_OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
 
      /**
       * Receive audio from near-field mic (ie. TV remote)
@@ -1387,15 +1383,14 @@
       * @hide
       */
     public static final int OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO =
-            AppProtoEnums.APP_OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO;
+            AppOpEnums.APP_OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO;
 
     /**
      * App can schedule user-initiated jobs.
      *
      * @hide
      */
-    public static final int OP_RUN_USER_INITIATED_JOBS =
-            AppProtoEnums.APP_OP_RUN_USER_INITIATED_JOBS;
+    public static final int OP_RUN_USER_INITIATED_JOBS = AppOpEnums.APP_OP_RUN_USER_INITIATED_JOBS;
 
     /**
      * Notify apps that they have been granted URI permission photos
@@ -1403,7 +1398,7 @@
      * @hide
      */
     public static final int OP_READ_MEDIA_VISUAL_USER_SELECTED =
-            AppProtoEnums.APP_OP_READ_MEDIA_VISUAL_USER_SELECTED;
+            AppOpEnums.APP_OP_READ_MEDIA_VISUAL_USER_SELECTED;
 
     /**
      * Prevent an app from being suspended.
@@ -1413,7 +1408,7 @@
      * @hide
      */
     public static final int OP_SYSTEM_EXEMPT_FROM_SUSPENSION =
-            AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_SUSPENSION;
+            AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_SUSPENSION;
 
     /**
      * Prevent an app from dismissible notifications. Starting from Android U, notifications with
@@ -1425,14 +1420,14 @@
      * @hide
      */
     public static final int OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS =
-            AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS;
+            AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS;
 
     /**
      * An app op for reading/writing health connect data.
      *
      * @hide
      */
-    public static final int OP_READ_WRITE_HEALTH_DATA = AppProtoEnums.APP_OP_READ_WRITE_HEALTH_DATA;
+    public static final int OP_READ_WRITE_HEALTH_DATA = AppOpEnums.APP_OP_READ_WRITE_HEALTH_DATA;
 
     /**
      * Use foreground service with the type
@@ -1441,7 +1436,7 @@
      * @hide
      */
     public static final int OP_FOREGROUND_SERVICE_SPECIAL_USE =
-            AppProtoEnums.APP_OP_FOREGROUND_SERVICE_SPECIAL_USE;
+            AppOpEnums.APP_OP_FOREGROUND_SERVICE_SPECIAL_USE;
 
     /**
      * Exempt an app from all power-related restrictions, including app standby and doze.
@@ -1453,7 +1448,7 @@
      * @hide
      */
     public static final int OP_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS =
-            AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS;
+            AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS;
 
     /**
      * Prevent an app from being placed into hibernation.
@@ -1463,7 +1458,7 @@
      * @hide
      */
     public static final int OP_SYSTEM_EXEMPT_FROM_HIBERNATION =
-            AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_HIBERNATION;
+            AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_HIBERNATION;
 
     /**
      * Allows an application to start an activity while running in the background.
@@ -1473,7 +1468,7 @@
      * @hide
      */
     public static final int OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION =
-            AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION;
+            AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION;
 
     /**
      * Allows an application to capture bugreport directly without consent dialog when using the
@@ -1482,33 +1477,31 @@
      * @hide
      */
     public static final int OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD =
-            AppProtoEnums.APP_OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD;
+            AppOpEnums.APP_OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD;
 
     // App op deprecated/removed.
-    private static final int OP_DEPRECATED_2 = AppProtoEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE;
+    private static final int OP_DEPRECATED_2 = AppOpEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE;
 
     /**
      * Send an intent to launch instead of posting the notification to the status bar.
      *
      * @hide
      */
-    public static final int OP_USE_FULL_SCREEN_INTENT = AppProtoEnums.APP_OP_USE_FULL_SCREEN_INTENT;
+    public static final int OP_USE_FULL_SCREEN_INTENT = AppOpEnums.APP_OP_USE_FULL_SCREEN_INTENT;
 
     /**
      * Hides camera indicator for sandboxed detection apps that directly access the service.
      *
      * @hide
      */
-    public static final int OP_CAMERA_SANDBOXED =
-            AppProtoEnums.APP_OP_CAMERA_SANDBOXED;
+    public static final int OP_CAMERA_SANDBOXED = AppOpEnums.APP_OP_CAMERA_SANDBOXED;
 
     /**
      * Hides microphone indicator for sandboxed detection apps that directly access the service.
      *
      * @hide
      */
-    public static final int OP_RECORD_AUDIO_SANDBOXED =
-            AppProtoEnums.APP_OP_RECORD_AUDIO_SANDBOXED;
+    public static final int OP_RECORD_AUDIO_SANDBOXED = AppOpEnums.APP_OP_RECORD_AUDIO_SANDBOXED;
 
     /**
      * Allows the assistant app to be voice-triggered by detected hotwords from a trusted detection
@@ -1517,14 +1510,14 @@
      * @hide
      */
     public static final int OP_RECEIVE_SANDBOX_TRIGGER_AUDIO =
-            AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
+            AppOpEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
 
     /**
      * This op has been deprecated.
      *
      */
     private static final int OP_DEPRECATED_3 =
-            AppProtoEnums.APP_OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA;
+            AppOpEnums.APP_OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA;
 
     /**
      * Creation of an overlay using accessibility services
@@ -1532,20 +1525,20 @@
      * @hide
      */
     public static final int OP_CREATE_ACCESSIBILITY_OVERLAY =
-            AppProtoEnums.APP_OP_CREATE_ACCESSIBILITY_OVERLAY;
+            AppOpEnums.APP_OP_CREATE_ACCESSIBILITY_OVERLAY;
 
     /**
      * Indicate that the user has enabled or disabled mobile data
      * @hide
      */
     public static final int OP_ENABLE_MOBILE_DATA_BY_USER =
-            AppProtoEnums.APP_OP_ENABLE_MOBILE_DATA_BY_USER;
+            AppOpEnums.APP_OP_ENABLE_MOBILE_DATA_BY_USER;
 
     /**
      * See {@link #OPSTR_MEDIA_ROUTING_CONTROL}.
      * @hide
      */
-    public static final int OP_MEDIA_ROUTING_CONTROL = AppProtoEnums.APP_OP_MEDIA_ROUTING_CONTROL;
+    public static final int OP_MEDIA_ROUTING_CONTROL = AppOpEnums.APP_OP_MEDIA_ROUTING_CONTROL;
 
     /**
      * Op code for use by tests to avoid interfering history logs that the wider system might
@@ -1553,7 +1546,7 @@
      *
      * @hide
      */
-    public static final int OP_RESERVED_FOR_TESTING = AppProtoEnums.APP_OP_RESERVED_FOR_TESTING;
+    public static final int OP_RESERVED_FOR_TESTING = AppOpEnums.APP_OP_RESERVED_FOR_TESTING;
 
     /**
      * Rapid clearing of notifications by a notification listener
@@ -1562,28 +1555,28 @@
      */
     // See b/289080543 for more details
     public static final int OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER =
-            AppProtoEnums.APP_OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER;
+            AppOpEnums.APP_OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER;
 
     /**
      * See {@link #OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER}.
      * @hide
      */
     public static final int OP_READ_SYSTEM_GRAMMATICAL_GENDER =
-            AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
+            AppOpEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
 
     /**
      * This app has been removed..
      *
      * @hide
      */
-    private static final int OP_DEPRECATED_4 = AppProtoEnums.APP_OP_RUN_BACKUP_JOBS;
+    private static final int OP_DEPRECATED_4 = AppOpEnums.APP_OP_RUN_BACKUP_JOBS;
 
     /**
      * Whether the app has enabled to receive the icon overlay for fetching archived apps.
      *
      * @hide
      */
-    public static final int OP_ARCHIVE_ICON_OVERLAY = AppProtoEnums.APP_OP_ARCHIVE_ICON_OVERLAY;
+    public static final int OP_ARCHIVE_ICON_OVERLAY = AppOpEnums.APP_OP_ARCHIVE_ICON_OVERLAY;
 
     /**
      * Whether the app has enabled compatibility support for unarchival.
@@ -1591,7 +1584,7 @@
      * @hide
      */
     public static final int OP_UNARCHIVAL_CONFIRMATION =
-            AppProtoEnums.APP_OP_UNARCHIVAL_CONFIRMATION;
+            AppOpEnums.APP_OP_UNARCHIVAL_CONFIRMATION;
 
     /**
      * Allows an app to access location without the traditional location permissions and while the
@@ -1603,7 +1596,7 @@
      *
      * @hide
      */
-    public static final int OP_EMERGENCY_LOCATION = AppProtoEnums.APP_OP_EMERGENCY_LOCATION;
+    public static final int OP_EMERGENCY_LOCATION = AppOpEnums.APP_OP_EMERGENCY_LOCATION;
 
     /**
      * Allows apps with a NotificationListenerService to receive notifications with sensitive
@@ -1613,14 +1606,24 @@
      * @hide
      */
     public static final int OP_RECEIVE_SENSITIVE_NOTIFICATIONS =
-            AppProtoEnums.APP_OP_RECEIVE_SENSITIVE_NOTIFICATIONS;
+            AppOpEnums.APP_OP_RECEIVE_SENSITIVE_NOTIFICATIONS;
 
     /** @hide Access to read heart rate sensor. */
-    public static final int OP_READ_HEART_RATE = AppProtoEnums.APP_OP_READ_HEART_RATE;
+    public static final int OP_READ_HEART_RATE = AppOpEnums.APP_OP_READ_HEART_RATE;
+
+    /** @hide Access to read skin temperature. */
+    public static final int OP_READ_SKIN_TEMPERATURE = 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 = 150;
+    public static final int _NUM_OP = 152;
 
     /**
      * All app ops represented as strings.
@@ -1774,6 +1777,8 @@
             OPSTR_EMERGENCY_LOCATION,
             OPSTR_RECEIVE_SENSITIVE_NOTIFICATIONS,
             OPSTR_READ_HEART_RATE,
+            OPSTR_READ_SKIN_TEMPERATURE,
+            OPSTR_RANGING,
     })
     public @interface AppOpString {}
 
@@ -2516,6 +2521,16 @@
     @FlaggedApi(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
     public static final String OPSTR_READ_HEART_RATE = "android:read_heart_rate";
 
+    /** @hide Access to read skin temperature. */
+    @SystemApi
+    @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} */
@@ -2587,10 +2602,12 @@
             OP_BLUETOOTH_ADVERTISE,
             OP_UWB_RANGING,
             OP_NEARBY_WIFI_DEVICES,
+            Flags.rangingPermissionEnabled() ? OP_RANGING : OP_NONE,
             // Notifications
             OP_POST_NOTIFICATION,
             // Health
             Flags.replaceBodySensorPermissionEnabled() ? OP_READ_HEART_RATE : OP_NONE,
+            Flags.platformSkinTemperatureEnabled() ? OP_READ_SKIN_TEMPERATURE : OP_NONE,
     };
 
     /**
@@ -3103,6 +3120,15 @@
             .setPermission(Flags.replaceBodySensorPermissionEnabled() ?
                 HealthPermissions.READ_HEART_RATE : null)
             .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_READ_SKIN_TEMPERATURE, OPSTR_READ_SKIN_TEMPERATURE,
+            "READ_SKIN_TEMPERATURE").setPermission(
+                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
@@ -7792,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;
@@ -8846,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();
         }
@@ -9036,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,
@@ -9279,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));
             }
@@ -9319,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/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 003104a..ec7b72e 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -242,6 +242,9 @@
 
     boolean supportsLocalVoiceInteraction();
 
+    // Sets whether system educational dialogs should be limited
+    void setLimitSystemEducationDialogs(IBinder appToken, boolean limitSystemEducationDialogs);
+
     // Get device configuration
     ConfigurationInfo getDeviceConfigurationInfo();
 
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/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 5acb9b5..f693e9b 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -24,6 +24,8 @@
 import android.app.IWallpaperManagerCallback;
 import android.app.ILocalWallpaperColorConsumer;
 import android.app.WallpaperInfo;
+import android.app.wallpaper.WallpaperDescription;
+import android.app.wallpaper.WallpaperInstance;
 import android.content.ComponentName;
 import android.app.WallpaperColors;
 
@@ -61,8 +63,8 @@
     /**
      * Set the live wallpaper.
      */
-    void setWallpaperComponentChecked(in ComponentName name, in String callingPackage, int which,
-            int userId);
+    void setWallpaperComponentChecked(in WallpaperDescription description, in String callingPackage,
+            int which, int userId);
 
     /**
      * Set the live wallpaper. This only affects the system wallpaper.
@@ -129,6 +131,13 @@
      */
     WallpaperInfo getWallpaperInfoWithFlags(int which, int userId);
 
+   /**
+    * Return the instance information about the wallpaper described by `which`, or null if lock
+    * screen is requested and it is the same as home.
+    */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL)")
+    WallpaperInstance getWallpaperInstance(int which, int userId);
+
     /**
      * Return a file descriptor for the file that contains metadata about the given user's
      * wallpaper.
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 26bfec3..f432a22 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -17,6 +17,7 @@
 package android.app;
 
 import static android.text.TextUtils.formatSimple;
+import static com.android.internal.util.Preconditions.checkArgumentPositive;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -40,6 +41,7 @@
 
 import dalvik.annotation.optimization.CriticalNative;
 import dalvik.annotation.optimization.FastNative;
+import dalvik.annotation.optimization.NeverCompile;
 
 import java.io.ByteArrayOutputStream;
 import java.io.FileOutputStream;
@@ -201,6 +203,23 @@
     }
 
     /**
+     * The list of known and legal modules.  The list is not sorted.
+     */
+    private static final String[] sValidModule = {
+        MODULE_SYSTEM, MODULE_BLUETOOTH, MODULE_TELEPHONY, MODULE_TEST,
+    };
+
+    /**
+     * Verify that the module string is in the legal list.  Throw if it is not.
+     */
+    private static void throwIfInvalidModule(@NonNull String name) {
+        for (int i = 0; i < sValidModule.length; i++) {
+            if (sValidModule[i].equals(name)) return;
+        }
+        throw new IllegalArgumentException("invalid module: " + name);
+    }
+
+    /**
      * All legal keys start with one of the following strings.
      */
     private static final String[] sValidKeyPrefix = {
@@ -254,8 +273,11 @@
     // written to global store.
     private static final int NONCE_BYPASS = 3;
 
+    // The largest reserved nonce value.  Update this whenever a reserved nonce is added.
+    private static final int MAX_RESERVED_NONCE = NONCE_BYPASS;
+
     private static boolean isReservedNonce(long n) {
-        return n >= NONCE_UNSET && n <= NONCE_BYPASS;
+        return n >= NONCE_UNSET && n <= MAX_RESERVED_NONCE;
     }
 
     /**
@@ -293,7 +315,7 @@
     private long mMisses = 0;
 
     @GuardedBy("mLock")
-    private long[] mSkips = new long[]{ 0, 0, 0, 0 };
+    private long[] mSkips = new long[MAX_RESERVED_NONCE + 1];
 
     @GuardedBy("mLock")
     private long mMissOverflow = 0;
@@ -695,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;
@@ -714,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);
         }
     }
 
@@ -811,10 +846,20 @@
         return false; // Always disable shared memory on Ravenwood. (for now)
     }
 
+    /**
+     * Keys that cannot be put in shared memory yet.
+     */
+    private static boolean inSharedMemoryDenyList(@NonNull String name) {
+        final String pkginfo = PREFIX_SYSTEM + "package_info";
+        return name.equals(pkginfo);
+    };
+
     // Return true if this cache can use shared memory for its nonce.  Shared memory may be used
     // if the module is the system.
     private static boolean sharedMemoryOkay(@NonNull String name) {
-        return sSharedMemoryAvailable && name.startsWith(PREFIX_SYSTEM);
+        return sSharedMemoryAvailable
+                && name.startsWith(PREFIX_SYSTEM)
+                && !inSharedMemoryDenyList(name);
     }
 
     /**
@@ -844,6 +889,73 @@
     }
 
     /**
+     * A public argument builder to configure cache behavior.  The root instance requires a
+     * module; this is immutable.  New instances are created with member methods.  It is important
+     * to note that the member methods create new instances: they do not modify 'this'.  The api
+     * is allowed to be null in the record constructor to facility reuse of Args instances.
+     * @hide
+     */
+    public static record Args(@NonNull String mModule, @Nullable String mApi, int mMaxEntries) {
+
+        // Validation: the module must be one of the known module strings and the maxEntries must
+        // be positive.
+        public Args {
+            throwIfInvalidModule(mModule);
+            checkArgumentPositive(mMaxEntries, "max cache size must be positive");
+        }
+
+        // The base constructor must include the module.  Modules do not change in a source file,
+        // so even if the Args is reused, the module will not/should not change.  The api is null,
+        // which is not legal, but there is no reasonable default.  Clients must call the api
+        // method to set the field properly.
+        public Args(@NonNull String module) {
+            this(module, /* api */ null, /* maxEntries */ 32);
+        }
+
+        public Args api(@NonNull String api) {
+            return new Args(mModule, api, mMaxEntries);
+        }
+
+        public Args maxEntries(int val) {
+            return new Args(mModule, mApi, val);
+        }
+    }
+
+    /**
+     * Make a new property invalidated cache.  The key is computed from the module and api
+     * parameters.
+     *
+     * @param args The cache configuration.
+     * @param cacheName Name of this cache in debug and dumpsys
+     * @param computer The code to compute values that are not in the cache.
+     * @hide
+     */
+    public PropertyInvalidatedCache(@NonNull Args args, @NonNull String cacheName,
+            @Nullable QueryHandler<Query, Result> computer) {
+        mPropertyName = createPropertyName(args.mModule, args.mApi);
+        mCacheName = cacheName;
+        mNonce = getNonceHandler(mPropertyName);
+        mMaxEntries = args.mMaxEntries;
+        mCache = createMap();
+        mComputer = (computer != null) ? computer : new DefaultComputer<>(this);
+        registerCache();
+    }
+
+    /**
+     * Burst a property name into module and api.  Throw if the key is invalid.  This method is
+     * used in to transition legacy cache constructors to the args constructor.
+     */
+    private static Args parseProperty(@NonNull String name) {
+        throwIfInvalidCacheKey(name);
+        // Strip off the leading well-known prefix.
+        String base = name.substring(CACHE_KEY_PREFIX.length() + 1);
+        int dot = base.indexOf(".");
+        String module = base.substring(0, dot);
+        String api = base.substring(dot + 1);
+        return new Args(module).api(api);
+    }
+
+    /**
      * Make a new property invalidated cache.  This constructor names the cache after the
      * property name.  New clients should prefer the constructor that takes an explicit
      * cache name.
@@ -857,7 +969,7 @@
      * @hide
      */
     public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) {
-        this(maxEntries, propertyName, propertyName);
+        this(parseProperty(propertyName).maxEntries(maxEntries), propertyName, null);
     }
 
     /**
@@ -873,13 +985,7 @@
      */
     public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName,
             @NonNull String cacheName) {
-        mPropertyName = propertyName;
-        mCacheName = cacheName;
-        mNonce = getNonceHandler(mPropertyName);
-        mMaxEntries = maxEntries;
-        mComputer = new DefaultComputer<>(this);
-        mCache = createMap();
-        registerCache();
+        this(parseProperty(propertyName).maxEntries(maxEntries), cacheName, null);
     }
 
     /**
@@ -897,13 +1003,7 @@
     @TestApi
     public PropertyInvalidatedCache(int maxEntries, @NonNull String module, @NonNull String api,
             @NonNull String cacheName, @NonNull QueryHandler<Query, Result> computer) {
-        mPropertyName = createPropertyName(module, api);
-        mCacheName = cacheName;
-        mNonce = getNonceHandler(mPropertyName);
-        mMaxEntries = maxEntries;
-        mComputer = computer;
-        mCache = createMap();
-        registerCache();
+        this(new Args(module).maxEntries(maxEntries).api(api), cacheName, computer);
     }
 
     // Create a map.  This should be called only from the constructor.
@@ -1171,7 +1271,8 @@
     public @Nullable Result query(@NonNull Query query) {
         // Let access to mDisabled race: it's atomic anyway.
         long currentNonce = (!isDisabled()) ? getCurrentNonce() : NONCE_DISABLED;
-        if (bypass(query)) {
+        if (!isReservedNonce(currentNonce)
+            && bypass(query)) {
             currentNonce = NONCE_BYPASS;
         }
         for (;;) {
@@ -1639,54 +1740,62 @@
         return false;
     }
 
-    /**
-     * helper method to check if dump should be skipped due to zero values
-     * @param args takes command arguments to check if -brief is present
-     * @return True if dump should be skipped
-     */
-    private boolean skipDump(String[] args) {
-        for (String a : args) {
-            if (a.equals(BRIEF)) {
-                return (mSkips[NONCE_CORKED] + mSkips[NONCE_UNSET] + mSkips[NONCE_DISABLED]
-                      + mSkips[NONCE_BYPASS] + mHits + mMisses) == 0;
-            }
+    @GuardedBy("mLock")
+    private long getSkipsLocked() {
+        int sum = 0;
+        for (int i = 0; i < mSkips.length; i++) {
+            sum += mSkips[i];
         }
-        return false;
+        return sum;
     }
 
+    // Return true if this cache has had any activity.  If the hits, misses, and skips are all
+    // zero then the client never tried to use the cache.
+    private boolean isActive() {
+        synchronized (mLock) {
+            return mHits + mMisses + getSkipsLocked() > 0;
+        }
+    }
+
+    @NeverCompile
     private void dumpContents(PrintWriter pw, boolean detailed, String[] args) {
         // If the user has requested specific caches and this is not one of them, return
         // immediately.
         if (detailed && !showDetailed(args)) {
             return;
         }
+        // Does the user want brief output?
+        boolean brief = false;
+        for (String a : args) brief |= a.equals(BRIEF);
 
         NonceHandler.Stats stats = mNonce.getStats();
 
         synchronized (mLock) {
-            if (!skipDump(args)) {
-                pw.println(formatSimple("  Cache Name: %s", cacheName()));
-                pw.println(formatSimple("    Property: %s", mPropertyName));
-                final long skips =
-                        mSkips[NONCE_CORKED] + mSkips[NONCE_UNSET] + mSkips[NONCE_DISABLED]
-                                + mSkips[NONCE_BYPASS];
-                pw.println(formatSimple(
-                        "    Hits: %d, Misses: %d, Skips: %d, Clears: %d",
-                        mHits, mMisses, skips, mClears));
-                pw.println(formatSimple(
-                        "    Skip-corked: %d, Skip-unset: %d, Skip-bypass: %d, Skip-other: %d",
-                        mSkips[NONCE_CORKED], mSkips[NONCE_UNSET],
-                        mSkips[NONCE_BYPASS], mSkips[NONCE_DISABLED]));
-                pw.println(formatSimple(
-                        "    Nonce: 0x%016x, Invalidates: %d, CorkedInvalidates: %d",
-                        mLastSeenNonce, stats.invalidated, stats.corkedInvalidates));
-                pw.println(formatSimple(
-                        "    Current Size: %d, Max Size: %d, HW Mark: %d, Overflows: %d",
-                        mCache.size(), mMaxEntries, mHighWaterMark, mMissOverflow));
-                pw.println(formatSimple("    Enabled: %s", mDisabled ? "false" : "true"));
-                pw.println("");
+            if (brief && !isActive()) {
+                return;
             }
 
+            pw.println(formatSimple("  Cache Name: %s", cacheName()));
+            pw.println(formatSimple("    Property: %s", mPropertyName));
+            pw.println(formatSimple(
+                "    Hits: %d, Misses: %d, Skips: %d, Clears: %d",
+                mHits, mMisses, getSkipsLocked(), mClears));
+
+            // Print all the skip reasons.
+            pw.format("    Skip-%s: %d", sNonceName[0], mSkips[0]);
+            for (int i = 1; i < mSkips.length; i++) {
+                pw.format(", Skip-%s: %d", sNonceName[i], mSkips[i]);
+            }
+            pw.println();
+
+            pw.println(formatSimple(
+                "    Nonce: 0x%016x, Invalidates: %d, CorkedInvalidates: %d",
+                mLastSeenNonce, stats.invalidated, stats.corkedInvalidates));
+            pw.println(formatSimple(
+                "    Current Size: %d, Max Size: %d, HW Mark: %d, Overflows: %d",
+                mCache.size(), mMaxEntries, mHighWaterMark, mMissOverflow));
+            pw.println(formatSimple("    Enabled: %s", mDisabled ? "false" : "true"));
+
             // No specific cache was requested.  This is the default, and no details
             // should be dumped.
             if (!detailed) {
@@ -1713,6 +1822,7 @@
      * specific caches (selection is by cache name or property name); if these switches
      * are used then the output includes both cache statistics and cache entries.
      */
+    @NeverCompile
     private static void dumpCacheInfo(@NonNull PrintWriter pw, @NonNull String[] args) {
         if (!sEnabled) {
             pw.println("  Caching is disabled in this process.");
@@ -1745,6 +1855,7 @@
      * are used then the output includes both cache statistics and cache entries.
      * @hide
      */
+    @NeverCompile
     public static void dumpCacheInfo(@NonNull ParcelFileDescriptor pfd, @NonNull String[] args) {
         // Create a PrintWriter that uses a byte array.  The code can safely write to
         // this array without fear of blocking.  The completed byte array will be sent
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 63e03914..b7285c3 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -602,6 +602,15 @@
     @LoggingOnly
     private static final long MEDIA_CONTROL_BLANK_TITLE = 274775190L;
 
+    /**
+     * Media controls based on {@link android.app.Notification.MediaStyle} notifications will have
+     * actions from the associated {@link androidx.media3.MediaController}, if available.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT)
+    // TODO(b/360196209): Set target SDK to Baklava once available
+    private static final long MEDIA_CONTROL_MEDIA3_ACTIONS = 360196209L;
+
     @UnsupportedAppUsage
     private Context mContext;
     private IStatusBarService mService;
@@ -1270,6 +1279,21 @@
     }
 
     /**
+     * Checks whether the media controls for a given package should use a Media3 controller
+     *
+     * @param packageName App posting media controls
+     * @param user Current user handle
+     * @return true if Media3 should be used
+     *
+     * @hide
+     */
+    @RequiresPermission(allOf = {android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
+            android.Manifest.permission.LOG_COMPAT_CHANGE})
+    public static boolean useMedia3ControllerForApp(String packageName, UserHandle user) {
+        return CompatChanges.isChangeEnabled(MEDIA_CONTROL_MEDIA3_ACTIONS, packageName, user);
+    }
+
+    /**
      * Checks whether the supplied activity can {@link Activity#startActivityForResult(Intent, int)}
      * a system activity that captures content on the screen to take a screenshot.
      *
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 081ce31..aac963a 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -296,6 +296,12 @@
     public boolean isVisibleRequested;
 
     /**
+     * Whether the top activity is to be displayed. See {@link android.R.attr#windowNoDisplay}.
+     * @hide
+     */
+    public boolean isTopActivityNoDisplay;
+
+    /**
      * Whether this task is sleeping due to sleeping display.
      * @hide
      */
@@ -308,12 +314,6 @@
     public boolean isTopActivityTransparent;
 
     /**
-     * Whether the top activity has specified style floating.
-     * @hide
-     */
-    public boolean isTopActivityStyleFloating;
-
-    /**
      * The last non-fullscreen bounds the task was launched in or resized to.
      * @hide
      */
@@ -340,6 +340,12 @@
     public int requestedVisibleTypes;
 
     /**
+     * Whether the top activity has requested to limit educational dialogs shown by the system.
+     * @hide
+     */
+    public boolean isTopActivityLimitSystemEducationDialogs;
+
+    /**
      * Encapsulate specific App Compat information.
      * @hide
      */
@@ -358,8 +364,9 @@
         // Do nothing
     }
 
-    private TaskInfo(Parcel source) {
-        readFromParcel(source);
+    /** @hide */
+    public TaskInfo(Parcel source) {
+        readTaskFromParcel(source);
     }
 
     /**
@@ -476,16 +483,18 @@
                 && isFocused == that.isFocused
                 && isVisible == that.isVisible
                 && isVisibleRequested == that.isVisibleRequested
+                && isTopActivityNoDisplay == that.isTopActivityNoDisplay
                 && isSleeping == that.isSleeping
                 && Objects.equals(mTopActivityLocusId, that.mTopActivityLocusId)
                 && parentTaskId == that.parentTaskId
                 && Objects.equals(topActivity, that.topActivity)
                 && isTopActivityTransparent == that.isTopActivityTransparent
-                && isTopActivityStyleFloating == that.isTopActivityStyleFloating
-                && lastNonFullscreenBounds == this.lastNonFullscreenBounds
+                && Objects.equals(lastNonFullscreenBounds, that.lastNonFullscreenBounds)
                 && Objects.equals(capturedLink, that.capturedLink)
                 && capturedLinkTimestamp == that.capturedLinkTimestamp
                 && requestedVisibleTypes == that.requestedVisibleTypes
+                && isTopActivityLimitSystemEducationDialogs
+                    == that.isTopActivityLimitSystemEducationDialogs
                 && appCompatTaskInfo.equalsForTaskOrganizer(that.appCompatTaskInfo)
                 && Objects.equals(topActivityMainWindowFrame, that.topActivityMainWindowFrame);
     }
@@ -516,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();
@@ -553,23 +562,25 @@
         isFocused = source.readBoolean();
         isVisible = source.readBoolean();
         isVisibleRequested = source.readBoolean();
+        isTopActivityNoDisplay = source.readBoolean();
         isSleeping = source.readBoolean();
         mTopActivityLocusId = source.readTypedObject(LocusId.CREATOR);
         displayAreaFeatureId = source.readInt();
         isTopActivityTransparent = source.readBoolean();
-        isTopActivityStyleFloating = source.readBoolean();
         lastNonFullscreenBounds = source.readTypedObject(Rect.CREATOR);
         capturedLink = source.readTypedObject(Uri.CREATOR);
         capturedLinkTimestamp = source.readLong();
         requestedVisibleTypes = source.readInt();
+        isTopActivityLimitSystemEducationDialogs = source.readBoolean();
         appCompatTaskInfo = source.readTypedObject(AppCompatTaskInfo.CREATOR);
         topActivityMainWindowFrame = source.readTypedObject(Rect.CREATOR);
     }
 
     /**
      * 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);
@@ -607,15 +618,16 @@
         dest.writeBoolean(isFocused);
         dest.writeBoolean(isVisible);
         dest.writeBoolean(isVisibleRequested);
+        dest.writeBoolean(isTopActivityNoDisplay);
         dest.writeBoolean(isSleeping);
         dest.writeTypedObject(mTopActivityLocusId, flags);
         dest.writeInt(displayAreaFeatureId);
         dest.writeBoolean(isTopActivityTransparent);
-        dest.writeBoolean(isTopActivityStyleFloating);
         dest.writeTypedObject(lastNonFullscreenBounds, flags);
         dest.writeTypedObject(capturedLink, flags);
         dest.writeLong(capturedLinkTimestamp);
         dest.writeInt(requestedVisibleTypes);
+        dest.writeBoolean(isTopActivityLimitSystemEducationDialogs);
         dest.writeTypedObject(appCompatTaskInfo, flags);
         dest.writeTypedObject(topActivityMainWindowFrame, flags);
     }
@@ -651,15 +663,17 @@
                 + " isFocused=" + isFocused
                 + " isVisible=" + isVisible
                 + " isVisibleRequested=" + isVisibleRequested
+                + " isTopActivityNoDisplay=" + isTopActivityNoDisplay
                 + " isSleeping=" + isSleeping
                 + " locusId=" + mTopActivityLocusId
                 + " displayAreaFeatureId=" + displayAreaFeatureId
                 + " isTopActivityTransparent=" + isTopActivityTransparent
-                + " isTopActivityStyleFloating=" + isTopActivityStyleFloating
                 + " lastNonFullscreenBounds=" + lastNonFullscreenBounds
                 + " capturedLink=" + capturedLink
                 + " capturedLinkTimestamp=" + capturedLinkTimestamp
                 + " requestedVisibleTypes=" + requestedVisibleTypes
+                + " isTopActivityLimitSystemEducationDialogs="
+                + isTopActivityLimitSystemEducationDialogs
                 + " appCompatTaskInfo=" + appCompatTaskInfo
                 + " topActivityMainWindowFrame=" + topActivityMainWindowFrame
                 + "}";
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 014e4660..479f3df 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -21,10 +21,12 @@
 import static android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING;
 
 import static com.android.window.flags.Flags.FLAG_MULTI_CROP;
 import static com.android.window.flags.Flags.multiCrop;
 
+import android.Manifest;
 import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
@@ -39,6 +41,8 @@
 import android.annotation.TestApi;
 import android.annotation.UiContext;
 import android.app.compat.CompatChanges;
+import android.app.wallpaper.WallpaperDescription;
+import android.app.wallpaper.WallpaperInstance;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -319,12 +323,12 @@
      * This is only used internally by the framework and the WallpaperBackupAgent.
      * @hide
      */
-    @IntDef(value = {
+    @IntDef(prefix = { "ORIENTATION_" }, value = {
             ORIENTATION_UNKNOWN,
-            PORTRAIT,
-            LANDSCAPE,
-            SQUARE_PORTRAIT,
-            SQUARE_LANDSCAPE,
+            ORIENTATION_PORTRAIT,
+            ORIENTATION_LANDSCAPE,
+            ORIENTATION_SQUARE_PORTRAIT,
+            ORIENTATION_SQUARE_LANDSCAPE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ScreenOrientation {}
@@ -338,25 +342,25 @@
      * Portrait orientation of most screens
      * @hide
      */
-    public static final int PORTRAIT = 0;
+    public static final int ORIENTATION_PORTRAIT = 0;
 
     /**
      * Landscape orientation of most screens
      * @hide
      */
-    public static final int LANDSCAPE = 1;
+    public static final int ORIENTATION_LANDSCAPE = 1;
 
     /**
      * Portrait orientation with similar width and height (e.g. the inner screen of a foldable)
      * @hide
      */
-    public static final int SQUARE_PORTRAIT = 2;
+    public static final int ORIENTATION_SQUARE_PORTRAIT = 2;
 
     /**
      * Landscape orientation with similar width and height (e.g. the inner screen of a foldable)
      * @hide
      */
-    public static final int SQUARE_LANDSCAPE = 3;
+    public static final int ORIENTATION_SQUARE_LANDSCAPE = 3;
 
     /**
      * Converts a (width, height) screen size to a {@link ScreenOrientation}.
@@ -367,10 +371,10 @@
     public static @ScreenOrientation int getOrientation(Point screenSize) {
         float ratio = ((float) screenSize.x) / screenSize.y;
         // ratios between 3/4 and 4/3 are considered square
-        return ratio >= 4 / 3f ? LANDSCAPE
-                : ratio > 1f ? SQUARE_LANDSCAPE
-                : ratio > 3 / 4f ? SQUARE_PORTRAIT
-                : PORTRAIT;
+        return ratio >= 4 / 3f ? ORIENTATION_LANDSCAPE
+                : ratio > 1f ? ORIENTATION_SQUARE_LANDSCAPE
+                : ratio > 3 / 4f ? ORIENTATION_SQUARE_PORTRAIT
+                : ORIENTATION_PORTRAIT;
     }
 
     /**
@@ -379,10 +383,10 @@
      */
     public static @ScreenOrientation int getRotatedOrientation(@ScreenOrientation int orientation) {
         switch (orientation) {
-            case PORTRAIT: return LANDSCAPE;
-            case LANDSCAPE: return PORTRAIT;
-            case SQUARE_PORTRAIT: return SQUARE_LANDSCAPE;
-            case SQUARE_LANDSCAPE: return SQUARE_PORTRAIT;
+            case ORIENTATION_PORTRAIT: return ORIENTATION_LANDSCAPE;
+            case ORIENTATION_LANDSCAPE: return ORIENTATION_PORTRAIT;
+            case ORIENTATION_SQUARE_PORTRAIT: return ORIENTATION_SQUARE_LANDSCAPE;
+            case ORIENTATION_SQUARE_LANDSCAPE: return ORIENTATION_SQUARE_PORTRAIT;
             default: return ORIENTATION_UNKNOWN;
         }
     }
@@ -2023,6 +2027,34 @@
     }
 
     /**
+     * Returns the description of the designated wallpaper. Returns null if the lock screen
+     * wallpaper is requested lock screen wallpaper is not set.
+
+     * @param which Specifies wallpaper to request (home or lock).
+     * @throws IllegalArgumentException if {@code which} is not exactly one of
+     * {{@link #FLAG_SYSTEM},{@link #FLAG_LOCK}}.
+     *
+     * @hide
+     */
+    @Nullable
+    @FlaggedApi(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+    @RequiresPermission(READ_WALLPAPER_INTERNAL)
+    @SystemApi
+    public WallpaperInstance getWallpaperInstance(@SetWallpaperFlags int which) {
+        checkExactlyOneWallpaperFlagSet(which);
+        try {
+            if (sGlobals.mService == null) {
+                Log.w(TAG, "WallpaperService not running");
+                throw new RuntimeException(new DeadSystemException());
+            } else {
+                return sGlobals.mService.getWallpaperInstance(which, mContext.getUserId());
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Get an open, readable file descriptor for the file that contains metadata about the
      * context user's wallpaper.
      *
@@ -2955,15 +2987,66 @@
      *
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT)
+    @RequiresPermission(allOf = {android.Manifest.permission.SET_WALLPAPER_COMPONENT,
+            Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional = true)
     public boolean setWallpaperComponentWithFlags(@NonNull ComponentName name,
             @SetWallpaperFlags int which, int userId) {
+        WallpaperDescription description = new WallpaperDescription.Builder().setComponent(
+                name).build();
+        return setWallpaperComponentWithDescription(description, which, userId);
+    }
+
+    /**
+     * Set the implementation of {@link android.service.wallpaper.WallpaperService} used to render
+     * wallpaper, along with associated metadata.
+     *
+     * <p>This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT
+     * permission. The caller must hold the INTERACT_ACROSS_USERS_FULL permission to change
+     * another user's wallpaper.
+     * </p>
+     *
+     * @param description wallpaper component and metadata to set
+     * @param which Specifies wallpaper destination (home and/or lock).
+     * @return true on success, otherwise false
+     *
+     * @hide
+     */
+    @FlaggedApi(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+    @SystemApi
+    @RequiresPermission(allOf = {android.Manifest.permission.SET_WALLPAPER_COMPONENT,
+            Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional = true)
+    public boolean setWallpaperComponentWithDescription(@NonNull WallpaperDescription description,
+            @SetWallpaperFlags int which) {
+        return setWallpaperComponentWithDescription(description, which, mContext.getUserId());
+    }
+
+    /**
+     * Set the implementation of {@link android.service.wallpaper.WallpaperService} used to render
+     * wallpaper, along with associated metadata.
+     *
+     * <p>This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT
+     * permission. The caller must hold the INTERACT_ACROSS_USERS_FULL permission to change
+     * another user's wallpaper.
+     * </p>
+     *
+     * @param description wallpaper component and metadata to set
+     * @param which Specifies wallpaper destination (home and/or lock).
+     * @param userId User for whom the component should be set.
+     * @return true on success, otherwise false
+     *
+     * @hide
+     */
+    @FlaggedApi(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+    @RequiresPermission(allOf = {android.Manifest.permission.SET_WALLPAPER_COMPONENT,
+            Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional = true)
+    public boolean setWallpaperComponentWithDescription(@NonNull WallpaperDescription description,
+            @SetWallpaperFlags int which, int userId) {
         if (sGlobals.mService == null) {
             Log.w(TAG, "WallpaperManagerService not running");
             throw new RuntimeException(new DeadSystemException());
         }
         try {
-            sGlobals.mService.setWallpaperComponentChecked(name, mContext.getOpPackageName(),
+            sGlobals.mService.setWallpaperComponentChecked(description, mContext.getOpPackageName(),
                     which, userId);
             return true;
         } catch (RemoteException e) {
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/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java
index 74cae07..5ddb590 100644
--- a/core/java/android/app/appfunctions/AppFunctionManager.java
+++ b/core/java/android/app/appfunctions/AppFunctionManager.java
@@ -53,8 +53,8 @@
  * offers a more convenient and type-safe way to build app functions. The SDK provides predefined
  * function schemas for common use cases and associated data classes for function parameters and
  * return values. Apps only have to implement the provided interfaces. Internally, the SDK converts
- * these data classes into {@link ExecuteAppFunctionRequest#getParameters()} and
- * {@link ExecuteAppFunctionResponse#getResultDocument()}.
+ * these data classes into {@link ExecuteAppFunctionRequest#getParameters()} and {@link
+ * ExecuteAppFunctionResponse#getResultDocument()}.
  *
  * <p>**Discovering App Functions:**
  *
@@ -69,13 +69,12 @@
  * <p>**Executing App Functions:**
  *
  * <p>To execute an app function, the caller app can retrieve the {@code functionIdentifier} from
- * the {@code AppFunctionStaticMetadata} document and use it to build an
- * {@link ExecuteAppFunctionRequest}. Then, invoke {@link #executeAppFunction} with the request
- * to execute the app function. Callers need the {@code android.permission.EXECUTE_APP_FUNCTIONS}
- * or {@code  android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} permission to execute app
- * functions from other apps. An app can always execute its own app functions and doesn't need these
- * permissions. AppFunction SDK provides a convenient way to achieve this and is the preferred
- * method.
+ * the {@code AppFunctionStaticMetadata} document and use it to build an {@link
+ * ExecuteAppFunctionRequest}. Then, invoke {@link #executeAppFunction} with the request to execute
+ * the app function. Callers need the {@code android.permission.EXECUTE_APP_FUNCTIONS} or {@code
+ * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} permission to execute app functions from other
+ * apps. An app can always execute its own app functions and doesn't need these permissions.
+ * AppFunction SDK provides a convenient way to achieve this and is the preferred method.
  *
  * <p>**Example:**
  *
@@ -213,12 +212,13 @@
     /**
      * Returns a boolean through a callback, indicating whether the app function is enabled.
      *
-     * <p>* This method can only check app functions owned by the caller, or those where the caller
+     * <p>This method can only check app functions owned by the caller, or those where the caller
      * has visibility to the owner package and holds either the {@link
      * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link
      * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission.
      *
-     * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
+     * <p>If the operation fails, the callback's {@link OutcomeReceiver#onError} is called with
+     * errors:
      *
      * <ul>
      *   <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
@@ -232,23 +232,47 @@
      * @param executor the executor to run the request
      * @param callback the callback to receive the function enabled check result
      */
+    @RequiresPermission(
+            anyOf = {
+                Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
+                Manifest.permission.EXECUTE_APP_FUNCTIONS
+            },
+            conditional = true)
     public void isAppFunctionEnabled(
             @NonNull String functionIdentifier,
             @NonNull String targetPackage,
             @NonNull Executor executor,
             @NonNull OutcomeReceiver<Boolean, Exception> callback) {
-        Objects.requireNonNull(functionIdentifier);
-        Objects.requireNonNull(targetPackage);
-        Objects.requireNonNull(executor);
-        Objects.requireNonNull(callback);
-        AppSearchManager appSearchManager = mContext.getSystemService(AppSearchManager.class);
-        if (appSearchManager == null) {
-            callback.onError(new IllegalStateException("Failed to get AppSearchManager."));
-            return;
-        }
+        isAppFunctionEnabledInternal(functionIdentifier, targetPackage, executor, callback);
+    }
 
-        AppFunctionManagerHelper.isAppFunctionEnabled(
-                functionIdentifier, targetPackage, appSearchManager, executor, callback);
+    /**
+     * Returns a boolean through a callback, indicating whether the app function is enabled.
+     *
+     * <p>This method can only check app functions owned by the caller, unlike {@link
+     * #isAppFunctionEnabled(String, String, Executor, OutcomeReceiver)}, which allows specifying a
+     * different target package.
+     *
+     * <p>If the operation fails, the callback's {@link OutcomeReceiver#onError} is called with
+     * errors:
+     *
+     * <ul>
+     *   <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
+     *       have access to it.
+     * </ul>
+     *
+     * @param functionIdentifier the identifier of the app function to check (unique within the
+     *     target package) and in most cases, these are automatically generated by the AppFunctions
+     *     SDK
+     * @param executor the executor to run the request
+     * @param callback the callback to receive the function enabled check result
+     */
+    public void isAppFunctionEnabled(
+            @NonNull String functionIdentifier,
+            @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Boolean, Exception> callback) {
+        isAppFunctionEnabledInternal(
+                functionIdentifier, mContext.getPackageName(), executor, callback);
     }
 
     /**
@@ -291,6 +315,25 @@
         }
     }
 
+    private void isAppFunctionEnabledInternal(
+            @NonNull String functionIdentifier,
+            @NonNull String targetPackage,
+            @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Boolean, Exception> callback) {
+        Objects.requireNonNull(functionIdentifier);
+        Objects.requireNonNull(targetPackage);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        AppSearchManager appSearchManager = mContext.getSystemService(AppSearchManager.class);
+        if (appSearchManager == null) {
+            callback.onError(new IllegalStateException("Failed to get AppSearchManager."));
+            return;
+        }
+
+        AppFunctionManagerHelper.isAppFunctionEnabled(
+                functionIdentifier, targetPackage, appSearchManager, executor, callback);
+    }
+
     private static class CallbackWrapper extends IAppFunctionEnabledCallback.Stub {
 
         private final OutcomeReceiver<Void, Exception> mCallback;
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index 83453a9..cdf02e6 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -73,38 +73,102 @@
      */
     public static final String PROPERTY_RETURN_VALUE = "returnValue";
 
-    /** The call was successful. */
+    /**
+     * The call was successful.
+     *
+     * <p>This result code does not belong in an error category.
+     */
     public static final int RESULT_OK = 0;
 
-    /** The caller does not have the permission to execute an app function. */
-    public static final int RESULT_DENIED = 1;
+    /**
+     * The caller does not have the permission to execute an app function.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+     */
+    public static final int RESULT_DENIED = 1000;
+
+    /**
+     * The caller supplied invalid arguments to the execution request.
+     *
+     * <p>This error may be considered similar to {@link IllegalArgumentException}.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+     */
+    public static final int RESULT_INVALID_ARGUMENT = 1001;
+
+    /**
+     * The caller tried to execute a disabled app function.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+     */
+    public static final int RESULT_DISABLED = 1002;
+
+    /**
+     * The caller tried to execute a function that does not exist.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+     */
+    public static final int RESULT_FUNCTION_NOT_FOUND = 1003;
+
+    /**
+     * An internal unexpected error coming from the system.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
+     */
+    public static final int RESULT_SYSTEM_ERROR = 2000;
+
+    /**
+     * The operation was cancelled. Use this error code to report that a cancellation is done after
+     * receiving a cancellation signal.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
+     */
+    public static final int RESULT_CANCELLED = 2001;
 
     /**
      * An unknown error occurred while processing the call in the AppFunctionService.
      *
      * <p>This error is thrown when the service is connected in the remote application but an
      * unexpected error is thrown from the bound application.
-     */
-    public static final int RESULT_APP_UNKNOWN_ERROR = 2;
-
-    /** An internal unexpected error coming from the system. */
-    public static final int RESULT_INTERNAL_ERROR = 3;
-
-    /**
-     * The caller supplied invalid arguments to the call.
      *
-     * <p>This error may be considered similar to {@link IllegalArgumentException}.
+     * <p>This error is in the {@link #ERROR_CATEGORY_APP} category.
      */
-    public static final int RESULT_INVALID_ARGUMENT = 4;
-
-    /** The caller tried to execute a disabled app function. */
-    public static final int RESULT_DISABLED = 5;
+    public static final int RESULT_APP_UNKNOWN_ERROR = 3000;
 
     /**
-     * The operation was cancelled. Use this error code to report that a cancellation is done after
-     * receiving a cancellation signal.
+     * The error category is unknown.
+     *
+     * <p>This is the default value for {@link #getErrorCategory}.
      */
-    public static final int RESULT_CANCELLED = 6;
+    public static final int ERROR_CATEGORY_UNKNOWN = 0;
+
+    /**
+     * The error is caused by the app requesting a function execution.
+     *
+     * <p>For example, the caller provided invalid parameters in the execution request e.g. an
+     * invalid function ID.
+     *
+     * <p>Errors in the category fall in the range 1000-1999 inclusive.
+     */
+    public static final int ERROR_CATEGORY_REQUEST_ERROR = 1;
+
+    /**
+     * The error is caused by an issue in the system.
+     *
+     * <p>For example, the AppFunctionService implementation is not found by the system.
+     *
+     * <p>Errors in the category fall in the range 2000-2999 inclusive.
+     */
+    public static final int ERROR_CATEGORY_SYSTEM = 2;
+
+    /**
+     * The error is caused by the app providing the function.
+     *
+     * <p>For example, the app crashed when the system is executing the request.
+     *
+     * <p>Errors in the category fall in the range 3000-3999 inclusive.
+     */
+    public static final int ERROR_CATEGORY_APP = 3;
 
     /** The result code of the app function execution. */
     @ResultCode private final int mResultCode;
@@ -198,6 +262,36 @@
     }
 
     /**
+     * Returns the error category of the {@link ExecuteAppFunctionResponse}.
+     *
+     * <p>This method categorizes errors based on their underlying cause, allowing developers to
+     * implement targeted error handling and provide more informative error messages to users. It
+     * maps ranges of result codes to specific error categories.
+     *
+     * <p>When constructing a {@link #newFailure} response, use the appropriate result code value to
+     * ensure correct categorization of the failed response.
+     *
+     * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the result code does not belong to
+     * any error category, for example, in the case of a successful result with {@link #RESULT_OK}.
+     *
+     * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding
+     * result code ranges.
+     */
+    @ErrorCategory
+    public int getErrorCategory() {
+        if (mResultCode >= 1000 && mResultCode < 2000) {
+            return ERROR_CATEGORY_REQUEST_ERROR;
+        }
+        if (mResultCode >= 2000 && mResultCode < 3000) {
+            return ERROR_CATEGORY_SYSTEM;
+        }
+        if (mResultCode >= 3000 && mResultCode < 4000) {
+            return ERROR_CATEGORY_APP;
+        }
+        return ERROR_CATEGORY_UNKNOWN;
+    }
+
+    /**
      * Returns a generic document containing the return value of the executed function.
      *
      * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value.
@@ -280,11 +374,28 @@
                 RESULT_OK,
                 RESULT_DENIED,
                 RESULT_APP_UNKNOWN_ERROR,
-                RESULT_INTERNAL_ERROR,
+                RESULT_FUNCTION_NOT_FOUND,
+                RESULT_SYSTEM_ERROR,
                 RESULT_INVALID_ARGUMENT,
                 RESULT_DISABLED,
                 RESULT_CANCELLED
             })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ResultCode {}
+
+    /**
+     * Error categories.
+     *
+     * @hide
+     */
+    @IntDef(
+            prefix = {"ERROR_CATEGORY_"},
+            value = {
+                ERROR_CATEGORY_UNKNOWN,
+                ERROR_CATEGORY_REQUEST_ERROR,
+                ERROR_CATEGORY_APP,
+                ERROR_CATEGORY_SYSTEM
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ErrorCategory {}
 }
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/jank/JankDataProcessor.java b/core/java/android/app/jank/JankDataProcessor.java
new file mode 100644
index 0000000..981a916
--- /dev/null
+++ b/core/java/android/app/jank/JankDataProcessor.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.jank;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.app.jank.StateTracker.StateData;
+import android.util.Log;
+import android.util.Pools.SimplePool;
+import android.view.SurfaceControl.JankData;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * This class is responsible for associating frames received from SurfaceFlinger to active widget
+ * states and logging those states back to the platform.
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+public class JankDataProcessor {
+
+    private static final int MAX_IN_MEMORY_STATS = 25;
+    private static final int LOG_BATCH_FREQUENCY = 50;
+    private int mCurrentBatchCount = 0;
+    private StateTracker mStateTracker = null;
+    private ArrayList<StateData> mPendingStates = new ArrayList<>();
+    private SimplePool<PendingJankStat> mPendingJankStatsPool =
+            new SimplePool<>(MAX_IN_MEMORY_STATS);
+    private HashMap<String, PendingJankStat> mPendingJankStats = new HashMap<>();
+
+    public JankDataProcessor(@NonNull StateTracker stateTracker) {
+        mStateTracker = stateTracker;
+    }
+
+    /**
+     * Called once per batch of JankData.
+     * @param jankData data received from SurfaceFlinger to be processed
+     * @param activityName name of the activity that is tracking jank metrics.
+     * @param appUid the uid of the app.
+     */
+    public void processJankData(List<JankData> jankData, String activityName, int appUid) {
+        mCurrentBatchCount++;
+        // add all the previous and active states to the pending states list.
+        mStateTracker.retrieveAllStates(mPendingStates);
+
+        // TODO b/376332122 Look to see if this logic can be optimized.
+        for (int i = 0; i < jankData.size(); i++) {
+            JankData frame = jankData.get(i);
+            // for each frame we need to check if the state was active during that time.
+            for (int j = 0; j < mPendingStates.size(); j++) {
+                StateData pendingState = mPendingStates.get(j);
+                // This state was active during the frame
+                if (frame.frameVsyncId >= pendingState.mVsyncIdStart
+                        && frame.frameVsyncId <= pendingState.mVsyncIdEnd) {
+                    recordFrameCount(frame, pendingState, activityName, appUid);
+
+                    pendingState.mProcessed = true;
+                }
+            }
+        }
+        // At this point we have attributed all frames to a state.
+        if (mCurrentBatchCount >= LOG_BATCH_FREQUENCY) {
+            logMetricCounts();
+        }
+        // return the StatData object back to the pool to be reused.
+        jankDataProcessingComplete();
+    }
+
+    /**
+     * Returns the aggregate map of different pending jank stats.
+     */
+    @VisibleForTesting
+    public HashMap<String, PendingJankStat> getPendingJankStats() {
+        return mPendingJankStats;
+    }
+
+    private void jankDataProcessingComplete() {
+        mStateTracker.stateProcessingComplete();
+        mPendingStates.clear();
+    }
+
+    /**
+     * Determine if frame is Janky and add to existing memory counter or create a new one.
+     */
+    private void recordFrameCount(JankData frameData, StateData stateData, String activityName,
+            int appUid) {
+        // Check if we have an existing Jank state
+        PendingJankStat jankStats = mPendingJankStats.get(stateData.mStateDataKey);
+
+        if (jankStats == null) {
+            // Check if we have space for another pending stat
+            if (mPendingJankStats.size() > MAX_IN_MEMORY_STATS) {
+                return;
+            }
+
+            jankStats = mPendingJankStatsPool.acquire();
+            if (jankStats == null) {
+                jankStats = new PendingJankStat();
+            }
+            jankStats.clearStats();
+            jankStats.mActivityName = activityName;
+            jankStats.mUid = appUid;
+            mPendingJankStats.put(stateData.mStateDataKey, jankStats);
+        }
+        // This state has already been accounted for
+        if (jankStats.processedVsyncId == frameData.frameVsyncId) return;
+
+        jankStats.mTotalFrames += 1;
+        if (frameData.jankType == JankData.JANK_APPLICATION) {
+            jankStats.mJankyFrames += 1;
+        }
+        jankStats.recordFrameOverrun(frameData.actualAppFrameTimeNs);
+        jankStats.processedVsyncId = frameData.frameVsyncId;
+
+    }
+
+    /**
+     * When called will log pending Jank stats currently stored in memory to the platform. Will not
+     * clear any pending widget states.
+     */
+    public void logMetricCounts() {
+        //TODO b/374607503 when api changes are in add enum mapping for category and state.
+
+        try {
+            mPendingJankStats.values().forEach(stat -> {
+                        FrameworkStatsLog.write(FrameworkStatsLog.JANK_FRAME_COUNT_BY_WIDGET,
+                                /*app uid*/ stat.getUid(),
+                                /*activity name*/ stat.getActivityName(),
+                                /*widget id*/ stat.getWidgetId(),
+                                /*refresh rate*/ stat.getRefreshRate(),
+                                /*widget category*/ 0,
+                                /*widget state*/ 0,
+                                /*total frames*/ stat.getTotalFrames(),
+                                /*janky frames*/ stat.getJankyFrames(),
+                                /*histogram*/ stat.mFrameOverrunBuckets);
+                        Log.d(stat.mActivityName, stat.toString());
+                        // return the pending stat to the pool it will be reset the next time its
+                        // used.
+                        mPendingJankStatsPool.release(stat);
+                    }
+            );
+            // All stats have been recorded and added back to the pool for reuse, clear the pending
+            // stats.
+            mPendingJankStats.clear();
+            mCurrentBatchCount = 0;
+        } catch (Exception exception) {
+            // TODO b/374608358 handle logging exceptions.
+        }
+    }
+
+    public static final class PendingJankStat {
+        private static final int NANOS_PER_MS = 1000000;
+        public long processedVsyncId = -1;
+
+        // UID of the app
+        private int mUid;
+
+        // The name of the activity that is currently collecting frame metrics.
+        private String mActivityName;
+
+        // The id that has been set for the widget.
+        private String mWidgetId;
+
+        // A general category that the widget applies to.
+        private String mWidgetCategory;
+
+        // The states that the UI elements can report
+        private String mWidgetState;
+
+        // The number of frames reported during this state.
+        private long mTotalFrames;
+
+        // Total number of frames determined to be janky during the reported state.
+        private long mJankyFrames;
+
+        private int mRefreshRate;
+
+        private static final int[] sFrameOverrunHistogramBounds =  {
+                Integer.MIN_VALUE, -200, -150, -100, -90, -80, -70, -60, -50, -40, -30, -25, -20,
+                -18, -16, -14, -12, -10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 25,
+                30, 40, 50, 60, 70, 80, 90, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000
+        };
+        private final int[] mFrameOverrunBuckets = new int[sFrameOverrunHistogramBounds.length];
+
+        // Histogram of frame duration overruns encoded in predetermined buckets.
+        public PendingJankStat() {
+        }
+        public long getProcessedVsyncId() {
+            return processedVsyncId;
+        }
+
+        public void setProcessedVsyncId(long processedVsyncId) {
+            this.processedVsyncId = processedVsyncId;
+        }
+
+        public int getUid() {
+            return mUid;
+        }
+
+        public void setUid(int uid) {
+            mUid = uid;
+        }
+
+        public String getActivityName() {
+            return mActivityName;
+        }
+
+        public void setActivityName(String activityName) {
+            mActivityName = activityName;
+        }
+
+        public String getWidgetId() {
+            return mWidgetId;
+        }
+
+        public void setWidgetId(String widgetId) {
+            mWidgetId = widgetId;
+        }
+
+        public String getWidgetCategory() {
+            return mWidgetCategory;
+        }
+
+        public void setWidgetCategory(String widgetCategory) {
+            mWidgetCategory = widgetCategory;
+        }
+
+        public String getWidgetState() {
+            return mWidgetState;
+        }
+
+        public void setWidgetState(String widgetState) {
+            mWidgetState = widgetState;
+        }
+
+        public long getTotalFrames() {
+            return mTotalFrames;
+        }
+
+        public void setTotalFrames(long totalFrames) {
+            mTotalFrames = totalFrames;
+        }
+
+        public long getJankyFrames() {
+            return mJankyFrames;
+        }
+
+        public void setJankyFrames(long jankyFrames) {
+            mJankyFrames = jankyFrames;
+        }
+
+        public int[] getFrameOverrunBuckets() {
+            return mFrameOverrunBuckets;
+        }
+
+        public int getRefreshRate() {
+            return mRefreshRate;
+        }
+
+        public void setRefreshRate(int refreshRate) {
+            mRefreshRate = refreshRate;
+        }
+
+        /**
+         * Will convert the frame time from ns to ms and record how long the frame took to render.
+         */
+        public void recordFrameOverrun(long frameTimeNano) {
+            try {
+                // TODO b/375650163 calculate frame overrun from refresh rate.
+                int frameTimeMillis = (int) frameTimeNano / NANOS_PER_MS;
+                mFrameOverrunBuckets[indexForFrameOverrun(frameTimeMillis)]++;
+            } catch (IndexOutOfBoundsException exception) {
+                // TODO b/375650163 figure out how to handle this if it happens.
+            }
+        }
+
+        /**
+         * resets all fields in the object back to defaults.
+         */
+        public void clearStats() {
+            this.mUid = -1;
+            this.mActivityName = "";
+            this.processedVsyncId = -1;
+            this.mJankyFrames = 0;
+            this.mTotalFrames = 0;
+            this.mWidgetCategory = "";
+            this.mWidgetState = "";
+            this.mRefreshRate = 0;
+            clearHistogram();
+        }
+
+        private void clearHistogram() {
+            for (int i = 0; i < mFrameOverrunBuckets.length; i++) {
+                mFrameOverrunBuckets[i] = 0;
+            }
+        }
+
+        // This takes the overrun time and returns what bucket it belongs to in the histogram.
+        private int indexForFrameOverrun(int overrunTime) {
+            if (overrunTime < 20) {
+                if (overrunTime >= -20) {
+                    return (overrunTime + 20) / 2 + 12;
+                }
+                if (overrunTime >= -30) {
+                    return (overrunTime + 30) / 5 + 10;
+                }
+                if (overrunTime >= -100) {
+                    return (overrunTime + 100) / 10 + 3;
+                }
+                if (overrunTime >= -200) {
+                    return (overrunTime + 200) / 50 + 1;
+                }
+                return 0;
+            }
+            if (overrunTime < 30) {
+                return (overrunTime - 20) / 5 + 32;
+            }
+            if (overrunTime < 100) {
+                return (overrunTime - 30) / 10 + 34;
+            }
+            if (overrunTime < 200) {
+                return (overrunTime - 50) / 100 + 41;
+            }
+            if (overrunTime < 1000) {
+                return (overrunTime - 200) / 100 + 43;
+            }
+            return sFrameOverrunHistogramBounds.length - 1;
+        }
+
+    }
+}
diff --git a/core/java/android/app/jank/JankTracker.java b/core/java/android/app/jank/JankTracker.java
new file mode 100644
index 0000000..df422e0
--- /dev/null
+++ b/core/java/android/app/jank/JankTracker.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.jank;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.view.AttachedSurfaceControl;
+import android.view.Choreographer;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+
+/**
+ * This class is responsible for registering callbacks that will receive JankData batches.
+ * It handles managing the background thread that JankData will be processed on. As well as acting
+ * as an intermediary between widgets and the state tracker, routing state changes to the tracker.
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+public class JankTracker {
+
+    // Tracks states reported by widgets.
+    private StateTracker mStateTracker;
+    // Processes JankData batches and associates frames to widget states.
+    private JankDataProcessor mJankDataProcessor;
+
+    // Background thread responsible for processing JankData batches.
+    private HandlerThread mHandlerThread = new HandlerThread("AppJankTracker");
+    private Handler mHandler = null;
+
+    // Needed so we know when the view is attached to a window.
+    private ViewTreeObserver mViewTreeObserver;
+
+    // Handle to a registered OnJankData listener.
+    private SurfaceControl.OnJankDataListenerRegistration mJankDataListenerRegistration;
+
+    // The interface to the windowing system that enables us to register for JankData.
+    private AttachedSurfaceControl mSurfaceControl;
+    // Name of the activity that is currently tracking Jank metrics.
+    private String mActivityName;
+    // The apps uid.
+    private int mAppUid;
+    // View that gives us access to ViewTreeObserver.
+    private View mDecorView;
+
+    /**
+     * Set by the activity to enable or disable jank tracking. Activities may disable tracking if
+     * they are paused or not enable tracking if they are not visible or if the app category is not
+     * set.
+     */
+    private boolean mTrackingEnabled = false;
+    /**
+     * Set to true once listeners are registered and JankData will start to be received. Both
+     * mTrackingEnabled and mListenersRegistered need to be true for JankData to be processed.
+     */
+    private boolean mListenersRegistered = false;
+
+
+    public JankTracker(Choreographer choreographer, View decorView) {
+        mStateTracker = new StateTracker(choreographer);
+        mJankDataProcessor = new JankDataProcessor(mStateTracker);
+        mDecorView = decorView;
+        mHandlerThread.start();
+        registerWindowListeners();
+    }
+
+    public void setActivityName(@NonNull String activityName) {
+        mActivityName = activityName;
+    }
+
+    public void setAppUid(int uid) {
+        mAppUid = uid;
+    }
+
+    /**
+     * Will add the widget category, id and state as a UI state to associate frames to it.
+     * @param widgetCategory preselected general widget category
+     * @param widgetId developer defined widget id if available.
+     * @param widgetState the current active widget state.
+     */
+    public void addUiState(String widgetCategory, String widgetId, String widgetState) {
+        if (!shouldTrack()) return;
+
+        mStateTracker.putState(widgetCategory, widgetId, widgetState);
+    }
+
+    /**
+     * Will remove the widget category, id and state as a ui state and no longer attribute frames
+     * to it.
+     * @param widgetCategory preselected general widget category
+     * @param widgetId developer defined widget id if available.
+     * @param widgetState no longer active widget state.
+     */
+    public void removeUiState(String widgetCategory, String widgetId, String widgetState) {
+        if (!shouldTrack()) return;
+
+        mStateTracker.removeState(widgetCategory, widgetId, widgetState);
+    }
+
+    /**
+     * Call to update a jank state to a different state.
+     * @param widgetCategory preselected general widget category.
+     * @param widgetId developer defined widget id if available.
+     * @param currentState current state of the widget.
+     * @param nextState the state the widget will be in.
+     */
+    public void updateUiState(String widgetCategory, String widgetId, String currentState,
+            String nextState) {
+        if (!shouldTrack()) return;
+
+        mStateTracker.updateState(widgetCategory, widgetId, currentState, nextState);
+    }
+
+    /**
+     * Will enable jank tracking, and add the activity as a state to associate frames to.
+     */
+    public void enableAppJankTracking() {
+        // Add the activity as a state, this will ensure we track frames to the activity without the
+        // need of a decorated widget to be used.
+        // TODO b/376116199 replace "NONE" with UNSPECIFIED once the API changes are merged.
+        mStateTracker.putState("NONE", mActivityName, "NONE");
+        mTrackingEnabled = true;
+    }
+
+    /**
+     * Will disable jank tracking, and remove the activity as a state to associate frames to.
+     */
+    public void disableAppJankTracking() {
+        mTrackingEnabled = false;
+        // TODO b/376116199 replace "NONE" with UNSPECIFIED once the API changes are merged.
+        mStateTracker.removeState("NONE", mActivityName, "NONE");
+    }
+
+    /**
+     * Retrieve all pending widget states, this is intended for testing purposes only.
+     * @param stateDataList the ArrayList that will be populated with the pending states.
+     */
+    @VisibleForTesting
+    public void getAllUiStates(@NonNull ArrayList<StateTracker.StateData> stateDataList) {
+        mStateTracker.retrieveAllStates(stateDataList);
+    }
+
+    /**
+     * Only intended to be used by tests, the runnable that registers the listeners may not run
+     * in time for tests to pass. This forces them to run immediately.
+     */
+    @VisibleForTesting
+    public void forceListenerRegistration() {
+        mSurfaceControl = mDecorView.getRootSurfaceControl();
+        registerForJankData();
+        // TODO b/376116199 Check if registration is good.
+        mListenersRegistered = true;
+    }
+
+    private void registerForJankData() {
+        if (mSurfaceControl == null) return;
+        /*
+        TODO b/376115668 Register for JankData batches from new JankTracking API
+         */
+    }
+
+    private boolean shouldTrack() {
+        return mTrackingEnabled && mListenersRegistered;
+    }
+
+    /**
+     * Need to know when the decor view gets attached to the window in order to get
+     * AttachedSurfaceControl. In order to register a callback for OnJankDataListener
+     * AttachedSurfaceControl needs to be created which only happens after onWindowAttached is
+     * called. This is why there is a delay in posting the runnable.
+     */
+    private void registerWindowListeners() {
+        if (mDecorView == null) return;
+        mViewTreeObserver = mDecorView.getViewTreeObserver();
+        mViewTreeObserver.addOnWindowAttachListener(new ViewTreeObserver.OnWindowAttachListener() {
+            @Override
+            public void onWindowAttached() {
+                getHandler().postDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        forceListenerRegistration();
+                    }
+                }, 1000);
+            }
+
+            @Override
+            public void onWindowDetached() {
+                // TODO b/376116199  do we un-register the callback or just not process the data.
+            }
+        });
+    }
+
+    private Handler getHandler() {
+        if (mHandler == null) {
+            mHandler = new Handler(mHandlerThread.getLooper());
+        }
+        return mHandler;
+    }
+}
diff --git a/core/java/android/app/jank/StateTracker.java b/core/java/android/app/jank/StateTracker.java
index cb457ff..c86d5a5 100644
--- a/core/java/android/app/jank/StateTracker.java
+++ b/core/java/android/app/jank/StateTracker.java
@@ -36,7 +36,6 @@
  * @hide
  */
 @FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
-@VisibleForTesting
 public class StateTracker {
 
     // Used to synchronize access to mPreviousStates.
@@ -188,7 +187,6 @@
     /**
      * @hide
      */
-    @VisibleForTesting
     public static class StateData {
 
         // Concatenated string of widget category, widget state and widget id.
diff --git a/core/java/android/app/performance.aconfig b/core/java/android/app/performance.aconfig
index 7484a85..f51f748 100644
--- a/core/java/android/app/performance.aconfig
+++ b/core/java/android/app/performance.aconfig
@@ -7,7 +7,7 @@
      is_exported: true
      is_fixed_read_only: true
      description: "PropertyInvalidatedCache uses shared memory for nonces."
-     bug: "366552454"
+     bug: "360897450"
 }
 
 flag {
@@ -16,5 +16,5 @@
      is_exported: true
      is_fixed_read_only: true
      description: "Enforce PropertyInvalidatedCache.setTestMode() protocol"
-     bug: "366552454"
+     bug: "360897450"
 }
diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl
index 740f593..730bb73 100644
--- a/core/java/android/app/trust/ITrustManager.aidl
+++ b/core/java/android/app/trust/ITrustManager.aidl
@@ -41,5 +41,6 @@
     void unlockedByBiometricForUser(int userId, in BiometricSourceType source);
     void clearAllBiometricRecognized(in BiometricSourceType target, int unlockedUser);
     boolean isActiveUnlockRunning(int userId);
+    @EnforcePermission("ACCESS_FINE_LOCATION")
     boolean isInSignificantPlace();
 }
diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java
index 88d4d69..1ef83cd 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -305,6 +305,7 @@
      *
      * @hide
      */
+    @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION)
     public boolean isInSignificantPlace() {
         try {
             return mService.isInSignificantPlace();
diff --git a/core/java/android/app/wallpaper/WallpaperDescription.java b/core/java/android/app/wallpaper/WallpaperDescription.java
index dedcb48..8ffda72 100644
--- a/core/java/android/app/wallpaper/WallpaperDescription.java
+++ b/core/java/android/app/wallpaper/WallpaperDescription.java
@@ -255,7 +255,7 @@
         mContent = PersistableBundle.CREATOR.createFromParcel(in);
     }
 
-    @Nullable
+    @NonNull
     public static final Creator<WallpaperDescription> CREATOR = new Creator<>() {
         @Override
         public WallpaperDescription createFromParcel(Parcel source) {
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index 72f992a..79eeaa9 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -306,6 +306,38 @@
     }
 
     /**
+     * Returns an {@link IntentSender} for starting the configuration activity for the widget.
+     *
+     * @return The {@link IntentSender} or null if service is currently unavailable
+     *
+     * @throws android.content.ActivityNotFoundException If configuration activity is not found.
+     *
+     * @see #startAppWidgetConfigureActivityForResult
+     *
+     * @hide
+     */
+    @Nullable
+    public final IntentSender getIntentSenderForConfigureActivity(int appWidgetId,
+            int intentFlags)  {
+        if (sService == null) {
+            return null;
+        }
+
+        IntentSender intentSender;
+        try {
+            intentSender = sService.createAppWidgetConfigIntentSender(mContextOpPackageName,
+                    appWidgetId, intentFlags);
+        } catch (RemoteException e) {
+            throw new RuntimeException("system server dead?", e);
+        }
+
+        if (intentSender == null) {
+            throw new ActivityNotFoundException();
+        }
+        return intentSender;
+    }
+
+    /**
      * Starts an app widget provider configure activity for result on behalf of the caller.
      * Use this method if the provider is in another profile as you are not allowed to start
      * an activity in another profile. You can optionally provide a request code that is
@@ -330,18 +362,11 @@
             return;
         }
         try {
-            IntentSender intentSender = sService.createAppWidgetConfigIntentSender(
-                    mContextOpPackageName, appWidgetId, intentFlags);
-            if (intentSender != null) {
-                activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0,
-                        options);
-            } else {
-                throw new ActivityNotFoundException();
-            }
+            IntentSender intentSender = getIntentSenderForConfigureActivity(appWidgetId,
+                    intentFlags);
+            activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0, options);
         } catch (IntentSender.SendIntentException e) {
             throw new ActivityNotFoundException();
-        } catch (RemoteException e) {
-            throw new RuntimeException("system server dead?", e);
         }
     }
 
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index d8142fd..8f20ea0 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -246,7 +246,8 @@
      * this widget. Can have the value {@link
      * AppWidgetProviderInfo#WIDGET_CATEGORY_HOME_SCREEN} or {@link
      * AppWidgetProviderInfo#WIDGET_CATEGORY_KEYGUARD} or {@link
-     * AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX}.
+     * AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX} or {@link
+     * AppWidgetProviderInfo#WIDGET_CATEGORY_NOT_KEYGUARD}.
      */
     public static final String OPTION_APPWIDGET_HOST_CATEGORY = "appWidgetCategory";
 
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index 1a80cac2..75c76d1 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -93,11 +93,26 @@
      */
     public static final int WIDGET_CATEGORY_SEARCHBOX = 4;
 
+    /**
+     * Indicates that the widget should never be shown on the keyguard.
+     *
+     * <p>Some keyguard style features may decide that {@link #WIDGET_CATEGORY_KEYGUARD} isn't
+     * required to be added by an app to show on the feature when chosen by a user.
+     * This category allows for a stronger statement about placement of the widget that, even in the
+     * above case, this widget should not be offered on the keyguard.
+     *
+     * <p>Setting this category doesn't change the behavior of AppWidgetManager queries, it is the
+     * responsibility of the widget surface to respect this value.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_NOT_KEYGUARD_CATEGORY)
+    public static final int WIDGET_CATEGORY_NOT_KEYGUARD = 8;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "FLAG_" }, value = {
             WIDGET_CATEGORY_HOME_SCREEN,
             WIDGET_CATEGORY_KEYGUARD,
             WIDGET_CATEGORY_SEARCHBOX,
+            WIDGET_CATEGORY_NOT_KEYGUARD,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CategoryFlags {}
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index 3839b5f..ce51576 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -55,7 +55,7 @@
   name: "remote_views_proto"
   namespace: "app_widgets"
   description: "Enable support for persisting RemoteViews previews to Protobuf"
-  bug: "306546610"
+  bug: "364420494"
 }
 
 flag {
@@ -83,3 +83,12 @@
   is_exported: true
   is_fixed_read_only: true
 }
+
+flag {
+  name: "not_keyguard_category"
+  namespace: "app_widgets"
+  description: "Adds the NOT_KEYGUARD category to AppWidgetInfo categories"
+  bug: "365169792"
+  is_exported: true
+  is_fixed_read_only: true
+}
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 65f9cbe..2be27da 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -160,7 +160,7 @@
      */
     @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS, POLICY_TYPE_AUDIO,
             POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY, POLICY_TYPE_CLIPBOARD, POLICY_TYPE_CAMERA,
-            POLICY_TYPE_BLOCKED_ACTIVITY})
+            POLICY_TYPE_BLOCKED_ACTIVITY, POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS})
     @Retention(RetentionPolicy.SOURCE)
     @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
     public @interface PolicyType {}
@@ -301,6 +301,21 @@
     @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
     public static final int POLICY_TYPE_BLOCKED_ACTIVITY = 6;
 
+    /**
+     * Tells the virtual device framework how to handle camera access of the default device by apps
+     * running on the virtual device.
+     *
+     * <ul>
+     *     <li>{@link #DEVICE_POLICY_DEFAULT}: Default device camera access will be allowed.
+     *     <li>{@link #DEVICE_POLICY_CUSTOM}: Default device camera access will be blocked.
+     * </ul>
+     *
+     * @see Context#DEVICE_ID_DEFAULT
+     */
+    @FlaggedApi(android.companion.virtualdevice.flags.Flags
+            .FLAG_DEFAULT_DEVICE_CAMERA_ACCESS_POLICY)
+    public static final int POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS = 7;
+
     private final int mLockState;
     @NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
     @NavigationPolicy
@@ -318,6 +333,8 @@
     @Nullable private final IVirtualSensorCallback mVirtualSensorCallback;
     private final int mAudioPlaybackSessionId;
     private final int mAudioRecordingSessionId;
+    private final long mDimDuration;
+    private final long mScreenOffTimeout;
 
     private VirtualDeviceParams(
             @LockState int lockState,
@@ -333,7 +350,9 @@
             @NonNull List<VirtualSensorConfig> virtualSensorConfigs,
             @Nullable IVirtualSensorCallback virtualSensorCallback,
             int audioPlaybackSessionId,
-            int audioRecordingSessionId) {
+            int audioRecordingSessionId,
+            long dimDuration,
+            long screenOffTimeout) {
         mLockState = lockState;
         mUsersWithMatchingAccounts =
                 new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts));
@@ -351,6 +370,8 @@
         mVirtualSensorCallback = virtualSensorCallback;
         mAudioPlaybackSessionId = audioPlaybackSessionId;
         mAudioRecordingSessionId = audioRecordingSessionId;
+        mDimDuration = dimDuration;
+        mScreenOffTimeout = screenOffTimeout;
     }
 
     @SuppressWarnings("unchecked")
@@ -371,6 +392,8 @@
         mAudioRecordingSessionId = parcel.readInt();
         mHomeComponent = parcel.readTypedObject(ComponentName.CREATOR);
         mInputMethodComponent = parcel.readTypedObject(ComponentName.CREATOR);
+        mDimDuration = parcel.readLong();
+        mScreenOffTimeout = parcel.readLong();
     }
 
     /**
@@ -382,6 +405,26 @@
     }
 
     /**
+     * Returns the dim duration for the displays of this device.
+     *
+     * @see Builder#setDimDuration(Duration)
+     */
+    @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+    public @NonNull Duration getDimDuration() {
+        return Duration.ofMillis(mDimDuration);
+    }
+
+    /**
+     * Returns the screen off timeout of the displays of this device.
+     *
+     * @see Builder#setDimDuration(Duration)
+     */
+    @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+    public @NonNull Duration getScreenOffTimeout() {
+        return Duration.ofMillis(mScreenOffTimeout);
+    }
+
+    /**
      * Returns the custom component used as home on all displays owned by this virtual device that
      * support home activities.
      *
@@ -604,6 +647,8 @@
         dest.writeInt(mAudioRecordingSessionId);
         dest.writeTypedObject(mHomeComponent, flags);
         dest.writeTypedObject(mInputMethodComponent, flags);
+        dest.writeLong(mDimDuration);
+        dest.writeLong(mScreenOffTimeout);
     }
 
     @Override
@@ -638,7 +683,9 @@
                 && Objects.equals(mHomeComponent, that.mHomeComponent)
                 && Objects.equals(mInputMethodComponent, that.mInputMethodComponent)
                 && mAudioPlaybackSessionId == that.mAudioPlaybackSessionId
-                && mAudioRecordingSessionId == that.mAudioRecordingSessionId;
+                && mAudioRecordingSessionId == that.mAudioRecordingSessionId
+                && mDimDuration == that.mDimDuration
+                && mScreenOffTimeout == that.mScreenOffTimeout;
     }
 
     @Override
@@ -647,7 +694,7 @@
                 mLockState, mUsersWithMatchingAccounts, mCrossTaskNavigationExemptions,
                 mDefaultNavigationPolicy, mActivityPolicyExemptions, mDefaultActivityPolicy, mName,
                 mDevicePolicies, mHomeComponent, mInputMethodComponent, mAudioPlaybackSessionId,
-                mAudioRecordingSessionId);
+                mAudioRecordingSessionId, mDimDuration, mScreenOffTimeout);
         for (int i = 0; i < mDevicePolicies.size(); i++) {
             hashCode = 31 * hashCode + mDevicePolicies.keyAt(i);
             hashCode = 31 * hashCode + mDevicePolicies.valueAt(i);
@@ -671,6 +718,8 @@
                 + " mInputMethodComponent=" + mInputMethodComponent
                 + " mAudioPlaybackSessionId=" + mAudioPlaybackSessionId
                 + " mAudioRecordingSessionId=" + mAudioRecordingSessionId
+                + " mDimDuration=" + mDimDuration
+                + " mScreenOffTimeout=" + mScreenOffTimeout
                 + ")";
     }
 
@@ -692,11 +741,13 @@
         pw.println(prefix + "mInputMethodComponent=" + mInputMethodComponent);
         pw.println(prefix + "mAudioPlaybackSessionId=" + mAudioPlaybackSessionId);
         pw.println(prefix + "mAudioRecordingSessionId=" + mAudioRecordingSessionId);
+        pw.println(prefix + "mDimDuration=" + mDimDuration);
+        pw.println(prefix + "mScreenOffTimeout=" + mScreenOffTimeout);
     }
 
     @NonNull
     public static final Parcelable.Creator<VirtualDeviceParams> CREATOR =
-            new Parcelable.Creator<VirtualDeviceParams>() {
+            new Parcelable.Creator<>() {
                 public VirtualDeviceParams createFromParcel(Parcel in) {
                     return new VirtualDeviceParams(in);
                 }
@@ -711,6 +762,8 @@
      */
     public static final class Builder {
 
+        private static final Duration INFINITE_TIMEOUT = Duration.ofDays(365 * 1000);
+
         private @LockState int mLockState = LOCK_STATE_DEFAULT;
         @NonNull private Set<UserHandle> mUsersWithMatchingAccounts = Collections.emptySet();
         @NonNull private Set<ComponentName> mCrossTaskNavigationExemptions = Collections.emptySet();
@@ -733,6 +786,8 @@
         @Nullable private VirtualSensorDirectChannelCallback mVirtualSensorDirectChannelCallback;
         @Nullable private ComponentName mHomeComponent;
         @Nullable private ComponentName mInputMethodComponent;
+        private Duration mDimDuration = Duration.ZERO;
+        private Duration mScreenOffTimeout = Duration.ZERO;
 
         private static class VirtualSensorCallbackDelegate extends IVirtualSensorCallback.Stub {
             @NonNull
@@ -810,6 +865,57 @@
         }
 
         /**
+         * Sets the dim duration for all trusted non-mirror displays of the device.
+         *
+         * <p>The system will reduce the display brightness for the specified duration if there
+         * has been no interaction just before the displays turn off.</p>
+         *
+         * <p>If set, the screen off timeout must also be set to a value larger than the dim
+         * duration. If left unset or set to zero, then the display brightness will not be reduced.
+         * </p>
+         *
+         * @throws IllegalArgumentException if the dim duration is negative or if the dim duration
+         *   is longer than the screen off timeout.
+         * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED
+         * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
+         * @see #setScreenOffTimeout
+         */
+        @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+        @NonNull
+        public Builder setDimDuration(@NonNull Duration dimDuration) {
+            if (Objects.requireNonNull(dimDuration).compareTo(Duration.ZERO) < 0) {
+                throw new IllegalArgumentException("The dim duration cannot be negative");
+            }
+            mDimDuration = dimDuration;
+            return this;
+        }
+
+        /**
+         * Sets the timeout, after which all trusted non-mirror displays of the device will turn
+         * off, if there has been no interaction with the device.
+         *
+         * <p>If dim duration is set, the screen off timeout must be set to a value larger than the
+         * dim duration. If left unset or set to zero, then the displays will never be turned off
+         * due to inactivity.</p>
+         *
+         * @throws IllegalArgumentException if the screen off timeout is negative or if the dim
+         *   duration is longer than the screen off timeout.
+         * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED
+         * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
+         * @see #setDimDuration
+         * @see VirtualDeviceManager.VirtualDevice#goToSleep()
+         */
+        @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+        @NonNull
+        public Builder setScreenOffTimeout(@NonNull Duration screenOffTimeout) {
+            if (Objects.requireNonNull(screenOffTimeout).compareTo(Duration.ZERO) < 0) {
+                throw new IllegalArgumentException("The screen off timeout cannot be negative");
+            }
+            mScreenOffTimeout = screenOffTimeout;
+            return this;
+        }
+
+        /**
          * Specifies a component to be used as home on all displays owned by this virtual device
          * that support home activities.
          * *
@@ -1205,6 +1311,14 @@
                 }
             }
 
+            if (mDimDuration.compareTo(mScreenOffTimeout) > 0) {
+                throw new IllegalArgumentException(
+                        "The dim duration cannot be greater than the screen off timeout.");
+            }
+            if (mScreenOffTimeout.compareTo(Duration.ZERO) == 0) {
+                mScreenOffTimeout = INFINITE_TIMEOUT;
+            }
+
             if (!Flags.crossDeviceClipboard()) {
                 mDevicePolicies.delete(POLICY_TYPE_CLIPBOARD);
             }
@@ -1213,6 +1327,10 @@
                 mDevicePolicies.delete(POLICY_TYPE_CAMERA);
             }
 
+            if (!android.companion.virtualdevice.flags.Flags.defaultDeviceCameraAccessPolicy()) {
+                mDevicePolicies.delete(POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS);
+            }
+
             if (!android.companion.virtualdevice.flags.Flags.activityControlApi()) {
                 mDevicePolicies.delete(POLICY_TYPE_BLOCKED_ACTIVITY);
             }
@@ -1250,7 +1368,9 @@
                     mVirtualSensorConfigs,
                     virtualSensorCallbackDelegate,
                     mAudioPlaybackSessionId,
-                    mAudioRecordingSessionId);
+                    mAudioRecordingSessionId,
+                    mDimDuration.toMillis(),
+                    mScreenOffTimeout.toMillis());
         }
     }
 }
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index fc9c94d..3e6919b 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -67,13 +67,6 @@
 }
 
 flag {
-  name: "stream_camera"
-  namespace: "virtual_devices"
-  description: "Enable streaming camera to Virtual Devices"
-  bug: "291740640"
-}
-
-flag {
   name: "persistent_device_id_api"
   is_exported: true
   namespace: "virtual_devices"
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 1d26b77..186f7b3 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -6677,8 +6677,8 @@
      *
      * @see #getSystemService(String)
      * @see android.telephony.satellite.SatelliteManager
-     * @hide
      */
+    @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_SATELLITE_STATE_CHANGE_LISTENER)
     public static final String SATELLITE_SERVICE = "satellite";
 
     /**
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/TEST_MAPPING b/core/java/android/content/res/TEST_MAPPING
index c2febae..e8893e4 100644
--- a/core/java/android/content/res/TEST_MAPPING
+++ b/core/java/android/content/res/TEST_MAPPING
@@ -5,6 +5,9 @@
     },
     {
       "path": "frameworks/base/core/tests/coretests/src/com/android/internal/content/res"
+    },
+    {
+      "path": "platform_testing/libraries/screenshot"
     }
   ],
   "presubmit": [
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index e98fc0c..26ecbd1 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -83,3 +83,15 @@
     bug: "364035303"
 }
 
+flag {
+    name: "system_context_handle_app_info_changed"
+    is_exported: true
+    namespace: "resource_manager"
+    description: "Feature flag for allowing system context to handle application info changes"
+    bug: "362420029"
+    # This flag is read at boot time.
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/database/BulkCursorNative.java b/core/java/android/database/BulkCursorNative.java
index 41585b3..7d6e7ad 100644
--- a/core/java/android/database/BulkCursorNative.java
+++ b/core/java/android/database/BulkCursorNative.java
@@ -215,7 +215,7 @@
             // If close() is being called from the finalizer thread, do not wait for a reply from
             // the remote side.
             final boolean fromFinalizer =
-                    android.database.sqlite.Flags.onewayFinalizerClose()
+                    android.database.sqlite.Flags.onewayFinalizerCloseFixed()
                     && "FinalizerDaemon".equals(Thread.currentThread().getName());
             mRemote.transact(CLOSE_TRANSACTION, data, reply,
                     fromFinalizer ? IBinder.FLAG_ONEWAY: 0);
diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
index 826b908..d43a669 100644
--- a/core/java/android/database/sqlite/flags.aconfig
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -2,8 +2,9 @@
 container: "system"
 
 flag {
-     name: "oneway_finalizer_close"
+     name: "oneway_finalizer_close_fixed"
      namespace: "system_performance"
+     is_fixed_read_only: true
      description: "Make BuildCursorNative.close oneway if in the the finalizer"
      bug: "368221351"
 }
diff --git a/core/java/android/hardware/DataSpace.java b/core/java/android/hardware/DataSpace.java
index 6117384..1cd9244 100644
--- a/core/java/android/hardware/DataSpace.java
+++ b/core/java/android/hardware/DataSpace.java
@@ -420,18 +420,38 @@
     public static final int DATASPACE_HEIF = 4100;
 
     /**
-     * ISO/IEC TBD
+     * Ultra HDR
      *
-     * JPEG image with embedded recovery map following the Jpeg/R specification.
+     * JPEG image with embedded HDR gain map following the Ultra HDR specification and
+     * starting with Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM V}
+     * ISO/CD 21496‐1
      *
-     * <p>This value must always remain aligned with the public ImageFormat Jpeg/R definition and is
-     * valid with formats:
-     *    HAL_PIXEL_FORMAT_BLOB: JPEG image encoded by Jpeg/R encoder according to ISO/IEC TBD.
-     * The image contains a standard SDR JPEG and a recovery map. Jpeg/R decoders can use the
-     * map to recover the input image.</p>
+     * <p>This value is valid with formats:</p>
+     * <ul>
+     *    <li>HAL_PIXEL_FORMAT_BLOB: JPEG image encoded by Jpeg/R encoder according to
+     *    ISO/CD 21496‐1</li>
+     * </ul>
+     * <p>
+     * The image contains a standard SDR JPEG and a gain map. Ultra HDR decoders can use the
+     * gain map to boost the brightness of the rendered image.</p>
      */
      public static final int DATASPACE_JPEG_R = 4101;
 
+    /**
+     * ISO/IEC 23008-12:2024
+     *
+     * High Efficiency Image File Format (HEIF) with embedded HDR gain map
+     *
+     * <p>This value is valid with formats:</p>
+     * <ul>
+     *    <li>HAL_PIXEL_FORMAT_BLOB: A HEIC image encoded by HEVC encoder
+     *    according to ISO/IEC 23008-12:2024 that includes an HDR gain map and
+     *    metadata according to ISO/CD 21496‐1.</li>
+     * </ul>
+     */
+    @FlaggedApi(com.android.internal.camera.flags.Flags.FLAG_CAMERA_HEIF_GAINMAP)
+    public static final int DATASPACE_HEIF_ULTRAHDR = 4102;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, value = {
@@ -660,6 +680,7 @@
         DATASPACE_DEPTH,
         DATASPACE_DYNAMIC_DEPTH,
         DATASPACE_HEIF,
+        DATASPACE_HEIF_ULTRAHDR,
         DATASPACE_JPEG_R,
         DATASPACE_UNKNOWN,
         DATASPACE_SCRGB_LINEAR,
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index e8d7e1e..a7e7a57 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -150,6 +150,7 @@
      * All values are in micro-Tesla (uT) and measure the ambient magnetic field
      * in the X, Y and Z axis.
      *
+     *
      * <h4>{@link android.hardware.Sensor#TYPE_GYROSCOPE Sensor.TYPE_GYROSCOPE}:
      * </h4> All values are in radians/second and measure the rate of rotation
      * around the device's local X, Y and Z axis. The coordinate system is the
@@ -406,10 +407,9 @@
      * </h4>
      *
      * <ul>
-     * <li> values[0]: ambient (room) temperature in degree Celsius.</li>
+     * <li> values[0]: Ambient (room) temperature in degrees Celsius</li>
      * </ul>
      *
-     *
      * <h4>{@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD_UNCALIBRATED
      * Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED}:</h4>
      * Similar to {@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD},
@@ -549,8 +549,20 @@
      *  <li> values[0]: 1.0 </li>
      * </ul>
      *
-     *   <h4>{@link android.hardware.Sensor#TYPE_HEART_BEAT
-     * Sensor.TYPE_HEART_BEAT}:</h4>
+     * <h4>{@link android.hardware.Sensor#TYPE_STEP_COUNTER Sensor.TYPE_STEP_COUNTER}:</h4>
+     *
+     * <ul>
+     * <li>values[0]: Number of steps taken by the user since the last reboot while the sensor is
+     * activated</li>
+     * </ul>
+     *
+     * <h4>{@link android.hardware.Sensor#TYPE_STEP_DETECTOR Sensor.TYPE_STEP_DETECTOR}:</h4>
+     *
+     * <ul>
+     * <li>values[0]: Always set to 1.0, representing a single step detected event</li>
+     * </ul>
+     *
+     * <h4>{@link android.hardware.Sensor#TYPE_HEART_BEAT Sensor.TYPE_HEART_BEAT}:</h4>
      *
      * A sensor of this type returns an event everytime a heart beat peak is
      * detected.
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 2d29900..7a23033 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -61,3 +61,13 @@
   description: "Feature flag for biometric prompt improvements in private space"
   bug: "365554098"
 }
+
+flag {
+  name: "effective_user_bp"
+  namespace: "biometrics_framework"
+  description: "Feature flag for using effective user in biometric prompt"
+  bug: "365094949"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 1137e1e..a37648f7 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -775,6 +775,46 @@
             new Key<int[]>("android.colorCorrection.availableAberrationModes", int[].class);
 
     /**
+     * <p>The range of supported color temperature values for
+     * {@link CaptureRequest#COLOR_CORRECTION_COLOR_TEMPERATURE android.colorCorrection.colorTemperature}.</p>
+     * <p>This key lists the valid range of color temperature values for
+     * {@link CaptureRequest#COLOR_CORRECTION_COLOR_TEMPERATURE android.colorCorrection.colorTemperature} supported by this camera device.</p>
+     * <p>This key will be null on devices that do not support CCT mode for
+     * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}.</p>
+     * <p><b>Range of valid values:</b><br></p>
+     * <p>The minimum supported range will be [2856K,6500K]. The maximum supported
+     * range will be [1000K,40000K].</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     *
+     * @see CaptureRequest#COLOR_CORRECTION_COLOR_TEMPERATURE
+     * @see CaptureRequest#COLOR_CORRECTION_MODE
+     */
+    @PublicKey
+    @NonNull
+    @FlaggedApi(Flags.FLAG_COLOR_TEMPERATURE)
+    public static final Key<android.util.Range<Integer>> COLOR_CORRECTION_COLOR_TEMPERATURE_RANGE =
+            new Key<android.util.Range<Integer>>("android.colorCorrection.colorTemperatureRange", new TypeReference<android.util.Range<Integer>>() {{ }});
+
+    /**
+     * <p>List of color correction modes for {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} that are
+     * supported by this camera device.</p>
+     * <p>This key lists the valid modes for {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}. If no
+     * color correction modes are available for a device, this key will be null.</p>
+     * <p>Camera devices that have a FULL hardware level will always include at least
+     * FAST, HIGH_QUALITY, and TRANSFORM_MATRIX modes.</p>
+     * <p><b>Range of valid values:</b><br>
+     * Any value listed in {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     *
+     * @see CaptureRequest#COLOR_CORRECTION_MODE
+     */
+    @PublicKey
+    @NonNull
+    @FlaggedApi(Flags.FLAG_COLOR_TEMPERATURE)
+    public static final Key<int[]> COLOR_CORRECTION_AVAILABLE_MODES =
+            new Key<int[]>("android.colorCorrection.availableModes", int[].class);
+
+    /**
      * <p>List of auto-exposure antibanding modes for {@link CaptureRequest#CONTROL_AE_ANTIBANDING_MODE android.control.aeAntibandingMode} that are
      * supported by this camera device.</p>
      * <p>Not all of the auto-exposure anti-banding modes may be
@@ -5735,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/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index acb48f3..86bbd4a 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -2169,6 +2169,22 @@
      */
     public static final int COLOR_CORRECTION_MODE_HIGH_QUALITY = 2;
 
+    /**
+     * <p>Use
+     * {@link CaptureRequest#COLOR_CORRECTION_COLOR_TEMPERATURE android.colorCorrection.colorTemperature} and
+     * {@link CaptureRequest#COLOR_CORRECTION_COLOR_TINT android.colorCorrection.colorTint} to adjust the white balance based
+     * on correlated color temperature.</p>
+     * <p>If AWB is enabled with <code>{@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode} != OFF</code>, then
+     * CCT is ignored.</p>
+     *
+     * @see CaptureRequest#COLOR_CORRECTION_COLOR_TEMPERATURE
+     * @see CaptureRequest#COLOR_CORRECTION_COLOR_TINT
+     * @see CaptureRequest#CONTROL_AWB_MODE
+     * @see CaptureRequest#COLOR_CORRECTION_MODE
+     */
+    @FlaggedApi(Flags.FLAG_COLOR_TEMPERATURE)
+    public static final int COLOR_CORRECTION_MODE_CCT = 3;
+
     //
     // Enumeration values for CaptureRequest#COLOR_CORRECTION_ABERRATION_MODE
     //
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index a5c5a99..8142bbe 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1086,11 +1086,17 @@
      *   <li>{@link #COLOR_CORRECTION_MODE_HIGH_QUALITY HIGH_QUALITY}</li>
      * </ul>
      *
+     * <p><b>Available values for this device:</b><br>
+     * Starting from API level 36, {@link CameraCharacteristics#COLOR_CORRECTION_AVAILABLE_MODES android.colorCorrection.availableModes}
+     * can be used to check the list of supported values. Prior to API level 36,
+     * TRANSFORM_MATRIX, HIGH_QUALITY, and FAST are guaranteed to be available
+     * as valid modes on devices that support this key.</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      * <p><b>Full capability</b> -
      * Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
      * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
      *
+     * @see CameraCharacteristics#COLOR_CORRECTION_AVAILABLE_MODES
      * @see CaptureRequest#COLOR_CORRECTION_GAINS
      * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM
      * @see CaptureRequest#CONTROL_AWB_MODE
@@ -1195,6 +1201,60 @@
             new Key<Integer>("android.colorCorrection.aberrationMode", int.class);
 
     /**
+     * <p>Specifies the color temperature for CCT mode in Kelvin
+     * to adjust the white balance of the image.</p>
+     * <p>Sets the color temperature in Kelvin units for when
+     * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is CCT to adjust the
+     * white balance of the image.</p>
+     * <p>If CCT mode is enabled without a requested color temperature,
+     * a default value will be set by the camera device. The default value can be
+     * retrieved by checking the corresponding capture result. Color temperatures
+     * requested outside the advertised {@link CameraCharacteristics#COLOR_CORRECTION_COLOR_TEMPERATURE_RANGE android.colorCorrection.colorTemperatureRange}
+     * will be clamped.</p>
+     * <p><b>Units</b>: Kelvin</p>
+     * <p><b>Range of valid values:</b><br>
+     * {@link CameraCharacteristics#COLOR_CORRECTION_COLOR_TEMPERATURE_RANGE android.colorCorrection.colorTemperatureRange}</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     *
+     * @see CameraCharacteristics#COLOR_CORRECTION_COLOR_TEMPERATURE_RANGE
+     * @see CaptureRequest#COLOR_CORRECTION_MODE
+     */
+    @PublicKey
+    @NonNull
+    @FlaggedApi(Flags.FLAG_COLOR_TEMPERATURE)
+    public static final Key<Integer> COLOR_CORRECTION_COLOR_TEMPERATURE =
+            new Key<Integer>("android.colorCorrection.colorTemperature", int.class);
+
+    /**
+     * <p>Specifies the color tint for CCT mode to adjust the white
+     * balance of the image.</p>
+     * <p>Sets the color tint for when {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}
+     * is CCT to adjust the white balance of the image.</p>
+     * <p>If CCT mode is enabled without a requested color tint,
+     * a default value will be set by the camera device. The default value can be
+     * retrieved by checking the corresponding capture result. Color tints requested
+     * outside the supported range will be clamped to the nearest limit (-50 or +50).</p>
+     * <p><b>Units</b>: D_uv defined as the distance from the Planckian locus on the CIE 1931 xy
+     * chromaticity diagram, with the range ±50 mapping to ±0.01 D_uv</p>
+     * <p><b>Range of valid values:</b><br>
+     * The supported range, -50 to +50, corresponds to a D_uv distance
+     * of ±0.01 below and above the Planckian locus. Some camera devices may have
+     * limitations to achieving the full ±0.01 D_uv range at some color temperatures
+     * (e.g., below 1500K). In these cases, the applied D_uv value may be clamped and
+     * the actual color tint will be reported in the {@link CaptureRequest#COLOR_CORRECTION_COLOR_TINT android.colorCorrection.colorTint}
+     * result.</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     *
+     * @see CaptureRequest#COLOR_CORRECTION_COLOR_TINT
+     * @see CaptureRequest#COLOR_CORRECTION_MODE
+     */
+    @PublicKey
+    @NonNull
+    @FlaggedApi(Flags.FLAG_COLOR_TEMPERATURE)
+    public static final Key<Integer> COLOR_CORRECTION_COLOR_TINT =
+            new Key<Integer>("android.colorCorrection.colorTint", int.class);
+
+    /**
      * <p>The desired setting for the camera device's auto-exposure
      * algorithm's antibanding compensation.</p>
      * <p>Some kinds of lighting fixtures, such as some fluorescent
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index a6bdb3f..ae72ca4 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -487,11 +487,17 @@
      *   <li>{@link #COLOR_CORRECTION_MODE_HIGH_QUALITY HIGH_QUALITY}</li>
      * </ul>
      *
+     * <p><b>Available values for this device:</b><br>
+     * Starting from API level 36, {@link CameraCharacteristics#COLOR_CORRECTION_AVAILABLE_MODES android.colorCorrection.availableModes}
+     * can be used to check the list of supported values. Prior to API level 36,
+     * TRANSFORM_MATRIX, HIGH_QUALITY, and FAST are guaranteed to be available
+     * as valid modes on devices that support this key.</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      * <p><b>Full capability</b> -
      * Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
      * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
      *
+     * @see CameraCharacteristics#COLOR_CORRECTION_AVAILABLE_MODES
      * @see CaptureRequest#COLOR_CORRECTION_GAINS
      * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM
      * @see CaptureRequest#CONTROL_AWB_MODE
@@ -596,6 +602,60 @@
             new Key<Integer>("android.colorCorrection.aberrationMode", int.class);
 
     /**
+     * <p>Specifies the color temperature for CCT mode in Kelvin
+     * to adjust the white balance of the image.</p>
+     * <p>Sets the color temperature in Kelvin units for when
+     * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is CCT to adjust the
+     * white balance of the image.</p>
+     * <p>If CCT mode is enabled without a requested color temperature,
+     * a default value will be set by the camera device. The default value can be
+     * retrieved by checking the corresponding capture result. Color temperatures
+     * requested outside the advertised {@link CameraCharacteristics#COLOR_CORRECTION_COLOR_TEMPERATURE_RANGE android.colorCorrection.colorTemperatureRange}
+     * will be clamped.</p>
+     * <p><b>Units</b>: Kelvin</p>
+     * <p><b>Range of valid values:</b><br>
+     * {@link CameraCharacteristics#COLOR_CORRECTION_COLOR_TEMPERATURE_RANGE android.colorCorrection.colorTemperatureRange}</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     *
+     * @see CameraCharacteristics#COLOR_CORRECTION_COLOR_TEMPERATURE_RANGE
+     * @see CaptureRequest#COLOR_CORRECTION_MODE
+     */
+    @PublicKey
+    @NonNull
+    @FlaggedApi(Flags.FLAG_COLOR_TEMPERATURE)
+    public static final Key<Integer> COLOR_CORRECTION_COLOR_TEMPERATURE =
+            new Key<Integer>("android.colorCorrection.colorTemperature", int.class);
+
+    /**
+     * <p>Specifies the color tint for CCT mode to adjust the white
+     * balance of the image.</p>
+     * <p>Sets the color tint for when {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}
+     * is CCT to adjust the white balance of the image.</p>
+     * <p>If CCT mode is enabled without a requested color tint,
+     * a default value will be set by the camera device. The default value can be
+     * retrieved by checking the corresponding capture result. Color tints requested
+     * outside the supported range will be clamped to the nearest limit (-50 or +50).</p>
+     * <p><b>Units</b>: D_uv defined as the distance from the Planckian locus on the CIE 1931 xy
+     * chromaticity diagram, with the range ±50 mapping to ±0.01 D_uv</p>
+     * <p><b>Range of valid values:</b><br>
+     * The supported range, -50 to +50, corresponds to a D_uv distance
+     * of ±0.01 below and above the Planckian locus. Some camera devices may have
+     * limitations to achieving the full ±0.01 D_uv range at some color temperatures
+     * (e.g., below 1500K). In these cases, the applied D_uv value may be clamped and
+     * the actual color tint will be reported in the {@link CaptureRequest#COLOR_CORRECTION_COLOR_TINT android.colorCorrection.colorTint}
+     * result.</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     *
+     * @see CaptureRequest#COLOR_CORRECTION_COLOR_TINT
+     * @see CaptureRequest#COLOR_CORRECTION_MODE
+     */
+    @PublicKey
+    @NonNull
+    @FlaggedApi(Flags.FLAG_COLOR_TEMPERATURE)
+    public static final Key<Integer> COLOR_CORRECTION_COLOR_TINT =
+            new Key<Integer>("android.colorCorrection.colorTint", int.class);
+
+    /**
      * <p>The desired setting for the camera device's auto-exposure
      * algorithm's antibanding compensation.</p>
      * <p>Some kinds of lighting fixtures, such as some fluorescent
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/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 75ffcc3..399184c 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -491,10 +491,16 @@
         public static final int POLICY_DIM = 2;
         // Policy: Make the screen bright as usual.
         public static final int POLICY_BRIGHT = 3;
+        // The maximum policy constant. Useful for iterating through all constants in tests.
+        public static final int POLICY_MAX = POLICY_BRIGHT;
 
         // The basic overall policy to apply: off, doze, dim or bright.
         public int policy;
 
+        // The reason behind the current policy.
+        @Display.StateReason
+        public int policyReason;
+
         // If true, the proximity sensor overrides the screen state when an object is
         // nearby, turning it off temporarily until the object is moved away.
         public boolean useProximitySensor;
@@ -541,6 +547,7 @@
 
         public DisplayPowerRequest() {
             policy = POLICY_BRIGHT;
+            policyReason = Display.STATE_REASON_DEFAULT_POLICY;
             useProximitySensor = false;
             screenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
             screenAutoBrightnessAdjustmentOverride = Float.NaN;
@@ -561,6 +568,7 @@
 
         public void copyFrom(DisplayPowerRequest other) {
             policy = other.policy;
+            policyReason = other.policyReason;
             useProximitySensor = other.useProximitySensor;
             screenBrightnessOverride = other.screenBrightnessOverride;
             screenBrightnessOverrideTag = other.screenBrightnessOverrideTag;
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 4a9efe0..96f6ad1 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -20,16 +20,19 @@
 import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS;
 import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG;
 import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG;
+import static com.android.hardware.input.Flags.enableCustomizableInputGestures;
 import static com.android.hardware.input.Flags.keyboardA11yBounceKeysFlag;
+import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
 import static com.android.hardware.input.Flags.keyboardA11ySlowKeysFlag;
 import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
-import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
 import static com.android.hardware.input.Flags.mouseReverseVerticalScrolling;
 import static com.android.hardware.input.Flags.mouseSwapPrimaryButton;
 import static com.android.hardware.input.Flags.touchpadTapDragging;
+import static com.android.hardware.input.Flags.touchpadThreeFingerTapShortcut;
 import static com.android.hardware.input.Flags.touchpadVisualizer;
-import static com.android.input.flags.Flags.enableInputFilterRustImpl;
+import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
 import static com.android.input.flags.Flags.FLAG_KEYBOARD_REPEAT_KEYS;
+import static com.android.input.flags.Flags.enableInputFilterRustImpl;
 import static com.android.input.flags.Flags.keyboardRepeatKeys;
 
 import android.Manifest;
@@ -379,6 +382,15 @@
     }
 
     /**
+     * Returns true if the feature flag for the touchpad three-finger tap shortcut is enabled.
+     *
+     * @hide
+     */
+    public static boolean isTouchpadThreeFingerTapShortcutFeatureFlagEnabled() {
+        return isCustomizableInputGesturesFeatureFlagEnabled() && touchpadThreeFingerTapShortcut();
+    }
+
+    /**
      * Returns true if the feature flag for mouse reverse vertical scrolling is enabled.
      * @hide
      */
@@ -498,6 +510,22 @@
     }
 
     /**
+     * Returns true if three-finger taps on the touchpad should trigger a customizable shortcut
+     * rather than a middle click.
+     *
+     * The returned value only applies to gesture-compatible touchpads.
+     *
+     * @param context The application context.
+     * @return Whether three-finger taps should trigger the shortcut.
+     *
+     * @hide
+     */
+    public static boolean useTouchpadThreeFingerTapShortcut(@NonNull Context context) {
+        // TODO(b/365063048): determine whether to enable the shortcut based on the settings.
+        return isTouchpadThreeFingerTapShortcutFeatureFlagEnabled();
+    }
+
+    /**
      * Whether a pointer icon will be shown over the location of a stylus pointer.
      *
      * @hide
@@ -1105,4 +1133,18 @@
                 Settings.Secure.KEY_REPEAT_DELAY_MS, delayTimeMillis,
                 UserHandle.USER_CURRENT);
     }
+
+    /**
+     * Whether "Customizable key gestures" feature flag is enabled.
+     *
+     * <p>
+     * ‘Customizable key gestures’ is a feature which allows users to customize key based
+     * shortcuts on the physical keyboard.
+     * </p>
+     *
+     * @hide
+     */
+    public static boolean isCustomizableInputGesturesFeatureFlagEnabled() {
+        return enableCustomizableInputGestures() && useKeyGestureEventHandler();
+    }
 }
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index e6982b9..38e32c6 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -1,7 +1,10 @@
 package: "com.android.hardware.input"
 container: "system"
 
-# Project link: https://gantry.corp.google.com/projects/android_platform_input_native/changes
+# Project link: https://gantry.corp.google.com/projects/android_platform_input/changes
+
+# NOTE: the input_native namespace is deprecated. New flags should be added to the input namespace
+# instead.
 
 flag {
     namespace: "input_native"
@@ -138,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"
 }
 
@@ -155,3 +158,17 @@
   description: "Allows privileged focused windows to capture power key events."
   bug: "357144512"
 }
+
+flag {
+  name: "touchpad_three_finger_tap_shortcut"
+  namespace: "input"
+  description: "Turns three-finger touchpad taps into a customizable shortcut."
+  bug: "365063048"
+}
+
+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..85cf949 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1750,8 +1750,8 @@
              *             internals, typically during enrollment.
              * @return the same Builder instance.
              */
-            public @NonNull Builder setData(@Nullable byte[] data) {
-                mData = data;
+            public @NonNull Builder setData(@NonNull byte[] data) {
+                mData = requireNonNull(data, "Data must not be null");
                 return this;
             }
 
diff --git a/core/java/android/hardware/usb/OWNERS b/core/java/android/hardware/usb/OWNERS
index a753f96..37604bc 100644
--- a/core/java/android/hardware/usb/OWNERS
+++ b/core/java/android/hardware/usb/OWNERS
@@ -1,7 +1,7 @@
 # Bug component: 175220
 
-aprasath@google.com
-kumarashishg@google.com
-sarup@google.com
 anothermark@google.com
+febinthattil@google.com
+aprasath@google.com
 badhri@google.com
+kumarashishg@google.com
\ No newline at end of file
diff --git a/core/java/android/net/flags.aconfig b/core/java/android/net/flags.aconfig
index 48eb968..f7dc790 100644
--- a/core/java/android/net/flags.aconfig
+++ b/core/java/android/net/flags.aconfig
@@ -5,13 +5,6 @@
 # Flags used for module APIs must be in aconfig files under each modules
 
 flag {
-  name: "ipsec_transform_state"
-  namespace: "core_networking_ipsec"
-  description: "The flag controls the access for getIpSecTransformState and IpSecTransformState"
-  bug: "308011229"
-}
-
-flag {
     name: "powered_off_finding_platform"
     namespace: "nearby"
     description: "Controls whether the Powered Off Finding feature is enabled"
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 6c3c285..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];
@@ -1315,7 +1315,7 @@
         }
 
         synchronized (BatteryUsageStats.class) {
-            if (!sInstances.isEmpty()) {
+            if (sInstances != null && !sInstances.isEmpty()) {
                 Exception callSite = sInstances.entrySet().iterator().next().getValue();
                 int count = sInstances.size();
                 sInstances.clear();
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 13d7e3c..102bdd0 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -40,8 +40,6 @@
 import android.util.Slog;
 import android.view.View;
 
-import com.android.internal.ravenwood.RavenwoodEnvironment;
-
 import dalvik.system.VMRuntime;
 
 import java.lang.annotation.Retention;
@@ -57,10 +55,6 @@
  */
 @RavenwoodKeepWholeClass
 public class Build {
-    static {
-        // Set up the default system properties.
-        RavenwoodEnvironment.ensureRavenwoodInitialized();
-    }
     private static final String TAG = "Build";
 
     /** Value used for when a build property is unknown. */
@@ -1571,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 6297318..acda0c5 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -21,6 +21,7 @@
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Process;
+import android.os.UserHandle;
 import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import android.ravenwood.annotation.RavenwoodRedirect;
 import android.ravenwood.annotation.RavenwoodRedirectionClass;
@@ -76,14 +77,14 @@
     private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
     private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
     private IdleHandler[] mPendingIdleHandlers;
-    private boolean mLegacyQuitting;
+    private boolean mQuitting;
 
     // Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
-    private boolean mLegacyBlocked;
+    private boolean mBlocked;
 
     // Tracks the number of async message. We use this in enqueueMessage() to avoid searching the
     // queue for async messages when inserting a message at the tail.
-    private int mLegacyAsyncMessageCount;
+    private int mAsyncMessageCount;
 
     /*
      * Select between two implementations of message queue. The legacy implementation is used
@@ -92,7 +93,7 @@
      * system processes and provides a higher level of concurrency and higher enqueue throughput
      * than the legacy implementation.
      */
-    private static boolean sForceConcurrent = false;
+    private boolean mUseConcurrent;
 
     @RavenwoodRedirect
     private native static long nativeInit();
@@ -109,9 +110,7 @@
     private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
 
     MessageQueue(boolean quitAllowed) {
-        if (!sForceConcurrent) {
-            sForceConcurrent = Process.myUid() < Process.FIRST_APPLICATION_UID;
-        }
+        mUseConcurrent = UserHandle.isCore(Process.myUid());
         mQuitAllowed = quitAllowed;
         mPtr = nativeInit();
     }
@@ -154,7 +153,7 @@
      * @return True if the looper is idle.
      */
     public boolean isIdle() {
-        if (sForceConcurrent) {
+        if (mUseConcurrent) {
             final long now = SystemClock.uptimeMillis();
 
             if (stackHasMessages(null, 0, null, null, now, mMatchDeliverableMessages, false)) {
@@ -204,7 +203,7 @@
         if (handler == null) {
             throw new NullPointerException("Can't add a null IdleHandler");
         }
-        if (sForceConcurrent) {
+        if (mUseConcurrent) {
             synchronized (mIdleHandlersLock) {
                 mIdleHandlers.add(handler);
             }
@@ -225,7 +224,7 @@
      * @param handler The IdleHandler to be removed.
      */
     public void removeIdleHandler(@NonNull IdleHandler handler) {
-        if (sForceConcurrent) {
+        if (mUseConcurrent) {
             synchronized (mIdleHandlersLock) {
                 mIdleHandlers.remove(handler);
             }
@@ -248,7 +247,7 @@
      * @hide
      */
     public boolean isPolling() {
-        if (sForceConcurrent) {
+        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);
@@ -261,8 +260,8 @@
 
     private boolean isPollingLocked() {
         // If the loop is quitting then it must not be idling.
-        // We can assume mPtr != 0 when mLegacyQuitting is false.
-        return !mLegacyQuitting && nativeIsPolling(mPtr);
+        // We can assume mPtr != 0 when mQuitting is false.
+        return !mQuitting && nativeIsPolling(mPtr);
     }
 
     /**
@@ -299,7 +298,7 @@
             throw new IllegalArgumentException("listener must not be null");
         }
 
-        if (sForceConcurrent) {
+        if (mUseConcurrent) {
             synchronized (mFileDescriptorRecordsLock) {
                 updateOnFileDescriptorEventListenerLocked(fd, events, listener);
             }
@@ -327,7 +326,7 @@
         if (fd == null) {
             throw new IllegalArgumentException("fd must not be null");
         }
-        if (sForceConcurrent) {
+        if (mUseConcurrent) {
             synchronized (mFileDescriptorRecordsLock) {
                 updateOnFileDescriptorEventListenerLocked(fd, 0, null);
             }
@@ -384,7 +383,7 @@
         final int oldWatchedEvents;
         final OnFileDescriptorEventListener listener;
         final int seq;
-        if (sForceConcurrent) {
+        if (mUseConcurrent) {
             synchronized (mFileDescriptorRecordsLock) {
                 record = mFileDescriptorRecords.get(fd);
                 if (record == null) {
@@ -427,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);
+                        }
                     }
                 }
             }
@@ -704,7 +716,7 @@
 
     @UnsupportedAppUsage
     Message next() {
-        if (sForceConcurrent) {
+        if (mUseConcurrent) {
             return nextConcurrent();
         }
 
@@ -743,7 +755,7 @@
                         nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                     } else {
                         // Got a message.
-                        mLegacyBlocked = false;
+                        mBlocked = false;
                         if (prevMsg != null) {
                             prevMsg.next = msg.next;
                             if (prevMsg.next == null) {
@@ -759,7 +771,7 @@
                         if (DEBUG) Log.v(TAG_L, "Returning message: " + msg);
                         msg.markInUse();
                         if (msg.isAsynchronous()) {
-                            mLegacyAsyncMessageCount--;
+                            mAsyncMessageCount--;
                         }
                         if (TRACE) {
                             Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
@@ -772,7 +784,7 @@
                 }
 
                 // Process the quit message now that all pending messages have been handled.
-                if (mLegacyQuitting) {
+                if (mQuitting) {
                     dispose();
                     return null;
                 }
@@ -786,7 +798,7 @@
                 }
                 if (pendingIdleHandlerCount <= 0) {
                     // No idle handlers to run.  Loop and wait some more.
-                    mLegacyBlocked = true;
+                    mBlocked = true;
                     continue;
                 }
 
@@ -830,7 +842,7 @@
             throw new IllegalStateException("Main thread not allowed to quit.");
         }
 
-        if (sForceConcurrent) {
+        if (mUseConcurrent) {
             synchronized (mIdleHandlersLock) {
                 if (sQuitting.compareAndSet(this, false, true)) {
                     if (safe) {
@@ -845,10 +857,10 @@
             }
         } else {
             synchronized (this) {
-                if (mLegacyQuitting) {
+                if (mQuitting) {
                     return;
                 }
-                mLegacyQuitting = true;
+                mQuitting = true;
 
                 if (safe) {
                     removeAllFutureMessagesLocked();
@@ -856,7 +868,7 @@
                     removeAllMessagesLocked();
                 }
 
-                // We can assume mPtr != 0 because mLegacyQuitting was previously false.
+                // We can assume mPtr != 0 because mQuitting was previously false.
                 nativeWake(mPtr);
             }
         }
@@ -894,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 (sForceConcurrent) {
+        if (mUseConcurrent) {
             final int token = mNextBarrierTokenAtomic.getAndIncrement();
 
             // b/376573804: apps and tests may expect to be able to use reflection
@@ -987,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 (sForceConcurrent) {
+        if (mUseConcurrent) {
             boolean removed;
             MessageNode first;
             final MatchBarrierToken matchBarrierToken = new MatchBarrierToken(token);
@@ -1042,8 +1054,8 @@
             p.recycleUnchecked();
 
             // If the loop is quitting then it is already awake.
-            // We can assume mPtr != 0 when mLegacyQuitting is false.
-            if (needWake && !mLegacyQuitting) {
+            // We can assume mPtr != 0 when mQuitting is false.
+            if (needWake && !mQuitting) {
                 nativeWake(mPtr);
             }
         }
@@ -1054,7 +1066,7 @@
             throw new IllegalArgumentException("Message must have a target.");
         }
 
-        if (sForceConcurrent) {
+        if (mUseConcurrent) {
             if (msg.isInUse()) {
                 throw new IllegalStateException(msg + " This message is already in use.");
             }
@@ -1067,7 +1079,7 @@
                 throw new IllegalStateException(msg + " This message is already in use.");
             }
 
-            if (mLegacyQuitting) {
+            if (mQuitting) {
                 IllegalStateException e = new IllegalStateException(
                         msg.target + " sending message to a Handler on a dead thread");
                 Log.w(TAG_L, e.getMessage(), e);
@@ -1083,7 +1095,7 @@
                 // New head, wake up the event queue if blocked.
                 msg.next = p;
                 mMessages = msg;
-                needWake = mLegacyBlocked;
+                needWake = mBlocked;
                 if (p == null) {
                     mLast = mMessages;
                 }
@@ -1091,14 +1103,14 @@
                 // Message is to be inserted at tail or middle of queue. Usually we don't have to
                 // wake up the event queue unless there is a barrier at the head of the queue and
                 // the message is the earliest asynchronous message in the queue.
-                needWake = mLegacyBlocked && p.target == null && msg.isAsynchronous();
+                needWake = mBlocked && p.target == null && msg.isAsynchronous();
 
                 // For readability, we split this portion of the function into two blocks based on
                 // whether tail tracking is enabled. This has a minor implication for the case
                 // where tail tracking is disabled. See the comment below.
                 if (Flags.messageQueueTailTracking()) {
                     if (when >= mLast.when) {
-                        needWake = needWake && mLegacyAsyncMessageCount == 0;
+                        needWake = needWake && mAsyncMessageCount == 0;
                         msg.next = null;
                         mLast.next = msg;
                         mLast = msg;
@@ -1156,10 +1168,10 @@
             }
 
             if (msg.isAsynchronous()) {
-                mLegacyAsyncMessageCount++;
+                mAsyncMessageCount++;
             }
 
-            // We can assume mPtr != 0 because mLegacyQuitting is false.
+            // We can assume mPtr != 0 because mQuitting is false.
             if (needWake) {
                 nativeWake(mPtr);
             }
@@ -1183,7 +1195,7 @@
         if (h == null) {
             return false;
         }
-        if (sForceConcurrent) {
+        if (mUseConcurrent) {
             return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject,
                     false);
         }
@@ -1215,7 +1227,7 @@
         if (h == null) {
             return false;
         }
-        if (sForceConcurrent) {
+        if (mUseConcurrent) {
             return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals,
                     false);
 
@@ -1249,7 +1261,7 @@
         if (h == null) {
             return false;
         }
-        if (sForceConcurrent) {
+        if (mUseConcurrent) {
             return findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject,
                     false);
         }
@@ -1281,7 +1293,7 @@
         if (h == null) {
             return false;
         }
-        if (sForceConcurrent) {
+        if (mUseConcurrent) {
             return findOrRemoveMessages(h, -1, null, null, 0, mMatchHandler, false);
         }
         synchronized (this) {
@@ -1300,7 +1312,7 @@
         if (h == null) {
             return;
         }
-        if (sForceConcurrent) {
+        if (mUseConcurrent) {
             findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, true);
             return;
         }
@@ -1313,7 +1325,7 @@
                 Message n = p.next;
                 mMessages = n;
                 if (p.isAsynchronous()) {
-                    mLegacyAsyncMessageCount--;
+                    mAsyncMessageCount--;
                 }
                 p.recycleUnchecked();
                 p = n;
@@ -1331,7 +1343,7 @@
                             && (object == null || n.obj == object)) {
                         Message nn = n.next;
                         if (n.isAsynchronous()) {
-                            mLegacyAsyncMessageCount--;
+                            mAsyncMessageCount--;
                         }
                         n.recycleUnchecked();
                         p.next = nn;
@@ -1351,7 +1363,7 @@
             return;
         }
 
-        if (sForceConcurrent) {
+        if (mUseConcurrent) {
             findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, true);
             return;
         }
@@ -1365,7 +1377,7 @@
                 Message n = p.next;
                 mMessages = n;
                 if (p.isAsynchronous()) {
-                    mLegacyAsyncMessageCount--;
+                    mAsyncMessageCount--;
                 }
                 p.recycleUnchecked();
                 p = n;
@@ -1383,7 +1395,7 @@
                             && (object == null || object.equals(n.obj))) {
                         Message nn = n.next;
                         if (n.isAsynchronous()) {
-                            mLegacyAsyncMessageCount--;
+                            mAsyncMessageCount--;
                         }
                         n.recycleUnchecked();
                         p.next = nn;
@@ -1403,7 +1415,7 @@
             return;
         }
 
-        if (sForceConcurrent) {
+        if (mUseConcurrent) {
             findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, true);
             return;
         }
@@ -1416,7 +1428,7 @@
                 Message n = p.next;
                 mMessages = n;
                 if (p.isAsynchronous()) {
-                    mLegacyAsyncMessageCount--;
+                    mAsyncMessageCount--;
                 }
                 p.recycleUnchecked();
                 p = n;
@@ -1434,7 +1446,7 @@
                             && (object == null || n.obj == object)) {
                         Message nn = n.next;
                         if (n.isAsynchronous()) {
-                            mLegacyAsyncMessageCount--;
+                            mAsyncMessageCount--;
                         }
                         n.recycleUnchecked();
                         p.next = nn;
@@ -1466,7 +1478,7 @@
             return;
         }
 
-        if (sForceConcurrent) {
+        if (mUseConcurrent) {
             findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObjectEquals, true);
             return;
         }
@@ -1479,7 +1491,7 @@
                 Message n = p.next;
                 mMessages = n;
                 if (p.isAsynchronous()) {
-                    mLegacyAsyncMessageCount--;
+                    mAsyncMessageCount--;
                 }
                 p.recycleUnchecked();
                 p = n;
@@ -1497,7 +1509,7 @@
                             && (object == null || object.equals(n.obj))) {
                         Message nn = n.next;
                         if (n.isAsynchronous()) {
-                            mLegacyAsyncMessageCount--;
+                            mAsyncMessageCount--;
                         }
                         n.recycleUnchecked();
                         p.next = nn;
@@ -1528,7 +1540,7 @@
             return;
         }
 
-        if (sForceConcurrent) {
+        if (mUseConcurrent) {
             findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObject, true);
             return;
         }
@@ -1541,7 +1553,7 @@
                 Message n = p.next;
                 mMessages = n;
                 if (p.isAsynchronous()) {
-                    mLegacyAsyncMessageCount--;
+                    mAsyncMessageCount--;
                 }
                 p.recycleUnchecked();
                 p = n;
@@ -1558,7 +1570,7 @@
                     if (n.target == h && (object == null || n.obj == object)) {
                         Message nn = n.next;
                         if (n.isAsynchronous()) {
-                            mLegacyAsyncMessageCount--;
+                            mAsyncMessageCount--;
                         }
                         n.recycleUnchecked();
                         p.next = nn;
@@ -1590,7 +1602,7 @@
             return;
         }
 
-        if (sForceConcurrent) {
+        if (mUseConcurrent) {
             findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObjectEquals, true);
             return;
         }
@@ -1603,7 +1615,7 @@
                 Message n = p.next;
                 mMessages = n;
                 if (p.isAsynchronous()) {
-                    mLegacyAsyncMessageCount--;
+                    mAsyncMessageCount--;
                 }
                 p.recycleUnchecked();
                 p = n;
@@ -1620,7 +1632,7 @@
                     if (n.target == h && (object == null || object.equals(n.obj))) {
                         Message nn = n.next;
                         if (n.isAsynchronous()) {
-                            mLegacyAsyncMessageCount--;
+                            mAsyncMessageCount--;
                         }
                         n.recycleUnchecked();
                         p.next = nn;
@@ -1644,7 +1656,7 @@
         }
         mMessages = null;
         mLast = null;
-        mLegacyAsyncMessageCount = 0;
+        mAsyncMessageCount = 0;
     }
 
     private void removeAllFutureMessagesLocked() {
@@ -1672,7 +1684,7 @@
                     p = n;
                     n = p.next;
                     if (p.isAsynchronous()) {
-                        mLegacyAsyncMessageCount--;
+                        mAsyncMessageCount--;
                     }
                     p.recycleUnchecked();
                 } while (n != null);
@@ -1738,7 +1750,7 @@
 
     @NeverCompile
     void dump(Printer pw, String prefix, Handler h) {
-        if (sForceConcurrent) {
+        if (mUseConcurrent) {
             long now = SystemClock.uptimeMillis();
             int n = 0;
 
@@ -1780,7 +1792,7 @@
                 n++;
             }
             pw.println(prefix + "(Total messages: " + n + ", polling=" + isPollingLocked()
-                    + ", quitting=" + mLegacyQuitting + ")");
+                    + ", quitting=" + mQuitting + ")");
         }
     }
 
@@ -1799,7 +1811,7 @@
 
     @NeverCompile
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
-        if (sForceConcurrent) {
+        if (mUseConcurrent) {
             final long messageQueueToken = proto.start(fieldId);
 
             StackNode node = (StackNode) sState.getVolatile(this);
@@ -1824,7 +1836,7 @@
                 msg.dumpDebug(proto, MessageQueueProto.MESSAGES);
             }
             proto.write(MessageQueueProto.IS_POLLING_LOCKED, isPollingLocked());
-            proto.write(MessageQueueProto.IS_QUITTING, mLegacyQuitting);
+            proto.write(MessageQueueProto.IS_QUITTING, mQuitting);
         }
         proto.end(messageQueueToken);
     }
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/IpcDataCache.java b/core/java/android/os/IpcDataCache.java
index 7875c23..8db1567 100644
--- a/core/java/android/os/IpcDataCache.java
+++ b/core/java/android/os/IpcDataCache.java
@@ -23,6 +23,7 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.PropertyInvalidatedCache;
+import android.app.PropertyInvalidatedCache.Args;
 import android.text.TextUtils;
 import android.util.ArraySet;
 
@@ -341,7 +342,7 @@
     public IpcDataCache(int maxEntries, @NonNull @IpcDataCacheModule String module,
             @NonNull String api, @NonNull String cacheName,
             @NonNull QueryHandler<Query, Result> computer) {
-        super(maxEntries, module, api, cacheName, computer);
+        super(new Args(module).maxEntries(maxEntries).api(api), cacheName, computer);
     }
 
     /**
@@ -563,7 +564,8 @@
      * @hide
      */
     public IpcDataCache(@NonNull Config config, @NonNull QueryHandler<Query, Result> computer) {
-        super(config.maxEntries(), config.module(), config.api(), config.name(), computer);
+      super(new Args(config.module()).maxEntries(config.maxEntries()).api(config.api()),
+          config.name(), computer);
     }
 
     /**
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index e80efd2..60eeb2b 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -41,7 +41,6 @@
 import android.net.Uri;
 import android.os.MessageQueue.OnFileDescriptorEventListener;
 import android.ravenwood.annotation.RavenwoodKeepWholeClass;
-import android.ravenwood.annotation.RavenwoodReplace;
 import android.ravenwood.annotation.RavenwoodThrow;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -51,8 +50,6 @@
 import android.util.Log;
 import android.util.Slog;
 
-import com.android.internal.ravenwood.RavenwoodEnvironment;
-
 import dalvik.system.VMRuntime;
 
 import libcore.io.IoUtils;
@@ -1254,15 +1251,10 @@
         }
     }
 
-    @RavenwoodReplace
     private static boolean isAtLeastQ() {
         return (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q);
     }
 
-    private static boolean isAtLeastQ$ravenwood() {
-        return RavenwoodEnvironment.workaround().isTargetSdkAtLeastQ();
-    }
-
     private static int ifAtLeastQ(int value) {
         return isAtLeastQ() ? value : 0;
     }
diff --git a/core/java/android/os/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 32db3be..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;
@@ -517,9 +519,15 @@
     public static final int GO_TO_SLEEP_REASON_DEVICE_FOLD = 13;
 
     /**
+     * Go to sleep reason code: reason unknown.
      * @hide
      */
-    public static final int GO_TO_SLEEP_REASON_MAX =  GO_TO_SLEEP_REASON_DEVICE_FOLD;
+    public static final int GO_TO_SLEEP_REASON_UNKNOWN = 14;
+
+    /**
+     * @hide
+     */
+    public static final int GO_TO_SLEEP_REASON_MAX =  GO_TO_SLEEP_REASON_UNKNOWN;
 
     /**
      * @hide
@@ -540,6 +548,7 @@
             case GO_TO_SLEEP_REASON_QUIESCENT: return "quiescent";
             case GO_TO_SLEEP_REASON_SLEEP_BUTTON: return "sleep_button";
             case GO_TO_SLEEP_REASON_TIMEOUT: return "timeout";
+            case GO_TO_SLEEP_REASON_UNKNOWN: return "unknown";
             default: return Integer.toString(sleepReason);
         }
     }
@@ -635,6 +644,7 @@
             GO_TO_SLEEP_REASON_QUIESCENT,
             GO_TO_SLEEP_REASON_SLEEP_BUTTON,
             GO_TO_SLEEP_REASON_TIMEOUT,
+            GO_TO_SLEEP_REASON_UNKNOWN,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface GoToSleepReason{}
@@ -1191,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}
@@ -2681,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);
     }
 
@@ -2701,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();
         }
     }
 
@@ -2733,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;
@@ -2786,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();
@@ -2831,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
@@ -2842,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/Process.java b/core/java/android/os/Process.java
index 71d29af..e728243 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -29,6 +29,11 @@
 import android.annotation.UptimeMillisLong;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build.VERSION_CODES;
+import android.ravenwood.annotation.RavenwoodKeep;
+import android.ravenwood.annotation.RavenwoodKeepPartialClass;
+import android.ravenwood.annotation.RavenwoodRedirect;
+import android.ravenwood.annotation.RavenwoodRedirectionClass;
+import android.ravenwood.annotation.RavenwoodReplace;
 import android.sysprop.MemoryProperties;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -37,8 +42,6 @@
 import android.util.Pair;
 import android.webkit.WebViewZygote;
 
-import com.android.internal.os.SomeArgs;
-import com.android.internal.util.Preconditions;
 import com.android.sdksandbox.flags.Flags;
 
 import dalvik.system.VMDebug;
@@ -55,6 +58,8 @@
 /**
  * Tools for managing OS processes.
  */
+@RavenwoodKeepPartialClass
+@RavenwoodRedirectionClass("Process_ravenwood")
 public class Process {
     private static final String LOG_TAG = "Process";
 
@@ -671,7 +676,6 @@
      */
     public static final ZygoteProcess ZYGOTE_PROCESS = new ZygoteProcess();
 
-
     /**
      * The process name set via {@link #setArgV0(String)}.
      */
@@ -845,47 +849,20 @@
     /**
      * Returns true if the current process is a 64-bit runtime.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final boolean is64Bit() {
         return VMRuntime.getRuntime().is64Bit();
     }
 
-    private static volatile ThreadLocal<SomeArgs> sIdentity$ravenwood;
-
-    /** @hide */
-    @android.ravenwood.annotation.RavenwoodKeep
-    public static void init$ravenwood(final int uid, final int pid) {
-        sIdentity$ravenwood = ThreadLocal.withInitial(() -> {
-            final SomeArgs args = SomeArgs.obtain();
-            args.argi1 = uid;
-            args.argi2 = pid;
-            args.argi3 = Long.hashCode(Thread.currentThread().getId());
-            args.argi4 = THREAD_PRIORITY_DEFAULT;
-            args.arg1 = Boolean.TRUE; // backgroundOk
-            return args;
-        });
-    }
-
-    /** @hide */
-    @android.ravenwood.annotation.RavenwoodKeep
-    public static void reset$ravenwood() {
-        sIdentity$ravenwood = null;
-    }
-
     /**
      * Returns the identifier of this process, which can be used with
      * {@link #killProcess} and {@link #sendSignal}.
      */
-    @android.ravenwood.annotation.RavenwoodReplace
+    @RavenwoodKeep
     public static final int myPid() {
         return Os.getpid();
     }
 
-    /** @hide */
-    public static final int myPid$ravenwood() {
-        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi2;
-    }
-
     /**
      * Returns the identifier of this process' parent.
      * @hide
@@ -899,39 +876,29 @@
      * Returns the identifier of the calling thread, which be used with
      * {@link #setThreadPriority(int, int)}.
      */
-    @android.ravenwood.annotation.RavenwoodReplace
+    @RavenwoodKeep
     public static final int myTid() {
         return Os.gettid();
     }
 
-    /** @hide */
-    public static final int myTid$ravenwood() {
-        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi3;
-    }
-
     /**
      * Returns the identifier of this process's uid.  This is the kernel uid
      * that the process is running under, which is the identity of its
      * app-specific sandbox.  It is different from {@link #myUserHandle} in that
      * a uid identifies a specific app sandbox in a specific user.
      */
-    @android.ravenwood.annotation.RavenwoodReplace
+    @RavenwoodKeep
     public static final int myUid() {
         return Os.getuid();
     }
 
-    /** @hide */
-    public static final int myUid$ravenwood() {
-        return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi1;
-    }
-
     /**
      * Returns this process's user handle.  This is the
      * user the process is running under.  It is distinct from
      * {@link #myUid()} in that a particular user will have multiple
      * distinct apps running under it each with their own uid.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static UserHandle myUserHandle() {
         return UserHandle.of(UserHandle.getUserId(myUid()));
     }
@@ -940,7 +907,7 @@
      * Returns whether the given uid belongs to a system core component or not.
      * @hide
      */
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static boolean isCoreUid(int uid) {
         return UserHandle.isCore(uid);
     }
@@ -951,7 +918,7 @@
      * @return Whether the uid corresponds to an application sandbox running in
      *     a specific user.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static boolean isApplicationUid(int uid) {
         return UserHandle.isApp(uid);
     }
@@ -959,7 +926,7 @@
     /**
      * Returns whether the current process is in an isolated sandbox.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final boolean isIsolated() {
         return isIsolated(myUid());
     }
@@ -971,7 +938,7 @@
     @Deprecated
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
             publicAlternatives = "Use {@link #isIsolatedUid(int)} instead.")
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final boolean isIsolated(int uid) {
         return isIsolatedUid(uid);
     }
@@ -979,7 +946,7 @@
     /**
      * Returns whether the process with the given {@code uid} is an isolated sandbox.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final boolean isIsolatedUid(int uid) {
         uid = UserHandle.getAppId(uid);
         return (uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID)
@@ -991,7 +958,7 @@
      * @see android.app.sdksandbox.SdkSandboxManager
      */
     @SuppressLint("UnflaggedApi") // promoting from @SystemApi.
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final boolean isSdkSandboxUid(int uid) {
         uid = UserHandle.getAppId(uid);
         return (uid >= FIRST_SDK_SANDBOX_UID && uid <= LAST_SDK_SANDBOX_UID);
@@ -1007,7 +974,7 @@
      * @throws IllegalArgumentException if input is not an sdk sandbox uid
      */
     @SuppressLint("UnflaggedApi") // promoting from @SystemApi.
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final int getAppUidForSdkSandboxUid(int uid) {
         if (!isSdkSandboxUid(uid)) {
             throw new IllegalArgumentException("Input UID is not an SDK sandbox UID");
@@ -1023,7 +990,7 @@
      */
     @SystemApi(client = MODULE_LIBRARIES)
     @TestApi
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     // TODO(b/318651609): Deprecate once Process#getSdkSandboxUidForAppUid is rolled out to 100%
     public static final int toSdkSandboxUid(int uid) {
         return uid + (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID);
@@ -1039,7 +1006,7 @@
      * @throws IllegalArgumentException if input is not an app uid
      */
     @FlaggedApi(Flags.FLAG_SDK_SANDBOX_UID_TO_APP_UID_API)
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final int getSdkSandboxUidForAppUid(int uid) {
         if (!isApplicationUid(uid)) {
             throw new IllegalArgumentException("Input UID is not an app UID");
@@ -1050,7 +1017,7 @@
     /**
      * Returns whether the current process is a sdk sandbox process.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
+    @RavenwoodKeep
     public static final boolean isSdkSandbox() {
         return isSdkSandboxUid(myUid());
     }
@@ -1127,28 +1094,11 @@
      * not have permission to modify the given thread, or to use the given
      * priority.
      */
-    @android.ravenwood.annotation.RavenwoodReplace
+    @RavenwoodRedirect
     public static final native void setThreadPriority(int tid,
             @IntRange(from = -20, to = THREAD_PRIORITY_LOWEST) int priority)
             throws IllegalArgumentException, SecurityException;
 
-    /** @hide */
-    public static final void setThreadPriority$ravenwood(int tid, int priority) {
-        final SomeArgs args =
-                Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get();
-        if (args.argi3 == tid) {
-            boolean backgroundOk = (args.arg1 == Boolean.TRUE);
-            if (priority >= THREAD_PRIORITY_BACKGROUND && !backgroundOk) {
-                throw new IllegalArgumentException(
-                        "Priority " + priority + " blocked by setCanSelfBackground()");
-            }
-            args.argi4 = priority;
-        } else {
-            throw new UnsupportedOperationException(
-                    "Cross-thread priority management not yet available in Ravenwood");
-        }
-    }
-
     /**
      * Call with 'false' to cause future calls to {@link #setThreadPriority(int)} to
      * throw an exception if passed a background-level thread priority.  This is only
@@ -1156,16 +1106,9 @@
      *
      * @hide
      */
-    @android.ravenwood.annotation.RavenwoodReplace
+    @RavenwoodRedirect
     public static final native void setCanSelfBackground(boolean backgroundOk);
 
-    /** @hide */
-    public static final void setCanSelfBackground$ravenwood(boolean backgroundOk) {
-        final SomeArgs args =
-                Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get();
-        args.arg1 = Boolean.valueOf(backgroundOk);
-    }
-
     /**
      * Sets the scheduling group for a thread.
      * @hide
@@ -1294,13 +1237,12 @@
      *
      * @see #setThreadPriority(int, int)
      */
-    @android.ravenwood.annotation.RavenwoodReplace
+    @RavenwoodReplace
     public static final native void setThreadPriority(
             @IntRange(from = -20, to = THREAD_PRIORITY_LOWEST) int priority)
             throws IllegalArgumentException, SecurityException;
 
-    /** @hide */
-    public static final void setThreadPriority$ravenwood(int priority) {
+    private static void setThreadPriority$ravenwood(int priority) {
         setThreadPriority(myTid(), priority);
     }
 
@@ -1317,23 +1259,11 @@
      * @throws IllegalArgumentException Throws IllegalArgumentException if
      * <var>tid</var> does not exist.
      */
-    @android.ravenwood.annotation.RavenwoodReplace
+    @RavenwoodRedirect
     @IntRange(from = -20, to = THREAD_PRIORITY_LOWEST)
     public static final native int getThreadPriority(int tid)
             throws IllegalArgumentException;
 
-    /** @hide */
-    public static final int getThreadPriority$ravenwood(int tid) {
-        final SomeArgs args =
-                Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get();
-        if (args.argi3 == tid) {
-            return args.argi4;
-        } else {
-            throw new UnsupportedOperationException(
-                    "Cross-thread priority management not yet available in Ravenwood");
-        }
-    }
-
     /**
      * Return the current scheduling policy of a thread, based on Linux.
      *
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index b5ecc14..edeb75b 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -33,6 +33,7 @@
 import android.app.ActivityTaskManager;
 import android.app.ActivityThread;
 import android.app.IActivityManager;
+import android.app.IActivityTaskManager;
 import android.app.IBackgroundActivityLaunchCallback;
 import android.app.IUnsafeIntentStrictModeCallback;
 import android.app.PendingIntent;
@@ -364,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)
@@ -500,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)
              */
@@ -508,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;
@@ -516,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.
@@ -543,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);
             }
@@ -609,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() {
@@ -642,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.
              *
@@ -654,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.
              *
@@ -664,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.
              */
@@ -684,7 +685,7 @@
             }
 
             /**
-             * Call #{@link OnThreadViolationListener#onThreadViolation(Violation)} on specified
+             * Calls #{@link OnThreadViolationListener#onThreadViolation(Violation)} on specified
              * executor every violation.
              */
             public @NonNull Builder penaltyListener(
@@ -714,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.
@@ -804,7 +805,7 @@
                 mMask = 0;
             }
 
-            /** Build upon an existing VmPolicy. */
+            /** Builds upon an existing VmPolicy. */
             public Builder(VmPolicy base) {
                 mMask = base.mask;
                 mClassInstanceLimitNeedCow = true;
@@ -814,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) {
@@ -837,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);
             }
@@ -851,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,
@@ -862,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.
@@ -872,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.
@@ -923,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.
@@ -934,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.
@@ -945,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
@@ -972,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
@@ -991,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}.
@@ -1007,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.
              *
@@ -1027,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>
@@ -1050,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
@@ -1071,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
@@ -1085,7 +1086,7 @@
             }
 
             /**
-             * Disable detection of incorrect context use.
+             * Disables detection of incorrect context use.
              *
              * @see #detectIncorrectContextUse()
              *
@@ -1097,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
@@ -1138,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
@@ -1213,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.
              */
@@ -1228,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) {
@@ -1257,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.
@@ -1473,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.
@@ -1511,7 +1512,7 @@
     }
 
     /**
-     * Initialize default {@link ThreadPolicy} for the current thread.
+     * Initializes default {@link ThreadPolicy} for the current thread.
      *
      * @hide
      */
@@ -1546,7 +1547,7 @@
     }
 
     /**
-     * Initialize default {@link VmPolicy} for the current VM.
+     * Initializes default {@link VmPolicy} for the current VM.
      *
      * @hide
      */
@@ -2189,8 +2190,11 @@
 
     private static void registerBackgroundActivityLaunchCallback() {
         try {
-            ActivityTaskManager.getService().registerBackgroundActivityStartCallback(
+            IActivityTaskManager service = ActivityTaskManager.getService();
+            if (service != null) {
+                service.registerBackgroundActivityStartCallback(
                     new BackgroundActivityLaunchCallback());
+            }
         } catch (DeadObjectException e) {
             // ignore
         } catch (RemoteException e) {
@@ -2240,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
@@ -2541,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) {
@@ -2808,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.
@@ -3052,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;
@@ -3127,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
          */
@@ -3156,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.
@@ -3199,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);
@@ -3244,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/UserManager.java b/core/java/android/os/UserManager.java
index fa99f35..4bc8fe0 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -5502,10 +5502,14 @@
             Manifest.permission.CREATE_USERS,
             Manifest.permission.QUERY_USERS}, conditional = true)
     public @NonNull int[] getProfileIds(@UserIdInt int userId, boolean enabledOnly) {
-        try {
-            return mService.getProfileIds(userId, enabledOnly);
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
+        if (android.multiuser.Flags.cacheProfileIdsReadOnly()) {
+            return enabledOnly ? getEnabledProfileIds(userId) : getProfileIdsWithDisabled(userId);
+        } else {
+            try {
+                return mService.getProfileIds(userId, enabledOnly);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
         }
     }
 
@@ -5518,8 +5522,14 @@
             Manifest.permission.MANAGE_USERS,
             Manifest.permission.CREATE_USERS,
             Manifest.permission.QUERY_USERS}, conditional = true)
+    @CachedProperty(api = "user_manager_users")
     public int[] getProfileIdsWithDisabled(@UserIdInt int userId) {
-        return getProfileIds(userId, false /* enabledOnly */);
+        if (android.multiuser.Flags.cacheProfileIdsReadOnly()) {
+            return UserManagerCache.getProfileIdsWithDisabled(
+                (Integer userIdentifuer) -> mService.getProfileIds(userIdentifuer, false), userId);
+        } else {
+            return getProfileIds(userId, false /* enabledOnly */);
+        }
     }
 
     /**
@@ -5530,8 +5540,21 @@
             Manifest.permission.MANAGE_USERS,
             Manifest.permission.CREATE_USERS,
             Manifest.permission.QUERY_USERS}, conditional = true)
+    @CachedProperty(api = "user_manager_users_enabled")
     public int[] getEnabledProfileIds(@UserIdInt int userId) {
-        return getProfileIds(userId, true /* enabledOnly */);
+        if (android.multiuser.Flags.cacheProfileIdsReadOnly()) {
+            return UserManagerCache.getEnabledProfileIds(
+                (Integer userIdentifuer) -> mService.getProfileIds(userIdentifuer, true), userId);
+        } else {
+            return getProfileIds(userId, true /* enabledOnly */);
+        }
+    }
+
+    /** @hide */
+    public static final void invalidateEnabledProfileIds() {
+        if (android.multiuser.Flags.cacheProfileIdsReadOnly()) {
+            UserManagerCache.invalidateEnabledProfileIds();
+        }
     }
 
     /**
@@ -6443,6 +6466,21 @@
         if (android.multiuser.Flags.cacheProfileParentReadOnly()) {
             UserManagerCache.invalidateProfileParent();
         }
+        invalidateEnabledProfileIds();
+    }
+
+    /**
+     * Invalidate caches when related to specific user info flags change.
+     *
+     * @param flag a combination of FLAG_ constants, from the list in
+     *        {@link UserInfo#UserInfoFlag}, whose value has changed and the associated
+     *        invalidations must therefore be performed.
+     * @hide
+     */
+    public static final void invalidateOnUserInfoFlagChange(@UserInfoFlag int flags) {
+        if ((flags & UserInfo.FLAG_DISABLED) > 0) {
+            invalidateEnabledProfileIds();
+        }
     }
 
     /**
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index a3844e2..0cffd9f 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -1740,7 +1740,9 @@
      *
      * <p>The waveform envelope builder offers more flexibility for creating waveform effects,
      * allowing control over vibration amplitude and frequency via smooth transitions between
-     * values.
+     * values. The waveform will start the first transition from the vibrator off state, using
+     * the same frequency of the first control point. To provide a different initial vibration
+     * frequency, use {@link #startWaveformEnvelope(float)}.
      *
      * <p>Note: To check whether waveform envelope effects are supported, use
      * {@link Vibrator#areEnvelopeEffectsSupported()}.
@@ -1754,6 +1756,32 @@
     }
 
     /**
+     * Start building a waveform vibration with an initial frequency.
+     *
+     * <p>The waveform envelope builder offers more flexibility for creating waveform effects,
+     * allowing control over vibration amplitude and frequency via smooth transitions between
+     * values.
+     *
+     * <p>This is the same as {@link #startWaveformEnvelope()}, but the waveform will start
+     * vibrating at given frequency, in hertz, while it transitions to the new amplitude and
+     * frequency of the first control point.
+     *
+     * <p>Note: To check whether waveform envelope effects are supported, use
+     * {@link Vibrator#areEnvelopeEffectsSupported()}.
+     *
+     * @param initialFrequencyHz The starting frequency of the vibration, in hertz. Must be greater
+     *                           than zero.
+     *
+     * @see VibrationEffect.WaveformEnvelopeBuilder
+     */
+    @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    @NonNull
+    public static VibrationEffect.WaveformEnvelopeBuilder startWaveformEnvelope(
+            @FloatRange(from = 0) float initialFrequencyHz) {
+        return new WaveformEnvelopeBuilder(initialFrequencyHz);
+    }
+
+    /**
      * A builder for waveform effects described by its envelope.
      *
      * <p>Waveform effect envelopes are defined by one or more control points describing a target
@@ -1810,6 +1838,10 @@
 
         private WaveformEnvelopeBuilder() {}
 
+        private WaveformEnvelopeBuilder(float initialFrequency) {
+            mLastFrequencyHz = initialFrequency;
+        }
+
         /**
          * Adds a new control point to the end of this waveform envelope.
          *
@@ -1841,7 +1873,7 @@
                 @FloatRange(from = 0, to = 1) float amplitude,
                 @FloatRange(from = 0) float frequencyHz, int timeMillis) {
 
-            if (mSegments.isEmpty()) {
+            if (mLastFrequencyHz == 0) {
                 mLastFrequencyHz = frequencyHz;
             }
 
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/os/vibrator/PwlePoint.java b/core/java/android/os/vibrator/PwlePoint.java
new file mode 100644
index 0000000..ea3ae6c
--- /dev/null
+++ b/core/java/android/os/vibrator/PwlePoint.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import java.util.Objects;
+
+/**
+ * A {@link PwlePoint} represents a single point in an envelope vibration effect. Defined by its
+ * amplitude, frequency and time to transition to this point from the previous one in the envelope.
+ *
+ * @hide
+ */
+public final class PwlePoint {
+    private final float mAmplitude;
+    private final float mFrequencyHz;
+    private final int mTimeMillis;
+
+    /** @hide */
+    public PwlePoint(float amplitude, float frequencyHz, int timeMillis) {
+        mAmplitude = amplitude;
+        mFrequencyHz = frequencyHz;
+        mTimeMillis = timeMillis;
+    }
+
+    public float getAmplitude() {
+        return mAmplitude;
+    }
+
+    public float getFrequencyHz() {
+        return mFrequencyHz;
+    }
+
+    public int getTimeMillis() {
+        return mTimeMillis;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof PwlePoint)) {
+            return false;
+        }
+        PwlePoint other = (PwlePoint) obj;
+        return Float.compare(mAmplitude, other.mAmplitude) == 0
+                && Float.compare(mFrequencyHz, other.mFrequencyHz) == 0
+                && mTimeMillis == other.mTimeMillis;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mAmplitude, mFrequencyHz, mTimeMillis);
+    }
+}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 7944be1..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"
@@ -314,3 +332,56 @@
     description: "Enable AppOp mode caching in AppOpsManager"
     bug: "366013082"
 }
+
+flag {
+    name: "permission_tree_apis_deprecated"
+    is_fixed_read_only: true
+    is_exported: true
+    namespace: "permissions"
+    description: "This flag is used to deprecate permission tree related APIs"
+    bug: "376535612"
+}
+
+flag {
+    name: "enable_otp_in_text_classifiers"
+    is_fixed_read_only: true
+    is_exported: true
+    namespace: "permissions"
+    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/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 8afc177..1b289fd 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -9383,7 +9383,14 @@
          * @param resolver the ContentResolver to query.
          * @return the default account for new contacts, or null if it's not set or set to NULL
          * account.
+         *
+         * @deprecated This API is only supported up to Android version
+         *      * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}. On later versions,
+         * {@link ContactsContract.RawContacts.DefaultAccount#getDefaultAccountForNewContacts}
+         * should be used.
          */
+        @Deprecated
+        @FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED)
         @Nullable
         public static Account getDefaultAccount(@NonNull ContentResolver resolver) {
             Bundle response = resolver.call(ContactsContract.AUTHORITY_URI,
@@ -9404,7 +9411,14 @@
          * @param resolver the ContentResolver to query.
          * @param account the account to be set to default.
          * @hide
+         *
+         * @deprecated This API is only supported up to Android version
+         *      * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}. On later versions,
+         * {@link ContactsContract.RawContacts.DefaultAccount#setDefaultAccountForNewContacts}
+         * should be used.
          */
+        @Deprecated
+        @FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED)
         @SystemApi
         @RequiresPermission(android.Manifest.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS)
         public static void setDefaultAccount(@NonNull ContentResolver resolver,
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index dec28c3..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
 }
@@ -87,6 +88,14 @@
 }
 
 flag {
+    name: "prevent_intent_redirect_show_toast"
+    namespace: "responsible_apis"
+    description: "Prevent intent redirect attacks by showing a toast when activity start is blocked"
+    bug: "361143368"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "enable_intent_matching_flags"
     is_exported: true
     namespace: "permissions"
diff --git a/core/java/android/service/games/GameSession.java b/core/java/android/service/games/GameSession.java
index e8d53d3..531e0b1 100644
--- a/core/java/android/service/games/GameSession.java
+++ b/core/java/android/service/games/GameSession.java
@@ -516,6 +516,8 @@
                         options,
                         future);
 
+        trampolineIntent.collectExtraIntentKeys();
+
         try {
             int result = ActivityTaskManager.getService().startActivityFromGameSession(
                     mContext.getIApplicationThread(), mContext.getPackageName(), "GameSession",
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/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index f1ae22e..e9e2646 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -22,6 +22,7 @@
 import android.graphics.RectF;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
+import android.app.wallpaper.WallpaperDescription;
 import android.os.Bundle;
 
 /**
@@ -50,4 +51,5 @@
     SurfaceControl mirrorSurfaceControl();
     oneway void applyDimming(float dimAmount);
     oneway void setWallpaperFlags(int which);
+    @nullable WallpaperDescription onApplyWallpaper(int which);
 }
diff --git a/core/java/android/service/wallpaper/IWallpaperService.aidl b/core/java/android/service/wallpaper/IWallpaperService.aidl
index 3262f3a..f76e6ce 100644
--- a/core/java/android/service/wallpaper/IWallpaperService.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperService.aidl
@@ -17,6 +17,7 @@
 package android.service.wallpaper;
 
 import android.app.WallpaperInfo;
+import android.app.wallpaper.WallpaperDescription;
 import android.graphics.Rect;
 import android.service.wallpaper.IWallpaperConnection;
 
@@ -27,6 +28,6 @@
     void attach(IWallpaperConnection connection,
             IBinder windowToken, int windowType, boolean isPreview,
             int reqWidth, int reqHeight, in Rect padding, int displayId, int which,
-            in WallpaperInfo info);
+            in WallpaperInfo info, in @nullable WallpaperDescription description);
     void detach(IBinder windowToken);
 }
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 7f54cf0..131fdc8 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -16,6 +16,7 @@
 
 package android.service.wallpaper;
 
+import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING;
 import static android.app.WallpaperManager.COMMAND_FREEZE;
 import static android.app.WallpaperManager.COMMAND_UNFREEZE;
 import static android.app.WallpaperManager.SetWallpaperFlags;
@@ -50,6 +51,7 @@
 import android.app.WallpaperInfo;
 import android.app.WallpaperManager;
 import android.app.compat.CompatChanges;
+import android.app.wallpaper.WallpaperDescription;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.EnabledSince;
@@ -922,6 +924,24 @@
         }
 
         /**
+         * Called when the wallpaper preview rendered by this engine is about to be persisted as
+         * a selected wallpaper. The returned WallpaperDescription (if any) will be persisted by
+         * the system and passed into subsequent calls to
+         * {@link WallpaperService#onCreateEngine(WallpaperDescription)}. This allows the Engine
+         * to perform any necessary bookkeeping before a wallpaper being previewed is set on
+         * the device, and update the description if necessary.
+         *
+         * @param which Specifies wallpaper destination: home, lock, or both
+         * @return the description of the applied wallpaper, or {@code null} if description is
+         * unchanged
+         */
+        @Nullable
+        @FlaggedApi(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+        public WallpaperDescription onApplyWallpaper(@SetWallpaperFlags int which) {
+            return null;
+        }
+
+        /**
          * Notifies the engine that wallpaper colors changed significantly.
          * This will trigger a {@link #onComputeColors()} call.
          */
@@ -2449,6 +2469,7 @@
         final Display mDisplay;
         final WallpaperManager mWallpaperManager;
         @Nullable final WallpaperInfo mInfo;
+        @NonNull final WallpaperDescription mDescription;
 
         Engine mEngine;
         @SetWallpaperFlags int mWhich;
@@ -2456,7 +2477,8 @@
         IWallpaperEngineWrapper(WallpaperService service,
                 IWallpaperConnection conn, IBinder windowToken,
                 int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
-                int displayId, @SetWallpaperFlags int which, @Nullable WallpaperInfo info) {
+                int displayId, @SetWallpaperFlags int which, @Nullable WallpaperInfo info,
+                @NonNull WallpaperDescription description) {
             mWallpaperManager = getSystemService(WallpaperManager.class);
             mCaller = new HandlerCaller(service, service.onProvideEngineLooper(), this, true);
             mConnection = conn;
@@ -2469,6 +2491,7 @@
             mDisplayId = displayId;
             mWhich = which;
             mInfo = info;
+            mDescription = description;
 
             // Create a display context before onCreateEngine.
             mDisplayManager = getSystemService(DisplayManager.class);
@@ -2593,9 +2616,19 @@
             return mEngine == null ? null : SurfaceControl.mirrorSurface(mEngine.mSurfaceControl);
         }
 
+        @Nullable
+        public WallpaperDescription onApplyWallpaper(@SetWallpaperFlags int which) {
+            return mEngine != null ? mEngine.onApplyWallpaper(which) : null;
+        }
+
         private void doAttachEngine() {
             Trace.beginSection("WPMS.onCreateEngine");
-            Engine engine = onCreateEngine();
+            Engine engine;
+            if (mDescription != null) {
+                engine = onCreateEngine(mDescription);
+            } else {
+                engine = onCreateEngine();
+            }
             Trace.endSection();
             mEngine = engine;
             Trace.beginSection("WPMS.mConnection.attachEngine-" + mDisplayId);
@@ -2804,11 +2837,13 @@
         @Override
         public void attach(IWallpaperConnection conn, IBinder windowToken,
                 int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
-                int displayId, @SetWallpaperFlags int which, @Nullable WallpaperInfo info) {
+                int displayId, @SetWallpaperFlags int which, WallpaperInfo info,
+                @NonNull WallpaperDescription description) {
             Trace.beginSection("WPMS.ServiceWrapper.attach");
             IWallpaperEngineWrapper engineWrapper =
                     new IWallpaperEngineWrapper(mTarget, conn, windowToken, windowType,
-                            isPreview, reqWidth, reqHeight, padding, displayId, which, info);
+                            isPreview, reqWidth, reqHeight, padding, displayId, which, info,
+                            description);
             synchronized (mActiveEngines) {
                 mActiveEngines.put(windowToken, engineWrapper);
             }
@@ -2883,6 +2918,19 @@
     @MainThread
     public abstract Engine onCreateEngine();
 
+    /**
+     * Creates a new engine instance to show the given content. See also {@link #onCreateEngine()}.
+     *
+     * @param description content to display
+     * @return the rendering engine
+     */
+    @FlaggedApi(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+    @MainThread
+    @Nullable
+    public Engine onCreateEngine(@NonNull WallpaperDescription description) {
+        return onCreateEngine();
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter out, String[] args) {
         out.print("State of wallpaper "); out.print(this); out.println(":");
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index 46e27dc..64a5533 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -1715,11 +1715,10 @@
          * @see TelephonyManager#EMERGENCY_CALLBACK_MODE_SMS
          *
          * @param timerDuration is the time remaining in the emergency callback mode.
-         * @param subId The subscription ID used to start the emergency callback mode.
+         * @param subscriptionId The subscription ID used to start the emergency callback mode.
          */
-        @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
         void onCallbackModeStarted(@TelephonyManager.EmergencyCallbackModeType int type,
-                @NonNull Duration timerDuration, int subId);
+                @NonNull Duration timerDuration, int subscriptionId);
 
         /**
          * Indicates that emergency callback mode has been re-started.
@@ -1734,11 +1733,10 @@
          * @see TelephonyManager#EMERGENCY_CALLBACK_MODE_SMS
          *
          * @param timerDuration is the time remaining in the emergency callback mode.
-         * @param subId The subscription ID used to restart the emergency callback mode.
+         * @param subscriptionId The subscription ID used to restart the emergency callback mode.
          */
-        @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
         void onCallbackModeRestarted(@TelephonyManager.EmergencyCallbackModeType int type,
-                @NonNull Duration timerDuration, int subId);
+                @NonNull Duration timerDuration, int subscriptionId);
 
         /**
          * Indicates that emergency callback mode has been stopped.
@@ -1759,11 +1757,10 @@
          * @see TelephonyManager#STOP_REASON_TIMER_EXPIRED
          * @see TelephonyManager#STOP_REASON_USER_ACTION
          *
-         * @param subId is the current subscription used the emergency callback mode.
+         * @param subscriptionId is the current subscription used the emergency callback mode.
          */
-        @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
         void onCallbackModeStopped(@TelephonyManager.EmergencyCallbackModeType int type,
-                @TelephonyManager.EmergencyCallbackModeStopReason int reason, int subId);
+                @TelephonyManager.EmergencyCallbackModeStopReason int reason, int subscriptionId);
     }
 
     /**
@@ -2192,7 +2189,7 @@
 
         @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
         public void onCallbackModeStarted(@TelephonyManager.EmergencyCallbackModeType int type,
-                long durationMillis, int subId) {
+                long durationMillis, int subscriptionId) {
             if (!Flags.emergencyCallbackModeNotification()) return;
 
             EmergencyCallbackModeListener listener =
@@ -2203,12 +2200,12 @@
             final Duration timerDuration = Duration.ofMillis(durationMillis);
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> listener.onCallbackModeStarted(type,
-                            timerDuration, subId)));
+                            timerDuration, subscriptionId)));
         }
 
         @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
         public void onCallbackModeRestarted(@TelephonyManager.EmergencyCallbackModeType int type,
-                long durationMillis, int subId) {
+                long durationMillis, int subscriptionId) {
             if (!Flags.emergencyCallbackModeNotification()) return;
 
             EmergencyCallbackModeListener listener =
@@ -2219,12 +2216,12 @@
             final Duration timerDuration = Duration.ofMillis(durationMillis);
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> listener.onCallbackModeRestarted(type,
-                            timerDuration, subId)));
+                            timerDuration, subscriptionId)));
         }
 
         @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
         public void onCallbackModeStopped(@TelephonyManager.EmergencyCallbackModeType int type,
-                @TelephonyManager.EmergencyCallbackModeStopReason int reason, int subId) {
+                @TelephonyManager.EmergencyCallbackModeStopReason int reason, int subscriptionId) {
             if (!Flags.emergencyCallbackModeNotification()) return;
 
             EmergencyCallbackModeListener listener =
@@ -2235,7 +2232,7 @@
 
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> listener.onCallbackModeStopped(type, reason,
-                            subId)));
+                            subscriptionId)));
         }
 
         public void onCarrierRoamingNtnModeChanged(boolean active) {
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 4d50a45..1dab2cf 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -47,6 +47,8 @@
 import android.telephony.ims.ImsCallSession;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.MediaQualityStatus;
+import android.telephony.satellite.SatelliteStateChangeListener;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 
@@ -55,12 +57,14 @@
 import com.android.internal.telephony.ICarrierConfigChangeListener;
 import com.android.internal.telephony.ICarrierPrivilegesCallback;
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
+import com.android.internal.telephony.ISatelliteStateChangeListener;
 import com.android.internal.telephony.ITelephonyRegistry;
 import com.android.server.telecom.flags.Flags;
 
 import java.lang.ref.WeakReference;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.WeakHashMap;
@@ -1482,6 +1486,111 @@
                 pkgName, attributionTag, callback, new int[0], notifyNow);
     }
 
+    @NonNull
+    @GuardedBy("sSatelliteStateChangeListeners")
+    private static final Map<SatelliteStateChangeListener,
+                WeakReference<SatelliteStateChangeListenerWrapper>>
+            sSatelliteStateChangeListeners = new ArrayMap<>();
+
+    /**
+     * Register a {@link SatelliteStateChangeListener} to receive notification when Satellite state
+     * has changed.
+     *
+     * @param executor The {@link Executor} where the {@code listener} will be invoked
+     * @param listener The listener to monitor the satellite state change
+     * @hide
+     */
+    public void addSatelliteStateChangeListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull SatelliteStateChangeListener listener) {
+        if (listener == null || executor == null) {
+            throw new IllegalArgumentException("Listener and executor must be non-null");
+        }
+
+        synchronized (sSatelliteStateChangeListeners) {
+            WeakReference<SatelliteStateChangeListenerWrapper> existing =
+                    sSatelliteStateChangeListeners.get(listener);
+            if (existing != null && existing.get() != null) {
+                Log.d(TAG, "addSatelliteStateChangeListener: listener already registered");
+                return;
+            }
+            SatelliteStateChangeListenerWrapper wrapper =
+                    new SatelliteStateChangeListenerWrapper(executor, listener);
+            try {
+                sRegistry.addSatelliteStateChangeListener(
+                        wrapper,
+                        mContext.getOpPackageName(),
+                        mContext.getAttributionTag());
+                sSatelliteStateChangeListeners.put(listener, new WeakReference<>(wrapper));
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Unregister a {@link SatelliteStateChangeListener} to stop receiving notification when
+     * satellite state has changed.
+     *
+     * @param listener The listener previously registered with addSatelliteStateChangeListener.
+     * @hide
+     */
+    public void removeSatelliteStateChangeListener(@NonNull SatelliteStateChangeListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must be non-null");
+        }
+
+        synchronized (sSatelliteStateChangeListeners) {
+            WeakReference<SatelliteStateChangeListenerWrapper> ref =
+                    sSatelliteStateChangeListeners.get(listener);
+            if (ref == null) return;
+            SatelliteStateChangeListenerWrapper wrapper = ref.get();
+            if (wrapper == null) return;
+            try {
+                sRegistry.removeSatelliteStateChangeListener(wrapper, mContext.getOpPackageName());
+                sSatelliteStateChangeListeners.remove(listener);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Notify the registrants that the satellite state has changed.
+     *
+     * @param isEnabled True if the satellite modem is enabled, false otherwise
+     * @hide
+     */
+    public void notifySatelliteStateChanged(boolean isEnabled) {
+        try {
+            sRegistry.notifySatelliteStateChanged(isEnabled);
+        } catch (RemoteException ex) {
+            // system process is dead
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    private static class SatelliteStateChangeListenerWrapper extends
+            ISatelliteStateChangeListener.Stub implements ListenerExecutor {
+        @NonNull private final WeakReference<SatelliteStateChangeListener> mListener;
+        @NonNull private final Executor mExecutor;
+
+        SatelliteStateChangeListenerWrapper(@NonNull Executor executor,
+                @NonNull SatelliteStateChangeListener listener) {
+            mExecutor = executor;
+            mListener = new WeakReference<>(listener);
+        }
+
+        @Override
+        public void onSatelliteEnabledStateChanged(boolean isEnabled) {
+            Binder.withCleanCallingIdentity(
+                    () ->
+                            executeSafely(
+                                    mExecutor,
+                                    mListener::get,
+                                    sscl -> sscl.onEnabledStateChanged(isEnabled)));
+        }
+    }
+
     private static class CarrierPrivilegesCallbackWrapper extends ICarrierPrivilegesCallback.Stub
             implements ListenerExecutor {
         @NonNull private final WeakReference<CarrierPrivilegesCallback> mCallback;
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index e830d89..02923ed 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -202,3 +202,10 @@
   description: "Deprecate the Paint#elegantTextHeight API and stick it to true"
   bug: "349519475"
 }
+
+flag {
+  name: "vertical_text_layout"
+  namespace: "text"
+  description: "Make Paint class work for vertical layout text."
+  bug: "355296926"
+}
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index 8358b9a..1dd9d46 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -75,8 +75,7 @@
 @android.ravenwood.annotation.RavenwoodClassLoadHook(
         "com.android.platform.test.ravenwood.runtimehelper.ClassLoadHook.onClassLoaded")
 // Uncomment the following annotation to switch to the Java substitution version.
-//@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass(
-//        "com.android.platform.test.ravenwood.nativesubstitution.Log_host")
+@android.ravenwood.annotation.RavenwoodRedirectionClass("Log_host")
 public final class Log {
     /** @hide */
     @IntDef({ASSERT, ERROR, WARN, INFO, DEBUG, VERBOSE})
@@ -250,6 +249,7 @@
      *         tag limit of concern after this API level.
      */
     @FastNative
+    @android.ravenwood.annotation.RavenwoodRedirect
     public static native boolean isLoggable(@Nullable String tag, @Level int level);
 
     /**
@@ -425,6 +425,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @android.ravenwood.annotation.RavenwoodRedirect
     public static native int println_native(int bufID, int priority, String tag, String msg);
 
     /**
@@ -452,6 +453,7 @@
      * Return the maximum payload the log daemon accepts without truncation.
      * @return LOGGER_ENTRY_MAX_PAYLOAD.
      */
+    @android.ravenwood.annotation.RavenwoodRedirect
     private static native int logger_entry_max_payload_native();
 
     /**
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index 1fe06d4..66d64d7 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -176,7 +176,7 @@
 
     /**
      * The user is executing a swipe/drag-style gesture, such as pull-to-refresh, where the
-     * gesture action is “eligible” at a certain threshold of movement, and can be cancelled by
+     * gesture action is "eligible" at a certain threshold of movement, and can be cancelled by
      * moving back past the threshold. This constant indicates that the user's motion has just
      * passed the threshold for the action to be activated on release.
      *
@@ -186,7 +186,7 @@
 
     /**
      * The user is executing a swipe/drag-style gesture, such as pull-to-refresh, where the
-     * gesture action is “eligible” at a certain threshold of movement, and can be cancelled by
+     * gesture action is "eligible" at a certain threshold of movement, and can be cancelled by
      * moving back past the threshold. This constant indicates that the user's motion has just
      * re-crossed back "under" the threshold for the action to be activated, meaning the gesture is
      * currently in a cancelled state.
diff --git a/core/java/android/view/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/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 7877352..acbd95bf 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -212,8 +212,7 @@
                 && mInitiallyVisible == that.mInitiallyVisible
                 && mSurfacePosition.equals(that.mSurfacePosition)
                 && mInsetsHint.equals(that.mInsetsHint)
-                && mSkipAnimationOnce == that.mSkipAnimationOnce
-                && Objects.equals(mImeStatsToken, that.mImeStatsToken);
+                && mSkipAnimationOnce == that.mSkipAnimationOnce;
     }
 
     @Override
diff --git a/core/java/android/view/RoundScrollbarRenderer.java b/core/java/android/view/RoundScrollbarRenderer.java
index 59c2598..5e1eada 100644
--- a/core/java/android/view/RoundScrollbarRenderer.java
+++ b/core/java/android/view/RoundScrollbarRenderer.java
@@ -35,7 +35,9 @@
  * @hide
  */
 public class RoundScrollbarRenderer {
-    private static final String BLUECHIP_ENABLED_SYSPROP = "persist.cw_build.bluechip.enabled";
+    /** @hide */
+    public static final String BLUECHIP_ENABLED_SYSPROP = "persist.cw_build.bluechip.enabled";
+
     // The range of the scrollbar position represented as an angle in degrees.
     private static final float SCROLLBAR_ANGLE_RANGE = 28.8f;
     private static final float MAX_SCROLLBAR_ANGLE_SWIPE = 26.3f; // 90%
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index b8b22e2..df54d31 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -312,6 +312,7 @@
     private static native void nativeNotifyShutdown();
     private static native void nativeSetLuts(long transactionObj, long nativeObject,
             float[] buffers, int[] slots, int[] dimensions, int[] sizes, int[] samplingKeys);
+    private static native void nativeEnableDebugLogCallPoints(long transactionObj);
 
     /**
      * Transforms that can be applied to buffers as they are displayed to a window.
@@ -4605,7 +4606,6 @@
         }
 
         /**
-         * TODO(b/366484871): To be removed once we have some logging in native
          * This is called when BlastBufferQueue.mergeWithNextTransaction() is called from java, and
          * for the purposes of logging that path.
          */
@@ -4616,6 +4616,7 @@
                 if (mCalls != null) {
                     mCalls.clear();
                 }
+                nativeEnableDebugLogCallPoints(mNativeObject);
             }
         }
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d9092ee..5ee229f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -30,7 +30,10 @@
 import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
+import static android.view.accessibility.Flags.FLAG_DEPRECATE_ACCESSIBILITY_ANNOUNCEMENT_APIS;
+import static android.view.accessibility.Flags.FLAG_SUPPLEMENTAL_DESCRIPTION;
 import static android.view.accessibility.Flags.removeChildHoverCheckForTouchExploration;
+import static android.view.accessibility.Flags.supplementalDescription;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN;
@@ -40,6 +43,7 @@
 import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API;
 import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
 import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+import static android.view.flags.Flags.calculateBoundsInParentFromBoundsInScreen;
 import static android.view.flags.Flags.enableUseMeasureCacheDuringForceLayout;
 import static android.view.flags.Flags.sensitiveContentAppProtection;
 import static android.view.flags.Flags.toolkitFrameRateAnimationBugfix25q1;
@@ -50,6 +54,7 @@
 import static android.view.flags.Flags.toolkitFrameRateViewEnablingReadOnly;
 import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision;
 import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
+import static android.view.flags.Flags.toolkitViewgroupSetRequestedFrameRateApi;
 import static android.view.flags.Flags.viewVelocityApi;
 import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
 import static android.view.inputmethod.Flags.initiationWithoutInputConnection;
@@ -967,6 +972,13 @@
     private static boolean sAlwaysRemeasureExactly = false;
 
     /**
+     * When true calculates the bounds in parent from bounds in screen relative to its parents.
+     * This addresses the deprecated API (setBoundsInParent) in Compose, which causes empty
+     * getBoundsInParent call for Compose apps.
+     */
+    private static boolean sCalculateBoundsInParentFromBoundsInScreenFlagValue = false;
+
+    /**
      * When true makes it possible to use onMeasure caches also when the force layout flag is
      * enabled. This helps avoiding multiple measures in the same frame with the same dimensions.
      */
@@ -2467,6 +2479,8 @@
             toolkitFrameRateVelocityMappingReadOnly();
     private static boolean sToolkitFrameRateAnimationBugfix25q1FlagValue =
             toolkitFrameRateAnimationBugfix25q1();
+    private static boolean sToolkitViewGroupFrameRateApiFlagValue =
+            toolkitViewgroupSetRequestedFrameRateApi();
 
     // Used to set frame rate compatibility.
     @Surface.FrameRateCompatibility int mFrameRateCompatibility =
@@ -2556,6 +2570,8 @@
 
         sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly();
         sToolkitMetricsForFrameRateDecisionFlagValue = toolkitMetricsForFrameRateDecision();
+        sCalculateBoundsInParentFromBoundsInScreenFlagValue =
+                calculateBoundsInParentFromBoundsInScreen();
         sUseMeasureCacheDuringForceLayoutFlagValue = enableUseMeasureCacheDuringForceLayout();
     }
 
@@ -3805,6 +3821,8 @@
      *     1                            PFLAG4_HAS_DRAWN
      *    1                             PFLAG4_HAS_MOVED
      *   1                              PFLAG4_HAS_VIEW_PROPERTY_INVALIDATION
+     *  1                               PFLAG4_FORCED_OVERRIDE_FRAME_RATE
+     * 1                                PFLAG4_SELF_REQUESTED_FRAME_RATE
      * |-------|-------|-------|-------|
      */
 
@@ -3955,6 +3973,17 @@
      */
     private static final int PFLAG4_HAS_VIEW_PROPERTY_INVALIDATION = 0x20000000;
 
+    /**
+     * When set, this indicates whether the frame rate of the children should be
+     * forcibly overridden, even if it has been explicitly configured by a user request.
+     */
+    private static final int PFLAG4_FORCED_OVERRIDE_FRAME_RATE = 0x40000000;
+
+    /**
+     * When set, this indicates that the frame rate is configured based on a user request.
+     */
+    private static final int PFLAG4_SELF_REQUESTED_FRAME_RATE = 0x80000000;
+
     /* End of masks for mPrivateFlags4 */
 
     /** @hide */
@@ -4864,6 +4893,11 @@
     private CharSequence mContentDescription;
 
     /**
+     * Brief supplemental information for view and is primarily used for accessibility support.
+     */
+    private CharSequence mSupplementalDescription;
+
+    /**
      * If this view represents a distinct part of the window, it can have a title that labels the
      * area.
      */
@@ -6534,6 +6568,13 @@
                 case R.styleable.View_contentSensitivity:
                     setContentSensitivity(a.getInt(attr, CONTENT_SENSITIVITY_AUTO));
                     break;
+                default: {
+                    if (supplementalDescription()) {
+                        if (attr == com.android.internal.R.styleable.View_supplementalDescription) {
+                            setSupplementalDescription(a.getString(attr));
+                        }
+                    }
+                }
             }
         }
 
@@ -8911,44 +8952,45 @@
     }
 
     /**
-     * Convenience method for sending a {@link AccessibilityEvent#TYPE_ANNOUNCEMENT}
-     * {@link AccessibilityEvent} to suggest that an accessibility service announce the
-     * specified text to its users.
-     * <p>
-     * Note: The event generated with this API carries no semantic meaning, and is appropriate only
-     * in exceptional situations. Apps can generally achieve correct behavior for accessibility by
-     * accurately supplying the semantics of their UI.
-     * They should not need to specify what exactly is announced to users.
+     * Convenience method for sending a {@link AccessibilityEvent#TYPE_ANNOUNCEMENT} {@link
+     * AccessibilityEvent} to suggest that an accessibility service announce the specified text to
+     * its users.
      *
-     * <p>
-     * In general, only announce transitions and don't generate a confirmation message for simple
-     * actions like a button press. Label your controls concisely and precisely instead, and for
-     * significant UI changes like window changes, use
-     * {@link android.app.Activity#setTitle(CharSequence)} and
-     * {@link #setAccessibilityPaneTitle(CharSequence)}.
+     * <p>Note: The event generated with this API carries no semantic meaning, and accessibility
+     * services may choose to ignore it. Apps that accurately supply accessibility with the
+     * semantics of their UI should not need to specify what exactly is announced.
      *
-     * <p>
-     * Use {@link #setAccessibilityLiveRegion(int)} to inform the user of changes to critical
+     * <p>In general, do not attempt to generate announcements as confirmation message for simple
+     * actions like a button press. Label your controls concisely and precisely instead.
+     *
+     * <p>To convey significant UI changes like window changes, use {@link
+     * android.app.Activity#setTitle(CharSequence)} and {@link
+     * #setAccessibilityPaneTitle(CharSequence)}.
+     *
+     * <p>Use {@link #setAccessibilityLiveRegion(int)} to inform the user of changes to critical
      * views within the user interface. These should still be used sparingly as they may generate
      * announcements every time a View is updated.
      *
-     * <p>
-     * For notifying users about errors, such as in a login screen with text that displays an
-     * "incorrect password" notification, that view should send an AccessibilityEvent of type
-     * {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_ERROR} and set
-     * {@link AccessibilityNodeInfo#setError(CharSequence)} instead. Custom widgets should expose
-     * error-setting methods that support accessibility automatically. For example, instead of
-     * explicitly sending this event when using a TextView, use
-     * {@link android.widget.TextView#setError(CharSequence)}.
-     *
-     * <p>
-     * Use {@link #setStateDescription(CharSequence)} to convey state changes to views within the
+     * <p>Use {@link #setStateDescription(CharSequence)} to convey state changes to views within the
      * user interface. While a live region may send different types of events generated by the view,
      * state description will send {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} events of
      * type {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_STATE_DESCRIPTION}.
      *
+     * <p>For notifying users about errors, such as in a login screen with text that displays an
+     * "incorrect password" notification, set {@link AccessibilityNodeInfo#setError(CharSequence)}
+     * and dispatch an {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event with a change
+     * type of {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_ERROR}, instead. Some widgets may
+     * expose methods that convey error states to accessibility automatically, such as {@link
+     * android.widget.TextView#setError(CharSequence)}, which manages these accessibility semantics
+     * and event dispatch for callers.
+     *
+     * @deprecated Use one of the methods described in the documentation above to semantically
+     *     describe UI instead of using an announcement, as accessibility services may choose to
+     *     ignore events dispatched with this method.
      * @param text The announcement text.
      */
+    @FlaggedApi(FLAG_DEPRECATE_ACCESSIBILITY_ANNOUNCEMENT_APIS)
+    @Deprecated
     public void announceForAccessibility(CharSequence text) {
         if (AccessibilityManager.getInstance(mContext).isEnabled() && mParent != null) {
             AccessibilityEvent event = AccessibilityEvent.obtain(
@@ -9776,7 +9818,7 @@
             structure.setChildCount(1);
             final ViewStructure root = structure.newChild(0);
             if (info != null) {
-                populateVirtualStructure(root, provider, info, forAutofill);
+                populateVirtualStructure(root, provider, info, null, forAutofill);
                 info.recycle();
             } else {
                 Log.w(AUTOFILL_LOG_TAG, "AccessibilityNodeInfo is null.");
@@ -11075,11 +11117,19 @@
 
     private void populateVirtualStructure(ViewStructure structure,
             AccessibilityNodeProvider provider, AccessibilityNodeInfo info,
-            boolean forAutofill) {
+            @Nullable AccessibilityNodeInfo parentInfo, boolean forAutofill) {
         structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()),
                 null, null, info.getViewIdResourceName());
         Rect rect = structure.getTempRect();
-        info.getBoundsInParent(rect);
+        // The bounds in parent for Jetpack Compose views aren't set as setBoundsInParent is
+        // deprecated, and only setBoundsInScreen is called.
+        // The bounds in parent can be calculated by diff'ing the child view's bounds in screen with
+        // the parent's.
+        if (sCalculateBoundsInParentFromBoundsInScreenFlagValue) {
+            getBoundsInParent(info, parentInfo, rect);
+        } else {
+            info.getBoundsInParent(rect);
+        }
         structure.setDimens(rect.left, rect.top, 0, 0, rect.width(), rect.height());
         structure.setVisibility(VISIBLE);
         structure.setEnabled(info.isEnabled());
@@ -11163,13 +11213,32 @@
                         AccessibilityNodeInfo.getVirtualDescendantId(info.getChildId(i)));
                 if (cinfo != null) {
                     ViewStructure child = structure.newChild(i);
-                    populateVirtualStructure(child, provider, cinfo, forAutofill);
+                    populateVirtualStructure(child, provider, cinfo, info, forAutofill);
                     cinfo.recycle();
                 }
             }
         }
     }
 
+    private void getBoundsInParent(@NonNull AccessibilityNodeInfo info,
+            @Nullable AccessibilityNodeInfo parentInfo, @NonNull Rect rect) {
+        info.getBoundsInParent(rect);
+        // Fallback to calculate bounds in parent by diffing the bounds in
+        // screen if it's all 0.
+        if ((rect.left | rect.top | rect.right | rect.bottom) == 0) {
+            if (parentInfo != null) {
+                Rect parentBoundsInScreen = parentInfo.getBoundsInScreen();
+                Rect boundsInScreen = info.getBoundsInScreen();
+                rect.set(boundsInScreen.left - parentBoundsInScreen.left,
+                        boundsInScreen.top - parentBoundsInScreen.top,
+                        boundsInScreen.right - parentBoundsInScreen.left,
+                        boundsInScreen.bottom - parentBoundsInScreen.top);
+            } else {
+                info.getBoundsInScreen(rect);
+            }
+        }
+    }
+
     /**
      * Dispatch creation of {@link ViewStructure} down the hierarchy.  The default
      * implementation calls {@link #onProvideStructure} and
@@ -11806,6 +11875,39 @@
     }
 
     /**
+     * Returns the {@link View}'s supplemental description.
+     * <p>
+     * A supplemental description provides
+     * brief supplemental information for this node, such as the purpose of the node when
+     * that purpose is not conveyed within its textual representation. For example, if a
+     * dropdown select has a purpose of setting font family, the supplemental description
+     * could be "font family". If this node has children, its supplemental description serves
+     * as additional information and is not intended to replace any existing information
+     * in the subtree. This is different from the {@link #getContentDescription()} in that
+     * this description is purely supplemental while a content description may be used
+     * to replace a description for a node or its subtree that an assistive technology
+     * would otherwise compute based on other properties of the node and its descendants.
+     *
+     * <p>
+     * <strong>Note:</strong> Do not override this method, as it will have no
+     * effect on the supplemental description presented to accessibility services.
+     * You must call {@link #setSupplementalDescription(CharSequence)} to modify the
+     * supplemental description.
+     *
+     * @return the supplemental description
+     * @see #setSupplementalDescription(CharSequence)
+     * @see #getContentDescription()
+     * @attr ref android.R.styleable#View_supplementalDescription
+     */
+    @FlaggedApi(FLAG_SUPPLEMENTAL_DESCRIPTION)
+    @ViewDebug.ExportedProperty(category = "accessibility")
+    @InspectableProperty
+    @Nullable
+    public CharSequence getSupplementalDescription() {
+        return mSupplementalDescription;
+    }
+
+    /**
      * Sets the {@link View}'s state description.
      * <p>
      * A state description briefly describes the states of the view and is primarily used
@@ -11892,6 +11994,53 @@
     }
 
     /**
+     * Sets the {@link View}'s supplemental description.
+     * <p>
+     * A supplemental description provides
+     * brief supplemental information for this node, such as the purpose of the node when
+     * that purpose is not conveyed within its textual representation. For example, if a
+     * dropdown select has a purpose of setting font family, the supplemental description
+     * could be "font family". If this node has children, its supplemental description serves
+     * as additional information and is not intended to replace any existing information
+     * in the subtree. This is different from the {@link #setContentDescription(CharSequence)}
+     * in that this description is purely supplemental while a content description may be used
+     * to replace a description for a node or its subtree that an assistive technology
+     * would otherwise compute based on other properties of the node and its descendants.
+     *
+     * <p>
+     * This should omit role or state. Role refers to the kind of user-interface element the View
+     * is, such as a Button or Checkbox. State refers to a frequently changing property of the View,
+     * such as an On/Off state of a button or the audio level of a volume slider.
+     *
+     * @param supplementalDescription The supplemental description.
+     * @see #getSupplementalDescription()
+     * @see #setContentDescription(CharSequence)
+     * @see #setStateDescription(CharSequence) for state changes.
+     * @attr ref android.R.styleable#View_supplementalDescription
+     */
+    @FlaggedApi(FLAG_SUPPLEMENTAL_DESCRIPTION)
+    @RemotableViewMethod
+    public void setSupplementalDescription(@Nullable CharSequence supplementalDescription) {
+        if (mSupplementalDescription == null) {
+            if (supplementalDescription == null) {
+                return;
+            }
+        } else if (mSupplementalDescription.equals(supplementalDescription)) {
+            return;
+        }
+        mSupplementalDescription = supplementalDescription;
+        final boolean nonEmptyDesc = supplementalDescription != null
+                && !supplementalDescription.isEmpty();
+        if (nonEmptyDesc && getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+            notifySubtreeAccessibilityStateChangedIfNeeded();
+        } else {
+            notifyViewAccessibilityStateChangedIfNeeded(
+                    AccessibilityEvent.CONTENT_CHANGE_TYPE_SUPPLEMENTAL_DESCRIPTION);
+        }
+    }
+
+    /**
      * Sets the id of a view that screen readers are requested to visit after this view.
      *
      * <p>
@@ -34198,9 +34347,20 @@
      */
     @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
     public void setRequestedFrameRate(float frameRate) {
+        // Skip setting the frame rate if it's currently in forced override mode.
+        if (sToolkitViewGroupFrameRateApiFlagValue && getForcedOverrideFrameRateFlag()) {
+            return;
+        }
+
         if (sToolkitSetFrameRateReadOnlyFlagValue) {
             mPreferredFrameRate = frameRate;
         }
+
+        if (sToolkitViewGroupFrameRateApiFlagValue) {
+            // If frameRate is Float.NaN, it means it's set to the default value.
+            // We only want to make the flag true, when the value is not Float.nan
+            setSelfRequestedFrameRateFlag(!Float.isNaN(mPreferredFrameRate));
+        }
     }
 
     /**
@@ -34224,4 +34384,35 @@
         }
         return 0;
     }
+
+    void overrideFrameRate(float frameRate, boolean forceOverride) {
+        setForcedOverrideFrameRateFlag(forceOverride);
+        if (forceOverride || !getSelfRequestedFrameRateFlag()) {
+            mPreferredFrameRate = frameRate;
+        }
+    }
+
+    void setForcedOverrideFrameRateFlag(boolean forcedOverride) {
+        if (forcedOverride) {
+            mPrivateFlags4 |= PFLAG4_FORCED_OVERRIDE_FRAME_RATE;
+        } else {
+            mPrivateFlags4 &= ~PFLAG4_FORCED_OVERRIDE_FRAME_RATE;
+        }
+    }
+
+    boolean getForcedOverrideFrameRateFlag() {
+        return (mPrivateFlags4 & PFLAG4_FORCED_OVERRIDE_FRAME_RATE) != 0;
+    }
+
+    void setSelfRequestedFrameRateFlag(boolean forcedOverride) {
+        if (forcedOverride) {
+            mPrivateFlags4 |= PFLAG4_SELF_REQUESTED_FRAME_RATE;
+        } else {
+            mPrivateFlags4 &= ~PFLAG4_SELF_REQUESTED_FRAME_RATE;
+        }
+    }
+
+    boolean getSelfRequestedFrameRateFlag() {
+        return (mPrivateFlags4 & PFLAG4_SELF_REQUESTED_FRAME_RATE) != 0;
+    }
 }
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 2237417..4a9916c 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -18,9 +18,12 @@
 
 import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE;
 import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
+import static android.view.flags.Flags.FLAG_TOOLKIT_VIEWGROUP_SET_REQUESTED_FRAME_RATE_API;
+import static android.view.flags.Flags.toolkitViewgroupSetRequestedFrameRateApi;
 
 import android.animation.LayoutTransition;
 import android.annotation.CallSuper;
+import android.annotation.FlaggedApi;
 import android.annotation.IdRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -448,6 +451,14 @@
     private static final int FLAG_SHOW_CONTEXT_MENU_WITH_COORDS = 0x20000000;
 
     /**
+     * When set, this indicates that the frame rate is passed down from the parent.
+     */
+    private static final int FLAG_PROPAGATED_FRAME_RATE = 0x40000000;
+
+    private static boolean sToolkitViewGroupFrameRateApiFlagValue =
+            toolkitViewgroupSetRequestedFrameRateApi();
+
+    /**
      * Indicates which types of drawing caches are to be kept in memory.
      * This field should be made private, so it is hidden from the SDK.
      * {@hide}
@@ -5361,6 +5372,12 @@
         }
 
         touchAccessibilityNodeProviderIfNeeded(child);
+
+        // If a propagated value exists, pass it to the child.
+        if (sToolkitViewGroupFrameRateApiFlagValue && !Float.isNaN(getRequestedFrameRate())
+                && (mGroupFlags & FLAG_PROPAGATED_FRAME_RATE) != 0) {
+            child.overrideFrameRate(getRequestedFrameRate(), getForcedOverrideFrameRateFlag());
+        }
     }
 
     /**
@@ -9465,4 +9482,75 @@
         }
         return null;
     }
+
+    /**
+     * You can set the preferred frame rate for a ViewGroup using a positive number
+     * or by specifying the preferred frame rate category using constants, including
+     * REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE, REQUESTED_FRAME_RATE_CATEGORY_LOW,
+     * REQUESTED_FRAME_RATE_CATEGORY_NORMAL, REQUESTED_FRAME_RATE_CATEGORY_HIGH.
+     * Keep in mind that the preferred frame rate affects the frame rate for the next frame,
+     * so use this method carefully. It's important to note that the preference is valid as
+     * long as the ViewGroup is invalidated. Please also be aware that the requested frame rate
+     * will not propagate to child views.
+     *
+     * @param frameRate the preferred frame rate of the ViewGroup.
+     */
+    @Override
+    @FlaggedApi(FLAG_TOOLKIT_VIEWGROUP_SET_REQUESTED_FRAME_RATE_API)
+    public void setRequestedFrameRate(float frameRate) {
+        if (sToolkitViewGroupFrameRateApiFlagValue) {
+            if (getForcedOverrideFrameRateFlag()) {
+                return;
+            }
+            super.setRequestedFrameRate(frameRate);
+            // If frameRate is Float.NaN, it means it's set to the default value.
+            // We only want to make the flag true, when the value is not Float.nan
+            setSelfRequestedFrameRateFlag(!Float.isNaN(getRequestedFrameRate()));
+            mGroupFlags &= ~FLAG_PROPAGATED_FRAME_RATE;
+        }
+    }
+
+    /**
+     * You can set the preferred frame rate for a ViewGroup and its children using a positive number
+     * or by specifying the preferred frame rate category using constants, including
+     * REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE, REQUESTED_FRAME_RATE_CATEGORY_LOW,
+     * REQUESTED_FRAME_RATE_CATEGORY_NORMAL, REQUESTED_FRAME_RATE_CATEGORY_HIGH.
+     * Keep in mind that the preferred frame rate affects the frame rate for the next frame,
+     * so use this method carefully. It's important to note that the preference is valid as
+     * long as the ViewGroup or any of its children is invalidated.
+     * To undo the frame rate propagation, call the API with REQUESTED_FRAME_RATE_CATEGORY_DEFAULT.
+     *
+     * @param frameRate the preferred frame rate of the ViewGroup.
+     * @param forceOverride indicate whether it should override the frame rate of
+     *        all the children with the given frame rate.
+     */
+    @FlaggedApi(FLAG_TOOLKIT_VIEWGROUP_SET_REQUESTED_FRAME_RATE_API)
+    public void propagateRequestedFrameRate(float frameRate, boolean forceOverride) {
+        if (sToolkitViewGroupFrameRateApiFlagValue) {
+            // Skip setting the frame rate if it's currently in forced override mode.
+            if (getForcedOverrideFrameRateFlag()) {
+                return;
+            }
+
+            // frame rate could be set previously with setRequestedFrameRate
+            // or propagateRequestedFrameRate
+            setSelfRequestedFrameRateFlag(false);
+            overrideFrameRate(frameRate, forceOverride);
+            setSelfRequestedFrameRateFlag(true);
+        }
+    }
+
+    @Override
+    void overrideFrameRate(float frameRate, boolean forceOverride) {
+        // if it's in forceOverrid mode or has no self requested frame rate,
+        // it will override the frame rate.
+        if (forceOverride || !getSelfRequestedFrameRateFlag()) {
+            super.overrideFrameRate(frameRate, forceOverride);
+            mGroupFlags |= FLAG_PROPAGATED_FRAME_RATE;
+
+            for (int i = 0; i < getChildCount(); i++) {
+                getChildAt(i).overrideFrameRate(frameRate, forceOverride);
+            }
+        }
+    }
 }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 5b39f62..b4b0687 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1426,6 +1426,31 @@
             "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE";
 
     /**
+     * Activity-level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+     * that specifies whether this activity can declare or request
+     * {@link android.R.attr#screenOrientation fixed orientation},
+     * {@link android.R.attr#minAspectRatio max aspect ratio},
+     * {@link android.R.attr#maxAspectRatio min aspect ratio}
+     * {@link android.R.attr#resizeableActivity unresizable} on large screen devices with the
+     * ignore orientation request display setting enabled since Android 16 (API level 36) or higher.
+     *
+     * <p>The default value is {@code false}.
+     *
+     * <p><b>Syntax:</b>
+     * <pre>
+     * &lt;activity&gt;
+     *   &lt;property
+     *     android:name="android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY"
+     *     android:value="true"/&gt;
+     * &lt;/activity&gt;
+     * </pre>
+     * @hide
+     */
+    // TODO(b/357141415): Make this public API.
+    String PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY =
+            "android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY";
+
+    /**
      * @hide
      */
     public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 4e9d054..0dfaf41 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -563,10 +563,13 @@
 
     /**
      * Represents the event of an application making an announcement.
-     * <p>
-     * In general, follow the practices described in
-     * {@link View#announceForAccessibility(CharSequence)}.
+     *
+     * @deprecated Use one of the semantic alternative methods described in the documentation of
+     *     {@link View#announceForAccessibility(CharSequence)} instead of using this event, as
+     *     accessibility services may choose to ignore dispatch of this event type.
      */
+    @FlaggedApi(Flags.FLAG_DEPRECATE_ACCESSIBILITY_ANNOUNCEMENT_APIS)
+    @Deprecated
     public static final int TYPE_ANNOUNCEMENT = 1 << 14;
 
     /**
@@ -810,6 +813,20 @@
     @FlaggedApi(Flags.FLAG_A11Y_EXPANSION_STATE_API)
     public static final int CONTENT_CHANGE_TYPE_EXPANDED = 1 << 14;
 
+    /**
+     * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+     * The source node changed its supplemental description, which is returned by
+     * {@link AccessibilityNodeInfo#getSupplementalDescription()}.
+     * The view changing its supplemental description should call
+     * {@link AccessibilityNodeInfo#setSupplementalDescription(CharSequence)} and
+     * then send this event.
+     *
+     * @see AccessibilityNodeInfo#getSupplementalDescription()
+     * @see AccessibilityNodeInfo#setSupplementalDescription(CharSequence)
+     */
+    @FlaggedApi(Flags.FLAG_SUPPLEMENTAL_DESCRIPTION)
+    public static final int CONTENT_CHANGE_TYPE_SUPPLEMENTAL_DESCRIPTION = 1 << 15;
+
     // Speech state change types.
 
     /** Change type for {@link #TYPE_SPEECH_STATE_CHANGE} event: another service is speaking. */
@@ -942,6 +959,7 @@
                 CONTENT_CHANGE_TYPE_CONTENT_INVALID,
                 CONTENT_CHANGE_TYPE_ERROR,
                 CONTENT_CHANGE_TYPE_ENABLED,
+                CONTENT_CHANGE_TYPE_SUPPLEMENTAL_DESCRIPTION,
             })
     public @interface ContentChangeTypes {}
 
@@ -1222,7 +1240,14 @@
                 return "CONTENT_CHANGE_TYPE_CONTENT_INVALID";
             case CONTENT_CHANGE_TYPE_ERROR: return "CONTENT_CHANGE_TYPE_ERROR";
             case CONTENT_CHANGE_TYPE_ENABLED: return "CONTENT_CHANGE_TYPE_ENABLED";
-            default: return Integer.toHexString(type);
+            default: {
+                if (Flags.supplementalDescription()) {
+                    if (type == CONTENT_CHANGE_TYPE_SUPPLEMENTAL_DESCRIPTION) {
+                        return "CONTENT_CHANGE_TYPE_SUPPLEMENTAL_DESCRIPTION";
+                    }
+                }
+                return Integer.toHexString(type);
+            }
         }
     }
 
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index f70f7e6..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()}.
      *
@@ -997,6 +1029,8 @@
 
     private static final int BOOLEAN_PROPERTY_SUPPORTS_GRANULAR_SCROLLING = 1 << 26;
 
+    private static final int BOOLEAN_PROPERTY_FIELD_REQUIRED = 1 << 27;
+
     /**
      * Bits that provide the id of a virtual descendant of a view.
      */
@@ -1085,6 +1119,7 @@
     private CharSequence mPaneTitle;
     private CharSequence mStateDescription;
     private CharSequence mContentDescription;
+    private CharSequence mSupplementalDescription;
     private CharSequence mTooltipText;
     private String mViewIdResourceName;
     private String mUniqueId;
@@ -2543,6 +2578,32 @@
     }
 
     /**
+     * Gets whether a node representing a form field requires input or selection.
+     *
+     * @return {@code true} if {@code this} node represents a form field that requires input or
+     *     selection, {@code false} otherwise.
+     */
+    @FlaggedApi(Flags.FLAG_A11Y_IS_REQUIRED_API)
+    public boolean isFieldRequired() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_FIELD_REQUIRED);
+    }
+
+    /**
+     * Sets whether {@code this} node represents a form field that requires input or selection.
+     *
+     * <p><strong>Note:</strong> Cannot be called from an AccessibilityService. This class is made
+     * immutable before being delivered to an AccessibilityService.
+     *
+     * @param required {@code true} if input or selection of this node should be required, {@code
+     *     false} otherwise.
+     * @throws IllegalStateException If called from an AccessibilityService
+     */
+    @FlaggedApi(Flags.FLAG_A11Y_IS_REQUIRED_API)
+    public void setFieldRequired(boolean required) {
+        setBooleanProperty(BOOLEAN_PROPERTY_FIELD_REQUIRED, required);
+    }
+
+    /**
      * Gets whether this node is focusable.
      *
      * <p>In the View system, this typically maps to {@link View#isFocusable()}.
@@ -3686,6 +3747,27 @@
         return mContentDescription;
     }
 
+    /**
+     * Gets the supplemental description of this node. A supplemental description provides
+     * brief supplemental information for this node, such as the purpose of the node when
+     * that purpose is not conveyed within its textual representation. For example, if a
+     * dropdown select has a purpose of setting font family, the supplemental description
+     * could be "font family". If this node has children, its supplemental description serves
+     * as additional information and is not intended to replace any existing information
+     * in the subtree. This is different from the {@link #getContentDescription()} in that
+     * this description is purely supplemental while a content description may be used
+     * to replace a description for a node or its subtree that an assistive technology
+     * would otherwise compute based on other properties of the node and its descendants.
+     *
+     * @return The supplemental description.
+     * @see #setSupplementalDescription(CharSequence)
+     * @see #getContentDescription()
+     */
+    @FlaggedApi(Flags.FLAG_SUPPLEMENTAL_DESCRIPTION)
+    @Nullable
+    public CharSequence getSupplementalDescription() {
+        return mSupplementalDescription;
+    }
 
     /**
      * Sets the state description of this node.
@@ -3724,6 +3806,35 @@
     }
 
     /**
+     * Sets the supplemental description of this node. A supplemental description provides
+     * brief supplemental information for this node, such as the purpose of the node when
+     * that purpose is not conveyed within its textual representation. For example, if a
+     * dropdown select has a purpose of setting font family, the supplemental description
+     * could be "font family". If this node has children, its supplemental description serves
+     * as additional information and is not intended to replace any existing information
+     * in the subtree. This is different from the {@link #setContentDescription(CharSequence)}
+     * in that this description is purely supplemental while a content description may be used
+     * to replace a description for a node or its subtree that an assistive technology
+     * would otherwise compute based on other properties of the node and its descendants.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     *
+     * @param supplementalDescription The supplemental description.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     * @see #getSupplementalDescription()
+     * @see #setContentDescription(CharSequence)
+     */
+    @FlaggedApi(Flags.FLAG_SUPPLEMENTAL_DESCRIPTION)
+    public void setSupplementalDescription(@Nullable CharSequence supplementalDescription) {
+        enforceNotSealed();
+        mSupplementalDescription = (supplementalDescription == null) ? null
+                : supplementalDescription.subSequence(0, supplementalDescription.length());
+    }
+
+    /**
      * Gets the tooltip text of this node.
      *
      * @return The tooltip text.
@@ -4657,6 +4768,10 @@
             nonDefaultFields |= bitAt(fieldIndex);
         }
         fieldIndex++;
+        if (!Objects.equals(mSupplementalDescription, DEFAULT.mSupplementalDescription)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
         if (!Objects.equals(mPaneTitle, DEFAULT.mPaneTitle)) {
             nonDefaultFields |= bitAt(fieldIndex);
         }
@@ -4843,6 +4958,9 @@
         if (isBitSet(nonDefaultFields, fieldIndex++)) {
             parcel.writeCharSequence(mContentDescription);
         }
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            parcel.writeCharSequence(mSupplementalDescription);
+        }
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mPaneTitle);
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mTooltipText);
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mContainerTitle);
@@ -4950,6 +5068,7 @@
         mError = other.mError;
         mStateDescription = other.mStateDescription;
         mContentDescription = other.mContentDescription;
+        mSupplementalDescription = other.mSupplementalDescription;
         mPaneTitle = other.mPaneTitle;
         mTooltipText = other.mTooltipText;
         mContainerTitle = other.mContainerTitle;
@@ -5119,6 +5238,9 @@
         if (isBitSet(nonDefaultFields, fieldIndex++)) {
             mContentDescription = parcel.readCharSequence();
         }
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            mSupplementalDescription = parcel.readCharSequence();
+        }
         if (isBitSet(nonDefaultFields, fieldIndex++)) mPaneTitle = parcel.readCharSequence();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mTooltipText = parcel.readCharSequence();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mContainerTitle = parcel.readCharSequence();
@@ -5370,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;
@@ -5487,8 +5589,6 @@
         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());
         builder.append("; focusable: ").append(isFocusable());
@@ -6417,6 +6517,19 @@
         /** Range type: percent with values from zero to one hundred. */
         public static final int RANGE_TYPE_PERCENT = 2;
 
+        /**
+         * Range type: indeterminate.
+         *
+         * A {@link RangeInfo} type used to represent a node which may typically expose range
+         * information but is presently in an indeterminate state, such as a {@link
+         * android.widget.ProgressBar} representing a loading operation of unknown duration.
+         * When using this type, the {@code min}, {@code max}, and {@code current} values used to
+         * construct an instance may be ignored. It is recommended to use {@code Float.NaN} for
+         * these values.
+         */
+        @FlaggedApi(Flags.FLAG_INDETERMINATE_RANGE_INFO)
+        public static final int RANGE_TYPE_INDETERMINATE = 3;
+
         private int mType;
         private float mMin;
         private float mMax;
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index ffa819f..7177ef3 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -85,6 +85,13 @@
 
 flag {
     namespace: "accessibility"
+    name: "deprecate_accessibility_announcement_apis"
+    description: "Controls the deprecation of platform APIs related to disruptive accessibility announcements"
+    bug: "376727542"
+}
+
+flag {
+    namespace: "accessibility"
     name: "fix_merged_content_change_event_v2"
     description: "Fixes event type and source of content change event merged in ViewRootImpl"
     bug: "277305460"
@@ -222,6 +229,13 @@
 }
 
 flag {
+    name: "supplemental_description"
+    namespace: "accessibility"
+    description: "Feature flag for supplemental description api"
+    bug: "375266174"
+}
+
+flag {
     name: "support_multiple_labeledby"
     namespace: "accessibility"
     description: "Feature flag for supporting multiple labels in AccessibilityNodeInfo labeledby api"
@@ -251,3 +265,10 @@
         purpose: PURPOSE_BUGFIX
     }
  }
+
+ flag {
+    name: "indeterminate_range_info"
+    namespace: "accessibility"
+    description: "Creates a way to create an INDETERMINATE RangeInfo"
+    bug: "376108874"
+ }
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index c31df73..1c7570e 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -122,6 +122,14 @@
 }
 
 flag {
+    name: "toolkit_viewgroup_set_requested_frame_rate_api"
+    namespace: "toolkit"
+    description: "Feature flag to introduce new frame rate setting APIs on ViewGroup"
+    bug: "335874198"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "toolkit_frame_rate_touch_boost_25q1"
     namespace: "toolkit"
     description: "Feature flag to not suppress touch boost for specific windowTypes in VRR V QPR2"
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index 2ca62a0..dd32d57 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -221,6 +221,7 @@
             PHASE_WM_INVOKING_IME_REQUESTED_LISTENER,
             PHASE_CLIENT_ALREADY_HIDDEN,
             PHASE_CLIENT_VIEW_HANDLER_AVAILABLE,
+            PHASE_SERVER_UPDATE_CLIENT_VISIBILITY,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface Phase {}
@@ -430,6 +431,11 @@
      * continue without.
      */
     int PHASE_CLIENT_VIEW_HANDLER_AVAILABLE = ImeProtoEnums.PHASE_CLIENT_VIEW_HANDLER_AVAILABLE;
+    /**
+     * ImeInsetsSourceProvider sets the reported visibility of the caller/client window (either the
+     * app or the RemoteInsetsControlTarget).
+     */
+    int PHASE_SERVER_UPDATE_CLIENT_VISIBILITY = ImeProtoEnums.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY;
 
     /**
      * Called when an IME request is started.
diff --git a/core/java/android/view/inputmethod/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/OnBackInvokedCallbackInfo.java b/core/java/android/window/OnBackInvokedCallbackInfo.java
index bb5fe96..44c7bd9 100644
--- a/core/java/android/window/OnBackInvokedCallbackInfo.java
+++ b/core/java/android/window/OnBackInvokedCallbackInfo.java
@@ -16,6 +16,8 @@
 
 package android.window;
 
+import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED;
+
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -29,19 +31,23 @@
     private final IOnBackInvokedCallback mCallback;
     private @OnBackInvokedDispatcher.Priority int mPriority;
     private final boolean mIsAnimationCallback;
+    private final @SystemOverrideOnBackInvokedCallback.OverrideBehavior int mOverrideBehavior;
 
     public OnBackInvokedCallbackInfo(@NonNull IOnBackInvokedCallback callback,
             int priority,
-            boolean isAnimationCallback) {
+            boolean isAnimationCallback,
+            int overrideBehavior) {
         mCallback = callback;
         mPriority = priority;
         mIsAnimationCallback = isAnimationCallback;
+        mOverrideBehavior = overrideBehavior;
     }
 
     private OnBackInvokedCallbackInfo(@NonNull Parcel in) {
         mCallback = IOnBackInvokedCallback.Stub.asInterface(in.readStrongBinder());
         mPriority = in.readInt();
         mIsAnimationCallback = in.readBoolean();
+        mOverrideBehavior = in.readInt();
     }
 
     @Override
@@ -54,6 +60,7 @@
         dest.writeStrongInterface(mCallback);
         dest.writeInt(mPriority);
         dest.writeBoolean(mIsAnimationCallback);
+        dest.writeInt(mOverrideBehavior);
     }
 
     public static final Creator<OnBackInvokedCallbackInfo> CREATOR =
@@ -70,7 +77,8 @@
             };
 
     public boolean isSystemCallback() {
-        return mPriority == OnBackInvokedDispatcher.PRIORITY_SYSTEM;
+        return mPriority == OnBackInvokedDispatcher.PRIORITY_SYSTEM
+                || mOverrideBehavior != OVERRIDE_UNDEFINED;
     }
 
     @NonNull
@@ -87,12 +95,18 @@
         return mIsAnimationCallback;
     }
 
+    @SystemOverrideOnBackInvokedCallback.OverrideBehavior
+    public int getOverrideBehavior() {
+        return mOverrideBehavior;
+    }
+
     @Override
     public String toString() {
         return "OnBackInvokedCallbackInfo{"
                 + "mCallback=" + mCallback
                 + ", mPriority=" + mPriority
                 + ", mIsAnimationCallback=" + mIsAnimationCallback
+                + ", mOverrideBehavior=" + mOverrideBehavior
                 + '}';
     }
 }
diff --git a/core/java/android/window/SystemOnBackInvokedCallbacks.java b/core/java/android/window/SystemOnBackInvokedCallbacks.java
new file mode 100644
index 0000000..f67520b
--- /dev/null
+++ b/core/java/android/window/SystemOnBackInvokedCallbacks.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.util.ArrayMap;
+
+import com.android.window.flags.Flags;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Utility class providing {@link OnBackInvokedCallback}s to override the default behavior when
+ * system back is invoked. e.g. {@link Activity#finish}
+ *
+ * <p>By registering these callbacks with the {@link OnBackInvokedDispatcher}, the system can
+ * trigger specific behaviors and play corresponding ahead-of-time animations when the back
+ * gesture is invoked.
+ *
+ * <p>For example, to trigger the {@link Activity#moveTaskToBack} behavior:
+ * <pre>
+ *   OnBackInvokedDispatcher dispatcher = activity.getOnBackInvokedDispatcher();
+ *   dispatcher.registerOnBackInvokedCallback(
+ *       OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ *       SystemOnBackInvokedCallbacks.moveTaskToBackCallback(activity));
+ * </pre>
+ */
+@SuppressWarnings("SingularCallback")
+@FlaggedApi(Flags.FLAG_PREDICTIVE_BACK_SYSTEM_OVERRIDE_CALLBACK)
+public final class SystemOnBackInvokedCallbacks {
+    private static final OverrideCallbackFactory<Activity> sMoveTaskToBackFactory = new
+            MoveTaskToBackCallbackFactory();
+    private static final OverrideCallbackFactory<Activity> sFinishAndRemoveTaskFactory = new
+            FinishAndRemoveTaskCallbackFactory();
+
+    private SystemOnBackInvokedCallbacks() {
+        throw new UnsupportedOperationException("This is a utility class and cannot be "
+                + "instantiated");
+    }
+
+    /**
+     * <p>Get a callback to triggers {@link Activity#moveTaskToBack(boolean)} on the associated
+     * {@link Activity}, moving the task containing the activity to the background. The system
+     * will play the corresponding transition animation, regardless of whether the activity
+     * is the root activity of the task.</p>
+     *
+     * @param activity The associated {@link Activity}
+     * @see Activity#moveTaskToBack(boolean)
+     */
+    @FlaggedApi(Flags.FLAG_PREDICTIVE_BACK_SYSTEM_OVERRIDE_CALLBACK)
+    @NonNull
+    public static OnBackInvokedCallback moveTaskToBackCallback(@NonNull Activity activity) {
+        return sMoveTaskToBackFactory.getOverrideCallback(activity);
+    }
+
+    /**
+     * <p>Get a callback to triggers {@link Activity#finishAndRemoveTask()} on the associated
+     * {@link Activity}. If the activity is the root activity of its task, the entire task
+     * will be removed from the recents task. The activity will be finished in all cases.
+     * The system will play the corresponding transition animation.</p>
+     *
+     * @param activity The associated {@link Activity}
+     * @see Activity#finishAndRemoveTask()
+     */
+    @FlaggedApi(Flags.FLAG_PREDICTIVE_BACK_SYSTEM_OVERRIDE_CALLBACK)
+    @NonNull
+    public static OnBackInvokedCallback finishAndRemoveTaskCallback(@NonNull Activity activity) {
+        return sFinishAndRemoveTaskFactory.getOverrideCallback(activity);
+    }
+
+    /**
+     * Abstract factory for creating system override {@link SystemOverrideOnBackInvokedCallback}
+     * instances.
+     *
+     * <p>Concrete implementations of this factory are responsible for creating callbacks that
+     * override the default system back navigation behavior. These callbacks should be used
+     * exclusively for system overrides and should never be invoked directly.</p>
+     */
+    private abstract static class OverrideCallbackFactory<TYPE> {
+        private final ArrayMap<WeakReference<TYPE>,
+                WeakReference<SystemOverrideOnBackInvokedCallback>> mObjectMap = new ArrayMap<>();
+
+        protected abstract SystemOverrideOnBackInvokedCallback createCallback(
+                @NonNull TYPE context);
+
+        @NonNull SystemOverrideOnBackInvokedCallback getOverrideCallback(@NonNull TYPE object) {
+            if (object == null) {
+                throw new NullPointerException("Input object cannot be null");
+            }
+            synchronized (mObjectMap) {
+                WeakReference<SystemOverrideOnBackInvokedCallback> callback = null;
+                for (int i = mObjectMap.size() - 1; i >= 0; --i) {
+                    final WeakReference<TYPE> next = mObjectMap.keyAt(i);
+                    if (next.get() == object) {
+                        callback = mObjectMap.get(next);
+                        break;
+                    }
+                }
+                if (callback != null) {
+                    return callback.get();
+                }
+                final SystemOverrideOnBackInvokedCallback contextCallback = createCallback(object);
+                if (contextCallback != null) {
+                    mObjectMap.put(new WeakReference<>(object),
+                            new WeakReference<>(contextCallback));
+                }
+                return contextCallback;
+            }
+        }
+    }
+
+    private static class MoveTaskToBackCallbackFactory extends OverrideCallbackFactory<Activity> {
+        @Override
+        protected SystemOverrideOnBackInvokedCallback createCallback(Activity activity) {
+            final WeakReference<Activity> activityRef = new WeakReference<>(activity);
+            return new SystemOverrideOnBackInvokedCallback() {
+                @Override
+                public void onBackInvoked() {
+                    if (activityRef.get() != null) {
+                        activityRef.get().moveTaskToBack(true /* nonRoot */);
+                    }
+                }
+
+                @Override
+                public int overrideBehavior() {
+                    return OVERRIDE_MOVE_TASK_TO_BACK;
+                }
+            };
+        }
+    }
+
+    private static class FinishAndRemoveTaskCallbackFactory extends
+            OverrideCallbackFactory<Activity> {
+        @Override
+        protected SystemOverrideOnBackInvokedCallback createCallback(Activity activity) {
+            final WeakReference<Activity> activityRef = new WeakReference<>(activity);
+            return new SystemOverrideOnBackInvokedCallback() {
+                @Override
+                public void onBackInvoked() {
+                    if (activityRef.get() != null) {
+                        activityRef.get().finishAndRemoveTask();
+                    }
+                }
+
+                @Override
+                public int overrideBehavior() {
+                    return OVERRIDE_FINISH_AND_REMOVE_TASK;
+                }
+            };
+        }
+    }
+}
diff --git a/core/java/android/window/SystemOverrideOnBackInvokedCallback.java b/core/java/android/window/SystemOverrideOnBackInvokedCallback.java
new file mode 100644
index 0000000..3360a19
--- /dev/null
+++ b/core/java/android/window/SystemOverrideOnBackInvokedCallback.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Non-default ahead-of-time system OnBackInvokedCallback.
+ * @hide
+ */
+public interface SystemOverrideOnBackInvokedCallback extends OnBackInvokedCallback {
+    /**
+     * No override request
+     */
+    int OVERRIDE_UNDEFINED = 0;
+
+    /**
+     * Navigating back will bring the task to back
+     */
+    int OVERRIDE_MOVE_TASK_TO_BACK = 1;
+
+    /**
+     * Navigating back will finish activity, and remove the task if this activity is root activity.
+     */
+    int OVERRIDE_FINISH_AND_REMOVE_TASK = 2;
+
+    /** @hide */
+    @IntDef({
+            OVERRIDE_UNDEFINED,
+            OVERRIDE_MOVE_TASK_TO_BACK,
+            OVERRIDE_FINISH_AND_REMOVE_TASK,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface OverrideBehavior {
+    }
+
+    /**
+     * @return Override type of this callback.
+     */
+    @OverrideBehavior
+    default int overrideBehavior() {
+        return OVERRIDE_UNDEFINED;
+    }
+}
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/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 34abf31..3fe63ab 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -1867,27 +1867,33 @@
             switch (type) {
                 case HIERARCHY_OP_TYPE_REPARENT: return "reparent";
                 case HIERARCHY_OP_TYPE_REORDER: return "reorder";
-                case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: return "ChildrenTasksReparent";
-                case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: return "SetLaunchRoot";
-                case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: return "SetAdjacentRoot";
-                case HIERARCHY_OP_TYPE_LAUNCH_TASK: return "LaunchTask";
-                case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT: return "SetAdjacentFlagRoot";
+                case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: return "childrenTasksReparent";
+                case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: return "setLaunchRoot";
+                case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: return "setAdjacentRoot";
+                case HIERARCHY_OP_TYPE_LAUNCH_TASK: return "launchTask";
+                case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT: return "setAdjacentFlagRoot";
                 case HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT:
-                    return "SetDisableLaunchAdjacent";
-                case HIERARCHY_OP_TYPE_PENDING_INTENT: return "PendingIntent";
-                case HIERARCHY_OP_TYPE_START_SHORTCUT: return "StartShortcut";
+                    return "setDisableLaunchAdjacent";
+                case HIERARCHY_OP_TYPE_PENDING_INTENT: return "pendingIntent";
+                case HIERARCHY_OP_TYPE_START_SHORTCUT: return "startShortcut";
+                case HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER: return "restoreTransientOrder";
                 case HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER: return "addInsetsFrameProvider";
                 case HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER:
                     return "removeInsetsFrameProvider";
                 case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: return "setAlwaysOnTop";
-                case HIERARCHY_OP_TYPE_REMOVE_TASK: return "RemoveTask";
+                case HIERARCHY_OP_TYPE_REMOVE_TASK: return "removeTask";
                 case HIERARCHY_OP_TYPE_FINISH_ACTIVITY: return "finishActivity";
-                case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS: return "ClearAdjacentRoot";
+                case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS: return "clearAdjacentRoot";
                 case HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH:
                     return "setReparentLeafTaskIfRelaunch";
                 case HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION:
                     return "addTaskFragmentOperation";
+                case HIERARCHY_OP_TYPE_MOVE_PIP_ACTIVITY_TO_PINNED_TASK:
+                    return "movePipActivityToPinnedTask";
+                case HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE: return "setIsTrimmable";
+                case HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION: return "restoreBackNav";
                 case HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES: return "setExcludeInsetsTypes";
+                case HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE: return "setKeyguardState";
                 default: return "HOP(" + type + ")";
             }
         }
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index c9d458f..0ea4bb4 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -16,6 +16,9 @@
 
 package android.window;
 
+import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED;
+
+import static com.android.window.flags.Flags.predictiveBackSystemOverrideCallback;
 import static com.android.window.flags.Flags.predictiveBackPrioritySystemNavigationObserver;
 import static com.android.window.flags.Flags.predictiveBackTimestampApi;
 
@@ -201,6 +204,15 @@
                 mImeDispatcher.registerOnBackInvokedCallback(priority, callback);
                 return;
             }
+            if (predictiveBackPrioritySystemNavigationObserver()
+                    && predictiveBackSystemOverrideCallback()) {
+                if (priority == PRIORITY_SYSTEM_NAVIGATION_OBSERVER
+                        && callback instanceof SystemOverrideOnBackInvokedCallback) {
+                    Log.e(TAG, "System override callbacks cannot be registered to "
+                            + "NAVIGATION_OBSERVER");
+                    return;
+                }
+            }
             if (predictiveBackPrioritySystemNavigationObserver()) {
                 if (priority == PRIORITY_SYSTEM_NAVIGATION_OBSERVER) {
                     registerSystemNavigationObserverCallback(callback);
@@ -365,7 +377,8 @@
     public void tryInvokeSystemNavigationObserverCallback() {
         OnBackInvokedCallback topCallback = getTopCallback();
         Integer callbackPriority = mAllCallbacks.getOrDefault(topCallback, null);
-        if (callbackPriority != null && callbackPriority == PRIORITY_SYSTEM) {
+        final boolean isSystemOverride = topCallback instanceof SystemOverrideOnBackInvokedCallback;
+        if ((callbackPriority != null && callbackPriority == PRIORITY_SYSTEM) || isSystemOverride) {
             invokeSystemNavigationObserverCallback();
         }
     }
@@ -384,14 +397,22 @@
             OnBackInvokedCallbackInfo callbackInfo = null;
             if (callback != null) {
                 int priority = mAllCallbacks.get(callback);
+                int overrideAnimation = OVERRIDE_UNDEFINED;
+                if (callback instanceof SystemOverrideOnBackInvokedCallback) {
+                    overrideAnimation = ((SystemOverrideOnBackInvokedCallback) callback)
+                            .overrideBehavior();
+                }
+                final boolean isSystemCallback = priority == PRIORITY_SYSTEM
+                        || overrideAnimation != OVERRIDE_UNDEFINED;
                 final IOnBackInvokedCallback iCallback = new OnBackInvokedCallbackWrapper(callback,
                         mTouchTracker, mProgressAnimator, mHandler, this::callOnKeyPreIme,
                         this::invokeSystemNavigationObserverCallback,
-                        /*isSystemCallback*/ priority == PRIORITY_SYSTEM);
+                        isSystemCallback /*isSystemCallback*/);
                 callbackInfo = new OnBackInvokedCallbackInfo(
                         iCallback,
                         priority,
-                        callback instanceof OnBackAnimationCallback);
+                        callback instanceof OnBackAnimationCallback,
+                        overrideAnimation);
             }
             mWindowSession.setOnBackInvokedCallbackInfo(mWindow, callbackInfo);
         } catch (RemoteException e) {
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/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index a2201bb..b2f125d 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -9,13 +9,6 @@
 }
 
 flag {
-    name: "bal_require_opt_in_same_uid"
-    namespace: "responsible_apis"
-    description: "Require the PendingIntent creator/sender to opt in if it is the same UID"
-    bug: "296478951"
-}
-
-flag {
     name: "bal_dont_bring_existing_background_task_stack_to_fg"
     namespace: "responsible_apis"
     description: "When starting a PendingIntent with ONLY creator privileges, don't bring the existing task stack to foreground"
@@ -23,13 +16,6 @@
 }
 
 flag {
-    name: "bal_show_toasts"
-    namespace: "responsible_apis"
-    description: "Enable toasts to indicate (potential) BAL blocking."
-    bug: "308059069"
-}
-
-flag {
     name: "bal_show_toasts_blocked"
     namespace: "responsible_apis"
     description: "Enable toasts to indicate actual BAL blocking."
@@ -71,14 +57,6 @@
     bug: "339720406"
 }
 
-# replaced by bal_strict_mode_ro
-flag {
-    name: "bal_strict_mode"
-    namespace: "responsible_apis"
-    description: "Strict mode flag"
-    bug: "324089586"
-}
-
 flag {
     name: "bal_strict_mode_ro"
     namespace: "responsible_apis"
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 460df31..392c307 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -13,14 +13,6 @@
 
 flag {
     namespace: "window_surfaces"
-    name: "explicit_refresh_rate_hints"
-    description: "Performance related hints during transitions"
-    is_fixed_read_only: true
-    bug: "300019131"
-}
-
-flag {
-    namespace: "window_surfaces"
     name: "delete_capture_display"
     description: "Delete uses of ScreenCapture#captureDisplay"
     is_fixed_read_only: true
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 3a03508..11f6849 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -49,6 +49,17 @@
 }
 
 flag {
+  name: "respect_animation_clip"
+  namespace: "windowing_frontend"
+  description: "Fix missing clip transformation of animation"
+  bug: "376601866"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "edge_to_edge_by_default"
   namespace: "windowing_frontend"
   description: "Make app go edge-to-edge by default when targeting SDK 35 or greater"
@@ -350,6 +361,17 @@
 }
 
 flag {
+  name: "defer_predictive_animation_if_no_snapshot"
+  namespace: "windowing_frontend"
+  description: "If no snapshot for previous window, start animation until the client has draw."
+  bug: "374621014"
+  is_fixed_read_only: true
+  metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
   name: "disallow_app_progress_embedded_window"
   namespace: "windowing_frontend"
   description: "Pilfer pointers when app transfer input gesture to embedded window."
@@ -358,4 +380,12 @@
   metadata {
     purpose: PURPOSE_BUGFIX
   }
+}
+
+flag {
+    name: "predictive_back_system_override_callback"
+    namespace: "windowing_frontend"
+    description: "Provide pre-make predictive back API extension"
+    is_fixed_read_only: true
+    bug: "362938401"
 }
\ No newline at end of file
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/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
index de3edeb..15736ed 100644
--- a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
@@ -18,14 +18,13 @@
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
-import dalvik.annotation.optimization.CriticalNative;
-
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -255,8 +254,8 @@
      * the delta in the supplied array container.
      */
     public void addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs,
-            LongArrayMultiStateCounter.LongArrayContainer deltaContainer) {
-        mInjector.addDelta(uid, counter, timestampMs, deltaContainer);
+            long[] delta) {
+        mInjector.addDelta(uid, counter, timestampMs, delta);
     }
 
     @VisibleForTesting
@@ -274,15 +273,13 @@
          * The delta is also returned via the optional deltaOut parameter.
          */
         public boolean addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs,
-                LongArrayMultiStateCounter.LongArrayContainer deltaOut) {
-            return addDeltaFromBpf(uid, counter.mNativeObject, timestampMs,
-                    deltaOut != null ? deltaOut.mNativeObject : 0);
+                long[] deltaOut) {
+            return addDeltaFromBpf(uid, counter.mNativeObject, timestampMs, deltaOut);
         }
 
-        @CriticalNative
         private static native boolean addDeltaFromBpf(int uid,
                 long longArrayMultiStateCounterNativePointer, long timestampMs,
-                long longArrayContainerNativePointer);
+                @Nullable long[] deltaOut);
 
         /**
          * Used for testing.
@@ -291,14 +288,14 @@
          */
         public boolean addDeltaForTest(int uid, LongArrayMultiStateCounter counter,
                 long timestampMs, long[][] timeInFreqDataNanos,
-                LongArrayMultiStateCounter.LongArrayContainer deltaOut) {
+                long[] deltaOut) {
             return addDeltaForTest(uid, counter.mNativeObject, timestampMs, timeInFreqDataNanos,
-                    deltaOut != null ? deltaOut.mNativeObject : 0);
+                    deltaOut);
         }
 
         private static native boolean addDeltaForTest(int uid,
                 long longArrayMultiStateCounterNativePointer, long timestampMs,
-                long[][] timeInFreqDataNanos, long longArrayContainerNativePointer);
+                long[][] timeInFreqDataNanos, long[] deltaOut);
     }
 
     @VisibleForTesting
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
index 489721f..b3480ab 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -30,9 +30,6 @@
 
 import libcore.util.NativeAllocationRegistry;
 
-import java.util.Arrays;
-import java.util.concurrent.atomic.AtomicReference;
-
 /**
  * Performs per-state counting of multi-element values over time. The class' behavior is illustrated
  * by this example:
@@ -44,15 +41,14 @@
  *   counter.setState(1, 1000);
  *
  *   // At 3000 ms, the tracked values are updated to {30, 300}
- *   arrayContainer.setValues(new long[]{{30, 300}};
- *   counter.updateValues(arrayContainer, 3000);
+ *   counter.updateValues(arrayContainer, new long[]{{30, 300}, 3000);
  *
  *   // The values are distributed between states 0 and 1 according to the time
  *   // spent in those respective states. In this specific case, 1000 and 2000 ms.
- *   counter.getValues(arrayContainer, 0);
- *   // arrayContainer now has values {10, 100}
- *   counter.getValues(arrayContainer, 1);
- *   // arrayContainer now has values {20, 200}
+ *   counter.getCounts(array, 0);
+ *   // array now has values {10, 100}
+ *   counter.getCounts(array, 1);
+ *   // array now has values {20, 200}
  * </pre>
  *
  * The tracked values are expected to increase monotonically.
@@ -62,110 +58,7 @@
 @RavenwoodKeepWholeClass
 @RavenwoodRedirectionClass("LongArrayMultiStateCounter_host")
 public final class LongArrayMultiStateCounter implements Parcelable {
-
-    /**
-     * Container for a native equivalent of a long[].
-     */
-    @RavenwoodKeepWholeClass
-    @RavenwoodRedirectionClass("LongArrayContainer_host")
-    public static class LongArrayContainer {
-        private static NativeAllocationRegistry sRegistry;
-
-        // Visible to other objects in this package so that it can be passed to @CriticalNative
-        // methods.
-        final long mNativeObject;
-        private final int mLength;
-
-        public LongArrayContainer(int length) {
-            mLength = length;
-            mNativeObject = native_init(length);
-            registerNativeAllocation();
-        }
-
-        @RavenwoodReplace
-        private void registerNativeAllocation() {
-            if (sRegistry == null) {
-                synchronized (LongArrayMultiStateCounter.class) {
-                    if (sRegistry == null) {
-                        sRegistry = NativeAllocationRegistry.createMalloced(
-                                LongArrayContainer.class.getClassLoader(), native_getReleaseFunc());
-                    }
-                }
-            }
-            sRegistry.registerNativeAllocation(this, mNativeObject);
-        }
-
-        private void registerNativeAllocation$ravenwood() {
-            // No-op under ravenwood
-        }
-
-        /**
-         * Copies the supplied values into the underlying native array.
-         */
-        public void setValues(long[] array) {
-            if (array.length != mLength) {
-                throw new IllegalArgumentException(
-                        "Invalid array length: " + array.length + ", expected: " + mLength);
-            }
-            native_setValues(mNativeObject, array);
-        }
-
-        /**
-         * Copies the underlying native array values to the supplied array.
-         */
-        public void getValues(long[] array) {
-            if (array.length != mLength) {
-                throw new IllegalArgumentException(
-                        "Invalid array length: " + array.length + ", expected: " + mLength);
-            }
-            native_getValues(mNativeObject, array);
-        }
-
-        /**
-         * Combines contained values into a smaller array by aggregating them
-         * according to an index map.
-         */
-        public boolean combineValues(long[] array, int[] indexMap) {
-            if (indexMap.length != mLength) {
-                throw new IllegalArgumentException(
-                        "Wrong index map size " + indexMap.length + ", expected " + mLength);
-            }
-            return native_combineValues(mNativeObject, array, indexMap);
-        }
-
-        @Override
-        public String toString() {
-            final long[] array = new long[mLength];
-            getValues(array);
-            return Arrays.toString(array);
-        }
-
-        @CriticalNative
-        @RavenwoodRedirect
-        private static native long native_init(int length);
-
-        @CriticalNative
-        @RavenwoodRedirect
-        private static native long native_getReleaseFunc();
-
-        @FastNative
-        @RavenwoodRedirect
-        private static native void native_setValues(long nativeObject, long[] array);
-
-        @FastNative
-        @RavenwoodRedirect
-        private static native void native_getValues(long nativeObject, long[] array);
-
-        @FastNative
-        @RavenwoodRedirect
-        private static native boolean native_combineValues(long nativeObject, long[] array,
-                int[] indexMap);
-    }
-
     private static volatile NativeAllocationRegistry sRegistry;
-    private static final AtomicReference<LongArrayContainer> sTmpArrayContainer =
-            new AtomicReference<>();
-
     private final int mStateCount;
     private final int mLength;
 
@@ -257,13 +150,14 @@
             throw new IllegalArgumentException(
                     "Invalid array length: " + values.length + ", expected: " + mLength);
         }
-        LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
-        if (container == null || container.mLength != values.length) {
-            container = new LongArrayContainer(values.length);
-        }
-        container.setValues(values);
-        native_setValues(mNativeObject, state, container.mNativeObject);
-        sTmpArrayContainer.set(container);
+        native_setValues(mNativeObject, state, values);
+    }
+
+    /**
+     * Adds the supplied values to the current accumulated values in the counter.
+     */
+    public void incrementValues(long[] values, long timestampMs) {
+        native_incrementValues(mNativeObject, values, timestampMs);
     }
 
     /**
@@ -272,51 +166,22 @@
      * since the previous call to updateValues.
      */
     public void updateValues(long[] values, long timestampMs) {
-        LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
-        if (container == null || container.mLength != values.length) {
-            container = new LongArrayContainer(values.length);
+        if (values.length != mLength) {
+            throw new IllegalArgumentException(
+                    "Invalid array length: " + values.length + ", expected: " + mLength);
         }
-        container.setValues(values);
-        updateValues(container, timestampMs);
-        sTmpArrayContainer.set(container);
+        native_updateValues(mNativeObject, values, timestampMs);
     }
 
     /**
      * Adds the supplied values to the current accumulated values in the counter.
      */
-    public void incrementValues(long[] values, long timestampMs) {
-        LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
-        if (container == null || container.mLength != values.length) {
-            container = new LongArrayContainer(values.length);
-        }
-        container.setValues(values);
-        native_incrementValues(mNativeObject, container.mNativeObject, timestampMs);
-        sTmpArrayContainer.set(container);
-    }
-
-    /**
-     * Sets the new values.  The delta between the previously set values and these values
-     * is distributed among the state according to the time the object spent in those states
-     * since the previous call to updateValues.
-     */
-    public void updateValues(LongArrayContainer longArrayContainer, long timestampMs) {
-        if (longArrayContainer.mLength != mLength) {
+    public void addCounts(long[] counts) {
+        if (counts.length != mLength) {
             throw new IllegalArgumentException(
-                    "Invalid array length: " + longArrayContainer.mLength + ", expected: "
-                            + mLength);
+                    "Invalid array length: " + counts.length + ", expected: " + mLength);
         }
-        native_updateValues(mNativeObject, longArrayContainer.mNativeObject, timestampMs);
-    }
-
-    /**
-     * Adds the supplied values to the current accumulated values in the counter.
-     */
-    public void addCounts(LongArrayContainer counts) {
-        if (counts.mLength != mLength) {
-            throw new IllegalArgumentException(
-                    "Invalid array length: " + counts.mLength + ", expected: " + mLength);
-        }
-        native_addCounts(mNativeObject, counts.mNativeObject);
+        native_addCounts(mNativeObject, counts);
     }
 
     /**
@@ -330,29 +195,15 @@
      * Populates the array with the accumulated counts for the specified state.
      */
     public void getCounts(long[] counts, int state) {
-        LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
-        if (container == null || container.mLength != counts.length) {
-            container = new LongArrayContainer(counts.length);
-        }
-        getCounts(container, state);
-        container.getValues(counts);
-        sTmpArrayContainer.set(container);
-    }
-
-    /**
-     * Populates longArrayContainer with the accumulated counts for the specified state.
-     */
-    public void getCounts(LongArrayContainer longArrayContainer, int state) {
         if (state < 0 || state >= mStateCount) {
             throw new IllegalArgumentException(
                     "State: " + state + ", outside the range: [0-" + mStateCount + "]");
         }
-        if (longArrayContainer.mLength != mLength) {
+        if (counts.length != mLength) {
             throw new IllegalArgumentException(
-                    "Invalid array length: " + longArrayContainer.mLength
-                            + ", expected: " + mLength);
+                    "Invalid array length: " + counts.length + ", expected: " + mLength);
         }
-        native_getCounts(mNativeObject, longArrayContainer.mNativeObject, state);
+        native_getCounts(mNativeObject, counts, state);
     }
 
     @Override
@@ -370,18 +221,17 @@
         return 0;
     }
 
-    public static final Creator<LongArrayMultiStateCounter> CREATOR =
-            new Creator<LongArrayMultiStateCounter>() {
-                @Override
-                public LongArrayMultiStateCounter createFromParcel(Parcel in) {
-                    return new LongArrayMultiStateCounter(in);
-                }
+    public static final Creator<LongArrayMultiStateCounter> CREATOR = new Creator<>() {
+        @Override
+        public LongArrayMultiStateCounter createFromParcel(Parcel in) {
+            return new LongArrayMultiStateCounter(in);
+        }
 
-                @Override
-                public LongArrayMultiStateCounter[] newArray(int size) {
-                    return new LongArrayMultiStateCounter[size];
-                }
-            };
+        @Override
+        public LongArrayMultiStateCounter[] newArray(int size) {
+            return new LongArrayMultiStateCounter[size];
+        }
+    };
 
 
     @CriticalNative
@@ -406,34 +256,31 @@
     private static native void native_copyStatesFrom(long nativeObjectTarget,
             long nativeObjectSource);
 
-    @CriticalNative
+    @FastNative
     @RavenwoodRedirect
-    private static native void native_setValues(long nativeObject, int state,
-            long longArrayContainerNativeObject);
+    private static native void native_setValues(long nativeObject, int state, long[] values);
 
-    @CriticalNative
+    @FastNative
     @RavenwoodRedirect
-    private static native void native_updateValues(long nativeObject,
-            long longArrayContainerNativeObject, long timestampMs);
+    private static native void native_updateValues(long nativeObject, long[] values,
+            long timestampMs);
 
-    @CriticalNative
+    @FastNative
     @RavenwoodRedirect
-    private static native void native_incrementValues(long nativeObject,
-            long longArrayContainerNativeObject, long timestampMs);
+    private static native void native_incrementValues(long nativeObject, long[] values,
+            long timestampMs);
 
-    @CriticalNative
+    @FastNative
     @RavenwoodRedirect
-    private static native void native_addCounts(long nativeObject,
-            long longArrayContainerNativeObject);
+    private static native void native_addCounts(long nativeObject, long[] counts);
 
     @CriticalNative
     @RavenwoodRedirect
     private static native void native_reset(long nativeObject);
 
-    @CriticalNative
+    @FastNative
     @RavenwoodRedirect
-    private static native void native_getCounts(long nativeObject,
-            long longArrayContainerNativeObject, int state);
+    private static native void native_getCounts(long nativeObject, long[] counts, int state);
 
     @FastNative
     @RavenwoodRedirect
diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
index c182b4a..6b6b81f 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
@@ -423,6 +423,8 @@
 
     private Map<String, Boolean> mFeatureFlagState = new ArrayMap<>();
 
+    private int mIntentMatchingFlags;
+
     @NonNull
     public static PackageImpl forParsing(@NonNull String packageName, @NonNull String baseCodePath,
             @NonNull String codePath, @NonNull TypedArray manifestArray, boolean isCoreApp,
@@ -3270,6 +3272,7 @@
         dest.writeLong(this.mBooleans);
         dest.writeLong(this.mBooleans2);
         dest.writeBoolean(this.mAllowCrossUidActivitySwitchFromBelow);
+        dest.writeInt(this.mIntentMatchingFlags);
     }
 
     private void writeFeatureFlagState(@NonNull Parcel dest) {
@@ -3461,6 +3464,7 @@
         this.mBooleans = in.readLong();
         this.mBooleans2 = in.readLong();
         this.mAllowCrossUidActivitySwitchFromBelow = in.readBoolean();
+        this.mIntentMatchingFlags = in.readInt();
 
         assignDerivedFields();
         assignDerivedFields2();
@@ -3699,6 +3703,17 @@
         return this;
     }
 
+    @Override
+    public ParsingPackage setIntentMatchingFlags(int intentMatchingFlags) {
+        mIntentMatchingFlags = intentMatchingFlags;
+        return this;
+    }
+
+    @Override
+    public int getIntentMatchingFlags() {
+        return mIntentMatchingFlags;
+    }
+
     // The following methods are explicitly not inside any interface. These are hidden under
     // PackageImpl which is only accessible to the system server. This is to prevent/discourage
     // usage of these fields outside of the utility classes.
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
index a35150b..219e885 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
@@ -109,7 +109,8 @@
                             R.styleable.AndroidManifestActivity_process,
                             R.styleable.AndroidManifestActivity_roundIcon,
                             R.styleable.AndroidManifestActivity_splitName,
-                            R.styleable.AndroidManifestActivity_attributionTags);
+                            R.styleable.AndroidManifestActivity_attributionTags,
+                            R.styleable.AndroidManifestActivity_intentMatchingFlags);
             if (result.isError()) {
                 return input.error(result);
             }
@@ -310,7 +311,8 @@
                     NOT_SET /*processAttr*/,
                     R.styleable.AndroidManifestActivityAlias_roundIcon,
                     NOT_SET /*splitNameAttr*/,
-                    R.styleable.AndroidManifestActivityAlias_attributionTags);
+                    R.styleable.AndroidManifestActivityAlias_attributionTags,
+                    R.styleable.AndroidManifestActivityAlias_intentMatchingFlags);
             if (result.isError()) {
                 return input.error(result);
             }
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedMainComponent.java b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponent.java
index 291ed0c..54e67360 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedMainComponent.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponent.java
@@ -45,4 +45,9 @@
 
     @Nullable
     String getSplitName();
+
+    /**
+     * Returns the intent matching flags.
+     */
+    int getIntentMatchingFlags();
 }
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentImpl.java
index bb8f565..678e999 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentImpl.java
@@ -24,7 +24,6 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
 
@@ -34,7 +33,6 @@
  * @hide
  */
 @DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = false)
-@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 public class ParsedMainComponentImpl extends ParsedComponentImpl implements ParsedMainComponent,
         Parcelable {
 
@@ -51,6 +49,30 @@
     @Nullable
     private String[] attributionTags;
 
+    private int mIntentMatchingFlags;
+
+    /**
+     * Opt-out of all intent filter matching rules. The value corresponds to the <code>none</code>
+     * value of {@link android.R.attr#intentMatchingFlags}
+     * @hide
+     */
+    public static final int INTENT_MATCHING_FLAGS_NONE = 1;
+
+    /**
+     * Opt-in to enforce intent filter matching. The value corresponds to the
+     * <code>enforceIntentFilter</code> value of {@link android.R.attr#intentMatchingFlags}
+     * @hide
+     */
+    public static final int INTENT_MATCHING_FLAGS_ENFORCE_INTENT_FILTER = 1 << 1;
+
+    /**
+     * Allows intent filters to match actions even when the action value is null. The value
+     * corresponds to the <code>allowNullAction</code> value of
+     * {@link android.R.attr#intentMatchingFlags}
+     * @hide
+     */
+    public static final int INTENT_MATCHING_FLAGS_ALLOW_NULL_ACTION = 1 << 2;
+
     public ParsedMainComponentImpl() {
     }
 
@@ -83,6 +105,20 @@
         return attributionTags == null ? EmptyArray.STRING : attributionTags;
     }
 
+    /**
+     * Sets the intent matching flags. This value is intended to be set from the "component" tags.
+     * @see android.R.styleable#AndroidManifestApplication_intentMatchingFlags
+     */
+    public ParsedMainComponent setIntentMatchingFlags(int intentMatchingFlags) {
+        mIntentMatchingFlags = intentMatchingFlags;
+        return this;
+    }
+
+    @Override
+    public int getIntentMatchingFlags() {
+        return this.mIntentMatchingFlags;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -98,6 +134,7 @@
         dest.writeInt(this.order);
         dest.writeString(this.splitName);
         dest.writeString8Array(this.attributionTags);
+        dest.writeInt(this.mIntentMatchingFlags);
     }
 
     protected ParsedMainComponentImpl(Parcel in) {
@@ -109,6 +146,7 @@
         this.order = in.readInt();
         this.splitName = in.readString();
         this.attributionTags = in.createString8Array();
+        this.mIntentMatchingFlags = in.readInt();
     }
 
     public static final Parcelable.Creator<ParsedMainComponentImpl> CREATOR =
@@ -139,6 +177,28 @@
     //@formatter:off
 
 
+    @android.annotation.IntDef(prefix = "INTENT_MATCHING_FLAGS_", value = {
+        INTENT_MATCHING_FLAGS_NONE,
+        INTENT_MATCHING_FLAGS_ENFORCE_INTENT_FILTER,
+        INTENT_MATCHING_FLAGS_ALLOW_NULL_ACTION
+    })
+    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface IntentMatchingFlags {}
+
+    @DataClass.Generated.Member
+    public static String intentMatchingFlagsToString(@IntentMatchingFlags int value) {
+        switch (value) {
+            case INTENT_MATCHING_FLAGS_NONE:
+                    return "INTENT_MATCHING_FLAGS_NONE";
+            case INTENT_MATCHING_FLAGS_ENFORCE_INTENT_FILTER:
+                    return "INTENT_MATCHING_FLAGS_ENFORCE_INTENT_FILTER";
+            case INTENT_MATCHING_FLAGS_ALLOW_NULL_ACTION:
+                    return "INTENT_MATCHING_FLAGS_ALLOW_NULL_ACTION";
+            default: return Integer.toHexString(value);
+        }
+    }
+
     @DataClass.Generated.Member
     public ParsedMainComponentImpl(
             @Nullable String processName,
@@ -147,7 +207,8 @@
             boolean exported,
             int order,
             @Nullable String splitName,
-            @Nullable String[] attributionTags) {
+            @Nullable String[] attributionTags,
+            int intentMatchingFlags) {
         this.processName = processName;
         this.directBootAware = directBootAware;
         this.enabled = enabled;
@@ -155,6 +216,7 @@
         this.order = order;
         this.splitName = splitName;
         this.attributionTags = attributionTags;
+        this.mIntentMatchingFlags = intentMatchingFlags;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -226,10 +288,10 @@
     }
 
     @DataClass.Generated(
-            time = 1701447884766L,
+            time = 1729613643190L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentImpl.java",
-            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String processName\nprivate  boolean directBootAware\nprivate  boolean enabled\nprivate  boolean exported\nprivate  int order\nprivate @android.annotation.Nullable java.lang.String splitName\nprivate @android.annotation.Nullable java.lang.String[] attributionTags\npublic static final  android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedMainComponentImpl> CREATOR\npublic  com.android.internal.pm.pkg.component.ParsedMainComponentImpl setProcessName(java.lang.String)\npublic  java.lang.String getClassName()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getAttributionTags()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedMainComponentImpl extends com.android.internal.pm.pkg.component.ParsedComponentImpl implements [com.android.internal.pm.pkg.component.ParsedMainComponent, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String processName\nprivate  boolean directBootAware\nprivate  boolean enabled\nprivate  boolean exported\nprivate  int order\nprivate @android.annotation.Nullable java.lang.String splitName\nprivate @android.annotation.Nullable java.lang.String[] attributionTags\nprivate  int mIntentMatchingFlags\npublic static final  int INTENT_MATCHING_FLAGS_NONE\npublic static final  int INTENT_MATCHING_FLAGS_ENFORCE_INTENT_FILTER\npublic static final  int INTENT_MATCHING_FLAGS_ALLOW_NULL_ACTION\npublic static final  android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedMainComponentImpl> CREATOR\npublic  com.android.internal.pm.pkg.component.ParsedMainComponentImpl setProcessName(java.lang.String)\npublic  java.lang.String getClassName()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getAttributionTags()\npublic  com.android.internal.pm.pkg.component.ParsedMainComponent setIntentMatchingFlags(int)\npublic @java.lang.Override int getIntentMatchingFlags()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedMainComponentImpl extends com.android.internal.pm.pkg.component.ParsedComponentImpl implements [com.android.internal.pm.pkg.component.ParsedMainComponent, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentUtils.java
index 7e56180..4feb894 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentUtils.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.pm.pkg.parsing.ParsingUtils.NOT_SET;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.IntentFilter;
@@ -39,7 +40,8 @@
 import java.io.IOException;
 
 /** @hide */
-class ParsedMainComponentUtils {
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class ParsedMainComponentUtils {
 
     private static final String TAG = ParsingUtils.TAG;
 
@@ -47,10 +49,11 @@
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     static <Component extends ParsedMainComponentImpl> ParseResult<Component> parseMainComponent(
             Component component, String tag, String[] separateProcesses, ParsingPackage pkg,
-            TypedArray array, int flags, boolean useRoundIcon,  @Nullable String defaultSplitName,
+            TypedArray array, int flags, boolean useRoundIcon, @Nullable String defaultSplitName,
             @NonNull ParseInput input, int bannerAttr, int descriptionAttr, int directBootAwareAttr,
             int enabledAttr, int iconAttr, int labelAttr, int logoAttr, int nameAttr,
-            int processAttr, int roundIconAttr, int splitNameAttr, int attributionTagsAttr) {
+            int processAttr, int roundIconAttr, int splitNameAttr, int attributionTagsAttr,
+            int intentMatchingFlagsAttr) {
         ParseResult<Component> result = ParsedComponentUtils.parseComponent(component, tag, pkg,
                 array, useRoundIcon, input, bannerAttr, descriptionAttr, iconAttr, labelAttr,
                 logoAttr, nameAttr, roundIconAttr);
@@ -107,6 +110,12 @@
             }
         }
 
+        if (android.security.Flags.enableIntentMatchingFlags()) {
+            int resolvedFlags = resolveIntentMatchingFlags(
+                        pkg.getIntentMatchingFlags(), array.getInt(intentMatchingFlagsAttr, 0));
+            component.setIntentMatchingFlags(resolvedFlags);
+        }
+
         return input.success(component);
     }
 
@@ -147,4 +156,21 @@
         return input.success(intentResult.getResult());
     }
 
+    /**
+     * Resolves intent matching flags from the application and a component, prioritizing the
+     * component's flags.
+     *
+     * @param applicationFlags The flag value from the "application" tag.
+     * @param componentFlags   The flag value from the "component" tags.
+     * @return The resolved intent matching flags.
+     */
+    @FlaggedApi(android.security.Flags.FLAG_ENABLE_INTENT_MATCHING_FLAGS)
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public static int resolveIntentMatchingFlags(int applicationFlags, int componentFlags) {
+        if (componentFlags == 0) {
+            return applicationFlags;
+        } else {
+            return componentFlags;
+        }
+    }
 }
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
index 3726b42..5c39827 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
@@ -79,7 +79,8 @@
                             R.styleable.AndroidManifestProvider_process,
                             R.styleable.AndroidManifestProvider_roundIcon,
                             R.styleable.AndroidManifestProvider_splitName,
-                            R.styleable.AndroidManifestProvider_attributionTags);
+                            R.styleable.AndroidManifestProvider_attributionTags,
+                            R.styleable.AndroidManifestProvider_intentMatchingFlags);
             if (result.isError()) {
                 return input.error(result);
             }
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
index 9601813..c469a7a 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
@@ -74,7 +74,8 @@
                     R.styleable.AndroidManifestService_process,
                     R.styleable.AndroidManifestService_roundIcon,
                     R.styleable.AndroidManifestService_splitName,
-                    R.styleable.AndroidManifestService_attributionTags
+                    R.styleable.AndroidManifestService_attributionTags,
+                    R.styleable.AndroidManifestService_intentMatchingFlags
             );
 
             if (result.isError()) {
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
index 341beed..f4bceb8 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
@@ -553,4 +553,15 @@
     boolean isNormalScreensSupported();
 
     boolean isSmallScreensSupported();
+
+    /**
+     * Sets the intent matching flags. This value is intended to be set from the "application" tag.
+     * @see android.R.styleable#AndroidManifestApplication_intentMatchingFlags
+     */
+    ParsingPackage setIntentMatchingFlags(int intentMatchingFlags);
+
+    /**
+     * Returns the intent matching flags.
+     */
+    int getIntentMatchingFlags();
 }
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index bb733f2..5db7b41 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -2187,6 +2187,8 @@
                     pkg.setKnownActivityEmbeddingCerts(knownActivityEmbeddingCerts);
                 }
             }
+            pkg.setIntentMatchingFlags(
+                    sa.getInt(R.styleable.AndroidManifestApplication_intentMatchingFlags, 0));
         } finally {
             sa.recycle();
         }
diff --git a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
index 30b160a..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;
@@ -28,19 +34,9 @@
 public final class RavenwoodEnvironment {
     public static final String TAG = "RavenwoodEnvironment";
 
-    private static final RavenwoodEnvironment sInstance;
-    private static final Workaround sWorkaround;
+    private static RavenwoodEnvironment sInstance = new RavenwoodEnvironment();
 
-    private RavenwoodEnvironment() {
-    }
-
-    static {
-        sInstance = new RavenwoodEnvironment();
-        sWorkaround = new Workaround();
-        ensureRavenwoodInitialized();
-    }
-
-    public static RuntimeException notSupportedOnDevice() {
+    private static RuntimeException notSupportedOnDevice() {
         return new UnsupportedOperationException("This method can only be used on Ravenwood");
     }
 
@@ -52,15 +48,6 @@
     }
 
     /**
-     * Initialize the ravenwood environment if it hasn't happened already, if running on Ravenwood.
-     *
-     * No-op if called on the device side.
-     */
-    @RavenwoodRedirect
-    public static void ensureRavenwoodInitialized() {
-    }
-
-    /**
      * USE IT SPARINGLY! Returns true if it's running on Ravenwood, hostside test environment.
      *
      * <p>Using this allows code to behave differently on a real device and on Ravenwood, but
@@ -91,18 +78,6 @@
     }
 
     /**
-     * See {@link Workaround}. It's only usable on Ravenwood.
-     */
-    @RavenwoodReplace
-    public static Workaround workaround() {
-        throw notSupportedOnDevice();
-    }
-
-    private static Workaround workaround$ravenwood() {
-        return sWorkaround;
-    }
-
-    /**
      * @return the "ravenwood-runtime" directory.
      */
     @RavenwoodRedirect
@@ -110,19 +85,19 @@
         throw notSupportedOnDevice();
     }
 
-    /**
-     * A set of APIs used to work around missing features on Ravenwood. Ideally, this class should
-     * be empty, and all its APIs should be able to be implemented properly.
-     */
-    public static class Workaround {
-        Workaround() {
-        }
+    /** @hide */
+    public static class CompatIdsForTest {
+        // Enabled by default
+        @ChangeId
+        public static final long TEST_COMPAT_ID_1 = 368131859L;
 
-        /**
-         * @return whether the app's target SDK level is at least Q.
-         */
-        public boolean isTargetSdkAtLeastQ() {
-            return true;
-        }
+        @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/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/core/java/com/android/internal/telephony/ISatelliteStateChangeListener.aidl
similarity index 79%
copy from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
copy to core/java/com/android/internal/telephony/ISatelliteStateChangeListener.aidl
index e21bf8f..4d195c2 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/core/java/com/android/internal/telephony/ISatelliteStateChangeListener.aidl
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.shared;
+package com.android.internal.telephony;
 
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
+oneway interface ISatelliteStateChangeListener {
+    void onSatelliteEnabledStateChanged(boolean isEnabled);
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index ca75abd..1c76a6c 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -38,6 +38,7 @@
 import com.android.internal.telephony.ICarrierPrivilegesCallback;
 import com.android.internal.telephony.IPhoneStateListener;
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
+import com.android.internal.telephony.ISatelliteStateChangeListener;
 
 interface ITelephonyRegistry {
     void addOnSubscriptionsChangedListener(String pkg, String featureId,
@@ -124,4 +125,8 @@
     void notifyCarrierRoamingNtnModeChanged(int subId, in boolean active);
     void notifyCarrierRoamingNtnEligibleStateChanged(int subId, in boolean eligible);
     void notifyCarrierRoamingNtnAvailableServicesChanged(int subId, in int[] availableServices);
+
+    void addSatelliteStateChangeListener(ISatelliteStateChangeListener listener, String pkg, String featureId);
+    void removeSatelliteStateChangeListener(ISatelliteStateChangeListener listener, String pkg);
+    void notifySatelliteStateChanged(boolean isEnabled);
 }
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/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java
index d430fe3..5350059 100644
--- a/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -1514,4 +1514,10 @@
      * @hide
      */
     boolean isAllowCrossUidActivitySwitchFromBelow();
+
+    /**
+     * Returns the intent matching flags.
+     * @hide
+     */
+    int getIntentMatchingFlags();
 }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 2bb6e71..2541258 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -76,7 +76,6 @@
         "android_content_res_ApkAssets.cpp",
         "android_os_SystemClock.cpp",
         "android_os_SystemProperties.cpp",
-        "android_os_Trace.cpp",
         "android_text_AndroidCharacter.cpp",
         "android_util_AssetManager.cpp",
         "android_util_EventLog.cpp",
@@ -104,10 +103,6 @@
         "system/media/private/camera/include",
     ],
 
-    shared_libs: [
-        "libtracing_perfetto",
-    ],
-
     static_libs: [
         "libziparchive_for_incfs",
         "libguiflags",
@@ -190,6 +185,7 @@
                 "android_os_ServiceManagerNative.cpp",
                 "android_os_SharedMemory.cpp",
                 "android_os_storage_StorageManager.cpp",
+                "android_os_Trace.cpp",
                 "android_os_UEventObserver.cpp",
                 "android_os_incremental_IncrementalManager.cpp",
                 "android_net_LocalSocketImpl.cpp",
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index df87a69..56292c3 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -2403,6 +2403,11 @@
     }
 }
 
+static void nativeEnableDebugLogCallPoints(JNIEnv* env, jclass clazz, jlong transactionObj) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+    transaction->enableDebugLogCallPoints();
+}
+
 static const JNINativeMethod sSurfaceControlMethods[] = {
         // clang-format off
     {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJLandroid/os/Parcel;)J",
@@ -2649,6 +2654,7 @@
     {"nativeNotifyShutdown", "()V",
             (void*)nativeNotifyShutdown },
     {"nativeSetLuts", "(JJ[F[I[I[I[I)V", (void*)nativeSetLuts },
+    {"nativeEnableDebugLogCallPoints", "(J)V", (void*)nativeEnableDebugLogCallPoints },
         // clang-format on
 };
 
diff --git a/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp b/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp
index bea5ffe..a5b5057 100644
--- a/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp
+++ b/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp
@@ -49,27 +49,26 @@
  * to the supplied multi-state counter in accordance with the counter's state.
  */
 static jboolean addCpuTimeInFreqDelta(
-        jint uid, jlong counterNativePtr, jlong timestampMs,
+        JNIEnv *env, jint uid, jlong counterNativePtr, jlong timestampMs,
         std::optional<std::vector<std::vector<uint64_t>>> timeInFreqDataNanos,
-        jlong deltaOutContainerNativePtr) {
+        jlongArray deltaOut) {
     if (!timeInFreqDataNanos) {
         return false;
     }
 
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(counterNativePtr);
+    auto counter = reinterpret_cast<battery::LongArrayMultiStateCounter *>(counterNativePtr);
     size_t s = 0;
     for (const auto &cluster : *timeInFreqDataNanos) s += cluster.size();
 
-    std::vector<uint64_t> flattened;
-    flattened.reserve(s);
-    auto offset = flattened.begin();
+    battery::Uint64ArrayRW flattened(s);
+    uint64_t *out = flattened.dataRW();
+    auto offset = out;
     for (const auto &cluster : *timeInFreqDataNanos) {
-        flattened.insert(offset, cluster.begin(), cluster.end());
+        memcpy(offset, cluster.data(), cluster.size() * sizeof(uint64_t));
         offset += cluster.size();
     }
     for (size_t i = 0; i < s; ++i) {
-        flattened[i] /= NSEC_PER_MSEC;
+        out[i] /= NSEC_PER_MSEC;
     }
     if (s != counter->getCount(0).size()) { // Counter has at least one state
         ALOGE("Mismatch between eBPF data size (%d) and the counter size (%d)", (int)s,
@@ -77,29 +76,32 @@
         return false;
     }
 
-    const std::vector<uint64_t> &delta = counter->updateValue(flattened, timestampMs);
-    if (deltaOutContainerNativePtr) {
-        std::vector<uint64_t> *vector =
-                reinterpret_cast<std::vector<uint64_t> *>(deltaOutContainerNativePtr);
-        *vector = delta;
+    const battery::Uint64Array &delta = counter->updateValue(flattened, timestampMs);
+    if (deltaOut) {
+        ScopedLongArrayRW scopedArray(env, deltaOut);
+        uint64_t *array = reinterpret_cast<uint64_t *>(scopedArray.get());
+        if (delta.data() != nullptr) {
+            memcpy(array, delta.data(), s * sizeof(uint64_t));
+        } else {
+            memset(array, 0, s * sizeof(uint64_t));
+        }
     }
 
     return true;
 }
 
-static jboolean addDeltaFromBpf(jint uid, jlong counterNativePtr, jlong timestampMs,
-                                jlong deltaOutContainerNativePtr) {
-    return addCpuTimeInFreqDelta(uid, counterNativePtr, timestampMs,
-                                 android::bpf::getUidCpuFreqTimes(uid), deltaOutContainerNativePtr);
+static jboolean addDeltaFromBpf(JNIEnv *env, jlong self, jint uid, jlong counterNativePtr,
+                                jlong timestampMs, jlongArray deltaOut) {
+    return addCpuTimeInFreqDelta(env, uid, counterNativePtr, timestampMs,
+                                 android::bpf::getUidCpuFreqTimes(uid), deltaOut);
 }
 
 static jboolean addDeltaForTest(JNIEnv *env, jclass, jint uid, jlong counterNativePtr,
                                 jlong timestampMs, jobjectArray timeInFreqDataNanos,
-                                jlong deltaOutContainerNativePtr) {
+                                jlongArray deltaOut) {
     if (!timeInFreqDataNanos) {
-        return addCpuTimeInFreqDelta(uid, counterNativePtr, timestampMs,
-                                     std::optional<std::vector<std::vector<uint64_t>>>(),
-                                     deltaOutContainerNativePtr);
+        return addCpuTimeInFreqDelta(env, uid, counterNativePtr, timestampMs,
+                                     std::optional<std::vector<std::vector<uint64_t>>>(), deltaOut);
     }
 
     std::vector<std::vector<uint64_t>> timeInFreqData;
@@ -113,18 +115,16 @@
         }
         timeInFreqData.push_back(cluster);
     }
-    return addCpuTimeInFreqDelta(uid, counterNativePtr, timestampMs, std::optional(timeInFreqData),
-                                 deltaOutContainerNativePtr);
+    return addCpuTimeInFreqDelta(env, uid, counterNativePtr, timestampMs,
+                                 std::optional(timeInFreqData), deltaOut);
 }
 
 static const JNINativeMethod g_single_methods[] = {
         {"readBpfData", "(I)[J", (void *)getUidCpuFreqTimeMs},
-
-        // @CriticalNative
-        {"addDeltaFromBpf", "(IJJJ)Z", (void *)addDeltaFromBpf},
+        {"addDeltaFromBpf", "(IJJ[J)Z", (void *)addDeltaFromBpf},
 
         // Used for testing
-        {"addDeltaForTest", "(IJJ[[JJ)Z", (void *)addDeltaForTest},
+        {"addDeltaForTest", "(IJJ[[J[J)Z", (void *)addDeltaForTest},
 };
 
 int register_com_android_internal_os_KernelSingleUidTimeReader(JNIEnv *env) {
diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
index b3c41df..7ffe0ed 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -26,16 +26,40 @@
 #include "core_jni_helpers.h"
 
 namespace android {
+namespace battery {
+
+/**
+ * Implementation of Uint64Array that wraps a Java long[]. Since it uses the "critical"
+ * version of JNI array access (halting GC), any usage of this class must be extra quick.
+ */
+class JavaUint64Array : public Uint64Array {
+    JNIEnv *mEnv;
+    jlongArray mJavaArray;
+    uint64_t *mData;
+
+public:
+    JavaUint64Array(JNIEnv *env, jlongArray values) : Uint64Array(env->GetArrayLength(values)) {
+        mEnv = env;
+        mJavaArray = values;
+        mData = reinterpret_cast<uint64_t *>(mEnv->GetPrimitiveArrayCritical(mJavaArray, nullptr));
+    }
+
+    ~JavaUint64Array() override {
+        mEnv->ReleasePrimitiveArrayCritical(mJavaArray, mData, 0);
+    }
+
+    const uint64_t *data() const override {
+        return mData;
+    }
+};
 
 static jlong native_init(jint stateCount, jint arrayLength) {
-    battery::LongArrayMultiStateCounter *counter =
-            new battery::LongArrayMultiStateCounter(stateCount, std::vector<uint64_t>(arrayLength));
+    auto *counter = new LongArrayMultiStateCounter(stateCount, Uint64Array(arrayLength));
     return reinterpret_cast<jlong>(counter);
 }
 
 static void native_dispose(void *nativePtr) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
     delete counter;
 }
 
@@ -44,80 +68,63 @@
 }
 
 static void native_setEnabled(jlong nativePtr, jboolean enabled, jlong timestamp) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
     counter->setEnabled(enabled, timestamp);
 }
 
 static void native_setState(jlong nativePtr, jint state, jlong timestamp) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
     counter->setState(state, timestamp);
 }
 
 static void native_copyStatesFrom(jlong nativePtrTarget, jlong nativePtrSource) {
-    battery::LongArrayMultiStateCounter *counterTarget =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrTarget);
-    battery::LongArrayMultiStateCounter *counterSource =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrSource);
+    auto *counterTarget = reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrTarget);
+    auto *counterSource = reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrSource);
     counterTarget->copyStatesFrom(*counterSource);
 }
 
-static void native_setValues(jlong nativePtr, jint state, jlong longArrayContainerNativePtr) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
-    std::vector<uint64_t> *vector =
-            reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
-
-    counter->setValue(state, *vector);
+static void native_setValues(JNIEnv *env, jclass, jlong nativePtr, jint state, jlongArray values) {
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
+    counter->setValue(state, JavaUint64Array(env, values));
 }
 
-static void native_updateValues(jlong nativePtr, jlong longArrayContainerNativePtr,
+static void native_updateValues(JNIEnv *env, jclass, jlong nativePtr, jlongArray values,
                                 jlong timestamp) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
-    std::vector<uint64_t> *vector =
-            reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
-
-    counter->updateValue(*vector, timestamp);
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
+    counter->updateValue(JavaUint64Array(env, values), timestamp);
 }
 
-static void native_incrementValues(jlong nativePtr, jlong longArrayContainerNativePtr,
+static void native_incrementValues(JNIEnv *env, jclass, jlong nativePtr, jlongArray values,
                                    jlong timestamp) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
-    std::vector<uint64_t> *vector =
-            reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
-
-    counter->incrementValue(*vector, timestamp);
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
+    counter->incrementValue(JavaUint64Array(env, values), timestamp);
 }
 
-static void native_addCounts(jlong nativePtr, jlong longArrayContainerNativePtr) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
-    std::vector<uint64_t> *vector =
-            reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
-    counter->addValue(*vector);
+static void native_addCounts(JNIEnv *env, jclass, jlong nativePtr, jlongArray values) {
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
+    counter->addValue(JavaUint64Array(env, values));
 }
 
 static void native_reset(jlong nativePtr) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
     counter->reset();
 }
 
-static void native_getCounts(jlong nativePtr, jlong longArrayContainerNativePtr, jint state) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
-    std::vector<uint64_t> *vector =
-            reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
-
-    *vector = counter->getCount(state);
+static void native_getCounts(JNIEnv *env, jclass, jlong nativePtr, jlongArray values, jint state) {
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
+    ScopedLongArrayRW scopedArray(env, values);
+    auto *data = counter->getCount(state).data();
+    auto size = env->GetArrayLength(values);
+    auto *outData = scopedArray.get();
+    if (data == nullptr) {
+        memset(outData, 0, size * sizeof(uint64_t));
+    } else {
+        memcpy(outData, data, size * sizeof(uint64_t));
+    }
 }
 
 static jobject native_toString(JNIEnv *env, jclass, jlong nativePtr) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
     return env->NewStringUTF(counter->toString().c_str());
 }
 
@@ -137,20 +144,26 @@
 
 static void native_writeToParcel(JNIEnv *env, jclass, jlong nativePtr, jobject jParcel,
                                  jint flags) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
     ndk::ScopedAParcel parcel(AParcel_fromJavaParcel(env, jParcel));
 
     uint16_t stateCount = counter->getStateCount();
     THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeInt32(parcel.get(), stateCount));
 
     // LongArrayMultiStateCounter has at least state 0
-    const std::vector<uint64_t> &anyState = counter->getCount(0);
+    const Uint64Array &anyState = counter->getCount(0);
     THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeInt32(parcel.get(), anyState.size()));
 
     for (battery::state_t state = 0; state < stateCount; state++) {
-        THROW_AND_RETURN_ON_WRITE_ERROR(
-                ndk::AParcel_writeVector(parcel.get(), counter->getCount(state)));
+        const Uint64Array &value = counter->getCount(state);
+        if (value.data() == nullptr) {
+            THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeBool(parcel.get(), false));
+        } else {
+            THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeBool(parcel.get(), true));
+            for (size_t i = 0; i < anyState.size(); i++) {
+                THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeUint64(parcel.get(), value.data()[i]));
+            }
+        }
     }
 }
 
@@ -183,40 +196,37 @@
     int32_t arrayLength;
     THROW_AND_RETURN_ON_READ_ERROR(AParcel_readInt32(parcel.get(), &arrayLength));
 
-    auto counter = std::make_unique<battery::LongArrayMultiStateCounter>(stateCount,
-                                                                         std::vector<uint64_t>(
-                                                                                 arrayLength));
-
-    std::vector<uint64_t> value;
-    value.reserve(arrayLength);
-
+    auto counter =
+            std::make_unique<LongArrayMultiStateCounter>(stateCount, Uint64Array(arrayLength));
+    Uint64ArrayRW array(arrayLength);
     for (battery::state_t state = 0; state < stateCount; state++) {
-        THROW_AND_RETURN_ON_READ_ERROR(ndk::AParcel_readVector(parcel.get(), &value));
-        counter->setValue(state, value);
+        bool hasValues;
+        THROW_AND_RETURN_ON_READ_ERROR(AParcel_readBool(parcel.get(), &hasValues));
+        if (hasValues) {
+            for (int i = 0; i < arrayLength; i++) {
+                THROW_AND_RETURN_ON_READ_ERROR(
+                        AParcel_readUint64(parcel.get(), &(array.dataRW()[i])));
+            }
+            counter->setValue(state, array);
+        }
     }
 
     return reinterpret_cast<jlong>(counter.release());
 }
 
 static jint native_getStateCount(jlong nativePtr) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
     return counter->getStateCount();
 }
 
 static jint native_getArrayLength(jlong nativePtr) {
-    battery::LongArrayMultiStateCounter *counter =
-            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+    auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
 
     // LongArrayMultiStateCounter has at least state 0
-    const std::vector<uint64_t> &anyState = counter->getCount(0);
+    const Uint64Array &anyState = counter->getCount(0);
     return anyState.size();
 }
 
-static jlong native_init_LongArrayContainer(jint length) {
-    return reinterpret_cast<jlong>(new std::vector<uint64_t>(length));
-}
-
 static const JNINativeMethod g_LongArrayMultiStateCounter_methods[] = {
         // @CriticalNative
         {"native_init", "(II)J", (void *)native_init},
@@ -228,18 +238,18 @@
         {"native_setState", "(JIJ)V", (void *)native_setState},
         // @CriticalNative
         {"native_copyStatesFrom", "(JJ)V", (void *)native_copyStatesFrom},
-        // @CriticalNative
-        {"native_setValues", "(JIJ)V", (void *)native_setValues},
-        // @CriticalNative
-        {"native_updateValues", "(JJJ)V", (void *)native_updateValues},
-        // @CriticalNative
-        {"native_incrementValues", "(JJJ)V", (void *)native_incrementValues},
-        // @CriticalNative
-        {"native_addCounts", "(JJ)V", (void *)native_addCounts},
+        // @FastNative
+        {"native_setValues", "(JI[J)V", (void *)native_setValues},
+        // @FastNative
+        {"native_updateValues", "(J[JJ)V", (void *)native_updateValues},
+        // @FastNative
+        {"native_incrementValues", "(J[JJ)V", (void *)native_incrementValues},
+        // @FastNative
+        {"native_addCounts", "(J[J)V", (void *)native_addCounts},
         // @CriticalNative
         {"native_reset", "(J)V", (void *)native_reset},
-        // @CriticalNative
-        {"native_getCounts", "(JJI)V", (void *)native_getCounts},
+        // @FastNative
+        {"native_getCounts", "(J[JI)V", (void *)native_getCounts},
         // @FastNative
         {"native_toString", "(J)Ljava/lang/String;", (void *)native_toString},
         // @FastNative
@@ -252,91 +262,12 @@
         {"native_getArrayLength", "(J)I", (void *)native_getArrayLength},
 };
 
-/////////////////////// LongArrayMultiStateCounter.LongArrayContainer ////////////////////////
-
-static void native_dispose_LongArrayContainer(jlong nativePtr) {
-    std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
-    delete vector;
-}
-
-static jlong native_getReleaseFunc_LongArrayContainer() {
-    return reinterpret_cast<jlong>(native_dispose_LongArrayContainer);
-}
-
-static void native_setValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
-                                                jlongArray jarray) {
-    std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
-    ScopedLongArrayRO scopedArray(env, jarray);
-    const uint64_t *array = reinterpret_cast<const uint64_t *>(scopedArray.get());
-    uint8_t size = scopedArray.size();
-
-    // Boundary checks are performed in the Java layer
-    std::copy(array, array + size, vector->data());
-}
-
-static void native_getValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
-                                                jlongArray jarray) {
-    std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
-    ScopedLongArrayRW scopedArray(env, jarray);
-
-    // Boundary checks are performed in the Java layer
-    std::copy(vector->data(), vector->data() + vector->size(), scopedArray.get());
-}
-
-static jboolean native_combineValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
-                                                        jlongArray jarray, jintArray jindexMap) {
-    std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
-    ScopedLongArrayRW scopedArray(env, jarray);
-    ScopedIntArrayRO scopedIndexMap(env, jindexMap);
-
-    const uint64_t *data = vector->data();
-    uint64_t *array = reinterpret_cast<uint64_t *>(scopedArray.get());
-    const uint8_t size = scopedArray.size();
-
-    for (int i = 0; i < size; i++) {
-        array[i] = 0;
-    }
-
-    bool nonZero = false;
-    for (size_t i = 0; i < vector->size(); i++) {
-        jint index = scopedIndexMap[i];
-        if (index < 0 || index >= size) {
-            jniThrowExceptionFmt(env, "java/lang/IndexOutOfBoundsException",
-                                 "Index %d is out of bounds: [0, %d]", index, size - 1);
-            return false;
-        }
-
-        if (data[i] != 0L) {
-            array[index] += data[i];
-            nonZero = true;
-        }
-    }
-
-    return nonZero;
-}
-
-static const JNINativeMethod g_LongArrayContainer_methods[] = {
-        // @CriticalNative
-        {"native_init", "(I)J", (void *)native_init_LongArrayContainer},
-        // @CriticalNative
-        {"native_getReleaseFunc", "()J", (void *)native_getReleaseFunc_LongArrayContainer},
-        // @FastNative
-        {"native_setValues", "(J[J)V", (void *)native_setValues_LongArrayContainer},
-        // @FastNative
-        {"native_getValues", "(J[J)V", (void *)native_getValues_LongArrayContainer},
-        // @FastNative
-        {"native_combineValues", "(J[J[I)Z", (void *)native_combineValues_LongArrayContainer},
-};
+} // namespace battery
 
 int register_com_android_internal_os_LongArrayMultiStateCounter(JNIEnv *env) {
     // 0 represents success, thus "|" and not "&"
     return RegisterMethodsOrDie(env, "com/android/internal/os/LongArrayMultiStateCounter",
-                                g_LongArrayMultiStateCounter_methods,
-                                NELEM(g_LongArrayMultiStateCounter_methods)) |
-            RegisterMethodsOrDie(env,
-                                 "com/android/internal/os/LongArrayMultiStateCounter"
-                                 "$LongArrayContainer",
-                                 g_LongArrayContainer_methods, NELEM(g_LongArrayContainer_methods));
+                                battery::g_LongArrayMultiStateCounter_methods,
+                                NELEM(battery::g_LongArrayMultiStateCounter_methods));
 }
-
 } // namespace android
diff --git a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
index 56d3fbb..b3bfd0bc 100644
--- a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
@@ -28,7 +28,7 @@
 
 namespace battery {
 
-typedef battery::MultiStateCounter<int64_t> LongMultiStateCounter;
+typedef battery::MultiStateCounter<int64_t, int64_t> LongMultiStateCounter;
 
 template <>
 bool LongMultiStateCounter::delta(const int64_t &previousValue, const int64_t &newValue,
@@ -47,12 +47,6 @@
         *value1 += value2;
     }
 }
-
-template <>
-std::string LongMultiStateCounter::valueToString(const int64_t &v) const {
-    return std::to_string(v);
-}
-
 } // namespace battery
 
 static inline battery::LongMultiStateCounter *asLongMultiStateCounter(const jlong nativePtr) {
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 3747299..7fca117 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -87,7 +87,6 @@
 extern int register_android_os_Parcel(JNIEnv* env);
 extern int register_android_os_SystemClock(JNIEnv* env);
 extern int register_android_os_SystemProperties(JNIEnv* env);
-extern int register_android_os_Trace(JNIEnv* env);
 extern int register_android_text_AndroidCharacter(JNIEnv* env);
 extern int register_android_util_EventLog(JNIEnv* env);
 extern int register_android_util_Log(JNIEnv* env);
@@ -133,7 +132,6 @@
 #endif
         {"android.os.SystemClock", REG_JNI(register_android_os_SystemClock)},
         {"android.os.SystemProperties", REG_JNI(register_android_os_SystemProperties)},
-        {"android.os.Trace", REG_JNI(register_android_os_Trace)},
         {"android.text.AndroidCharacter", REG_JNI(register_android_text_AndroidCharacter)},
         {"android.util.EventLog", REG_JNI(register_android_util_EventLog)},
         {"android.util.Log", REG_JNI(register_android_util_Log)},
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 654d83c..407790c 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -465,6 +465,7 @@
     repeated .android.graphics.RectProto unrestricted_keep_clear_areas = 46;
     repeated .android.view.InsetsSourceProto mergedLocalInsetsSources = 47;
     optional int32 requested_visible_types = 48;
+    optional .android.graphics.RectProto dim_bounds = 49;
 }
 
 message IdentifierProto {
diff --git a/core/proto/android/service/appwidget.proto b/core/proto/android/service/appwidget.proto
index 97350ef..fb90719 100644
--- a/core/proto/android/service/appwidget.proto
+++ b/core/proto/android/service/appwidget.proto
@@ -20,6 +20,8 @@
 option java_multiple_files = true;
 option java_outer_classname = "AppWidgetServiceProto";
 
+import "frameworks/base/core/proto/android/widget/remoteviews.proto";
+
 // represents the object holding the dump info of the app widget service
 message AppWidgetServiceDumpProto {
     repeated WidgetProto widgets = 1; // the array of bound widgets
@@ -38,3 +40,14 @@
     optional int32 maxHeight = 9;
     optional bool restoreCompleted = 10;
 }
+
+// represents a set of widget previews for a particular provider
+message GeneratedPreviewsProto {
+    repeated Preview previews = 1;
+
+    // represents a particular RemoteViews preview, which may be set for multiple categories
+    message Preview {
+        repeated int32 widget_categories = 1;
+        optional android.widget.RemoteViewsProto views = 2;
+    }
+}
\ No newline at end of file
diff --git a/core/res/Android.bp b/core/res/Android.bp
index f6ca821..66c2e12 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -171,6 +171,7 @@
         "android.security.flags-aconfig",
         "com.android.hardware.input.input-aconfig",
         "aconfig_trade_in_mode_flags",
+        "ranging_aconfig_flags",
     ],
 }
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5044a30..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
@@ -8543,6 +8553,18 @@
         android:name="android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED"
         android:featureFlag="android.content.pm.reduce_broadcasts_for_component_state_changes"/>
 
+    <!-- @SystemApi
+        @FlaggedApi("android.media.tv.flags.kids_mode_tvdb_sharing")
+        This permission is required when accessing information related to
+        singleUser-ed TIS session.
+        <p>This should only be used by OEM.
+        <p>Protection level: signature|privileged|vendorPrivileged
+        @hide
+    -->
+    <permission android:name="android.permission.SINGLE_USER_TIS_ACCESS"
+        android:protectionLevel="signature|privileged|vendorPrivileged"
+        android:featureFlag="android.media.tv.flags.kids_mode_tvdb_sharing"/>
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
diff --git a/core/res/res/drawable-watch/ic_lock_bugreport.xml b/core/res/res/drawable-watch/ic_lock_bugreport.xml
index b664fe4f..35834be 100644
--- a/core/res/res/drawable-watch/ic_lock_bugreport.xml
+++ b/core/res/res/drawable-watch/ic_lock_bugreport.xml
@@ -13,19 +13,6 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0"
-        android:tint="@android:color/white">
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M20,10V8h-2.81c-0.45,-0.78 -1.07,-1.46 -1.82,-1.96L17,4.41L15.59,3l-2.17,2.17c-0.03,-0.01 -0.05,-0.01 -0.08,-0.01c-0.16,-0.04 -0.32,-0.06 -0.49,-0.09c-0.06,-0.01 -0.11,-0.02 -0.17,-0.03C12.46,5.02 12.23,5 12,5h0c-0.49,0 -0.97,0.07 -1.42,0.18l0.02,-0.01L8.41,3L7,4.41l1.62,1.63l0.01,0C7.88,6.54 7.26,7.22 6.81,8H4v2h2.09C6.03,10.33 6,10.66 6,11v1H4v2h2v1c0,0.34 0.04,0.67 0.09,1H4v2h2.81c1.04,1.79 2.97,3 5.19,3h0c2.22,0 4.15,-1.21 5.19,-3H20v-2h-2.09l0,0c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1l0,0H20zM16,15c0,2.21 -1.79,4 -4,4c-2.21,0 -4,-1.79 -4,-4v-4c0,-2.21 1.79,-4 4,-4h0c2.21,0 4,1.79 4,4V15z"/>
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M10,14h4v2h-4z"/>
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M10,10h4v2h-4z"/>
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" android:tint="?attr/colorControlNormal">
+<path android:fillColor="@android:color/white" android:pathData="M480.01,848.13Q412.37,848.13 354.6,814.34Q296.83,780.54 263.87,721.91L193.78,721.91Q175.97,721.91 163.92,709.86Q151.87,697.81 151.87,680Q151.87,662.19 163.92,650.14Q175.97,638.09 193.78,638.09L235.63,638.09Q232.81,619.12 232.34,600.16Q231.87,581.2 231.87,561.91L193.78,561.91Q175.97,561.91 163.92,549.86Q151.87,537.81 151.87,520Q151.87,502.19 163.92,490.14Q175.97,478.09 193.78,478.09L231.87,478.09Q231.87,458.8 232.34,439.84Q232.81,420.88 235.63,401.91L193.78,401.91Q175.97,401.91 163.92,389.86Q151.87,377.81 151.87,360Q151.87,342.19 163.92,330.14Q175.97,318.09 193.78,318.09L265.07,318.09Q278.11,294.13 295.97,273.77Q313.83,253.41 337.07,237.93L301.26,201.37Q289.54,189.65 289.66,172.32Q289.78,154.98 302.26,142.5Q313.98,130.78 331.7,130.78Q349.41,130.78 361.13,142.5L417.7,199.07Q447.13,188.63 477.92,188.39Q508.72,188.15 538.15,198.35L597.2,140.3Q608.79,128.59 626.19,128.59Q643.59,128.59 656.07,141.07Q667.78,152.78 667.78,170.5Q667.78,188.22 656.07,199.93L619.98,236.02Q644.41,251.98 663.51,273.03Q682.61,294.09 696.38,320L767.17,320Q784.41,320 796.27,331.86Q808.13,343.72 808.13,360.96Q808.13,378.2 796.27,390.05Q784.41,401.91 767.17,401.91L724.37,401.91Q727.19,420.88 727.66,439.84Q728.13,458.8 728.13,478.09L766.22,478.09Q784.03,478.09 796.08,490.14Q808.13,502.19 808.13,520Q808.13,537.81 796.08,549.86Q784.03,561.91 766.22,561.91L728.13,561.91Q728.13,581.2 727.51,600.12Q726.89,619.04 724.13,638.09L766.22,638.09Q784.03,638.09 796.08,650.14Q808.13,662.19 808.13,680Q808.13,697.81 796.08,709.86Q784.03,721.91 766.22,721.91L696.13,721.91Q663.17,780.54 605.42,814.34Q547.66,848.13 480.01,848.13ZM441.91,641.91L518.09,641.91Q535.9,641.91 547.95,629.86Q560,617.81 560,600Q560,582.19 547.95,570.14Q535.9,558.09 518.09,558.09L441.91,558.09Q424.1,558.09 412.05,570.14Q400,582.19 400,600Q400,617.81 412.05,629.86Q424.1,641.91 441.91,641.91ZM441.91,481.91L518.09,481.91Q535.9,481.91 547.95,469.86Q560,457.81 560,440Q560,422.19 547.95,410.14Q535.9,398.09 518.09,398.09L441.91,398.09Q424.1,398.09 412.05,410.14Q400,422.19 400,440Q400,457.81 412.05,469.86Q424.1,481.91 441.91,481.91Z"/>
 </vector>
diff --git a/core/res/res/drawable-watch/ic_lock_power_off.xml b/core/res/res/drawable-watch/ic_lock_power_off.xml
index b437a4b..c42d7d2 100644
--- a/core/res/res/drawable-watch/ic_lock_power_off.xml
+++ b/core/res/res/drawable-watch/ic_lock_power_off.xml
@@ -14,13 +14,6 @@
   ~ limitations under the License.
   -->
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24"
-        android:viewportHeight="24"
-        android:tint="@android:color/white">
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M11,2h2v10h-2zM18.37,5.64l-1.41,1.41c2.73,2.73 2.72,7.16 -0.01,9.89 -2.73,2.73 -7.17,2.73 -9.89,0.01 -2.73,-2.73 -2.74,-7.18 -0.01,-9.91l-1.41,-1.4c-3.51,3.51 -3.51,9.21 0.01,12.73 3.51,3.51 9.21,3.51 12.72,-0.01 3.51,-3.51 3.51,-9.2 0,-12.72z"/>
-</vector>
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" android:tint="?attr/colorControlNormal">
+<path android:fillColor="@android:color/white" android:pathData="M480,888.13Q395.09,888.13 320.65,856.03Q246.22,823.93 191.14,768.86Q136.07,713.78 103.97,639.35Q71.87,564.91 71.87,480Q71.87,406.52 96.01,340.78Q120.15,275.04 162.91,222.33Q175.11,206.65 192.52,207.41Q209.93,208.17 222.61,219.37Q235.28,230.57 239.26,248.84Q243.24,267.11 227.8,287.22Q197.48,327.26 180.17,376.09Q162.87,424.91 162.87,480Q162.87,613.04 254.91,705.09Q346.96,797.13 480,797.13Q613.04,797.13 705.09,705.09Q797.13,613.04 797.13,480Q797.13,424.91 779.83,376.09Q762.52,327.26 732.2,287.22Q716.76,267.11 720.74,248.84Q724.72,230.57 737.39,219.37Q750.07,208.17 767.48,207.41Q784.89,206.65 797.09,222.33Q839.85,275.04 863.99,340.78Q888.13,406.52 888.13,480Q888.13,564.91 856.03,639.35Q823.93,713.78 768.86,768.86Q713.78,823.93 639.35,856.03Q564.91,888.13 480,888.13ZM480,525.5Q460.85,525.5 447.67,512.33Q434.5,499.15 434.5,480L434.5,117.37Q434.5,98.22 447.67,85.04Q460.85,71.87 480,71.87Q499.15,71.87 512.33,85.04Q525.5,98.22 525.5,117.37L525.5,480Q525.5,499.15 512.33,512.33Q499.15,525.5 480,525.5Z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable-watch/ic_restart.xml b/core/res/res/drawable-watch/ic_restart.xml
index 52933aa..ddcfd25 100644
--- a/core/res/res/drawable-watch/ic_restart.xml
+++ b/core/res/res/drawable-watch/ic_restart.xml
@@ -14,13 +14,6 @@
   ~ limitations under the License.
   -->
 
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24"
-        android:viewportHeight="24"
-        android:tint="@android:color/white">
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02c-2.83,-0.48 -5,-2.94 -5,-5.91zM20,13c0,-4.42 -3.58,-8 -8,-8 -0.06,0 -0.12,0.01 -0.18,0.01l1.09,-1.09L11.5,2.5 8,6l3.5,3.5 1.41,-1.41 -1.08,-1.08c0.06,0 0.12,-0.01 0.17,-0.01 3.31,0 6,2.69 6,6 0,2.97 -2.17,5.43 -5,5.91v2.02c3.95,-0.49 7,-3.85 7,-7.93z"/>
-</vector>
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" android:tint="?attr/colorControlNormal">
+<path android:fillColor="@android:color/white" android:pathData="M385.83,834.46Q282.35,803.54 217.11,717.61Q151.87,631.67 151.87,520.48Q151.87,464.91 169.91,414.25Q187.96,363.59 220.8,320.83Q232.76,305.72 252.11,304.98Q271.46,304.24 286.85,319.63Q298.57,331.35 299.3,348.66Q300.04,365.98 288.8,381.41Q266.72,411.22 254.79,446.54Q242.87,481.87 242.87,520.48Q242.87,599.33 288.46,661.27Q334.04,723.22 406.41,746.7Q421.09,751.65 430.54,764.09Q440,776.52 440,790.96Q440,814.3 423.73,827.6Q407.46,840.89 385.83,834.46ZM574.17,834.46Q552.54,840.89 536.27,827.22Q520,813.54 520,790.2Q520,776.76 529.46,764.21Q538.91,751.65 553.59,746.7Q625.72,722.22 671.42,660.65Q717.13,599.09 717.13,520.48Q717.13,423.11 649.64,354.3Q582.15,285.5 485.02,283.59L481.07,283.59L497.3,299.83Q509.02,311.54 509.02,329.14Q509.02,346.74 497.3,358.46Q485.59,370.17 467.99,370.17Q450.39,370.17 438.67,358.46L348.46,268.24Q341.74,261.52 338.76,253.45Q335.78,245.37 335.78,236.41Q335.78,227.46 338.76,219.38Q341.74,211.3 348.46,204.59L438.67,114.13Q450.39,102.41 467.99,102.41Q485.59,102.41 497.3,114.13Q509.02,125.85 509.02,143.45Q509.02,161.04 497.3,172.76L477.72,192.35L481.91,192.35Q618.54,192.35 713.34,287.98Q808.13,383.61 808.13,520.48Q808.13,630.91 742.89,717.11Q677.65,803.3 574.17,834.46Z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable-watch/ic_settings.xml b/core/res/res/drawable-watch/ic_settings.xml
new file mode 100644
index 0000000..cef10e9
--- /dev/null
+++ b/core/res/res/drawable-watch/ic_settings.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" android:tint="?attr/colorControlNormal">
+<path android:fillColor="@android:color/white" android:pathData="M428.46,888.13Q400.26,888.13 379.92,869.53Q359.59,850.93 355.59,823.74L346.59,757.5Q335.5,753.22 325.55,747.17Q315.61,741.13 306.04,734.33L244.28,760.33Q218.33,771.57 192.13,762.45Q165.93,753.33 151.46,729.13L99.91,638.76Q85.43,614.8 91.55,587.73Q97.67,560.65 119.63,543.17L172.39,503.17Q171.63,497.13 171.63,491.59Q171.63,486.04 171.63,480Q171.63,473.96 171.63,468.41Q171.63,462.87 172.39,456.83L119.63,417.07Q97.43,399.59 91.43,372.51Q85.43,345.43 99.91,321.24L151.46,231.11Q165.93,207.15 192.01,197.91Q218.09,188.67 244.04,199.91L306.52,225.91Q316.09,219.11 326.17,213.18Q336.26,207.26 346.59,202.98L355.59,136.5Q359.59,109.07 379.92,90.47Q400.26,71.87 428.46,71.87L531.54,71.87Q559.74,71.87 580.08,90.47Q600.41,109.07 604.41,136.5L613.41,202.98Q624.5,207.26 634.45,213.18Q644.39,219.11 653.96,225.91L715.72,199.91Q741.67,188.67 767.87,197.91Q794.07,207.15 808.54,231.11L860.09,321.24Q874.57,345.43 868.57,372.51Q862.57,399.59 840.37,417.07L787.37,456.83Q788.13,462.87 788.13,468.41Q788.13,473.96 788.13,480Q788.13,486.04 788.01,491.59Q787.89,497.13 786.37,503.17L839.37,542.93Q861.57,560.41 867.57,587.49Q873.57,614.57 859.09,638.76L806.54,729.13Q792.07,753.09 765.99,762.33Q739.91,771.57 713.96,760.33L653.48,734.33Q643.91,741.13 633.83,747.17Q623.74,753.22 613.41,757.5L604.41,823.74Q600.41,850.93 580.08,869.53Q559.74,888.13 531.54,888.13L428.46,888.13ZM481.28,620Q539.28,620 580.28,579Q621.28,538 621.28,480Q621.28,422 580.28,381Q539.28,340 481.28,340Q422.52,340 381.9,381Q341.28,422 341.28,480Q341.28,538 381.9,579Q422.52,620 481.28,620Z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/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-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index ef420eb..3fac736 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -1943,8 +1943,7 @@
     <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Off"</string>
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
-    <!-- no translation found for zen_mode_trigger_summary_range_words (7228261413029290750) -->
-    <skip />
+    <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> to <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Any calendar"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> is muting some sounds"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
@@ -2426,14 +2425,10 @@
     <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Send and receive messages without a mobile or Wi-Fi network"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string>
-    <!-- no translation found for satellite_manual_selection_state_popup_title (8545991934926661974) -->
-    <skip />
-    <!-- no translation found for satellite_manual_selection_state_popup_message (1928101658551382450) -->
-    <skip />
-    <!-- no translation found for satellite_manual_selection_state_popup_ok (2459664752624985095) -->
-    <skip />
-    <!-- no translation found for satellite_manual_selection_state_popup_cancel (973605633339469252) -->
-    <skip />
+    <string name="satellite_manual_selection_state_popup_title" msgid="8545991934926661974">"Turn on \"Automatically select network\""</string>
+    <string name="satellite_manual_selection_state_popup_message" msgid="1928101658551382450">"Turn on \"Automatically select network\" in Settings so your phone can find a network that works with satellite"</string>
+    <string name="satellite_manual_selection_state_popup_ok" msgid="2459664752624985095">"Turn on"</string>
+    <string name="satellite_manual_selection_state_popup_cancel" msgid="973605633339469252">"Go back"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending..."</string>
     <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Set up Fingerprint Unlock again"</string>
     <string name="fingerprint_dangling_notification_msg_1" msgid="5851784577768803510">"<xliff:g id="FINGERPRINT">%s</xliff:g> can no longer be recognized."</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 051c61a..4070f11 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1944,8 +1944,7 @@
     <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Off"</string>
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
-    <!-- no translation found for zen_mode_trigger_summary_range_words (7228261413029290750) -->
-    <skip />
+    <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"Da <xliff:g id="START">%1$s</xliff:g> a <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Qualsiasi calendario"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> sta disattivando alcuni suoni"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Si è verificato un problema interno con il dispositivo, che potrebbe essere instabile fino al ripristino dei dati di fabbrica."</string>
@@ -2427,14 +2426,10 @@
     <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Invia e ricevi messaggi senza una rete mobile o Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Apri Messaggi"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Come funziona"</string>
-    <!-- no translation found for satellite_manual_selection_state_popup_title (8545991934926661974) -->
-    <skip />
-    <!-- no translation found for satellite_manual_selection_state_popup_message (1928101658551382450) -->
-    <skip />
-    <!-- no translation found for satellite_manual_selection_state_popup_ok (2459664752624985095) -->
-    <skip />
-    <!-- no translation found for satellite_manual_selection_state_popup_cancel (973605633339469252) -->
-    <skip />
+    <string name="satellite_manual_selection_state_popup_title" msgid="8545991934926661974">"Attiva \"Seleziona rete automaticamente\""</string>
+    <string name="satellite_manual_selection_state_popup_message" msgid="1928101658551382450">"Attiva \"Seleziona rete automaticamente\" nelle Impostazioni in modo che lo smartphone possa trovare una rete compatibile con il satellite"</string>
+    <string name="satellite_manual_selection_state_popup_ok" msgid="2459664752624985095">"Attiva"</string>
+    <string name="satellite_manual_selection_state_popup_cancel" msgid="973605633339469252">"Indietro"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"In attesa…"</string>
     <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Riconfigura lo Sblocco con l\'Impronta"</string>
     <string name="fingerprint_dangling_notification_msg_1" msgid="5851784577768803510">"<xliff:g id="FINGERPRINT">%s</xliff:g> non può più essere riconosciuto."</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 2fea962..5684d78 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -590,7 +590,7 @@
     <string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"तपाईँको फोन मात्र होइन, मल्टिकास्ट ठेगानाहरूको प्रयोग गरे Wi-Fi नेटवर्कका सबै उपकरणहरूमा पठाइएका प्याकेटहरू प्राप्त गर्न एपलाई अनुमति दिन्छ। यसले गैर-मल्टिकास्ट मोडभन्दा बढी उर्जा प्रयोग गर्छ।"</string>
     <string name="permlab_bluetoothAdmin" msgid="6490373569441946064">"ब्लुटुथ सेटिङहरूमा पहुँच गर्नुहोस्"</string>
     <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"स्थानीय ब्लुटुथ ट्याब्लेटलाई कन्फिगर गर्नको लागि र टाढाका उपकरणहरूलाई पत्ता लगाउन र जोड्नको लागि एपलाई अनुमति दिन्छ।"</string>
-    <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"एपलाई तपाईंको Android टिभी डिभाइसको ब्लुटुथ कन्फिगर गर्ने तथा टाढा रहेका यन्त्रहरू पत्ता लगाई ती यन्त्रहरूसँग जोडा बनाउने अनुमति दिन्छ।"</string>
+    <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"एपलाई तपाईंको Android टिभी डिभाइसको ब्लुटुथ कन्फिगर गर्ने तथा टाढा रहेका यन्त्रहरू पत्ता लगाई ती यन्त्रहरूसँग कनेक्ट गर्ने अनुमति दिन्छ।"</string>
     <string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"एपलाई स्थानीय ब्लुटुथ फोन कन्फिगर गर्न र टाढाका उपकरणहरूसँग खोज गर्न र जोडी गर्न अनुमति दिन्छ।"</string>
     <string name="permlab_accessWimaxState" msgid="7029563339012437434">"WiMAXसँग जोड्नुहोस् वा छुटाउनुहोस्"</string>
     <string name="permdesc_accessWimaxState" msgid="5372734776802067708">"एपलाई वाइम्याक्स सक्षम छ कि छैन र जडान भएको कुनै पनि वाइम्याक्स नेटवर्कहरूको बारेमा जानकारी निर्धारिण गर्न अनुमति दिन्छ।"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 388c5a9..86e0fcd 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1944,8 +1944,7 @@
     <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Desativada"</string>
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
-    <!-- no translation found for zen_mode_trigger_summary_range_words (7228261413029290750) -->
-    <skip />
+    <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Qualquer calendário"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> está a desativar alguns sons."</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Existe um problema interno no seu dispositivo e pode ficar instável até efetuar uma reposição de dados de fábrica."</string>
@@ -2427,14 +2426,10 @@
     <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Envie e receba mensagens sem uma rede móvel ou Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abre a app Mensagens"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona"</string>
-    <!-- no translation found for satellite_manual_selection_state_popup_title (8545991934926661974) -->
-    <skip />
-    <!-- no translation found for satellite_manual_selection_state_popup_message (1928101658551382450) -->
-    <skip />
-    <!-- no translation found for satellite_manual_selection_state_popup_ok (2459664752624985095) -->
-    <skip />
-    <!-- no translation found for satellite_manual_selection_state_popup_cancel (973605633339469252) -->
-    <skip />
+    <string name="satellite_manual_selection_state_popup_title" msgid="8545991934926661974">"Ative a opção \"Selecionar rede automaticamente\""</string>
+    <string name="satellite_manual_selection_state_popup_message" msgid="1928101658551382450">"Ative a opção \"Selecionar rede automaticamente\" nas Definições para que o telemóvel possa encontrar uma rede que funcione com o satélite"</string>
+    <string name="satellite_manual_selection_state_popup_ok" msgid="2459664752624985095">"Ativar"</string>
+    <string name="satellite_manual_selection_state_popup_cancel" msgid="973605633339469252">"Retroceder"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendente…"</string>
     <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Configure o Desbloqueio por impressão digital novamente"</string>
     <string name="fingerprint_dangling_notification_msg_1" msgid="5851784577768803510">"Já não é possível reconhecer <xliff:g id="FINGERPRINT">%s</xliff:g>."</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 492dca7..1d9e00c 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -1937,7 +1937,7 @@
     <string name="zen_mode_default_weeknights_name" msgid="7902108149994062847">"వారపు రోజుల్లో రాత్రి"</string>
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"వారాంతం"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ఈవెంట్"</string>
-    <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"నిద్ర"</string>
+    <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"స్లీప్ మోడ్"</string>
     <string name="zen_mode_implicit_trigger_description" msgid="5714956693073007111">"<xliff:g id="APP_NAME">%1$s</xliff:g> ద్వారా మేనేజ్ చేయబడుతోంది"</string>
     <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ఆన్‌లో ఉంది"</string>
     <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ఆఫ్‌లో ఉంది"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index d750ff6..e6dedce 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3486,6 +3486,12 @@
              representation this attribute can be used for providing such. -->
         <attr name="contentDescription" format="string" localization="suggested" />
 
+        <!-- Provides brief supplemental information for the view, such as the purpose of
+             the view when that purpose is not conveyed within its textual representation.
+             This property is used primarily for accessibility. -->
+        <!-- @FlaggedApi("android.view.accessibility.supplemental_description") -->
+        <attr name="supplementalDescription" format="string" localization="suggested" />
+
         <!-- Sets the id of a view that screen readers are requested to visit after this view.
              Requests that a screen-reader visits the content of this view before the content of the
              one it precedes. This does nothing if either view is not important for accessibility.
@@ -4412,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)
@@ -9238,6 +9248,8 @@
             <flag name="home_screen" value="0x1" />
             <flag name="keyguard" value="0x2" />
             <flag name="searchbox" value="0x4" />
+            <!-- @FlaggedApi("android.appwidget.flags.not_keyguard_category") -->
+            <flag name="not_keyguard" value="0x8" />
         </attr>
         <!-- Flags indicating various features supported by the widget. These are hints to the
          widget host, and do not actually change the behavior of the widget. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 0be33c2..4d73f22 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1876,6 +1876,34 @@
          @hide @SystemApi @FlaggedApi("android.content.res.manifest_flagging") -->
     <attr name="featureFlag" format="string" />
 
+    <!-- This attribute provides a way to fine-tune how incoming intents are matched to application
+    components. By default, no special matching rules are applied. This attribute can be specified
+    on the {@code <application>} tag as well as at the component tags such as {@code <activity>},
+    {@code <activity-alias>}, {@code <receiver>}, {@code <service>}, {@code <provider>} and the
+    attribute on the component can be used to override what's on the {@code <application>} tag. -->
+    <attr name="intentMatchingFlags">
+        <!-- Disables all special matching rules for incoming intents. When specifying multiple
+        flags, conflicting values are resolved by giving precedence to the "none" flag. -->
+        <flag name="none" value="0x0001" />
+
+        <!-- Enforces stricter matching for incoming intents:
+        <ul>
+             <li>Explicit intents should match the target component's intent filter
+             <li>Intents without an action should not match any intent filter
+        </ul>
+        -->
+        <flag name="enforceIntentFilter" value="0x0002" />
+
+        <!-- Relaxes the matching rules to allow intents without a action to match. This flag to be
+        used in conjunction with enforceIntentFilter to achieve a specific behavior:
+        <ul>
+            <li>Explicit intents should match the target component's intent filter
+            <li>Intents without an action are allowed to match any intent filter
+        </ul>
+        -->
+        <flag name="allowNullAction" value="0x0004" />
+    </attr>
+
     <!-- The <code>manifest</code> tag is the root of an
          <code>AndroidManifest.xml</code> file,
          describing the contents of an Android package (.apk) file.  One
@@ -2243,6 +2271,8 @@
              {@link android.window.OnBackInvokedCallback#onBackInvoked
              OnBackInvokedCallback.onBackInvoked()} on the focused window. -->
         <attr name="enableOnBackInvokedCallback" format="boolean"/>
+
+        <attr name="intentMatchingFlags"/>
     </declare-styleable>
 
     <!-- An attribution is a logical part of an app and is identified by a tag.
@@ -2930,6 +2960,7 @@
              contained here. -->
         <attr name="attributionTags" />
         <attr name="systemUserOnly" format="boolean" />
+        <attr name="intentMatchingFlags"/>
     </declare-styleable>
 
     <!-- Attributes that can be supplied in an AndroidManifest.xml
@@ -3089,6 +3120,7 @@
         -->
         <attr name="allowSharedIsolatedProcess" format="boolean" />
         <attr name="systemUserOnly" format="boolean" />
+        <attr name="intentMatchingFlags"/>
     </declare-styleable>
 
     <!-- @hide The <code>apex-system-service</code> tag declares an apex system service
@@ -3156,6 +3188,7 @@
              Context.createAttributionContext() using the first attribution tag
              contained here. -->
         <attr name="attributionTags" />
+        <attr name="intentMatchingFlags"/>
     </declare-styleable>
 
     <!-- The <code>activity</code> tag declares an
@@ -3350,6 +3383,7 @@
                  URIs. -->
             <enum name="readAndWrite" value="4" />
         </attr>
+        <attr name="intentMatchingFlags"/>
     </declare-styleable>
 
     <!-- The <code>activity-alias</code> tag declares a new
@@ -3392,6 +3426,7 @@
         <attr name="attributionTags" />
         <attr name="allowUntrustedActivityEmbedding" />
         <attr name="knownActivityEmbeddingCerts" />
+        <attr name="intentMatchingFlags"/>
     </declare-styleable>
 
     <!-- The <code>meta-data</code> tag is used to attach additional
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ac9bb93..7402a2f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3087,6 +3087,12 @@
     <!-- Whether UI for multi user should be shown -->
     <bool name="config_enableMultiUserUI">false</bool>
 
+    <!-- Indicates the boot strategy in Headless System User Mode (HSUM)
+         This config has no effect if the device is not in HSUM.
+         0 (Default) : boot to the previous foreground user if there is one, otherwise the first switchable user.
+         1 : boot to the first switchable full user for initial boot (unprovisioned device), else to the headless system user, i.e. user 0. -->
+    <integer name="config_hsumBootStrategy">0</integer>
+
     <!-- Whether to boot system with the headless system user, i.e. user 0. If set to true,
          system will be booted with the headless system user, or user 0. It has no effect if device
          is not in Headless System User Mode (HSUM). -->
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 779422a..31e9913 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -472,10 +472,13 @@
     <integer name="config_mt_sms_polling_throttle_millis">300000</integer>
     <java-symbol type="integer" name="config_mt_sms_polling_throttle_millis" />
 
-
     <!-- The receiver class of the intent that hidden menu sends to start satellite non-emergency mode -->
     <string name="config_satellite_carrier_roaming_non_emergency_session_class" translatable="false"></string>
     <java-symbol type="string" name="config_satellite_carrier_roaming_non_emergency_session_class" />
 
+    <!-- Whether to show the system notification to users whenever there is a change
+     in the satellite availability state at the current location. -->
+    <bool name="config_satellite_should_notify_availability">false</bool>
+    <java-symbol type="bool" name="config_satellite_should_notify_availability" />
 
 </resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 8121545..70cc5f1 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -121,6 +121,12 @@
     <!-- @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled")
          @hide @SystemApi -->
     <public name="backgroundPermission"/>
+    <!-- @FlaggedApi(android.view.accessibility.supplemental_description) -->
+    <public name="supplementalDescription"/>
+    <!-- @FlaggedApi("android.security.enable_intent_matching_flags") -->
+    <public name="intentMatchingFlags"/>
+    <!-- @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API) -->
+    <public name="layoutLabel"/>
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01b60000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d3ef07ca..c13fdb1 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1766,6 +1766,11 @@
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=140]-->
     <string name="permdesc_nearby_wifi_devices">Allows the app to advertise, connect, and determine the relative position of nearby Wi\u2011Fi devices</string>
 
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=50]-->
+    <string name="permlab_ranging">determine relative position between nearby devices</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=120]-->
+    <string name="permdesc_ranging">Allow the app to determine relative position between nearby devices</string>
+
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_preferredPaymentInfo">Preferred NFC Payment Service Information</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -6522,6 +6527,54 @@
     <string name="satellite_manual_selection_state_popup_cancel">Go back</string>
     <!-- Initial/System provided label shown for an app which gets unarchived. [CHAR LIMIT=64]. -->
     <string name="unarchival_session_app_label">Pending...</string>
+    <!-- Notification title when satellite service is available. -->
+    <string name="satellite_sos_available_notification_title">Satellite SOS is now available</string>
+    <!-- Notification summary when satellite service is available. [CHAR LIMIT=NONE] -->
+    <string name="satellite_sos_available_notification_summary">You can message emergency services if there\'s no mobile or Wi-Fi network. Google Messages must be your default messaging app.</string>
+    <!-- Notification title when satellite service is not supported by device. -->
+    <string name="satellite_sos_not_supported_notification_title">Satellite SOS isn\'t supported</string>
+    <!-- Notification summary when satellite service is not supported by device. [CHAR LIMIT=NONE] -->
+    <string name="satellite_sos_not_supported_notification_summary">Satellite SOS isn\'t supported on this device</string>
+    <!-- Notification title when satellite service is not provisioned. [CHAR LIMIT=NONE] -->
+    <string name="satellite_sos_not_provisioned_notification_title">Satellite SOS isn\'t set up</string>
+    <!-- Notification summary when satellite service is not provisioned. [CHAR LIMIT=NONE] -->
+    <string name="satellite_sos_not_provisioned_notification_summary">Make sure you\'re connected to the internet and try setup again</string>
+    <!-- Notification title when satellite service is not allowed at current location. -->
+    <string name="satellite_sos_not_in_allowed_region_notification_title">Satellite SOS isn\'t available</string>
+    <!-- Notification summary when satellite service is not allowed at current location. [CHAR LIMIT=NONE] -->
+    <string name="satellite_sos_not_in_allowed_region_notification_summary">Satellite SOS isn\'t available in this country or region</string>
+    <!-- Notification title when default messaging app does not support satellite. [CHAR LIMIT=NONE] -->
+    <string name="satellite_sos_unsupported_default_sms_app_notification_title">Satellite SOS not set up</string>
+    <!-- Notification summary when default messaging app does not support satellite. [CHAR LIMIT=NONE] -->
+    <string name="satellite_sos_unsupported_default_sms_app_notification_summary">To message by satellite, set Google Messages as your default messaging app</string>
+    <!-- Notification title when location settings is disabled. -->
+    <string name="satellite_sos_location_disabled_notification_title">Satellite SOS isn\'t available</string>
+    <!-- Notification summary when location settings is disabled. [CHAR LIMIT=NONE] -->
+    <string name="satellite_sos_location_disabled_notification_summary">To check if satellite SOS is available in this country or region, turn on location settings</string>
+    <!-- Notification title when satellite service is available. -->
+    <string name="satellite_messaging_available_notification_title">Satellite messaging available</string>
+    <!-- Notification summary when satellite service is available. [CHAR LIMIT=NONE] -->
+    <string name="satellite_messaging_available_notification_summary">You can message by satellite if there\'s no mobile or Wi-Fi network. Google Messages must be your default messaging app.</string>
+    <!-- Notification title when satellite service is not supported by device. -->
+    <string name="satellite_messaging_not_supported_notification_title">Satellite messaging not supported</string>
+    <!-- Notification summary when satellite service is not supported by device. [CHAR LIMIT=NONE] -->
+    <string name="satellite_messaging_not_supported_notification_summary">Satellite messaging isn\'t supported on this device</string>
+    <!-- Notification title when satellite service is not provisioned. [CHAR LIMIT=NONE] -->
+    <string name="satellite_messaging_not_provisioned_notification_title">Satellite messaging not set up</string>
+    <!-- Notification summary when satellite service is not provisioned. [CHAR LIMIT=NONE] -->
+    <string name="satellite_messaging_not_provisioned_notification_summary">Make sure you\'re connected to the internet and try setup again</string>
+    <!-- Notification title when satellite service is not allowed at current location. -->
+    <string name="satellite_messaging_not_in_allowed_region_notification_title">Satellite messaging not available</string>
+    <!-- Notification summary when satellite service is not allowed at current location. [CHAR LIMIT=NONE] -->
+    <string name="satellite_messaging_not_in_allowed_region_notification_summary">Satellite messaging isn\'t available in this country or region</string>
+    <!-- Notification title when default messaging app does not support satellite. [CHAR LIMIT=NONE] -->
+    <string name="satellite_messaging_unsupported_default_sms_app_notification_title">Satellite messaging not set up</string>
+    <!-- Notification summary when default messaging app does not support satellite. [CHAR LIMIT=NONE] -->
+    <string name="satellite_messaging_unsupported_default_sms_app_notification_summary">To message by satellite, set Google Messages as your default messaging app</string>
+    <!-- Notification title when location settings is disabled. -->
+    <string name="satellite_messaging_location_disabled_notification_title">Satellite messaging not available</string>
+    <!-- Notification summary when location settings is disabled. [CHAR LIMIT=NONE] -->
+    <string name="satellite_messaging_location_disabled_notification_summary">To check if satellite messaging is available in this country or region, turn on location settings</string>
 
     <!-- Fingerprint dangling notification title -->
     <string name="fingerprint_dangling_notification_title">Set up Fingerprint Unlock again</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 515ebd5..aa08d5e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -312,6 +312,7 @@
   <java-symbol type="bool" name="config_disableLockscreenByDefault" />
   <java-symbol type="bool" name="config_enableBurnInProtection" />
   <java-symbol type="bool" name="config_hotswapCapable" />
+  <java-symbol type="integer" name="config_hsumBootStrategy" />
   <java-symbol type="bool" name="config_mms_content_disposition_support" />
   <java-symbol type="bool" name="config_networkSamplingWakesDevice" />
   <java-symbol type="bool" name="config_showMenuShortcutsWhenKeyboardPresent" />
@@ -5551,6 +5552,30 @@
   <java-symbol type="string" name="satellite_manual_selection_state_popup_cancel" />
   <java-symbol type="drawable" name="ic_satellite_alt_24px" />
   <java-symbol type="drawable" name="ic_android_satellite_24px" />
+  <java-symbol type="string" name="satellite_sos_available_notification_title" />
+  <java-symbol type="string" name="satellite_sos_available_notification_summary" />
+  <java-symbol type="string" name="satellite_sos_not_in_allowed_region_notification_title" />
+  <java-symbol type="string" name="satellite_sos_not_in_allowed_region_notification_summary" />
+  <java-symbol type="string" name="satellite_sos_not_supported_notification_title" />
+  <java-symbol type="string" name="satellite_sos_not_supported_notification_summary" />
+  <java-symbol type="string" name="satellite_sos_not_provisioned_notification_title" />
+  <java-symbol type="string" name="satellite_sos_not_provisioned_notification_summary" />
+  <java-symbol type="string" name="satellite_sos_unsupported_default_sms_app_notification_title" />
+  <java-symbol type="string" name="satellite_sos_unsupported_default_sms_app_notification_summary" />
+  <java-symbol type="string" name="satellite_sos_location_disabled_notification_title" />
+  <java-symbol type="string" name="satellite_sos_location_disabled_notification_summary" />
+  <java-symbol type="string" name="satellite_messaging_available_notification_title" />
+  <java-symbol type="string" name="satellite_messaging_available_notification_summary" />
+  <java-symbol type="string" name="satellite_messaging_not_in_allowed_region_notification_title" />
+  <java-symbol type="string" name="satellite_messaging_not_in_allowed_region_notification_summary" />
+  <java-symbol type="string" name="satellite_messaging_not_supported_notification_title" />
+  <java-symbol type="string" name="satellite_messaging_not_supported_notification_summary" />
+  <java-symbol type="string" name="satellite_messaging_not_provisioned_notification_title" />
+  <java-symbol type="string" name="satellite_messaging_not_provisioned_notification_summary" />
+  <java-symbol type="string" name="satellite_messaging_unsupported_default_sms_app_notification_title" />
+  <java-symbol type="string" name="satellite_messaging_unsupported_default_sms_app_notification_summary" />
+  <java-symbol type="string" name="satellite_messaging_location_disabled_notification_title" />
+  <java-symbol type="string" name="satellite_messaging_location_disabled_notification_summary" />
 
   <!-- DisplayManager configs. -->
   <java-symbol type="bool" name="config_evenDimmerEnabled" />
@@ -5622,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/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index c2d8f91..da1fffa 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -17,6 +17,9 @@
 package android.app;
 
 import static android.app.PropertyInvalidatedCache.NONCE_UNSET;
+import static android.app.PropertyInvalidatedCache.MODULE_BLUETOOTH;
+import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM;
+import static android.app.PropertyInvalidatedCache.MODULE_TEST;
 import static android.app.PropertyInvalidatedCache.NonceStore.INVALID_NONCE_INDEX;
 import static com.android.internal.os.Flags.FLAG_APPLICATION_SHARED_MEMORY_ENABLED;
 
@@ -27,6 +30,8 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.app.PropertyInvalidatedCache.Args;
+import android.annotation.SuppressLint;
 import com.android.internal.os.ApplicationSharedMemory;
 
 import android.platform.test.annotations.IgnoreUnderRavenwood;
@@ -57,7 +62,7 @@
             DeviceFlagsValueProvider.createCheckFlagsRule();
 
     // Configuration for creating caches
-    private static final String MODULE = PropertyInvalidatedCache.MODULE_TEST;
+    private static final String MODULE = MODULE_TEST;
     private static final String API = "testApi";
 
     // This class is a proxy for binder calls.  It contains a counter that increments
@@ -245,6 +250,12 @@
             mQuery = query;
         }
 
+        // Create a cache from the args.  The name of the cache is the api.
+        TestCache(Args args, TestQuery query) {
+            super(args, args.mApi(), query);
+            mQuery = query;
+        }
+
         public int getRecomputeCount() {
             return mQuery.getRecomputeCount();
         }
@@ -374,14 +385,11 @@
     @Test
     public void testPropertyNames() {
         String n1;
-        n1 = PropertyInvalidatedCache.createPropertyName(
-            PropertyInvalidatedCache.MODULE_SYSTEM, "getPackageInfo");
+        n1 = PropertyInvalidatedCache.createPropertyName(MODULE_SYSTEM, "getPackageInfo");
         assertEquals(n1, "cache_key.system_server.get_package_info");
-        n1 = PropertyInvalidatedCache.createPropertyName(
-            PropertyInvalidatedCache.MODULE_SYSTEM, "get_package_info");
+        n1 = PropertyInvalidatedCache.createPropertyName(MODULE_SYSTEM, "get_package_info");
         assertEquals(n1, "cache_key.system_server.get_package_info");
-        n1 = PropertyInvalidatedCache.createPropertyName(
-            PropertyInvalidatedCache.MODULE_BLUETOOTH, "getState");
+        n1 = PropertyInvalidatedCache.createPropertyName(MODULE_BLUETOOTH, "getState");
         assertEquals(n1, "cache_key.bluetooth.get_state");
     }
 
@@ -391,7 +399,7 @@
             reason = "SystemProperties doesn't have permission check")
     public void testPermissionFailure() {
         // Create a cache that will write a system nonce.
-        TestCache sysCache = new TestCache(PropertyInvalidatedCache.MODULE_SYSTEM, "mode1");
+        TestCache sysCache = new TestCache(MODULE_SYSTEM, "mode1");
         try {
             // Invalidate the cache, which writes the system property.  There must be a permission
             // failure.
@@ -407,7 +415,7 @@
     @Test
     public void testTestMode() {
         // Create a cache that will write a system nonce.
-        TestCache sysCache = new TestCache(PropertyInvalidatedCache.MODULE_SYSTEM, "mode1");
+        TestCache sysCache = new TestCache(MODULE_SYSTEM, "mode1");
 
         sysCache.testPropertyName();
         // Invalidate the cache.  This must succeed because the property has been marked for
@@ -416,7 +424,7 @@
 
         // Create a cache that uses MODULE_TEST.  Invalidation succeeds whether or not the
         // property is tagged as being tested.
-        TestCache testCache = new TestCache(PropertyInvalidatedCache.MODULE_TEST, "mode2");
+        TestCache testCache = new TestCache(MODULE_TEST, "mode2");
         testCache.invalidateCache();
         testCache.testPropertyName();
         testCache.invalidateCache();
@@ -432,7 +440,7 @@
             // The expected exception.
         }
         // Configuring a property for testing must fail if test mode is false.
-        TestCache cache2 = new TestCache(PropertyInvalidatedCache.MODULE_SYSTEM, "mode3");
+        TestCache cache2 = new TestCache(MODULE_SYSTEM, "mode3");
         try {
             cache2.testPropertyName();
             fail("expected an IllegalStateException");
@@ -444,6 +452,34 @@
         PropertyInvalidatedCache.setTestMode(true);
     }
 
+    // Test the Args-style constructor.
+    @Test
+    public void testArgsConstructor() {
+        // Create a cache with a maximum of four entries.
+        TestCache cache = new TestCache(new Args(MODULE_TEST).api("init1").maxEntries(4),
+                new TestQuery());
+
+        cache.invalidateCache();
+        for (int i = 1; i <= 4; i++) {
+            assertEquals("foo" + i, cache.query(i));
+            assertEquals(i, cache.getRecomputeCount());
+        }
+        // Everything is in the cache.  The recompute count must not increase.
+        for (int i = 1; i <= 4; i++) {
+            assertEquals("foo" + i, cache.query(i));
+            assertEquals(4, cache.getRecomputeCount());
+        }
+        // Overflow the max entries.  The recompute count increases by one.
+        assertEquals("foo5", cache.query(5));
+        assertEquals(5, cache.getRecomputeCount());
+        // The oldest entry (1) has been evicted.  Iterating through the first four entries will
+        // sequentially evict them all because the loop is proceeding oldest to newest.
+        for (int i = 1; i <= 4; i++) {
+            assertEquals("foo" + i, cache.query(i));
+            assertEquals(5+i, cache.getRecomputeCount());
+        }
+    }
+
     // Verify the behavior of shared memory nonce storage.  This does not directly test the cache
     // storing nonces in shared memory.
     @RequiresFlagsEnabled(FLAG_APPLICATION_SHARED_MEMORY_ENABLED)
@@ -495,4 +531,43 @@
 
         shmem.close();
     }
+
+    // Verify that an invalid module causes an exception.
+    private void testInvalidModule(String module) {
+        try {
+            @SuppressLint("UnusedVariable")
+            Args arg = new Args(module);
+            fail("expected an invalid module exception: module=" + module);
+        } catch (IllegalArgumentException e) {
+            // Expected exception.
+        }
+    }
+
+    // Test various instantiation errors.  The good path is tested in other methods.
+    @Test
+    public void testArgumentErrors() {
+        // Verify that an illegal module throws an exception.
+        testInvalidModule(MODULE_SYSTEM.substring(0, MODULE_SYSTEM.length() - 1));
+        testInvalidModule(MODULE_SYSTEM + "x");
+        testInvalidModule("mymodule");
+
+        // Verify that a negative max entries throws.
+        Args arg = new Args(MODULE_SYSTEM);
+        try {
+            arg.maxEntries(0);
+            fail("expected an invalid maxEntries exception");
+        } catch (IllegalArgumentException e) {
+            // Expected exception.
+        }
+
+        // Verify that creating a cache with an invalid property string throws.
+        try {
+            final String badKey = "cache_key.volume_list";
+            @SuppressLint("UnusedVariable")
+            var cache = new PropertyInvalidatedCache<Integer, Void>(4, badKey);
+            fail("expected bad property exception: prop=" + badKey);
+        } catch (IllegalArgumentException e) {
+            // Expected exception.
+        }
+    }
 }
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/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 930e03d..3b0eab4 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -46,7 +46,7 @@
     // The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
     // See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
     // and assertAccessibilityNodeInfoCleared in that class.
-    private static final int NUM_MARSHALLED_PROPERTIES = 46;
+    private static final int NUM_MARSHALLED_PROPERTIES = 47;
 
     /**
      * The number of properties that are purposely not marshalled
@@ -58,7 +58,7 @@
 
     // The number of flags held in boolean properties. Their values should also be double-checked
     // in the methods above.
-    private static final int NUM_BOOLEAN_PROPERTIES = 27;
+    private static final int NUM_BOOLEAN_PROPERTIES = 28;
 
     @Test
     public void testStandardActions_serializationFlagIsValid() {
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
index 120a4de..3239598 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
@@ -301,18 +301,14 @@
                         {1_000_000, 2_000_000, 3_000_000},
                         {4_000_000, 5_000_000}});
 
-        LongArrayMultiStateCounter.LongArrayContainer array =
-                new LongArrayMultiStateCounter.LongArrayContainer(5);
+
         long[] out = new long[5];
 
-        success = mInjector.addDelta(TEST_UID, counter, 2000, array);
+        success = mInjector.addDelta(TEST_UID, counter, 2000, out);
         assertThat(success).isTrue();
-
-        array.getValues(out);
         assertThat(out).isEqualTo(new long[]{1, 2, 3, 4, 5});
 
-        counter.getCounts(array, 0);
-        array.getValues(out);
+        counter.getCounts(out, 0);
         assertThat(out).isEqualTo(new long[]{1, 2, 3, 4, 5});
 
         counter.setState(1, 3000);
@@ -322,18 +318,14 @@
                         {11_000_000, 22_000_000, 33_000_000},
                         {44_000_000, 55_000_000}});
 
-        success = mInjector.addDelta(TEST_UID, counter, 4000, array);
+        success = mInjector.addDelta(TEST_UID, counter, 4000, out);
         assertThat(success).isTrue();
-
-        array.getValues(out);
         assertThat(out).isEqualTo(new long[]{10, 20, 30, 40, 50});
 
-        counter.getCounts(array, 0);
-        array.getValues(out);
+        counter.getCounts(out, 0);
         assertThat(out).isEqualTo(new long[]{1 + 5, 2 + 10, 3 + 15, 4 + 20, 5 + 25});
 
-        counter.getCounts(array, 1);
-        array.getValues(out);
+        counter.getCounts(out, 1);
         assertThat(out).isEqualTo(new long[]{5, 10, 15, 20, 25});
     }
 
@@ -385,7 +377,7 @@
 
         @Override
         public boolean addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs,
-                LongArrayMultiStateCounter.LongArrayContainer deltaOut) {
+                long[] deltaOut) {
             return addDeltaForTest(uid, counter, timestampMs, mCpuTimeInStatePerClusterNs,
                     deltaOut);
         }
diff --git a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
index b86dc58..7e5d0a4 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
@@ -24,14 +24,11 @@
 import android.os.Parcel;
 import android.platform.test.ravenwood.RavenwoodRule;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.runner.RunWith;
 
-@RunWith(AndroidJUnit4.class)
 @SmallTest
 public class LongArrayMultiStateCounterTest {
     @Rule
@@ -41,11 +38,11 @@
     public void setStateAndUpdateValue() {
         LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
 
-        updateValue(counter, new long[]{0, 0, 0, 0}, 1000);
+        counter.updateValues(new long[]{0, 0, 0, 0}, 1000);
         counter.setState(0, 1000);
         counter.setState(1, 2000);
         counter.setState(0, 4000);
-        updateValue(counter, new long[]{100, 200, 300, 400}, 9000);
+        counter.updateValues(new long[]{100, 200, 300, 400}, 9000);
 
         assertCounts(counter, 0, new long[]{75, 150, 225, 300});
         assertCounts(counter, 1, new long[]{25, 50, 75, 100});
@@ -55,15 +52,28 @@
     }
 
     @Test
+    public void increment() {
+        LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
+
+        counter.updateValues(new long[]{0, 0, 0, 0}, 1000);
+        counter.setState(0, 1000);
+        counter.incrementValues(new long[]{1, 2, 3, 4}, 2000);
+        counter.incrementValues(new long[]{100, 200, 300, 400}, 3000);
+
+        assertCounts(counter, 0, new long[]{101, 202, 303, 404});
+        assertCounts(counter, 1, new long[]{0, 0, 0, 0});
+    }
+
+    @Test
     public void copyStatesFrom() {
         LongArrayMultiStateCounter source = new LongArrayMultiStateCounter(2, 1);
-        updateValue(source, new long[]{0}, 1000);
+        source.updateValues(new long[]{0}, 1000);
         source.setState(0, 1000);
         source.setState(1, 2000);
 
         LongArrayMultiStateCounter target = new LongArrayMultiStateCounter(2, 1);
         target.copyStatesFrom(source);
-        updateValue(target, new long[]{1000}, 5000);
+        target.updateValues(new long[]{1000}, 5000);
 
         assertCounts(target, 0, new long[]{250});
         assertCounts(target, 1, new long[]{750});
@@ -83,25 +93,25 @@
     public void setEnabled() {
         LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
         counter.setState(0, 1000);
-        updateValue(counter, new long[]{0, 0, 0, 0}, 1000);
-        updateValue(counter, new long[]{100, 200, 300, 400}, 2000);
+        counter.updateValues(new long[]{0, 0, 0, 0}, 1000);
+        counter.updateValues(new long[]{100, 200, 300, 400}, 2000);
 
         assertCounts(counter, 0, new long[]{100, 200, 300, 400});
 
         counter.setEnabled(false, 3000);
 
         // Partially included, because the counter is disabled after the previous update
-        updateValue(counter, new long[]{200, 300, 400, 500}, 4000);
+        counter.updateValues(new long[]{200, 300, 400, 500}, 4000);
 
         // Count only 50%, because the counter was disabled for 50% of the time
         assertCounts(counter, 0, new long[]{150, 250, 350, 450});
 
         // Not counted because the counter is disabled
-        updateValue(counter, new long[]{250, 350, 450, 550}, 5000);
+        counter.updateValues(new long[]{250, 350, 450, 550}, 5000);
 
         counter.setEnabled(true, 6000);
 
-        updateValue(counter, new long[]{300, 400, 500, 600}, 7000);
+        counter.updateValues(new long[]{300, 400, 500, 600}, 7000);
 
         // Again, take 50% of the delta
         assertCounts(counter, 0, new long[]{175, 275, 375, 475});
@@ -111,8 +121,8 @@
     public void reset() {
         LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
         counter.setState(0, 1000);
-        updateValue(counter, new long[]{0, 0, 0, 0}, 1000);
-        updateValue(counter, new long[]{100, 200, 300, 400}, 2000);
+        counter.updateValues(new long[]{0, 0, 0, 0}, 1000);
+        counter.updateValues(new long[]{100, 200, 300, 400}, 2000);
 
         assertCounts(counter, 0, new long[]{100, 200, 300, 400});
 
@@ -120,8 +130,8 @@
 
         assertCounts(counter, 0, new long[]{0, 0, 0, 0});
 
-        updateValue(counter, new long[]{200, 300, 400, 500}, 3000);
-        updateValue(counter, new long[]{300, 400, 500, 600}, 4000);
+        counter.updateValues(new long[]{200, 300, 400, 500}, 3000);
+        counter.updateValues(new long[]{300, 400, 500, 600}, 4000);
 
         assertCounts(counter, 0, new long[]{100, 100, 100, 100});
     }
@@ -129,11 +139,11 @@
     @Test
     public void parceling() {
         LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
-        updateValue(counter, new long[]{0, 0, 0, 0}, 1000);
+        counter.updateValues(new long[]{0, 0, 0, 0}, 1000);
         counter.setState(0, 1000);
-        updateValue(counter, new long[]{100, 200, 300, 400}, 2000);
+        counter.updateValues(new long[]{100, 200, 300, 400}, 2000);
         counter.setState(1, 2000);
-        updateValue(counter, new long[]{101, 202, 304, 408}, 3000);
+        counter.updateValues(new long[]{101, 202, 304, 408}, 3000);
 
         assertCounts(counter, 0, new long[]{100, 200, 300, 400});
         assertCounts(counter, 1, new long[]{1, 2, 4, 8});
@@ -158,27 +168,17 @@
 
         // State, last update timestamp and current counts are undefined at this point.
         newCounter.setState(0, 100);
-        updateValue(newCounter, new long[]{300, 400, 500, 600}, 100);
+        newCounter.updateValues(new long[]{300, 400, 500, 600}, 100);
 
         // A new base state and counters are established; we can continue accumulating deltas
-        updateValue(newCounter, new long[]{316, 432, 564, 728}, 200);
+        newCounter.updateValues(new long[]{316, 432, 564, 728}, 200);
 
         assertCounts(newCounter, 0, new long[]{116, 232, 364, 528});
     }
 
-    private void updateValue(LongArrayMultiStateCounter counter, long[] values, int timestamp) {
-        LongArrayMultiStateCounter.LongArrayContainer container =
-                new LongArrayMultiStateCounter.LongArrayContainer(values.length);
-        container.setValues(values);
-        counter.updateValues(container, timestamp);
-    }
-
     private void assertCounts(LongArrayMultiStateCounter counter, int state, long[] expected) {
-        LongArrayMultiStateCounter.LongArrayContainer container =
-                new LongArrayMultiStateCounter.LongArrayContainer(expected.length);
         long[] counts = new long[expected.length];
-        counter.getCounts(container, state);
-        container.getValues(counts);
+        counter.getCounts(counts, state);
         assertThat(counts).isEqualTo(expected);
     }
 
@@ -230,33 +230,4 @@
         parcel.writeInt(endPos - startPos);
         parcel.setDataPosition(endPos);
     }
-
-    @Test
-    public void combineValues() {
-        long[] values = new long[] {0, 1, 2, 3, 42};
-        LongArrayMultiStateCounter.LongArrayContainer container =
-                new LongArrayMultiStateCounter.LongArrayContainer(values.length);
-        container.setValues(values);
-
-        long[] out = new long[3];
-        int[] indexes = {2, 1, 1, 0, 0};
-        boolean nonZero = container.combineValues(out, indexes);
-        assertThat(nonZero).isTrue();
-        assertThat(out).isEqualTo(new long[]{45, 3, 0});
-
-        // All zeros
-        container.setValues(new long[]{0, 0, 0, 0, 0});
-        nonZero = container.combineValues(out, indexes);
-        assertThat(nonZero).isFalse();
-        assertThat(out).isEqualTo(new long[]{0, 0, 0});
-
-        // Index out of range
-        IndexOutOfBoundsException e1 = assertThrows(
-                IndexOutOfBoundsException.class,
-                () -> container.combineValues(out, new int[]{0, 1, -1, 0, 0}));
-        assertThat(e1.getMessage()).isEqualTo("Index -1 is out of bounds: [0, 2]");
-        IndexOutOfBoundsException e2 = assertThrows(IndexOutOfBoundsException.class,
-                () -> container.combineValues(out, new int[]{0, 1, 4, 0, 0}));
-        assertThat(e2.getMessage()).isEqualTo("Index 4 is out of bounds: [0, 2]");
-    }
 }
diff --git a/core/tests/packagemanagertests/Android.bp b/core/tests/packagemanagertests/Android.bp
index 8ff4998..c2713ad 100644
--- a/core/tests/packagemanagertests/Android.bp
+++ b/core/tests/packagemanagertests/Android.bp
@@ -16,6 +16,7 @@
         "androidx.test.rules",
         "frameworks-base-testutils",
         "mockito-target-minus-junit4",
+        "platform-test-annotations",
     ],
     libs: ["android.test.runner.stubs.system"],
     platform_apis: true,
diff --git a/core/tests/packagemanagertests/src/com/android/internal/pm/pkg/component/ParsedMainComponentUtilsTest.java b/core/tests/packagemanagertests/src/com/android/internal/pm/pkg/component/ParsedMainComponentUtilsTest.java
new file mode 100644
index 0000000..23bcb71
--- /dev/null
+++ b/core/tests/packagemanagertests/src/com/android/internal/pm/pkg/component/ParsedMainComponentUtilsTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.pm.pkg.component;
+
+import static android.security.Flags.FLAG_ENABLE_INTENT_MATCHING_FLAGS;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.RequiresFlagsEnabled;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@RunWith(AndroidJUnit4.class)
+public class ParsedMainComponentUtilsTest {
+
+    private final Map<String, Integer> mStringToFlagMap = new HashMap<>();
+
+    {
+        mStringToFlagMap.put("none", ParsedMainComponentImpl.INTENT_MATCHING_FLAGS_NONE);
+        mStringToFlagMap.put("enforceIntentFilter",
+                ParsedMainComponentImpl.INTENT_MATCHING_FLAGS_ENFORCE_INTENT_FILTER);
+        mStringToFlagMap.put("allowNullAction",
+                ParsedMainComponentImpl.INTENT_MATCHING_FLAGS_ALLOW_NULL_ACTION);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_ENABLE_INTENT_MATCHING_FLAGS)
+    public void testResolveIntentMatchingFlags() {
+        assertResolution("", "", "");
+        assertResolution("none", "none", "none");
+
+        assertResolution("none", "enforceIntentFilter", "enforceIntentFilter");
+        assertResolution("enforceIntentFilter", "none", "none");
+        assertResolution("enforceIntentFilter|allowNullAction", "none",
+                "none");
+        assertResolution("enforceIntentFilter|allowNullAction", "enforceIntentFilter",
+                "enforceIntentFilter");
+
+        assertResolution("none", "", "none");
+        assertResolution("enforceIntentFilter", "", "enforceIntentFilter");
+        assertResolution("enforceIntentFilter|allowNullAction", "",
+                "enforceIntentFilter|allowNullAction");
+
+        assertResolution("", "none", "none");
+        assertResolution("", "enforceIntentFilter", "enforceIntentFilter");
+        assertResolution("", "enforceIntentFilter|allowNullAction",
+                "enforceIntentFilter|allowNullAction");
+    }
+
+    private void assertResolution(String applicationStringFlags, String componentStringFlags,
+            String expectedStringFlags) {
+        int applicationFlag = stringToFlag(applicationStringFlags);
+        int componentFlag = stringToFlag(componentStringFlags);
+
+        int expectedFlag = stringToFlag(expectedStringFlags);
+        int resolvedFlag = ParsedMainComponentUtils.resolveIntentMatchingFlags(applicationFlag,
+                componentFlag);
+
+        assertEquals(expectedFlag, resolvedFlag);
+    }
+
+    private int stringToFlag(String flags) {
+        int result = 0;
+        String[] flagList = flags.split("\\|");
+        for (String flag : flagList) {
+            String trimmedFlag = flag.trim();
+            result |= mStringToFlagMap.getOrDefault(trimmedFlag, 0);
+        }
+        return result;
+    }
+}
diff --git a/core/tests/vibrator/src/android/os/VibrationEffectTest.java b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
index beb6985..1cd1190 100644
--- a/core/tests/vibrator/src/android/os/VibrationEffectTest.java
+++ b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
@@ -425,6 +425,15 @@
                 .build();
 
         assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+        effect = VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 60)
+                .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20)
+                .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 50)
+                .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 80)
+                .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 40)
+                .build();
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
     }
 
     @Test
@@ -643,6 +652,14 @@
                 .build()
                 .validate();
 
+        VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 30)
+                .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20)
+                .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 50)
+                .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 80)
+                .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 40)
+                .build()
+                .validate();
+
         VibrationEffect.createRepeatingEffect(
                 /*preamble=*/ VibrationEffect.startWaveformEnvelope()
                         .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f,
@@ -693,6 +710,34 @@
                                 /*timeMillis=*/ 0)
                         .build()
                         .validate());
+
+        assertThrows(IllegalStateException.class,
+                () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30)
+                        .build().validate());
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30)
+                        .addControlPoint(/*amplitude=*/ -1.0f, /*frequencyHz=*/ 60f,
+                                /*timeMillis=*/ 20)
+                        .build()
+                        .validate());
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30)
+                        .addControlPoint(/*amplitude=*/ 1.1f, /*frequencyHz=*/ 60f,
+                                /*timeMillis=*/ 20)
+                        .build()
+                        .validate());
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30)
+                        .addControlPoint(/*amplitude=*/ 0.8f, /*frequencyHz=*/ 0f,
+                                /*timeMillis=*/ 20)
+                        .build()
+                        .validate());
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30)
+                        .addControlPoint(/*amplitude=*/ 0.8f, /*frequencyHz=*/ 100f,
+                                /*timeMillis=*/ 0)
+                        .build()
+                        .validate());
     }
 
     @Test
@@ -1331,6 +1376,11 @@
                 .addTransition(Duration.ofMillis(500), targetAmplitude(0))
                 .build()
                 .isHapticFeedbackCandidate());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testIsHapticFeedbackCandidate_longEnvelopeEffects_notCandidates() {
         assertFalse(VibrationEffect.startWaveformEnvelope()
                 .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 200)
                 .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500)
@@ -1338,6 +1388,13 @@
                 .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 400)
                 .build()
                 .isHapticFeedbackCandidate());
+        assertFalse(VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 40)
+                .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 200)
+                .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500)
+                .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 800)
+                .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 400)
+                .build()
+                .isHapticFeedbackCandidate());
     }
 
     @Test
@@ -1351,12 +1408,23 @@
                 .addTransition(Duration.ofMillis(300), targetAmplitude(0))
                 .build()
                 .isHapticFeedbackCandidate());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testIsHapticFeedbackCandidate_shortEnvelopeEffects_areCandidates() {
         assertTrue(VibrationEffect.startWaveformEnvelope()
                 .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500)
                 .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 400)
                 .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 100)
                 .build()
                 .isHapticFeedbackCandidate());
+        assertTrue(VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 30)
+                .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500)
+                .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 400)
+                .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 100)
+                .build()
+                .isHapticFeedbackCandidate());
     }
 
     @Test
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/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
index 82b3f68..71baed8 100644
--- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
@@ -37,6 +37,7 @@
 import android.util.TypedValue;
 import android.view.View;
 
+import com.android.graphics.hwui.flags.Flags;
 import com.android.internal.R;
 
 import dalvik.annotation.optimization.FastNative;
@@ -525,6 +526,35 @@
         }
     }
 
+    @Override
+    public void setFilterBitmap(boolean filterBitmap) {
+        if (!Flags.animatedImageDrawableFilterBitmap()) {
+            super.setFilterBitmap(filterBitmap);
+            return;
+        }
+        if (mState.mNativePtr == 0) {
+            throw new IllegalStateException(
+              "called setFilterBitmap on empty AnimatedImageDrawable"
+            );
+        }
+        if (nSetFilterBitmap(mState.mNativePtr, filterBitmap)) {
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public boolean isFilterBitmap() {
+        if (!Flags.animatedImageDrawableFilterBitmap()) {
+            return super.isFilterBitmap();
+        }
+        if (mState.mNativePtr == 0) {
+            throw new IllegalStateException(
+                "called isFilterBitmap on empty AnimatedImageDrawable"
+            );
+        }
+        return nGetFilterBitmap(mState.mNativePtr);
+    }
+
     private void postOnAnimationStart() {
         if (mAnimationCallbacks == null) {
             return;
@@ -618,4 +648,8 @@
     private static native void nSetMirrored(long nativePtr, boolean mirror);
     @FastNative
     private static native void nSetBounds(long nativePtr, Rect rect);
+    @FastNative
+    private static native boolean nSetFilterBitmap(long nativePtr, boolean filterBitmap);
+    @FastNative
+    private static native boolean nGetFilterBitmap(long nativePtr);
 }
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/res/drawable/desktop_mode_ic_handle_menu_change_aspect_ratio.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_change_aspect_ratio.xml
new file mode 100644
index 0000000..4442e9d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_change_aspect_ratio.xml
@@ -0,0 +1,25 @@
+<?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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="20dp"
+    android:height="20dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@color/compat_controls_text"
+        android:pathData="M19,12h-2v3h-3v2h5v-5zM7,9h3L10,7L5,7v5h2L7,9zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.99h18v14.02z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_promo_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_promo_background.xml
new file mode 100644
index 0000000..645d24d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_promo_background.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+
+<shape android:shape="rectangle"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <solid android:color="?androidprv:attr/materialColorSurfaceContainerLow" />
+    <corners android:radius="@dimen/desktop_windowing_education_promo_corner_radius" />
+</shape>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index 5609663..f90e165 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -157,6 +157,14 @@
             android:drawableStart="@drawable/desktop_mode_ic_handle_menu_manage_windows"
             android:drawableTint="?androidprv:attr/materialColorOnSurface"
             style="@style/DesktopModeHandleMenuActionButton" />
+
+        <Button
+            android:id="@+id/change_aspect_ratio_button"
+            android:contentDescription="@string/change_aspect_ratio_text"
+            android:text="@string/change_aspect_ratio_text"
+            android:drawableStart="@drawable/desktop_mode_ic_handle_menu_change_aspect_ratio"
+            android:drawableTint="?androidprv:attr/materialColorOnSurface"
+            style="@style/DesktopModeHandleMenuActionButton" />
     </LinearLayout>
 
     <LinearLayout
diff --git a/libs/WindowManager/Shell/res/layout/desktop_windowing_education_promo.xml b/libs/WindowManager/Shell/res/layout/desktop_windowing_education_promo.xml
new file mode 100644
index 0000000..eebfd4b
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_windowing_education_promo.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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/education_container"
+    android:layout_width="@dimen/desktop_windowing_education_promo_width"
+    android:layout_height="@dimen/desktop_windowing_education_promo_height"
+    android:elevation="1dp"
+    android:orientation="vertical"
+    android:paddingHorizontal="32dp"
+    android:paddingVertical="24dp"
+    android:background="@drawable/desktop_windowing_education_promo_background">
+    <TextView
+        android:id="@+id/education_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="24sp"
+        android:lineHeight="32dp"
+        android:textFontWeight="400"
+        android:fontFamily="google-sans-text"
+        android:layout_gravity="center_horizontal"
+        android:gravity="center"/>
+</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index c36783f..a8febc8 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"تكبير الشاشة إلى أقصى حدّ"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"التقاط صورة للشاشة"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"لا يمكن نقل التطبيق إلى هنا"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"مجسَّم"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"استعادة"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"تكبير"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"استعادة"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"المحاذاة إلى اليسار"</string>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 71d95ed..8c924e3 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"স্ক্ৰীন মেক্সিমাইজ কৰক"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"স্ক্ৰীন স্নেপ কৰক"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ইয়ালৈ এপ্‌টো আনিব নোৱাৰি"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ইমাৰ্ছিভ"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"পুনঃস্থাপন কৰক"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"মেক্সিমাইজ কৰক"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"পুনঃস্থাপন কৰক"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"বাওঁফাললৈ স্নেপ কৰক"</string>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 4412497..22a445f 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"স্ক্রিন বড় করুন"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"স্ক্রিনে অ্যাপ মানানসই হিসেবে ছোট বড় করুন"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"অ্যাপটি এখানে সরানো যাবে না"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ইমারসিভ"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ফিরিয়ে আনুন"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"বড় করুন"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"ফিরিয়ে আনুন"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"বাঁদিকে স্ন্যাপ করুন"</string>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index 674022b..73f30d7 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimiziraj ekran"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snimi ekran"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ne možete premjestiti aplikaciju ovdje"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Interaktivno"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Vrati"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimiziranje"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Vraćanje"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Pomicanje ulijevo"</string>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index fc6b679..6a5780e 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximalizovat obrazovku"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Rozpůlit obrazovku"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikaci sem nelze přesunout"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Pohlcující"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Obnovit"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximalizovat"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Obnovit"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Přichytit vlevo"</string>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index d06b46f..d02fae2 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Μεγιστοποίηση οθόνης"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Προβολή στο μισό της οθόνης"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Δεν είναι δυνατή η μετακίνηση της εφαρμογής εδώ"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Καθηλωτικό"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Επαναφορά"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Μεγιστοποίηση"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Επαναφορά"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Κούμπωμα αριστερά"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index c1a42448..f991145 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snap screen"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restore"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximise"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restore"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Snap left"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index c1a42448..f991145 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snap screen"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restore"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximise"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restore"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Snap left"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index c1a42448..f991145 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snap screen"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restore"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximise"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restore"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Snap left"</string>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index ff675a9..527793e 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"स्क्रीन को बड़ा करें"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"स्नैप स्क्रीन"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ऐप्लिकेशन को यहां मूव नहीं किया जा सकता"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"इमर्सिव"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"वापस लाएं"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"बड़ा करें"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"पहले जैसा करें"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"बाईं ओर स्नैप करें"</string>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index 16fbcd0..659d1ec 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimalno povećaj zaslon"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Izradi snimku zaslona"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacija se ne može premjestiti ovdje"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Interaktivno"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Vrati"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimiziraj"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Vrati"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Poravnaj lijevo"</string>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index f595765..ca1bc15 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Stækka skjá"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Smelluskjár"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ekki er hægt að færa forritið hingað"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Umlykjandi"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Endurheimta"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Stækka"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Endurheimta"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Smella til vinstri"</string>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index 8b7c15f..87919b5 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Massimizza schermo"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Aggancia schermo"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Impossibile spostare l\'app qui"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersivo"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Ripristina"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Ingrandisci"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Ripristina"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Aggancia a sinistra"</string>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index 5464cfa..c7a77d9 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"画面の最大化"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"画面のスナップ"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"アプリはここに移動できません"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"没入モード"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"復元"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"最大化"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"復元"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"左にスナップ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index 7ea9daf..39362ef 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"აპლიკაციის გაშლა სრულ ეკრანზე"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"აპლიკაციის დაპატარავება ეკრანზე"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"აპის აქ გადატანა შეუძლებელია"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"იმერსიული"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"აღდგენა"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"მაქსიმალურად გაშლა"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"აღდგენა"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"მარცხნივ გადატანა"</string>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index 83bd374..9c4ae05 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ពង្រីកអេក្រង់"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ថតអេក្រង់"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"មិនអាចផ្លាស់ទីកម្មវិធីមកទីនេះបានទេ"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ជក់ចិត្ត"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ស្ដារ"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ពង្រីក"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"ស្ដារ"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ផ្លាស់ទីទៅឆ្វេង"</string>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index 502e0c9..f365cfb 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ಸ್ಕ್ರೀನ್ ಅನ್ನು ಮ್ಯಾಕ್ಸಿಮೈಸ್ ಮಾಡಿ"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ಸ್ನ್ಯಾಪ್ ಸ್ಕ್ರೀನ್"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ಆ್ಯಪ್ ಅನ್ನು ಇಲ್ಲಿಗೆ ಸರಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ಇಮ್ಮರ್ಸಿವ್"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ಮರುಸ್ಥಾಪಿಸಿ"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ಮ್ಯಾಕ್ಸಿಮೈಸ್ ಮಾಡಿ"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"ಮರುಸ್ಥಾಪಿಸಿ"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ಎಡಕ್ಕೆ ಸ್ನ್ಯಾಪ್ ಮಾಡಿ"</string>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index 4551f72..5a7f58e 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Išskleisti ekraną"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Sutraukti ekraną"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Programos negalima perkelti čia"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Įtraukiantis"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Atkurti"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Padidinti"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Atkurti"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Pritraukti kairėje"</string>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index a2c610e..871bc3f 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"स्क्रीन मोठी करा"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"स्क्रीन स्नॅप करा"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"अ‍ॅप इथे हलवू शकत नाही"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"इमर्सिव्ह"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"रिस्टोअर करा"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"मोठे करा"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"रिस्टोअर करा"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"डावीकडे स्नॅप करा"</string>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index 2102a38..71666ca 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimumkan Skrin"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Tangkap Skrin"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Apl tidak boleh dialihkan ke sini"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Mengasyikkan"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Pulihkan"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimumkan"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Pulihkan"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Autojajar ke kiri"</string>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index e9a5306..7015b2c 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"स्क्रिन ठुलो बनाउनुहोस्"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"स्क्रिन स्न्याप गर्नुहोस्"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"एप सारेर यहाँ ल्याउन सकिएन"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"इमर्सिभ"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"रिस्टोर गर्नुहोस्"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ठुलो बनाउनुहोस्"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"रिस्टोर गर्नुहोस्"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"बायाँतिर स्न्याप गर्नुहोस्"</string>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index 14b7a09..5f78b13 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksymalizuj ekran"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Przyciągnij ekran"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Nie można przenieść aplikacji tutaj"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Tryb immersyjny"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Przywróć"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksymalizuj"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Przywróć"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Przyciągnij do lewej"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index 9631c45..cd78ef9 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar ecrã"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Encaixar ecrã"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Não é possível mover a app para aqui"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Envolvente"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizar"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restaurar"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Encaixar à esquerda"</string>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index 2012c8a..ae7e524 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimiraj zaslon"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Pripni zaslon"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacije ni mogoče premakniti sem"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Poglobljeno"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Obnovi"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimiraj"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Obnovi"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Pripni levo"</string>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index c4243f7..2c73d3a 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"திரையைப் பெரிதாக்கு"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"திரையை ஸ்னாப் செய்"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ஆப்ஸை இங்கே நகர்த்த முடியாது"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ஈடுபட வைக்கும்"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"மீட்டெடுக்கும்"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"பெரிதாக்கும்"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"மீட்டெடுக்கும்"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"இடதுபுறம் நகர்த்தும்"</string>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 47fc48d..b17d4d1 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"స్క్రీన్ సైజ్‌ను పెంచండి"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"స్క్రీన్‌ను స్నాప్ చేయండి"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"యాప్‌ను ఇక్కడకి తరలించడం సాధ్యం కాదు"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"లీనమయ్యే"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"రీస్టోర్ చేయండి"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"మ్యాగ్జిమైజ్ చేయండి"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"రీస్టోర్ చేయండి"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ఎడమ వైపున స్నాప్ చేయండి"</string>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index 2608075..43cee41 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ขยายหน้าจอให้ใหญ่สุด"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"สแนปหน้าจอ"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ย้ายแอปมาที่นี่ไม่ได้"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"สมจริง"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"คืนค่า"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ขยายใหญ่สุด"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"คืนค่า"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"จัดพอดีกับทางซ้าย"</string>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index 94166ef..4284995 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"I-maximize ang Screen"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"I-snap ang Screen"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Hindi mailipat dito ang app"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"I-restore"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"I-maximize"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"I-restore"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"I-snap pakaliwa"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index ad50a04..2fb3f5a 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"最大化屏幕"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"屏幕快照"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"无法将应用移至此处"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"沉浸式"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"恢复"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"最大化"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"恢复"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"贴靠左侧"</string>
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index a14461a..59e6c77 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -186,4 +186,6 @@
 
     <!-- Apps that can trigger Desktop Windowing App handle Education -->
     <string-array name="desktop_windowing_app_handle_education_allowlist_apps"></string-array>
+    <!-- Apps that can trigger Desktop Windowing App-To-Web Education -->
+    <string-array name="desktop_windowing_app_to_web_education_allowlist_apps"></string-array>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index fa1aa19..249e9a2 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -523,8 +523,9 @@
     <dimen name="desktop_mode_handle_menu_width">216dp</dimen>
 
     <!-- The maximum height of the handle menu in desktop mode. Three pills at 52dp each,
-         additional actions pill 156dp, plus 2dp spacing between them plus 4dp top padding. -->
-    <dimen name="desktop_mode_handle_menu_height">322dp</dimen>
+         additional actions pill 208dp, plus 2dp spacing between them plus 4dp top padding.
+         52*3 + 52*4 + (4-1)*2 + 4 = 374 -->
+    <dimen name="desktop_mode_handle_menu_height">374dp</dimen>
 
     <!-- The elevation set on the handle menu pills. -->
     <dimen name="desktop_mode_handle_menu_pill_elevation">1dp</dimen>
@@ -547,6 +548,9 @@
     <!-- The height of the handle menu's "Open in browser" pill in desktop mode. -->
     <dimen name="desktop_mode_handle_menu_open_in_browser_pill_height">52dp</dimen>
 
+    <!-- The height of the handle menu's "Change aspect ratio" pill in desktop mode. -->
+    <dimen name="desktop_mode_handle_menu_change_aspect_ratio_height">52dp</dimen>
+
     <!-- The margin between pills of the handle menu in desktop mode. -->
     <dimen name="desktop_mode_handle_menu_pill_spacing_margin">2dp</dimen>
 
@@ -650,4 +654,11 @@
     <dimen name="open_by_default_settings_dialog_radio_button_height">64dp</dimen>
     <!-- The width of radio buttons in the open by default settings dialog. -->
     <dimen name="open_by_default_settings_dialog_radio_button_width">316dp</dimen>
+
+    <!-- The width of the desktop windowing education promo. -->
+    <dimen name="desktop_windowing_education_promo_width">412dp</dimen>
+    <!-- The height of the desktop windowing education promo. -->
+    <dimen name="desktop_windowing_education_promo_height">352dp</dimen>
+    <!-- The corner radius of the desktop windowing education promo. -->
+    <dimen name="desktop_windowing_education_promo_corner_radius">28dp</dimen>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index ef0386a..8f1ef6c 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -305,6 +305,8 @@
     <string name="new_window_text">New Window</string>
     <!-- Accessibility text for the handle menu new window button [CHAR LIMIT=NONE] -->
     <string name="manage_windows_text">Manage Windows</string>
+    <!-- Accessibility text for the handle menu change aspect ratio button [CHAR LIMIT=NONE] -->
+    <string name="change_aspect_ratio_text">Change aspect ratio</string>
     <!-- Accessibility text for the handle menu close button [CHAR LIMIT=NONE] -->
     <string name="close_text">Close</string>
     <!-- Accessibility text for the handle menu close menu button [CHAR LIMIT=NONE] -->
@@ -339,4 +341,7 @@
     <string name="open_by_default_dialog_in_browser_text">In your browser</string>
     <!-- Text for open by default settings dialog dismiss button. -->
     <string name="open_by_default_dialog_dismiss_button_text">OK</string>
+
+    <!-- Text for the App-to-Web education promo. -->
+    <string name="desktop_windowing_app_to_web_education_text">Quickly open apps in your browser here</string>
 </resources>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java
deleted file mode 100644
index 65e079e..0000000
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.shared;
-
-import android.annotation.IntDef;
-import android.app.ActivityManager;
-import android.app.WindowConfiguration;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.wm.shell.shared.split.SplitBounds;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Simple container for recent tasks.  May contain either a single or pair of tasks.
- */
-public class GroupedRecentTaskInfo implements Parcelable {
-
-    public static final int TYPE_SINGLE = 1;
-    public static final int TYPE_SPLIT = 2;
-    public static final int TYPE_FREEFORM = 3;
-
-    @IntDef(prefix = {"TYPE_"}, value = {
-            TYPE_SINGLE,
-            TYPE_SPLIT,
-            TYPE_FREEFORM
-    })
-    public @interface GroupType {}
-
-    @NonNull
-    private final ActivityManager.RecentTaskInfo[] mTasks;
-    @Nullable
-    private final SplitBounds mSplitBounds;
-    @GroupType
-    private final int mType;
-    // TODO(b/348332802): move isMinimized inside each Task object instead once we have a
-    //  replacement for RecentTaskInfo
-    private final int[] mMinimizedTaskIds;
-
-    /**
-     * Create new for a single task
-     */
-    public static GroupedRecentTaskInfo forSingleTask(
-            @NonNull ActivityManager.RecentTaskInfo task) {
-        return new GroupedRecentTaskInfo(new ActivityManager.RecentTaskInfo[]{task}, null,
-                TYPE_SINGLE, null /* minimizedFreeformTasks */);
-    }
-
-    /**
-     * Create new for a pair of tasks in split screen
-     */
-    public static GroupedRecentTaskInfo forSplitTasks(@NonNull ActivityManager.RecentTaskInfo task1,
-            @NonNull ActivityManager.RecentTaskInfo task2, @Nullable SplitBounds splitBounds) {
-        return new GroupedRecentTaskInfo(new ActivityManager.RecentTaskInfo[]{task1, task2},
-                splitBounds, TYPE_SPLIT, null /* minimizedFreeformTasks */);
-    }
-
-    /**
-     * Create new for a group of freeform tasks
-     */
-    public static GroupedRecentTaskInfo forFreeformTasks(
-            @NonNull ActivityManager.RecentTaskInfo[] tasks,
-            @NonNull Set<Integer> minimizedFreeformTasks) {
-        return new GroupedRecentTaskInfo(
-                tasks,
-                null /* splitBounds */,
-                TYPE_FREEFORM,
-                minimizedFreeformTasks.stream().mapToInt(i -> i).toArray());
-    }
-
-    private GroupedRecentTaskInfo(
-            @NonNull ActivityManager.RecentTaskInfo[] tasks,
-            @Nullable SplitBounds splitBounds,
-            @GroupType int type,
-            @Nullable int[] minimizedFreeformTaskIds) {
-        mTasks = tasks;
-        mSplitBounds = splitBounds;
-        mType = type;
-        mMinimizedTaskIds = minimizedFreeformTaskIds;
-        ensureAllMinimizedIdsPresent(tasks, minimizedFreeformTaskIds);
-    }
-
-    private static void ensureAllMinimizedIdsPresent(
-            @NonNull ActivityManager.RecentTaskInfo[] tasks,
-            @Nullable int[] minimizedFreeformTaskIds) {
-        if (minimizedFreeformTaskIds == null) {
-            return;
-        }
-        if (!Arrays.stream(minimizedFreeformTaskIds).allMatch(
-                taskId -> Arrays.stream(tasks).anyMatch(task -> task.taskId == taskId))) {
-            throw new IllegalArgumentException("Minimized task IDs contain non-existent Task ID.");
-        }
-    }
-
-    GroupedRecentTaskInfo(Parcel parcel) {
-        mTasks = parcel.createTypedArray(ActivityManager.RecentTaskInfo.CREATOR);
-        mSplitBounds = parcel.readTypedObject(SplitBounds.CREATOR);
-        mType = parcel.readInt();
-        mMinimizedTaskIds = parcel.createIntArray();
-    }
-
-    /**
-     * Get primary {@link ActivityManager.RecentTaskInfo}
-     */
-    @NonNull
-    public ActivityManager.RecentTaskInfo getTaskInfo1() {
-        return mTasks[0];
-    }
-
-    /**
-     * Get secondary {@link ActivityManager.RecentTaskInfo}.
-     *
-     * Used in split screen.
-     */
-    @Nullable
-    public ActivityManager.RecentTaskInfo getTaskInfo2() {
-        if (mTasks.length > 1) {
-            return mTasks[1];
-        }
-        return null;
-    }
-
-    /**
-     * Get all {@link ActivityManager.RecentTaskInfo}s grouped together.
-     */
-    @NonNull
-    public List<ActivityManager.RecentTaskInfo> getTaskInfoList() {
-        return Arrays.asList(mTasks);
-    }
-
-    /**
-     * Return {@link SplitBounds} if this is a split screen entry or {@code null}
-     */
-    @Nullable
-    public SplitBounds getSplitBounds() {
-        return mSplitBounds;
-    }
-
-    /**
-     * Get type of this recents entry. One of {@link GroupType}
-     */
-    @GroupType
-    public int getType() {
-        return mType;
-    }
-
-    public int[] getMinimizedTaskIds() {
-        return mMinimizedTaskIds;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder taskString = new StringBuilder();
-        for (int i = 0; i < mTasks.length; i++) {
-            if (i == 0) {
-                taskString.append("Task");
-            } else {
-                taskString.append(", Task");
-            }
-            taskString.append(i + 1).append(": ").append(getTaskInfo(mTasks[i]));
-        }
-        if (mSplitBounds != null) {
-            taskString.append(", SplitBounds: ").append(mSplitBounds);
-        }
-        taskString.append(", Type=");
-        switch (mType) {
-            case TYPE_SINGLE:
-                taskString.append("TYPE_SINGLE");
-                break;
-            case TYPE_SPLIT:
-                taskString.append("TYPE_SPLIT");
-                break;
-            case TYPE_FREEFORM:
-                taskString.append("TYPE_FREEFORM");
-                break;
-        }
-        taskString.append(", Minimized Task IDs: ");
-        taskString.append(Arrays.toString(mMinimizedTaskIds));
-        return taskString.toString();
-    }
-
-    private String getTaskInfo(ActivityManager.RecentTaskInfo taskInfo) {
-        if (taskInfo == null) {
-            return null;
-        }
-        return "id=" + taskInfo.taskId
-                + " baseIntent=" + (taskInfo.baseIntent != null
-                        ? taskInfo.baseIntent.getComponent()
-                        : "null")
-                + " winMode=" + WindowConfiguration.windowingModeToString(
-                        taskInfo.getWindowingMode());
-    }
-
-    @Override
-    public void writeToParcel(Parcel parcel, int flags) {
-        parcel.writeTypedArray(mTasks, flags);
-        parcel.writeTypedObject(mSplitBounds, flags);
-        parcel.writeInt(mType);
-        parcel.writeIntArray(mMinimizedTaskIds);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    public static final @android.annotation.NonNull Creator<GroupedRecentTaskInfo> CREATOR =
-            new Creator<GroupedRecentTaskInfo>() {
-        public GroupedRecentTaskInfo createFromParcel(Parcel source) {
-            return new GroupedRecentTaskInfo(source);
-        }
-        public GroupedRecentTaskInfo[] newArray(int size) {
-            return new GroupedRecentTaskInfo[size];
-        }
-    };
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.aidl
similarity index 95%
rename from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.aidl
index e21bf8f..93e635d 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.aidl
@@ -16,4 +16,4 @@
 
 package com.android.wm.shell.shared;
 
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
+parcelable GroupedTaskInfo;
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
new file mode 100644
index 0000000..03e0ab0
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.shared;
+
+import android.annotation.IntDef;
+import android.app.ActivityManager.RecentTaskInfo;
+import android.app.TaskInfo;
+import android.app.WindowConfiguration;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.shared.split.SplitBounds;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Simple container for recent tasks which should be presented as a single task within the
+ * Overview UI.
+ */
+public class GroupedTaskInfo implements Parcelable {
+
+    public static final int TYPE_FULLSCREEN = 1;
+    public static final int TYPE_SPLIT = 2;
+    public static final int TYPE_FREEFORM = 3;
+
+    @IntDef(prefix = {"TYPE_"}, value = {
+            TYPE_FULLSCREEN,
+            TYPE_SPLIT,
+            TYPE_FREEFORM
+    })
+    public @interface GroupType {}
+
+    /**
+     * The type of this particular task info, can be one of TYPE_FULLSCREEN, TYPE_SPLIT or
+     * TYPE_FREEFORM.
+     */
+    @GroupType
+    protected final int mType;
+
+    /**
+     * The list of tasks associated with this single recent task info.
+     * TYPE_FULLSCREEN: Contains the stack of tasks associated with a single "task" in overview
+     * TYPE_SPLIT: Contains the two split roots of each side
+     * TYPE_FREEFORM: Contains the set of tasks currently in freeform mode
+     */
+    @NonNull
+    protected final List<TaskInfo> mTasks;
+
+    /**
+     * Only set for TYPE_SPLIT.
+     *
+     * Information about the split bounds.
+     */
+    @Nullable
+    protected final SplitBounds mSplitBounds;
+
+    /**
+     * Only set for TYPE_FREEFORM.
+     *
+     * TODO(b/348332802): move isMinimized inside each Task object instead once we have a
+     *  replacement for RecentTaskInfo
+     */
+    @Nullable
+    protected final int[] mMinimizedTaskIds;
+
+    /**
+     * Create new for a stack of fullscreen tasks
+     */
+    public static GroupedTaskInfo forFullscreenTasks(@NonNull TaskInfo task) {
+        return new GroupedTaskInfo(List.of(task), null, TYPE_FULLSCREEN,
+                null /* minimizedFreeformTasks */);
+    }
+
+    /**
+     * Create new for a pair of tasks in split screen
+     */
+    public static GroupedTaskInfo forSplitTasks(@NonNull TaskInfo task1,
+                    @NonNull TaskInfo task2, @Nullable SplitBounds splitBounds) {
+        return new GroupedTaskInfo(List.of(task1, task2), splitBounds, TYPE_SPLIT,
+                null /* minimizedFreeformTasks */);
+    }
+
+    /**
+     * Create new for a group of freeform tasks
+     */
+    public static GroupedTaskInfo forFreeformTasks(
+                    @NonNull List<TaskInfo> tasks,
+                    @NonNull Set<Integer> minimizedFreeformTasks) {
+        return new GroupedTaskInfo(tasks, null /* splitBounds */, TYPE_FREEFORM,
+                minimizedFreeformTasks.stream().mapToInt(i -> i).toArray());
+    }
+
+    private GroupedTaskInfo(
+            @NonNull List<TaskInfo> tasks,
+            @Nullable SplitBounds splitBounds,
+            @GroupType int type,
+            @Nullable int[] minimizedFreeformTaskIds) {
+        mTasks = tasks;
+        mSplitBounds = splitBounds;
+        mType = type;
+        mMinimizedTaskIds = minimizedFreeformTaskIds;
+        ensureAllMinimizedIdsPresent(tasks, minimizedFreeformTaskIds);
+    }
+
+    private void ensureAllMinimizedIdsPresent(
+            @NonNull List<TaskInfo> tasks,
+            @Nullable int[] minimizedFreeformTaskIds) {
+        if (minimizedFreeformTaskIds == null) {
+            return;
+        }
+        if (!Arrays.stream(minimizedFreeformTaskIds).allMatch(
+                taskId -> tasks.stream().anyMatch(task -> task.taskId == taskId))) {
+            throw new IllegalArgumentException("Minimized task IDs contain non-existent Task ID.");
+        }
+    }
+
+    protected GroupedTaskInfo(@NonNull Parcel parcel) {
+        mTasks = new ArrayList();
+        final int numTasks = parcel.readInt();
+        for (int i = 0; i < numTasks; i++) {
+            mTasks.add(new TaskInfo(parcel));
+        }
+        mSplitBounds = parcel.readTypedObject(SplitBounds.CREATOR);
+        mType = parcel.readInt();
+        mMinimizedTaskIds = parcel.createIntArray();
+    }
+
+    /**
+     * Get primary {@link RecentTaskInfo}
+     */
+    @NonNull
+    public TaskInfo getTaskInfo1() {
+        return mTasks.getFirst();
+    }
+
+    /**
+     * Get secondary {@link RecentTaskInfo}.
+     *
+     * Used in split screen.
+     */
+    @Nullable
+    public TaskInfo getTaskInfo2() {
+        if (mTasks.size() > 1) {
+            return mTasks.get(1);
+        }
+        return null;
+    }
+
+    /**
+     * Get all {@link RecentTaskInfo}s grouped together.
+     */
+    @NonNull
+    public List<TaskInfo> getTaskInfoList() {
+        return mTasks;
+    }
+
+    /**
+     * Return {@link SplitBounds} if this is a split screen entry or {@code null}
+     */
+    @Nullable
+    public SplitBounds getSplitBounds() {
+        return mSplitBounds;
+    }
+
+    /**
+     * Get type of this recents entry. One of {@link GroupType}
+     */
+    @GroupType
+    public int getType() {
+        return mType;
+    }
+
+    @Nullable
+    public int[] getMinimizedTaskIds() {
+        return mMinimizedTaskIds;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof GroupedTaskInfo)) {
+            return false;
+        }
+        GroupedTaskInfo other = (GroupedTaskInfo) obj;
+        return mType == other.mType
+                && Objects.equals(mTasks, other.mTasks)
+                && Objects.equals(mSplitBounds, other.mSplitBounds)
+                && Arrays.equals(mMinimizedTaskIds, other.mMinimizedTaskIds);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mType, mTasks, mSplitBounds, Arrays.hashCode(mMinimizedTaskIds));
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder taskString = new StringBuilder();
+        for (int i = 0; i < mTasks.size(); i++) {
+            if (i == 0) {
+                taskString.append("Task");
+            } else {
+                taskString.append(", Task");
+            }
+            taskString.append(i + 1).append(": ").append(getTaskInfo(mTasks.get(i)));
+        }
+        if (mSplitBounds != null) {
+            taskString.append(", SplitBounds: ").append(mSplitBounds);
+        }
+        taskString.append(", Type=");
+        switch (mType) {
+            case TYPE_FULLSCREEN:
+                taskString.append("TYPE_FULLSCREEN");
+                break;
+            case TYPE_SPLIT:
+                taskString.append("TYPE_SPLIT");
+                break;
+            case TYPE_FREEFORM:
+                taskString.append("TYPE_FREEFORM");
+                break;
+        }
+        taskString.append(", Minimized Task IDs: ");
+        taskString.append(Arrays.toString(mMinimizedTaskIds));
+        return taskString.toString();
+    }
+
+    private String getTaskInfo(TaskInfo taskInfo) {
+        if (taskInfo == null) {
+            return null;
+        }
+        return "id=" + taskInfo.taskId
+                + " baseIntent=" + (taskInfo.baseIntent != null
+                        ? taskInfo.baseIntent.getComponent()
+                        : "null")
+                + " winMode=" + WindowConfiguration.windowingModeToString(
+                        taskInfo.getWindowingMode());
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        // We don't use the parcel list methods because we want to only write the TaskInfo state
+        // and not the subclasses (Recents/RunningTaskInfo) whose fields are all deprecated
+        parcel.writeInt(mTasks.size());
+        for (int i = 0; i < mTasks.size(); i++) {
+            mTasks.get(i).writeTaskToParcel(parcel, flags);
+        }
+        parcel.writeTypedObject(mSplitBounds, flags);
+        parcel.writeInt(mType);
+        parcel.writeIntArray(mMinimizedTaskIds);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<GroupedTaskInfo> CREATOR = new Creator() {
+        @Override
+        public GroupedTaskInfo createFromParcel(Parcel in) {
+            return new GroupedTaskInfo(in);
+        }
+
+        @Override
+        public GroupedTaskInfo[] newArray(int size) {
+            return new GroupedTaskInfo[size];
+        }
+    };
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index 88878c6..e033f67 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -360,7 +360,7 @@
             windowConfiguration = new WindowConfiguration();
         }
 
-        Rect localBounds = new Rect();
+        Rect bounds = windowConfiguration.getBounds();
         RemoteAnimationTarget target = new RemoteAnimationTarget(
                 taskId,
                 newModeToLegacyMode(mode),
@@ -373,12 +373,12 @@
                 new Rect(0, 0, 0, 0),
                 order,
                 null,
-                localBounds,
-                new Rect(),
+                bounds,
+                bounds,
                 windowConfiguration,
                 isNotInRecents,
                 null,
-                new Rect(),
+                bounds,
                 taskInfo,
                 false,
                 INVALID_WINDOW_TYPE
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt
index fc3dc14..f93b35e 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt
@@ -20,7 +20,7 @@
 import android.util.ArrayMap
 import androidx.dynamicanimation.animation.FloatPropertyCompat
 import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils.prepareForTest
-import java.util.*
+import java.util.ArrayDeque
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
 import kotlin.collections.ArrayList
@@ -74,14 +74,17 @@
 
     @JvmStatic
     fun tearDown() {
-        val latch = CountDownLatch(1)
-        animationThreadHandler.post {
+        if (Looper.myLooper() == animationThreadHandler.looper) {
             animatorTestHelpers.keys.forEach { it.cancel() }
-            latch.countDown()
+        } else {
+            val latch = CountDownLatch(1)
+            animationThreadHandler.post {
+                animatorTestHelpers.keys.forEach { it.cancel() }
+                latch.countDown()
+            }
+            latch.await(5, TimeUnit.SECONDS)
         }
 
-        latch.await()
-
         animatorTestHelpers.clear()
         animators.clear()
         allAnimatedObjects.clear()
@@ -348,8 +351,9 @@
      * Returns all of the values that have ever been reported to update listeners, per property.
      */
     @Suppress("UNCHECKED_CAST")
-    fun <T : Any> getAnimationUpdateFrames(animator: PhysicsAnimator<T>):
-            UpdateFramesPerProperty<T> {
+    fun <T : Any> getAnimationUpdateFrames(
+        animator: PhysicsAnimator<T>
+    ): UpdateFramesPerProperty<T> {
         return animatorTestHelpers[animator]?.getUpdates() as UpdateFramesPerProperty<T>
     }
 
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt
index 7086691..bd129a2 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt
@@ -56,6 +56,7 @@
         onLeft = initialLocationOnLeft
         screenCenterX = screenSizeProvider.invoke().x / 2
         dismissZone = getExclusionRect()
+        listener?.onStart(if (initialLocationOnLeft) LEFT else RIGHT)
     }
 
     /** View has moved to [x] and [y] screen coordinates */
@@ -109,6 +110,7 @@
 
     /** Get width for exclusion rect where dismiss takes over drag */
     protected abstract fun getExclusionRectWidth(): Float
+
     /** Get height for exclusion rect where dismiss takes over drag */
     protected abstract fun getExclusionRectHeight(): Float
 
@@ -184,6 +186,9 @@
 
     /** Receive updates on location changes */
     interface LocationChangeListener {
+        /** Bubble bar dragging has started. Includes the initial location of the bar */
+        fun onStart(location: BubbleBarLocation) {}
+
         /**
          * Bubble bar has been dragged to a new [BubbleBarLocation]. And the drag is still in
          * progress.
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/OWNERS b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/OWNERS
new file mode 100644
index 0000000..20d5c33
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/OWNERS
@@ -0,0 +1,4 @@
+# WM shell sub-module PiP owner
+hwwang@google.com
+gabiyev@google.com
+wuperry@google.com
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
index eb7ef14..62ca5c6 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
@@ -18,6 +18,7 @@
 
 import static android.util.TypedValue.COMPLEX_UNIT_DIP;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -170,26 +171,34 @@
 
         private final Context mContext;
         private final int mAppIconSizePx;
-        private final Rect mAppBounds;
+        /**
+         * The bounds of the application window relative to the task leash.
+         */
+        private final Rect mRelativeAppBounds;
         private final int mOverlayHalfSize;
         private final Matrix mTmpTransform = new Matrix();
         private final float[] mTmpFloat9 = new float[9];
 
         private Bitmap mBitmap;
 
-        public PipAppIconOverlay(Context context, Rect appBounds, Rect destinationBounds,
-                Drawable appIcon, int appIconSizePx) {
+        // TODO(b/356277166): add non-match_parent support on PIP2.
+        /**
+         * @param context the {@link Context} that contains the icon information
+         * @param relativeAppBounds the bounds of the app window frame relative to the task leash
+         * @param destinationBounds the bounds for rhe PIP task
+         * @param appIcon the app icon {@link Drawable}
+         * @param appIconSizePx the icon dimension in pixel
+         */
+        public PipAppIconOverlay(@NonNull Context context, @NonNull Rect relativeAppBounds,
+                @NonNull Rect destinationBounds, @NonNull Drawable appIcon, int appIconSizePx) {
             mContext = context;
             final int maxAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP,
                     MAX_APP_ICON_SIZE_DP, context.getResources().getDisplayMetrics());
             mAppIconSizePx = Math.min(maxAppIconSizePx, appIconSizePx);
 
-            final int overlaySize = getOverlaySize(appBounds, destinationBounds);
+            final int overlaySize = getOverlaySize(relativeAppBounds, destinationBounds);
             mOverlayHalfSize = overlaySize >> 1;
-
-            // When the activity is in the secondary split, make sure the scaling center is not
-            // offset.
-            mAppBounds = new Rect(0, 0, appBounds.width(), appBounds.height());
+            mRelativeAppBounds = relativeAppBounds;
 
             mBitmap = Bitmap.createBitmap(overlaySize, overlaySize, Bitmap.Config.ARGB_8888);
             prepareAppIconOverlay(appIcon);
@@ -206,9 +215,9 @@
          * the overlay will be drawn with the max size of the start and end bounds in different
          * rotation.
          */
-        public static int getOverlaySize(Rect appBounds, Rect destinationBounds) {
-            final int appWidth = appBounds.width();
-            final int appHeight = appBounds.height();
+        public static int getOverlaySize(Rect overlayBounds, Rect destinationBounds) {
+            final int appWidth = overlayBounds.width();
+            final int appHeight = overlayBounds.height();
 
             return Math.max(Math.max(appWidth, appHeight),
                     Math.max(destinationBounds.width(), destinationBounds.height())) + 1;
@@ -230,15 +239,15 @@
             mTmpTransform.reset();
             // In order for the overlay to always cover the pip window, the overlay may have a
             // size larger than the pip window. Make sure that app icon is at the center.
-            final int appBoundsCenterX = mAppBounds.centerX();
-            final int appBoundsCenterY = mAppBounds.centerY();
+            final int appBoundsCenterX = mRelativeAppBounds.centerX();
+            final int appBoundsCenterY = mRelativeAppBounds.centerY();
             mTmpTransform.setTranslate(
                     appBoundsCenterX - mOverlayHalfSize,
                     appBoundsCenterY - mOverlayHalfSize);
             // Scale back the bitmap with the pivot point at center.
             final float scale = Math.min(
-                    (float) mAppBounds.width() / currentBounds.width(),
-                    (float) mAppBounds.height() / currentBounds.height());
+                    (float) mRelativeAppBounds.width() / currentBounds.width(),
+                    (float) mRelativeAppBounds.height() / currentBounds.height());
             mTmpTransform.postScale(scale, scale, appBoundsCenterX, appBoundsCenterY);
             atomicTx.setMatrix(mLeash, mTmpTransform, mTmpFloat9)
                     .setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index f59fed9..dfe76b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -487,6 +487,20 @@
         return mHomeTaskOverlayContainer;
     }
 
+    /**
+     * Returns the home task surface, not for wide use.
+     */
+    @Nullable
+    public SurfaceControl getHomeTaskSurface() {
+        for (int i = 0; i < mTasks.size(); i++) {
+            final TaskAppearedInfo info = mTasks.valueAt(i);
+            if (info.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) {
+                return info.getLeash();
+            }
+        }
+        return null;
+    }
+
     @Override
     public void addStartingWindow(StartingWindowInfo info) {
         if (mStartingWindow != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index ae0485f..b9a3050 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -29,6 +29,7 @@
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
 import static com.android.window.flags.Flags.migratePredictiveBackTransition;
 import static com.android.window.flags.Flags.predictiveBackSystemAnims;
+import static com.android.window.flags.Flags.unifyBackNavigationTransition;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
 import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
 
@@ -1279,6 +1280,13 @@
             if (transition == mClosePrepareTransition && aborted) {
                 mClosePrepareTransition = null;
                 applyFinishOpenTransition();
+            } else if (!aborted && unifyBackNavigationTransition()) {
+                // Since the closing target participates in the predictive back transition, the
+                // merged transition must be applied with the first transition to ensure a seamless
+                // animation.
+                if (mFinishOpenTransaction != null && finishTransaction != null) {
+                    mFinishOpenTransaction.merge(finishTransaction);
+                }
             }
         }
 
@@ -1300,7 +1308,7 @@
             final TransitionInfo init = mOpenTransitionInfo;
             // Find prepare open target
             boolean openShowWallpaper = false;
-            final ArrayList<OpenChangeInfo> targets = new ArrayList<>();
+            final ArrayList<SurfaceControl> openSurfaces = new ArrayList<>();
             int tmpSize;
             for (int j = init.getChanges().size() - 1; j >= 0; --j) {
                 final TransitionInfo.Change change = init.getChanges().get(j);
@@ -1313,13 +1321,13 @@
                             && openToken == null) {
                         continue;
                     }
-                    targets.add(new OpenChangeInfo(openComponent, openTaskId, openToken));
+                    openSurfaces.add(change.getLeash());
                     if (change.hasFlags(FLAG_SHOW_WALLPAPER)) {
                         openShowWallpaper = true;
                     }
                 }
             }
-            if (targets.isEmpty()) {
+            if (openSurfaces.isEmpty()) {
                 // This shouldn't happen, but if that happen, consume the initial transition anyway.
                 Log.e(TAG, "Unable to merge following transition, cannot find the gesture "
                         + "animated target from the open transition=" + mOpenTransitionInfo);
@@ -1331,7 +1339,7 @@
             tmpSize = info.getChanges().size();
             for (int j = 0; j < tmpSize; ++j) {
                 final TransitionInfo.Change change = info.getChanges().get(j);
-                if (isOpenChangeMatched(targets, change)) {
+                if (isOpenSurfaceMatched(openSurfaces, change)) {
                     // This is original close target, potential be close, but cannot determine
                     // from it.
                     if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
@@ -1352,7 +1360,7 @@
                 boolean mergePredictive = false;
                 for (int j = info.getChanges().size() - 1; j >= 0; --j) {
                     final TransitionInfo.Change change = info.getChanges().get(j);
-                    if (isOpenChangeMatched(targets, change)) {
+                    if (isOpenSurfaceMatched(openSurfaces, change)) {
                         if (TransitionUtil.isClosingMode(change.getMode())) {
                             excludeOpenTarget = true;
                         }
@@ -1373,7 +1381,7 @@
                         if (change.hasFlags(FLAG_IS_WALLPAPER)) {
                             continue;
                         }
-                        if (isOpenChangeMatched(targets, change)) {
+                        if (isOpenSurfaceMatched(openSurfaces, change)) {
                             if (excludeOpenTarget) {
                                 // App has triggered another change during predictive back
                                 // transition, filter out predictive back target.
@@ -1408,7 +1416,7 @@
                 if (nonBackClose && nonBackOpen) {
                     for (int j = info.getChanges().size() - 1; j >= 0; --j) {
                         final TransitionInfo.Change change = info.getChanges().get(j);
-                        if (isOpenChangeMatched(targets, change)) {
+                        if (isOpenSurfaceMatched(openSurfaces, change)) {
                             info.getChanges().remove(j);
                         } else if ((openShowWallpaper && change.hasFlags(FLAG_IS_WALLPAPER))) {
                             info.getChanges().remove(j);
@@ -1692,22 +1700,10 @@
         return INVALID_TASK_ID;
     }
 
-    private static boolean isSameChangeTarget(ComponentName topActivity, int taskId,
-            WindowContainerToken token, TransitionInfo.Change change) {
-        final ComponentName openChange = findComponentName(change);
-        final int firstTaskId = findTaskId(change);
-        final WindowContainerToken openToken = findToken(change);
-        return (openChange != null && openChange.equals(topActivity))
-                || (firstTaskId != INVALID_TASK_ID && firstTaskId == taskId)
-                || (openToken != null && openToken.equals(token));
-    }
-
-    static boolean isOpenChangeMatched(@NonNull ArrayList<OpenChangeInfo> targets,
+    static boolean isOpenSurfaceMatched(@NonNull ArrayList<SurfaceControl> openSurfaces,
             TransitionInfo.Change change) {
-        for (int i = targets.size() - 1; i >= 0; --i) {
-            final OpenChangeInfo next = targets.get(i);
-            if (isSameChangeTarget(next.mOpenComponent, next.mOpenTaskId, next.mOpenToken,
-                    change)) {
+        for (int i = openSurfaces.size() - 1; i >= 0; --i) {
+            if (openSurfaces.get(i).isSameSurface(change.getLeash())) {
                 return true;
             }
         }
@@ -1771,16 +1767,4 @@
             }
         }
     }
-
-    static class OpenChangeInfo {
-        final ComponentName mOpenComponent;
-        final int mOpenTaskId;
-        final WindowContainerToken mOpenToken;
-        OpenChangeInfo(ComponentName openComponent, int openTaskId,
-                WindowContainerToken openToken) {
-            mOpenComponent = openComponent;
-            mOpenTaskId = openTaskId;
-            mOpenToken = openToken;
-        }
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d1246d3..14f8cc7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1212,7 +1212,7 @@
      */
     public void startBubbleDrag(String bubbleKey) {
         if (mBubbleData.getSelectedBubble() != null) {
-            mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ false);
+            collapseExpandedViewForBubbleBar();
         }
         if (mBubbleStateListener != null) {
             boolean overflow = BubbleOverflow.KEY.equals(bubbleKey);
@@ -1304,6 +1304,7 @@
         if (BubbleOverflow.KEY.equals(key)) {
             mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow());
             mLayerView.showExpandedView(mBubbleData.getOverflow());
+            mLogger.log(BubbleLogger.Event.BUBBLE_BAR_EXPANDED);
             return;
         }
 
@@ -1315,6 +1316,7 @@
             // already in the stack
             mBubbleData.setSelectedBubbleFromLauncher(b);
             mLayerView.showExpandedView(b);
+            mLogger.log(b, BubbleLogger.Event.BUBBLE_BAR_EXPANDED);
         } else if (mBubbleData.hasOverflowBubbleWithKey(b.getKey())) {
             // TODO: (b/271468319) handle overflow
         } else {
@@ -1630,6 +1632,7 @@
         if (!isShowingAsBubbleBar()) {
             callback = b -> {
                 if (mStackView != null) {
+                    b.setSuppressFlyout(true);
                     mStackView.addBubble(b);
                     mStackView.setSelectedBubble(b);
                 } else {
@@ -2024,12 +2027,16 @@
         public void expansionChanged(boolean isExpanded) {
             // in bubble bar mode, let the request to show the expanded view come from launcher.
             // only collapse here if we're collapsing.
-            if (mLayerView != null && !isExpanded) {
-                if (mBubblePositioner.isImeVisible()) {
-                    // If we're collapsing, hide the IME
-                    hideCurrentInputMethod();
-                }
-                mLayerView.collapse();
+            if (!isExpanded) {
+                collapseExpandedViewForBubbleBar();
+            }
+
+            BubbleLogger.Event event = isExpanded ? BubbleLogger.Event.BUBBLE_BAR_EXPANDED
+                    : BubbleLogger.Event.BUBBLE_BAR_COLLAPSED;
+            if (mBubbleData.getSelectedBubble() instanceof Bubble bubble) {
+                mLogger.log(bubble, event);
+            } else {
+                mLogger.log(event);
             }
         }
 
@@ -2182,6 +2189,16 @@
         }
     }
 
+    private void collapseExpandedViewForBubbleBar() {
+        if (mLayerView != null && mLayerView.isExpanded()) {
+            if (mBubblePositioner.isImeVisible()) {
+                // If we're collapsing, hide the IME
+                hideCurrentInputMethod();
+            }
+            mLayerView.collapse();
+        }
+    }
+
     private void updateOverflowButtonDot() {
         BubbleOverflow overflow = mBubbleData.getOverflow();
         if (overflow == null) return;
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/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
index b29e49a..813772f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -71,6 +71,11 @@
      */
     private static final int SNAP_MODE_MINIMIZED = 3;
 
+    /**
+     * A mode where apps can be "flexibly offscreen" on smaller displays.
+     */
+    private static final int SNAP_FLEXIBLE_SPLIT = 4;
+
     private final float mMinFlingVelocityPxPerSecond;
     private final float mMinDismissVelocityPxPerSecond;
     private final int mDisplayWidth;
@@ -78,6 +83,7 @@
     private final int mDividerSize;
     private final ArrayList<SnapTarget> mTargets = new ArrayList<>();
     private final Rect mInsets = new Rect();
+    private final Rect mPinnedTaskbarInsets = new Rect();
     private final int mSnapMode;
     private final boolean mFreeSnapMode;
     private final int mMinimalSizeResizableTask;
@@ -88,6 +94,8 @@
     /** Allows split ratios that go offscreen (a.k.a. "flexible split") */
     private final boolean mAllowOffscreenRatios;
     private final boolean mIsLeftRightSplit;
+    /** In SNAP_MODE_MINIMIZED, the side of the screen on which an app will "dock" when minimized */
+    private final int mDockSide;
 
     /** The first target which is still splitting the screen */
     private final SnapTarget mFirstSplitTarget;
@@ -101,14 +109,14 @@
 
 
     public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
-            boolean isLeftRightSplit, Rect insets, int dockSide) {
+            boolean isLeftRightSplit, Rect insets, Rect pinnedTaskbarInsets, int dockSide) {
         this(res, displayWidth, displayHeight, dividerSize, isLeftRightSplit, insets,
-                dockSide, false /* minimized */, true /* resizable */);
+                pinnedTaskbarInsets, dockSide, false /* minimized */, true /* resizable */);
     }
 
     public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
-            boolean isLeftRightSplit, Rect insets, int dockSide, boolean isMinimizedMode,
-            boolean isHomeResizable) {
+            boolean isLeftRightSplit, Rect insets, Rect pinnedTaskbarInsets, int dockSide,
+            boolean isMinimizedMode, boolean isHomeResizable) {
         mMinFlingVelocityPxPerSecond =
                 MIN_FLING_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
         mMinDismissVelocityPxPerSecond =
@@ -117,10 +125,11 @@
         mDisplayWidth = displayWidth;
         mDisplayHeight = displayHeight;
         mIsLeftRightSplit = isLeftRightSplit;
+        mDockSide = dockSide;
         mInsets.set(insets);
+        mPinnedTaskbarInsets.set(pinnedTaskbarInsets);
         if (Flags.enableFlexibleTwoAppSplit()) {
-            // In flexible split, we always use a fixed ratio (50%, 66%, or 90%) for splitting
-            mSnapMode = SNAP_FIXED_RATIO;
+            mSnapMode = SNAP_FLEXIBLE_SPLIT;
         } else {
             // Set SNAP_MODE_MINIMIZED, SNAP_MODE_16_9, or SNAP_FIXED_RATIO depending on config
             mSnapMode = isMinimizedMode
@@ -140,7 +149,7 @@
         mAllowOffscreenRatios = SplitScreenUtils.allowOffscreenRatios(res);
         mTaskHeightInMinimizedMode = isHomeResizable ? res.getDimensionPixelSize(
                 com.android.internal.R.dimen.task_height_of_minimized_mode) : 0;
-        calculateTargets(isLeftRightSplit, dockSide);
+        calculateTargets();
         mFirstSplitTarget = mTargets.get(1);
         mLastSplitTarget = mTargets.get(mTargets.size() - 2);
         mDismissStartTarget = mTargets.get(0);
@@ -276,28 +285,31 @@
         return mTargets.get(minIndex);
     }
 
-    private void calculateTargets(boolean isLeftRightSplit, int dockedSide) {
+    private void calculateTargets() {
         mTargets.clear();
-        int dividerMax = isLeftRightSplit
+        int dividerMax = mIsLeftRightSplit
                 ? mDisplayWidth
                 : mDisplayHeight;
         int startPos = -mDividerSize;
-        if (dockedSide == DOCKED_RIGHT) {
+        if (mDockSide == DOCKED_RIGHT) {
             startPos += mInsets.left;
         }
         mTargets.add(new SnapTarget(startPos, SNAP_TO_START_AND_DISMISS, 0.35f));
         switch (mSnapMode) {
             case SNAP_MODE_16_9:
-                addRatio16_9Targets(isLeftRightSplit, dividerMax);
+                addRatio16_9Targets(mIsLeftRightSplit, dividerMax);
                 break;
             case SNAP_FIXED_RATIO:
-                addFixedDivisionTargets(isLeftRightSplit, dividerMax);
+                addFixedDivisionTargets(mIsLeftRightSplit, dividerMax);
                 break;
             case SNAP_ONLY_1_1:
-                addMiddleTarget(isLeftRightSplit);
+                addMiddleTarget(mIsLeftRightSplit);
                 break;
             case SNAP_MODE_MINIMIZED:
-                addMinimizedTarget(isLeftRightSplit, dockedSide);
+                addMinimizedTarget(mIsLeftRightSplit, mDockSide);
+                break;
+            case SNAP_FLEXIBLE_SPLIT:
+                addFlexSplitTargets(mIsLeftRightSplit, dividerMax);
                 break;
         }
         mTargets.add(new SnapTarget(dividerMax, SNAP_TO_END_AND_DISMISS, 0.35f));
@@ -321,18 +333,9 @@
                 ? mDisplayWidth - mInsets.right
                 : mDisplayHeight - mInsets.bottom;
 
-        int size;
-        if (Flags.enableFlexibleTwoAppSplit()) {
-            float ratio = areOffscreenRatiosSupported()
-                    ? SplitLayout.OFFSCREEN_ASYMMETRIC_RATIO
-                    : SplitLayout.ONSCREEN_ONLY_ASYMMETRIC_RATIO;
-            size = (int) (ratio * (end - start)) - mDividerSize / 2;
-        } else {
-            size = (int) (mFixedRatio * (end - start)) - mDividerSize / 2;
-
-            if (mCalculateRatiosBasedOnAvailableSpace) {
-                size = Math.max(size, mMinimalSizeResizableTask);
-            }
+        int size = (int) (mFixedRatio * (end - start)) - mDividerSize / 2;
+        if (mCalculateRatiosBasedOnAvailableSpace) {
+            size = Math.max(size, mMinimalSizeResizableTask);
         }
 
         int topPosition = start + size;
@@ -340,6 +343,24 @@
         addNonDismissingTargets(isLeftRightSplit, topPosition, bottomPosition, dividerMax);
     }
 
+    private void addFlexSplitTargets(boolean isLeftRightSplit, int dividerMax) {
+        int start = 0;
+        int end = isLeftRightSplit ? mDisplayWidth : mDisplayHeight;
+        int pinnedTaskbarShiftStart = isLeftRightSplit
+                ? mPinnedTaskbarInsets.left : mPinnedTaskbarInsets.top;
+        int pinnedTaskbarShiftEnd = isLeftRightSplit
+                ? mPinnedTaskbarInsets.right : mPinnedTaskbarInsets.bottom;
+
+        float ratio = areOffscreenRatiosSupported()
+                ? SplitLayout.OFFSCREEN_ASYMMETRIC_RATIO
+                : SplitLayout.ONSCREEN_ONLY_ASYMMETRIC_RATIO;
+        int size = (int) (ratio * (end - start)) - mDividerSize / 2;
+
+        int leftTopPosition = start + pinnedTaskbarShiftStart + size;
+        int rightBottomPosition = end - pinnedTaskbarShiftEnd - size - mDividerSize;
+        addNonDismissingTargets(isLeftRightSplit, leftTopPosition, rightBottomPosition, dividerMax);
+    }
+
     private void addRatio16_9Targets(boolean isLeftRightSplit, int dividerMax) {
         int start = isLeftRightSplit ? mInsets.left : mInsets.top;
         int end = isLeftRightSplit
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index b1e0e9e..dab30b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -49,10 +49,13 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.Insets;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Handler;
 import android.view.Display;
+import android.view.InsetsController;
+import android.view.InsetsSource;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.RoundedCorner;
@@ -153,6 +156,7 @@
     private final ResizingEffectPolicy mSurfaceEffectPolicy;
     private final ShellTaskOrganizer mTaskOrganizer;
     private final InsetsState mInsetsState = new InsetsState();
+    private Insets mPinnedTaskbarInsets = Insets.NONE;
 
     private Context mContext;
     @VisibleForTesting DividerSnapAlgorithm mDividerSnapAlgorithm;
@@ -523,6 +527,7 @@
     @Override
     public void insetsChanged(InsetsState insetsState) {
         mInsetsState.set(insetsState);
+
         if (!mInitialized) {
             return;
         }
@@ -531,9 +536,51 @@
             // flicker.
             return;
         }
+
+        // Check to see if insets changed in such a way that the divider algorithm needs to be
+        // recalculated.
+        Insets pinnedTaskbarInsets = calculatePinnedTaskbarInsets(insetsState);
+        if (!mPinnedTaskbarInsets.equals(pinnedTaskbarInsets)) {
+            mPinnedTaskbarInsets = pinnedTaskbarInsets;
+            // Refresh the DividerSnapAlgorithm.
+            mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
+            // If the divider is no longer placed on a snap point, animate it to the nearest one.
+            DividerSnapAlgorithm.SnapTarget snapTarget =
+                    findSnapTarget(mDividerPosition, 0, false /* hardDismiss */);
+            if (snapTarget.position != mDividerPosition) {
+                snapToTarget(mDividerPosition, snapTarget,
+                        InsetsController.ANIMATION_DURATION_RESIZE,
+                        InsetsController.RESIZE_INTERPOLATOR);
+            }
+        }
+
         mSplitWindowManager.onInsetsChanged(insetsState);
     }
 
+    /**
+     * Calculates the insets that might trigger a divider algorithm recalculation. Currently, only
+     * pinned Taskbar does this, and only when the IME is not showing.
+     */
+    private Insets calculatePinnedTaskbarInsets(InsetsState insetsState) {
+        if (insetsState.isSourceOrDefaultVisible(InsetsSource.ID_IME, WindowInsets.Type.ime())) {
+            return Insets.NONE;
+        }
+
+        // If IME is not showing...
+        for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
+            final InsetsSource source = insetsState.sourceAt(i);
+            // and Taskbar is pinned...
+            if (source.getType() == WindowInsets.Type.navigationBars()
+                    && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
+                // Return Insets representing the pinned taskbar state.
+                return source.calculateVisibleInsets(mRootBounds);
+            }
+        }
+
+        // Else, divider can calculate based on the full display.
+        return Insets.NONE;
+    }
+
     @Override
     public void insetsControlChanged(InsetsState insetsState,
             InsetsSourceControl[] activeControls) {
@@ -631,8 +678,8 @@
     }
 
     /**
-     * Same as {@link #snapToTarget(int, SnapTarget)}, with default animation duration and
-     * interpolator.
+     * Same as {@link #snapToTarget(int, SnapTarget, int, Interpolator)}, with default animation
+     * duration and interpolator.
      */
     public void snapToTarget(int currentPosition, SnapTarget snapTarget) {
         snapToTarget(currentPosition, snapTarget, FLING_RESIZE_DURATION,
@@ -683,6 +730,7 @@
                 mDividerSize,
                 mIsLeftRightSplit,
                 insets,
+                mPinnedTaskbarInsets.toRect(),
                 mIsLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP /* dockSide */);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
index d1b2347..62d5098 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
@@ -23,9 +23,15 @@
 import com.android.internal.R
 
 // TODO(b/347289970): Consider replacing with API
+/**
+ * If the top activity should be exempt from desktop windowing and forced back to fullscreen.
+ * Currently includes all system ui activities and modal dialogs. However is the top activity is not
+ * being displayed, regardless of its configuration, we will not exempt it as to remain in the
+ * desktop windowing environment.
+ */
 fun isTopActivityExemptFromDesktopWindowing(context: Context, task: TaskInfo) =
-    isSystemUiTask(context, task) || (task.isTopActivityTransparent && task.numActivities == 1
-            && !task.isTopActivityStyleFloating)
+    (isSystemUiTask(context, task) || (task.isTopActivityTransparent && task.numActivities == 1))
+            && !task.isTopActivityNoDisplay
 
 private fun isSystemUiTask(context: Context, task: TaskInfo): Boolean {
     val sysUiPackageName: String =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 6146ecd..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
@@ -688,6 +688,12 @@
 
     private void launchUserAspectRatioSettings(
             @NonNull TaskInfo taskInfo, @NonNull ShellTaskOrganizer.TaskListener taskListener) {
+        launchUserAspectRatioSettings(mContext, taskInfo);
+    }
+
+    /** Launch the user aspect ratio settings for the package of the given task. */
+    public static void launchUserAspectRatioSettings(
+            @NonNull Context context, @NonNull TaskInfo taskInfo) {
         final Intent intent = new Intent(Settings.ACTION_MANAGE_USER_ASPECT_RATIO_SETTINGS);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
@@ -697,7 +703,7 @@
             intent.setData(packageUri);
         }
         final UserHandle userHandle = UserHandle.of(taskInfo.userId);
-        mContext.startActivityAsUser(intent, userHandle);
+        context.startActivityAsUser(intent, userHandle);
     }
 
     @VisibleForTesting
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 2a5a519..77e041e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -401,9 +401,6 @@
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
             RootTaskDisplayAreaOrganizer rootTdaOrganizer) {
-        if (!com.android.window.flags.Flags.explicitRefreshRateHints()) {
-            return Optional.empty();
-        }
         final PerfHintController perfHintController =
                 new PerfHintController(context, shellInit, shellCommandHandler, rootTdaOrganizer);
         return Optional.of(perfHintController.getHinter());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 615dad3..a472f79 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -67,6 +67,7 @@
 import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler;
 import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler;
 import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
+import com.android.wm.shell.desktopmode.DesktopDisplayEventHandler;
 import com.android.wm.shell.desktopmode.DesktopImmersiveController;
 import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler;
 import com.android.wm.shell.desktopmode.DesktopModeDragAndDropTransitionHandler;
@@ -86,7 +87,10 @@
 import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
 import com.android.wm.shell.desktopmode.education.AppHandleEducationController;
 import com.android.wm.shell.desktopmode.education.AppHandleEducationFilter;
+import com.android.wm.shell.desktopmode.education.AppToWebEducationController;
+import com.android.wm.shell.desktopmode.education.AppToWebEducationFilter;
 import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository;
+import com.android.wm.shell.desktopmode.education.data.AppToWebEducationDatastoreRepository;
 import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository;
 import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer;
 import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializerImpl;
@@ -132,6 +136,7 @@
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer;
+import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationPromoController;
 import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController;
 import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel;
 
@@ -288,6 +293,7 @@
             MultiInstanceHelper multiInstanceHelper,
             Optional<DesktopTasksLimiter> desktopTasksLimiter,
             AppHandleEducationController appHandleEducationController,
+            AppToWebEducationController appToWebEducationController,
             WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
             Optional<DesktopActivityOrientationChangeHandler> desktopActivityOrientationHandler,
             FocusTransitionObserver focusTransitionObserver,
@@ -317,6 +323,7 @@
                     multiInstanceHelper,
                     desktopTasksLimiter,
                     appHandleEducationController,
+                    appToWebEducationController,
                     windowDecorCaptionHandleRepository,
                     desktopActivityOrientationHandler,
                     focusTransitionObserver,
@@ -772,7 +779,8 @@
             ShellTaskOrganizer shellTaskOrganizer,
             ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
             ReturnToDragStartAnimator returnToDragStartAnimator,
-            @DynamicOverride DesktopRepository desktopRepository) {
+            @DynamicOverride DesktopRepository desktopRepository,
+            DesktopModeEventLogger desktopModeEventLogger) {
         return new DesktopTilingDecorViewModel(
                 context,
                 displayController,
@@ -782,7 +790,8 @@
                 shellTaskOrganizer,
                 toggleResizeDesktopTaskTransitionHandler,
                 returnToDragStartAnimator,
-                desktopRepository
+                desktopRepository,
+                desktopModeEventLogger
         );
     }
 
@@ -970,7 +979,10 @@
             CloseDesktopTaskTransitionHandler closeDesktopTaskTransitionHandler,
             Optional<DesktopImmersiveController> desktopImmersiveController,
             InteractionJankMonitor interactionJankMonitor,
-            @ShellMainThread Handler handler) {
+            @ShellMainThread Handler handler,
+            ShellInit shellInit,
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
+    ) {
         if (!DesktopModeStatus.canEnterDesktopMode(context)) {
             return Optional.empty();
         }
@@ -983,7 +995,9 @@
                         closeDesktopTaskTransitionHandler,
                         desktopImmersiveController.get(),
                         interactionJankMonitor,
-                        handler));
+                        handler,
+                        shellInit,
+                        rootTaskDisplayAreaOrganizer));
     }
 
     @WMSingleton
@@ -1005,6 +1019,30 @@
 
     @WMSingleton
     @Provides
+    static Optional<DesktopDisplayEventHandler> provideDesktopDisplayEventHandler(
+            Context context,
+            ShellInit shellInit,
+            Transitions transitions,
+            DisplayController displayController,
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+            IWindowManager windowManager
+    ) {
+        if (!DesktopModeStatus.canEnterDesktopMode(context)
+                || !Flags.enableDisplayWindowingModeSwitching()) {
+            return Optional.empty();
+        }
+        return Optional.of(
+                new DesktopDisplayEventHandler(
+                        context,
+                        shellInit,
+                        transitions,
+                        displayController,
+                        rootTaskDisplayAreaOrganizer,
+                        windowManager));
+    }
+
+    @WMSingleton
+    @Provides
     static AppHandleEducationDatastoreRepository provideAppHandleEducationDatastoreRepository(
             Context context) {
         return new AppHandleEducationDatastoreRepository(context);
@@ -1035,6 +1073,20 @@
                 context, additionalSystemViewContainerFactory, displayController);
     }
 
+    @WMSingleton
+    @Provides
+    static DesktopWindowingEducationPromoController provideDesktopWindowingEducationPromoController(
+            Context context,
+            AdditionalSystemViewContainer.Factory additionalSystemViewContainerFactory,
+            DisplayController displayController
+    ) {
+        return new DesktopWindowingEducationPromoController(
+                context,
+                additionalSystemViewContainerFactory,
+                displayController
+        );
+    }
+
     @OptIn(markerClass = ExperimentalCoroutinesApi.class)
     @WMSingleton
     @Provides
@@ -1058,6 +1110,38 @@
 
     @WMSingleton
     @Provides
+    static AppToWebEducationDatastoreRepository provideAppToWebEducationDatastoreRepository(
+            Context context) {
+        return new AppToWebEducationDatastoreRepository(context);
+    }
+
+    @WMSingleton
+    @Provides
+    static AppToWebEducationFilter provideAppToWebEducationFilter(
+            Context context,
+            AppToWebEducationDatastoreRepository appToWebEducationDatastoreRepository) {
+        return new AppToWebEducationFilter(context, appToWebEducationDatastoreRepository);
+    }
+
+    @OptIn(markerClass = ExperimentalCoroutinesApi.class)
+    @WMSingleton
+    @Provides
+    static AppToWebEducationController provideAppToWebEducationController(
+            Context context,
+            AppToWebEducationFilter appToWebEducationFilter,
+            AppToWebEducationDatastoreRepository appToWebEducationDatastoreRepository,
+            WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
+            DesktopWindowingEducationPromoController desktopWindowingEducationPromoController,
+            @ShellMainThread CoroutineScope applicationScope,
+            @ShellBackgroundThread MainCoroutineDispatcher backgroundDispatcher) {
+        return new AppToWebEducationController(context, appToWebEducationFilter,
+                appToWebEducationDatastoreRepository, windowDecorCaptionHandleRepository,
+                desktopWindowingEducationPromoController, applicationScope,
+                backgroundDispatcher);
+    }
+
+    @WMSingleton
+    @Provides
     static DesktopPersistentRepository provideDesktopPersistentRepository(
             Context context, @ShellBackgroundThread CoroutineScope bgScope) {
         return new DesktopPersistentRepository(context, bgScope);
@@ -1123,7 +1207,8 @@
     @Provides
     static Object provideIndependentShellComponentsToCreate(
             DragAndDropController dragAndDropController,
-            Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional) {
+            Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional,
+            Optional<DesktopDisplayEventHandler> desktopDisplayEventHandler) {
         return new Object();
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
new file mode 100644
index 0000000..ba383fa
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.android.wm.shell.desktopmode
+
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.content.Context
+import android.provider.Settings
+import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.IWindowManager
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+
+/** Handles display events in desktop mode */
+class DesktopDisplayEventHandler(
+    private val context: Context,
+    shellInit: ShellInit,
+    private val transitions: Transitions,
+    private val displayController: DisplayController,
+    private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+    private val windowManager: IWindowManager,
+) : OnDisplaysChangedListener {
+
+    init {
+        shellInit.addInitCallback({ onInit() }, this)
+    }
+
+    private fun onInit() {
+        displayController.addDisplayWindowListener(this)
+    }
+
+    override fun onDisplayAdded(displayId: Int) {
+        if (displayId == DEFAULT_DISPLAY) {
+            return
+        }
+        refreshDisplayWindowingMode()
+    }
+
+    override fun onDisplayRemoved(displayId: Int) {
+        if (displayId == DEFAULT_DISPLAY) {
+            return
+        }
+        refreshDisplayWindowingMode()
+    }
+
+    private fun refreshDisplayWindowingMode() {
+        // TODO: b/375319538 - Replace the check with a DisplayManager API once it's available.
+        val isExtendedDisplayEnabled = 0 != Settings.Global.getInt(
+            context.contentResolver, DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0
+        )
+        if (!isExtendedDisplayEnabled) {
+            // No action needed in mirror or projected mode.
+            return
+        }
+
+        val hasNonDefaultDisplay = rootTaskDisplayAreaOrganizer.getDisplayIds()
+            .any { displayId -> displayId != DEFAULT_DISPLAY }
+        val targetDisplayWindowingMode =
+            if (hasNonDefaultDisplay) {
+                WINDOWING_MODE_FREEFORM
+            } else {
+                // Use the default display windowing mode when no non-default display.
+                windowManager.getWindowingMode(DEFAULT_DISPLAY)
+            }
+        val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)
+        requireNotNull(tdaInfo) { "DisplayAreaInfo of DEFAULT_DISPLAY must be non-null." }
+        if (tdaInfo.configuration.windowConfiguration.windowingMode == targetDisplayWindowingMode) {
+            // Already in the target mode.
+            return
+        }
+
+        val wct = WindowContainerTransaction()
+        wct.setWindowingMode(tdaInfo.token, targetDisplayWindowingMode)
+        transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
index 0bc571f..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
@@ -25,6 +25,7 @@
 import android.view.WindowManager
 import android.window.DesktopModeFlags
 import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
 import android.window.TransitionRequestInfo
 import android.window.WindowContainerTransaction
 import androidx.annotation.VisibleForTesting
@@ -32,10 +33,13 @@
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.protolog.ProtoLog
 import com.android.window.flags.Flags
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.freeform.FreeformTaskTransitionHandler
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.TransitionUtil
 import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.MixedTransitionHandler
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
@@ -50,8 +54,14 @@
     private val desktopImmersiveController: DesktopImmersiveController,
     private val interactionJankMonitor: InteractionJankMonitor,
     @ShellMainThread private val handler: Handler,
+    shellInit: ShellInit,
+    private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
 ) : MixedTransitionHandler, FreeformTaskTransitionStarter {
 
+    init {
+        shellInit.addInitCallback ({ transitions.addHandler(this) }, this)
+    }
+
     @VisibleForTesting
     val pendingMixedTransitions = mutableListOf<PendingMixedTransition>()
 
@@ -85,9 +95,11 @@
         @WindowManager.TransitionType transitionType: Int,
         wct: WindowContainerTransaction,
         taskId: Int,
+        minimizingTaskId: Int? = null,
         exitingImmersiveTask: Int? = null,
     ): IBinder {
-        if (!Flags.enableFullyImmersiveInDesktop()) {
+        if (!Flags.enableFullyImmersiveInDesktop() &&
+            !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) {
             return transitions.startTransition(transitionType, wct, /* handler= */ null)
         }
         if (exitingImmersiveTask == null) {
@@ -103,11 +115,17 @@
                 pendingMixedTransitions.add(PendingMixedTransition.Launch(
                     transition = transition,
                     launchingTask = taskId,
-                    exitingImmersiveTask = exitingImmersiveTask
+                    minimizingTask = minimizingTaskId,
+                    exitingImmersiveTask = exitingImmersiveTask,
                 ))
             }
     }
 
+    /** Notifies this handler that there is a pending transition for it to handle. */
+    fun addPendingMixedTransition(pendingMixedTransition: PendingMixedTransition) {
+        pendingMixedTransitions.add(pendingMixedTransition)
+    }
+
     /** Returns null, as it only handles transitions started from Shell. */
     override fun handleRequest(
         transition: IBinder,
@@ -123,7 +141,7 @@
     ): Boolean {
         val pending = pendingMixedTransitions.find { pending -> pending.transition == transition }
             ?: return false.also {
-                logW("Should have pending desktop transition")
+                logV("No pending desktop transition")
             }
         pendingMixedTransitions.remove(pending)
         logV("Animating pending mixed transition: %s", pending)
@@ -187,12 +205,18 @@
         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)
         }
-        val launchChange = findDesktopTaskChange(info, pending.launchingTask)
-            ?: error("Should have pending launching task change")
+        val minimizeChange = pending.minimizingTask?.let { minimizingTask ->
+            findDesktopTaskChange(info, minimizingTask)
+        }
 
         var subAnimationCount = -1
         var combinedWct: WindowContainerTransaction? = null
@@ -204,9 +228,17 @@
         }
 
         logV(
-            "Animating pending mixed launch transition task#%d immersiveExitTask#%s",
-            launchChange.taskInfo!!.taskId, immersiveExitChange?.taskInfo?.taskId
+            "Animating mixed launch transition task#%d, minimizingTask#%s immersiveExitTask#%s",
+            launchChange.taskInfo!!.taskId, minimizeChange?.taskInfo?.taskId,
+            immersiveExitChange?.taskInfo?.taskId
         )
+        if (DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) {
+            // Only apply minimize change reparenting here if we implement the new app launch
+            // transitions, otherwise this reparenting is handled in the default handler.
+            minimizeChange?.let {
+                applyMinimizeChangeReparenting(info, minimizeChange, startTransaction)
+            }
+        }
         if (immersiveExitChange != null) {
             subAnimationCount = 2
             // Animate the immersive exit change separately.
@@ -257,7 +289,7 @@
         change: TransitionInfo.Change,
         startTransaction: SurfaceControl.Transaction,
         finishTransaction: SurfaceControl.Transaction,
-        finishCallback: Transitions.TransitionFinishCallback,
+        finishCallback: TransitionFinishCallback,
     ): Boolean {
         // Starting the jank trace if closing the last window in desktop mode.
         interactionJankMonitor.begin(
@@ -280,6 +312,28 @@
         )
     }
 
+    /**
+     * Reparent the minimizing task back to its root display area.
+     *
+     * During the launch/minimize animation the all animated tasks will be reparented to a
+     * transition leash shown in front of other desktop tasks. Reparenting the minimizing task back
+     * to its root display area ensures that task stays behind other desktop tasks during the
+     * animation.
+     */
+    private fun applyMinimizeChangeReparenting(
+        info: TransitionInfo,
+        minimizeChange: Change,
+        startTransaction: SurfaceControl.Transaction,
+    ) {
+        require(TransitionUtil.isOpeningMode(info.type))
+        require(minimizeChange.taskInfo != null)
+        val taskInfo = minimizeChange.taskInfo!!
+        require(taskInfo.isFreeform)
+        logV("Reparenting minimizing task#%d", taskInfo.taskId)
+        rootTaskDisplayAreaOrganizer.reparentToDisplayArea(
+            taskInfo.displayId, minimizeChange.leash, startTransaction)
+    }
+
     private fun dispatchToLeftoverHandler(
         transition: IBinder,
         info: TransitionInfo,
@@ -341,6 +395,7 @@
         data class Launch(
             override val transition: IBinder,
             val launchingTask: Int,
+            val minimizingTask: Int?,
             val exitingImmersiveTask: Int?,
         ) : PendingMixedTransition()
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index edcc877..c7cf310 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -275,7 +275,7 @@
         }
 
         // Then check if the activity is portrait when letterboxed
-        appCompatTaskInfo.isTopActivityLetterboxed -> appCompatTaskInfo.isTopActivityPillarboxed
+        appCompatTaskInfo.isTopActivityLetterboxed -> appCompatTaskInfo.isTopActivityPillarboxShaped
 
         // Then check if the activity is portrait
         appBounds != null -> appBounds.height() > appBounds.width()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 515b203d..162879c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -554,19 +554,6 @@
         }
     }
 
-    /** Move a desktop app to split screen. */
-    fun moveToSplit(task: RunningTaskInfo) {
-        logV( "moveToSplit taskId=%s", task.taskId)
-        desktopTilingDecorViewModel.removeTaskIfTiled(task.displayId, task.taskId)
-        val wct = WindowContainerTransaction()
-        wct.setBounds(task.token, Rect())
-        // Rather than set windowing mode to multi-window at task level, set it to
-        // undefined and inherit from split stage.
-        wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED)
-
-        transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
-    }
-
     private fun exitSplitIfApplicable(wct: WindowContainerTransaction, taskInfo: RunningTaskInfo) {
         if (splitScreenController.isTaskInSplitScreen(taskInfo.taskId)) {
             splitScreenController.prepareExitSplitScreen(
@@ -698,6 +685,7 @@
                 transitionType = transitionType,
                 wct = wct,
                 taskId = taskId,
+                minimizingTaskId = taskIdToMinimize,
                 exitingImmersiveTask = exitingImmersiveTask,
             )
             taskIdToMinimize?.let { addPendingMinimizeTransition(t, it) }
@@ -1434,6 +1422,7 @@
                 )
             val transition = transitions.startTransition(TRANSIT_OPEN, wct, null)
             taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
+            addPendingAppLaunchTransition(transition, requestedTaskId, taskIdToMinimize)
             exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
         } else {
             val splitPosition = splitScreenController.determineNewInstancePosition(callingTask)
@@ -1574,6 +1563,7 @@
         desktopImmersiveController.exitImmersiveIfApplicable(transition, wct, task.displayId)
         // 2) minimize a Task if needed.
         val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
+        addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize)
         if (taskIdToMinimize != null) {
             addPendingMinimizeTransition(transition, taskIdToMinimize)
             return wct
@@ -1605,6 +1595,7 @@
                 // minimize another Task.
                 val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
                 taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
+                addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize)
                 desktopImmersiveController.exitImmersiveIfApplicable(
                     transition, wct, task.displayId
                 )
@@ -1785,6 +1776,20 @@
         }
     }
 
+    private fun addPendingAppLaunchTransition(
+        transition: IBinder,
+        launchTaskId: Int,
+        minimizeTaskId: Int?,
+    ) {
+        if (!DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) {
+            return
+        }
+        // TODO b/359523924: pass immersive task here?
+        desktopMixedTransitionHandler.addPendingMixedTransition(
+            DesktopMixedTransitionHandler.PendingMixedTransition.Launch(
+                transition, launchTaskId, minimizeTaskId, /* exitingImmersiveTask= */ null))
+    }
+
     fun removeDesktop(displayId: Int) {
         if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return
 
@@ -2032,6 +2037,18 @@
     }
 
     /**
+     * Cancel the drag-to-desktop transition.
+     *
+     * @param taskInfo the task being dragged.
+     */
+    fun onDragPositioningCancelThroughStatusBar(
+        taskInfo: RunningTaskInfo,
+    ) {
+        interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
+        cancelDragToDesktop(taskInfo)
+    }
+
+    /**
      * Perform checks required when drag ends under status bar area.
      *
      * @param taskInfo the task being dragged.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index 7bcc5d1..f0e3a2b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -133,7 +133,6 @@
         }
 
         override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
-            logV("transition %s finished", transition)
             if (activeTransitionTokensAndTasks.remove(transition) != null) {
                 if (aborted) {
                     interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)
@@ -252,10 +251,6 @@
         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
     }
 
-    private fun logE(msg: String, vararg arguments: Any?) {
-        ProtoLog.e(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
-    }
-
     private companion object {
         const val TAG = "DesktopTasksLimiter"
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
index 96719fa..9411150 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -21,6 +21,7 @@
 import android.animation.ValueAnimator
 import android.graphics.Rect
 import android.os.IBinder
+import android.view.Choreographer
 import android.view.SurfaceControl
 import android.view.WindowManager.TRANSIT_CHANGE
 import android.window.TransitionInfo
@@ -126,6 +127,7 @@
                         tx.setPosition(leash, rect.left.toFloat(), rect.top.toFloat())
                             .setWindowCrop(leash, rect.width(), rect.height())
                             .show(leash)
+                            .setFrameTimeline(Choreographer.getInstance().getVsyncId())
                         onTaskResizeAnimationListener.onBoundsChange(taskId, tx, rect)
                     }
                     start()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt
index 7ae5370..8bfcca0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt
@@ -18,6 +18,10 @@
 
 import android.app.ActivityManager.RunningTaskInfo
 import android.graphics.Rect
+import com.android.wm.shell.desktopmode.CaptionState.AppHandle
+import com.android.wm.shell.desktopmode.CaptionState.AppHeader
+import com.android.wm.shell.desktopmode.CaptionState.NoCaption
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 
@@ -26,11 +30,20 @@
   private val _captionStateFlow = MutableStateFlow<CaptionState>(CaptionState.NoCaption)
   /** Observer for app handle state changes. */
   val captionStateFlow: StateFlow<CaptionState> = _captionStateFlow
+  private val _appToWebUsageFlow = MutableSharedFlow<Unit>()
+  /** Observer for App-to-Web usage. */
+  val appToWebUsageFlow = _appToWebUsageFlow
+
 
   /** Notifies [captionStateFlow] if there is a change to caption state. */
   fun notifyCaptionChanged(captionState: CaptionState) {
     _captionStateFlow.value = captionState
   }
+
+  /** Notifies [appToWebUsageFlow] if App-to-Web feature is used. */
+  fun onAppToWebUsage() {
+    _appToWebUsageFlow.tryEmit(Unit)
+  }
 }
 
 /**
@@ -41,17 +54,19 @@
  * * [AppHeader]: Indicating that there is at least one visible app chip on the screen.
  * * [NoCaption]: Signifying that no caption handle is currently visible on the device.
  */
-sealed class CaptionState {
+sealed class CaptionState{
   data class AppHandle(
-      val runningTaskInfo: RunningTaskInfo,
-      val isHandleMenuExpanded: Boolean,
-      val globalAppHandleBounds: Rect
+    val runningTaskInfo: RunningTaskInfo,
+    val isHandleMenuExpanded: Boolean,
+    val globalAppHandleBounds: Rect,
+    val isCapturedLinkAvailable: Boolean
   ) : CaptionState()
 
   data class AppHeader(
-      val runningTaskInfo: RunningTaskInfo,
-      val isHeaderMenuExpanded: Boolean,
-      val globalAppChipBounds: Rect
+    val runningTaskInfo: RunningTaskInfo,
+    val isHeaderMenuExpanded: Boolean,
+    val globalAppChipBounds: Rect,
+    val isCapturedLinkAvailable: Boolean
   ) : CaptionState()
 
   data object NoCaption : CaptionState()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
index f21a124..e01c448 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
@@ -36,7 +36,7 @@
 import com.android.wm.shell.windowdecor.common.DecorThemeUtil
 import com.android.wm.shell.windowdecor.common.Theme
 import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController
-import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.EducationViewConfig
+import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipEducationViewConfig
 import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipColorScheme
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
@@ -137,7 +137,7 @@
     // TODO: b/370546801 - Differentiate between user dismissing the tooltip vs following the cue.
     // Populate information important to inflate app handle education tooltip.
     val appHandleTooltipConfig =
-        EducationViewConfig(
+        TooltipEducationViewConfig(
             tooltipViewLayout = R.layout.desktop_windowing_education_top_arrow_tooltip,
             tooltipColorScheme = tooltipColorScheme,
             tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
@@ -196,7 +196,7 @@
                       windowingOptionPillHeight / 2)
           // Populate information important to inflate windowing image button education tooltip.
           val windowingImageButtonTooltipConfig =
-              EducationViewConfig(
+              TooltipEducationViewConfig(
                   tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip,
                   tooltipColorScheme = tooltipColorScheme,
                   tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
@@ -249,7 +249,7 @@
                   globalAppChipBounds.top + globalAppChipBounds.height() / 2)
           // Populate information important to inflate exit desktop mode education tooltip.
           val exitWindowingTooltipConfig =
-              EducationViewConfig(
+              TooltipEducationViewConfig(
                   tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip,
                   tooltipColorScheme = tooltipColorScheme,
                   tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt
new file mode 100644
index 0000000..693da81
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode.education
+
+import android.annotation.DimenRes
+import android.annotation.StringRes
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Point
+import android.os.SystemProperties
+import androidx.compose.ui.graphics.toArgb
+import com.android.window.flags.Flags
+import com.android.wm.shell.R
+import com.android.wm.shell.desktopmode.CaptionState
+import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository
+import com.android.wm.shell.desktopmode.education.data.AppToWebEducationDatastoreRepository
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.canEnterDesktopMode
+import com.android.wm.shell.windowdecor.common.DecorThemeUtil
+import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationPromoController
+import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationPromoController.EducationColorScheme
+import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationPromoController.EducationViewConfig
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.MainCoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * Controls App-to-Web education end to end.
+ *
+ * Listen to usages of App-to-Web, calls an api to check if the education
+ * should be shown and controls education UI.
+ */
+@OptIn(kotlinx.coroutines.FlowPreview::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class AppToWebEducationController(
+    private val context: Context,
+    private val appToWebEducationFilter: AppToWebEducationFilter,
+    private val appToWebEducationDatastoreRepository: AppToWebEducationDatastoreRepository,
+    private val windowDecorCaptionHandleRepository: WindowDecorCaptionHandleRepository,
+    private val windowingEducationViewController: DesktopWindowingEducationPromoController,
+    @ShellMainThread private val applicationCoroutineScope: CoroutineScope,
+    @ShellBackgroundThread private val backgroundDispatcher: MainCoroutineDispatcher,
+) {
+    private val decorThemeUtil = DecorThemeUtil(context)
+
+    init {
+        runIfEducationFeatureEnabled {
+            applicationCoroutineScope.launch {
+                // Central block handling the App-to-Web's educational flow end-to-end.
+                isEducationViewLimitReachedFlow()
+                    .flatMapLatest { countExceedsMaximum ->
+                        if (countExceedsMaximum) {
+                            // If the education has been viewed the maximum amount of times then
+                            // return emptyFlow() that completes immediately. This will help us to
+                            // not listen to [captionHandleStateFlow] after the education should
+                            // not be shown.
+                            emptyFlow()
+                        } else {
+                            // Listen for changes to window decor's caption.
+                            windowDecorCaptionHandleRepository.captionStateFlow
+                                // Wait for few seconds before emitting the latest state.
+                                .debounce(APP_TO_WEB_EDUCATION_DELAY_MILLIS)
+                                .filter { captionState ->
+                                    captionState !is CaptionState.NoCaption &&
+                                            appToWebEducationFilter
+                                                .shouldShowAppToWebEducation(captionState)
+                                }
+                        }
+                    }
+                    .flowOn(backgroundDispatcher)
+                    .collectLatest { captionState ->
+                        val educationColorScheme = educationColorScheme(captionState)
+                        showEducation(captionState, educationColorScheme!!)
+                        // After showing first tooltip, increase count of education views
+                        appToWebEducationDatastoreRepository.updateEducationShownCount()
+                    }
+            }
+
+            applicationCoroutineScope.launch {
+                if (isFeatureUsed()) return@launch
+                windowDecorCaptionHandleRepository.appToWebUsageFlow
+                    .collect {
+                        // If user utilizes App-to-Web, mark user has used the feature
+                        appToWebEducationDatastoreRepository
+                            .updateFeatureUsedTimestampMillis(isViewed = true)
+                    }
+            }
+        }
+    }
+
+    private inline fun runIfEducationFeatureEnabled(block: () -> Unit) {
+        if (canEnterDesktopMode(context) && Flags.enableDesktopWindowingAppToWebEducation()) block()
+    }
+
+    private fun showEducation(captionState: CaptionState, colorScheme: EducationColorScheme) {
+        val educationGlobalCoordinates: Point
+        val taskId: Int
+        when (captionState) {
+            is CaptionState.AppHandle -> {
+                val appHandleBounds = captionState.globalAppHandleBounds
+                val educationWidth =
+                    loadDimensionPixelSize(R.dimen.desktop_windowing_education_promo_width)
+                educationGlobalCoordinates = Point(
+                    appHandleBounds.centerX() - educationWidth / 2,
+                    appHandleBounds.bottom
+                )
+                taskId = captionState.runningTaskInfo.taskId
+            }
+
+            is CaptionState.AppHeader -> {
+                val taskBounds =
+                    captionState.runningTaskInfo.configuration.windowConfiguration.bounds
+                educationGlobalCoordinates =
+                    Point(taskBounds.left, captionState.globalAppChipBounds.bottom)
+                taskId = captionState.runningTaskInfo.taskId
+            }
+
+            else -> return
+        }
+
+        // Populate information important to inflate education promo.
+        val educationConfig =
+            EducationViewConfig(
+                viewLayout = R.layout.desktop_windowing_education_promo,
+                educationColorScheme = colorScheme,
+                viewGlobalCoordinates = educationGlobalCoordinates,
+                educationText = getString(R.string.desktop_windowing_app_to_web_education_text),
+                widthId = R.dimen.desktop_windowing_education_promo_width,
+                heightId = R.dimen.desktop_windowing_education_promo_height
+            )
+
+        windowingEducationViewController.showEducation(
+            viewConfig = educationConfig, taskId = taskId)
+    }
+
+    private fun educationColorScheme(captionState: CaptionState): EducationColorScheme? {
+        val taskInfo: RunningTaskInfo = when (captionState) {
+            is CaptionState.AppHandle -> captionState.runningTaskInfo
+            is CaptionState.AppHeader -> captionState.runningTaskInfo
+            else -> return null
+        }
+
+        val colorScheme = decorThemeUtil.getColorScheme(taskInfo)
+        val tooltipContainerColor = colorScheme.surfaceBright.toArgb()
+        val tooltipTextColor = colorScheme.onSurface.toArgb()
+        return EducationColorScheme(tooltipContainerColor, tooltipTextColor)
+    }
+
+    /**
+     * Listens to changes in the number of times the education has been viewed, mapping the count to
+     * true if the education has been viewed the maximum amount of times.
+     */
+    private fun isEducationViewLimitReachedFlow(): Flow<Boolean> =
+        appToWebEducationDatastoreRepository.dataStoreFlow
+            .map { preferences ->
+                appToWebEducationFilter.isEducationViewLimitReached(preferences)}
+            .distinctUntilChanged()
+
+    /**
+     * Listens to the changes to [WindowingEducationProto#hasFeatureUsedTimestampMillis()] in
+     * datastore proto object.
+     */
+    private suspend fun isFeatureUsed(): Boolean =
+        appToWebEducationDatastoreRepository.dataStoreFlow.first().hasFeatureUsedTimestampMillis()
+
+    private fun loadDimensionPixelSize(@DimenRes resourceId: Int): Int {
+        if (resourceId == Resources.ID_NULL) return 0
+        return context.resources.getDimensionPixelSize(resourceId)
+    }
+
+    private fun getString(@StringRes resId: Int): String = context.resources.getString(resId)
+
+    companion object {
+        const val TAG = "AppToWebEducationController"
+        val APP_TO_WEB_EDUCATION_DELAY_MILLIS: Long
+            get() = SystemProperties.getLong(
+                "persist.windowing_app_handle_education_delay",
+                3000L
+            )
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationFilter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationFilter.kt
new file mode 100644
index 0000000..feee6ed
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationFilter.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode.education
+
+import android.annotation.IntegerRes
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.Context
+import android.os.SystemClock
+import android.provider.Settings.Secure
+import com.android.wm.shell.R
+import com.android.wm.shell.desktopmode.CaptionState
+import com.android.wm.shell.desktopmode.education.data.AppToWebEducationDatastoreRepository
+import com.android.wm.shell.desktopmode.education.data.WindowingEducationProto
+import java.time.Duration
+
+/** Filters incoming App-to-Web education triggers based on set conditions. */
+class AppToWebEducationFilter(
+    private val context: Context,
+    private val appToWebEducationDatastoreRepository: AppToWebEducationDatastoreRepository
+) {
+
+    /** Returns true if conditions to show App-to-web education are met, returns false otherwise. */
+    suspend fun shouldShowAppToWebEducation(captionState: CaptionState): Boolean {
+        val (taskInfo: RunningTaskInfo, isCapturedLinkAvailable: Boolean) = when (captionState) {
+            is CaptionState.AppHandle ->
+                Pair(captionState.runningTaskInfo, captionState.isCapturedLinkAvailable)
+            is CaptionState.AppHeader ->
+                Pair(captionState.runningTaskInfo, captionState.isCapturedLinkAvailable)
+            else -> return false
+        }
+
+        val focusAppPackageName = taskInfo.topActivityInfo?.packageName ?: return false
+        val windowingEducationProto = appToWebEducationDatastoreRepository.windowingEducationProto()
+
+        return !isOtherEducationShowing() &&
+                !isEducationViewLimitReached(windowingEducationProto) &&
+                hasSufficientTimeSinceSetup() &&
+                !isFeatureUsedBefore(windowingEducationProto) &&
+                isCapturedLinkAvailable &&
+                isFocusAppInAllowlist(focusAppPackageName)
+    }
+
+    private fun isFocusAppInAllowlist(focusAppPackageName: String): Boolean =
+        focusAppPackageName in
+                context.resources.getStringArray(
+                    R.array.desktop_windowing_app_to_web_education_allowlist_apps)
+
+    // TODO: b/350953004 - Add checks based on App compat
+    // TODO: b/350951797 - Add checks based on PKT tips education
+    private fun isOtherEducationShowing(): Boolean = isTaskbarEducationShowing() ||
+            isCompatUiEducationShowing()
+
+    private fun isTaskbarEducationShowing(): Boolean =
+        Secure.getInt(context.contentResolver, Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING, 0) == 1
+
+    private fun isCompatUiEducationShowing(): Boolean =
+        Secure.getInt(context.contentResolver, Secure.COMPAT_UI_EDUCATION_SHOWING, 0) == 1
+
+    private fun hasSufficientTimeSinceSetup(): Boolean =
+        Duration.ofMillis(SystemClock.elapsedRealtime()) >
+                convertIntegerResourceToDuration(
+                    R.integer.desktop_windowing_education_required_time_since_setup_seconds)
+
+    /** Returns true if education is viewed maximum amount of times it should be shown. */
+    fun isEducationViewLimitReached(windowingEducationProto: WindowingEducationProto): Boolean =
+        windowingEducationProto.getAppToWebEducation().getEducationShownCount() >=
+                MAXIMUM_TIMES_EDUCATION_SHOWN
+
+    private fun isFeatureUsedBefore(windowingEducationProto: WindowingEducationProto): Boolean =
+        windowingEducationProto.hasFeatureUsedTimestampMillis()
+
+    private fun convertIntegerResourceToDuration(@IntegerRes resourceId: Int): Duration =
+        Duration.ofSeconds(context.resources.getInteger(resourceId).toLong())
+
+    companion object {
+        private const val MAXIMUM_TIMES_EDUCATION_SHOWN = 100
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppToWebEducationDatastoreRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppToWebEducationDatastoreRepository.kt
new file mode 100644
index 0000000..8be6e6d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppToWebEducationDatastoreRepository.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode.education.data
+
+import android.content.Context
+import android.util.Slog
+import androidx.datastore.core.CorruptionException
+import androidx.datastore.core.DataStore
+import androidx.datastore.core.DataStoreFactory
+import androidx.datastore.core.Serializer
+import androidx.datastore.dataStoreFile
+import com.android.framework.protobuf.InvalidProtocolBufferException
+import com.android.internal.annotations.VisibleForTesting
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.first
+import java.io.IOException
+import java.io.InputStream
+import java.io.OutputStream
+
+/** Updates data in App-to-Web's education datastore. */
+class AppToWebEducationDatastoreRepository
+@VisibleForTesting
+constructor(private val dataStore: DataStore<WindowingEducationProto>) {
+    constructor(
+        context: Context
+    ) : this(
+        DataStoreFactory.create(
+            serializer = WindowingEducationProtoSerializer,
+            produceFile = { context.dataStoreFile(APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH) }))
+
+    /** Provides dataStore.data flow and handles exceptions thrown during collection */
+    val dataStoreFlow: Flow<WindowingEducationProto> =
+        dataStore.data.catch { exception ->
+            // dataStore.data throws an IOException when an error is encountered when reading data
+            if (exception is IOException) {
+                Slog.e(
+                    TAG,
+                    "Error in reading App-to-Web education related data from datastore," +
+                            "data is stored in a file named" +
+                            "$APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH", exception)
+            } else {
+                throw exception
+            }
+        }
+
+    /**
+     * Reads and returns the [WindowingEducationProto] Proto object from the DataStore. If the
+     * DataStore is empty or there's an error reading, it returns the default value of Proto.
+     */
+    suspend fun windowingEducationProto(): WindowingEducationProto = dataStoreFlow.first()
+
+    /**
+     * Updates [WindowingEducationProto.featureUsedTimestampMillis_] field in datastore with current
+     * timestamp if [isViewed] is true, if not then clears the field.
+     */
+    suspend fun updateFeatureUsedTimestampMillis(isViewed: Boolean) {
+        dataStore.updateData { preferences ->
+            if (isViewed) {
+                preferences
+                    .toBuilder().setFeatureUsedTimestampMillis(System.currentTimeMillis()).build()
+            } else {
+                preferences.toBuilder().clearFeatureUsedTimestampMillis().build()
+            }
+        }
+    }
+
+    /**
+     * Increases [AppToWebEducation.educationShownCount] field by one.
+     */
+    suspend fun updateEducationShownCount() {
+        val currentAppHandleProto = windowingEducationProto().appToWebEducation.toBuilder()
+        currentAppHandleProto
+            .setEducationShownCount(currentAppHandleProto.getEducationShownCount() + 1)
+        dataStore.updateData { preferences ->
+            preferences.toBuilder().setAppToWebEducation(currentAppHandleProto).build()
+        }
+    }
+
+
+    companion object {
+        private const val TAG = "AppToWebEducationDatastoreRepository"
+        private const val APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH = "app_to_web_education.pb"
+
+        object WindowingEducationProtoSerializer : Serializer<WindowingEducationProto> {
+
+            override val defaultValue: WindowingEducationProto =
+                WindowingEducationProto.getDefaultInstance()
+
+            override suspend fun readFrom(input: InputStream): WindowingEducationProto =
+                try {
+                    WindowingEducationProto.parseFrom(input)
+                } catch (exception: InvalidProtocolBufferException) {
+                    throw CorruptionException("Cannot read proto.", exception)
+                }
+
+            override suspend fun writeTo(
+                windowingProto: WindowingEducationProto,
+                output: OutputStream
+            ) = windowingProto.writeTo(output)
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/proto/windowing_education.proto b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/proto/windowing_education.proto
index d29ec53..4cddd01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/proto/windowing_education.proto
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/proto/windowing_education.proto
@@ -28,6 +28,8 @@
   oneof education_data {
     // Fields specific to app handle education
     AppHandleEducation app_handle_education = 3;
+    // Fields specific to App-to-Web education
+    AppToWebEducation app_to_web_education = 4;
   }
 
   message AppHandleEducation {
@@ -36,4 +38,9 @@
     // Timestamp of when app_usage_stats was last cached
     optional int64 app_usage_stats_last_update_timestamp_millis = 2;
   }
+
+  message AppToWebEducation {
+    // Number of times education is shown
+    optional int64 education_shown_count = 1;
+  }
 }
\ No newline at end of file
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/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index f060158..4aeecbe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -30,6 +30,7 @@
 import android.app.TaskInfo;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.SystemClock;
 import android.view.Surface;
@@ -152,7 +153,6 @@
         return mCurrentAnimator;
     }
 
-    @SuppressWarnings("unchecked")
     /**
      * Construct and return an animator that animates from the {@param startBounds} to the
      * {@param endBounds} with the given {@param direction}. If {@param direction} is type
@@ -171,6 +171,7 @@
      * leaving PiP to fullscreen, and the {@param endBounds} is the fullscreen bounds before the
      * rotation change.
      */
+    @SuppressWarnings("unchecked")
     @VisibleForTesting
     public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash,
             Rect baseBounds, Rect startBounds, Rect endBounds, Rect sourceHintRect,
@@ -566,7 +567,7 @@
                     }
                     getSurfaceTransactionHelper()
                             .resetScale(tx, leash, getDestinationBounds())
-                            .crop(tx, leash, getDestinationBounds())
+                            .cropAndPosition(tx, leash, getDestinationBounds())
                             .round(tx, leash, true /* applyCornerRadius */)
                             .shadow(tx, leash, shouldApplyShadowRadius());
                     tx.show(leash);
@@ -590,18 +591,50 @@
             // Just for simplicity we'll interpolate between the source rect hint insets and empty
             // insets to calculate the window crop
             final Rect initialSourceValue;
+            final Rect mainWindowFrame = taskInfo.topActivityMainWindowFrame;
+            final boolean hasNonMatchFrame = mainWindowFrame != null;
+            final boolean changeOrientation =
+                    rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270;
+            final Rect baseBounds = new Rect(baseValue);
+            final Rect startBounds = new Rect(startValue);
+            final Rect endBounds = new Rect(endValue);
             if (isOutPipDirection) {
-                initialSourceValue = new Rect(endValue);
+                // TODO(b/356277166): handle rotation change with activity that provides main window
+                //  frame.
+                if (hasNonMatchFrame && !changeOrientation) {
+                    endBounds.set(mainWindowFrame);
+                }
+                initialSourceValue = new Rect(endBounds);
+            } else if (isInPipDirection) {
+                if (hasNonMatchFrame) {
+                    baseBounds.set(mainWindowFrame);
+                    if (startValue.equals(baseValue)) {
+                        // If the start value is at initial state as in PIP animation, also override
+                        // the start bounds with nonMatchParentBounds.
+                        startBounds.set(mainWindowFrame);
+                    }
+                }
+                initialSourceValue = new Rect(baseBounds);
             } else {
-                initialSourceValue = new Rect(baseValue);
+                // Note that we assume the window bounds always match task bounds in PIP mode.
+                initialSourceValue = new Rect(baseBounds);
+            }
+
+            final Point leashOffset;
+            if (isInPipDirection) {
+                leashOffset = new Point(baseValue.left, baseValue.top);
+            } else if (isOutPipDirection) {
+                leashOffset = new Point(endValue.left, endValue.top);
+            } else {
+                leashOffset = new Point(baseValue.left, baseValue.top);
             }
 
             final Rect rotatedEndRect;
             final Rect lastEndRect;
             final Rect initialContainerRect;
-            if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) {
-                lastEndRect = new Rect(endValue);
-                rotatedEndRect = new Rect(endValue);
+            if (changeOrientation) {
+                lastEndRect = new Rect(endBounds);
+                rotatedEndRect = new Rect(endBounds);
                 // Rotate the end bounds according to the rotation delta because the display will
                 // be rotated to the same orientation.
                 rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta);
@@ -617,9 +650,9 @@
                 // Crop a Rect matches the aspect ratio and pivots at the center point.
                 // This is done for entering case only.
                 if (isInPipDirection(direction)) {
-                    final float aspectRatio = endValue.width() / (float) endValue.height();
+                    final float aspectRatio = endBounds.width() / (float) endBounds.height();
                     adjustedSourceRectHint.set(PipUtils.getEnterPipWithOverlaySrcRectHint(
-                            startValue, aspectRatio));
+                            startBounds, aspectRatio));
                 }
             } else {
                 adjustedSourceRectHint.set(sourceRectHint);
@@ -644,7 +677,7 @@
 
             // construct new Rect instances in case they are recycled
             return new PipTransitionAnimator<Rect>(taskInfo, leash, ANIM_TYPE_BOUNDS,
-                    endValue, new Rect(baseValue), new Rect(startValue), new Rect(endValue)) {
+                    endBounds, new Rect(baseBounds), new Rect(startBounds), new Rect(endBounds)) {
                 private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
                 private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect());
 
@@ -668,11 +701,22 @@
                     setCurrentValue(bounds);
                     if (inScaleTransition() || adjustedSourceRectHint.isEmpty()) {
                         if (isOutPipDirection) {
-                            getSurfaceTransactionHelper().crop(tx, leash, end)
-                                    .scale(tx, leash, end, bounds);
+                            // Use the bounds relative to the task leash in case the leash does not
+                            // start from (0, 0).
+                            final Rect relativeEndBounds = new Rect(end);
+                            relativeEndBounds.offset(-leashOffset.x, -leashOffset.y);
+                            getSurfaceTransactionHelper()
+                                    .crop(tx, leash, relativeEndBounds)
+                                    .scale(tx, leash, relativeEndBounds, bounds,
+                                            false /* shouldOffset */);
                         } else {
-                            getSurfaceTransactionHelper().crop(tx, leash, base)
-                                    .scale(tx, leash, base, bounds, angle)
+                            // TODO(b/356277166): add support to specify sourceRectHint with
+                            //  non-match parent activity.
+                            // If there's a PIP resize animation, we should offset the bounds to
+                            // (0, 0) since the window bounds should match the leash bounds in PIP
+                            // mode.
+                            getSurfaceTransactionHelper().cropAndPosition(tx, leash, base)
+                                    .scale(tx, leash, base, bounds, angle, inScaleTransition())
                                     .round(tx, leash, base, bounds)
                                     .shadow(tx, leash, shouldApplyShadowRadius());
                         }
@@ -680,7 +724,7 @@
                         final Rect insets = computeInsets(fraction);
                         getSurfaceTransactionHelper().scaleAndCrop(tx, leash,
                                 adjustedSourceRectHint, initialSourceValue, bounds, insets,
-                                isInPipDirection, fraction);
+                                isInPipDirection, fraction, leashOffset);
                         final Rect sourceBounds = new Rect(initialContainerRect);
                         sourceBounds.inset(insets);
                         getSurfaceTransactionHelper()
@@ -733,8 +777,7 @@
                     getSurfaceTransactionHelper()
                             .rotateAndScaleWithCrop(tx, leash, initialContainerRect, bounds,
                                     insets, degree, x, y, isOutPipDirection,
-                                    rotationDelta == ROTATION_270 /* clockwise */);
-                    getSurfaceTransactionHelper()
+                                    rotationDelta == ROTATION_270 /* clockwise */)
                             .round(tx, leash, sourceBounds, bounds)
                             .shadow(tx, leash, shouldApplyShadowRadius());
                     if (!handlePipTransaction(leash, tx, bounds, 1f /* alpha */)) {
@@ -772,7 +815,7 @@
                         tx.setPosition(leash, 0, 0);
                         tx.setWindowCrop(leash, 0, 0);
                     } else {
-                        getSurfaceTransactionHelper().crop(tx, leash, destBounds);
+                        getSurfaceTransactionHelper().cropAndPosition(tx, leash, destBounds);
                     }
                     if (mContentOverlay != null) {
                         clearContentOverlay();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index 3d1994c..b02bd0f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -16,8 +16,10 @@
 
 package com.android.wm.shell.pip;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.graphics.Matrix;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.view.Choreographer;
@@ -68,52 +70,102 @@
      * Operates the crop (and position) on a given transaction and leash
      * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
      */
-    public PipSurfaceTransactionHelper crop(SurfaceControl.Transaction tx, SurfaceControl leash,
-            Rect destinationBounds) {
+    public PipSurfaceTransactionHelper cropAndPosition(@NonNull SurfaceControl.Transaction tx,
+            @NonNull SurfaceControl leash, @NonNull Rect destinationBounds) {
         tx.setWindowCrop(leash, destinationBounds.width(), destinationBounds.height())
                 .setPosition(leash, destinationBounds.left, destinationBounds.top);
         return this;
     }
 
     /**
+     * Operates {@link SurfaceControl.Transaction#setCrop} on a given transaction and leash.
+     *
+     * @param tx the transaction to  apply
+     * @param leash the leash to crop
+     * @param relativeDestinationBounds the bounds to crop, which is relative to the leash
+     *                                  coordinate
+     * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+     */
+    public PipSurfaceTransactionHelper crop(@NonNull SurfaceControl.Transaction tx,
+            @NonNull SurfaceControl leash, @NonNull Rect relativeDestinationBounds) {
+        tx.setCrop(leash, relativeDestinationBounds);
+        return this;
+    }
+
+    /**
      * Operates the scale (setMatrix) on a given transaction and leash
      * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
      */
     public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
             Rect sourceBounds, Rect destinationBounds) {
         mTmpDestinationRectF.set(destinationBounds);
-        return scale(tx, leash, sourceBounds, mTmpDestinationRectF, 0 /* degrees */);
+        return scale(tx, leash, sourceBounds, mTmpDestinationRectF, 0 /* degrees */,
+                true /* shouldOffset */);
     }
 
     /**
      * Operates the scale (setMatrix) on a given transaction and leash
      * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
      */
-    public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
-            Rect sourceBounds, RectF destinationBounds) {
-        return scale(tx, leash, sourceBounds, destinationBounds, 0 /* degrees */);
+    public PipSurfaceTransactionHelper scale(@NonNull SurfaceControl.Transaction tx,
+            @NonNull SurfaceControl leash, @NonNull Rect sourceBounds,
+            @NonNull RectF destinationBounds) {
+        return scale(tx, leash, sourceBounds, destinationBounds, 0 /* degrees */,
+                true /* shouldOffset */);
     }
 
     /**
-     * Operates the scale (setMatrix) on a given transaction and leash
+     * Operates the scale (setMatrix) on a given transaction and leash.
+     *
+     * @param shouldOffset {@code true} to offset the leash to (0, 0)
      * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
      */
-    public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
-            Rect sourceBounds, Rect destinationBounds, float degrees) {
+    public PipSurfaceTransactionHelper scale(@NonNull SurfaceControl.Transaction tx,
+            @NonNull SurfaceControl leash, @NonNull Rect sourceBounds,
+            @NonNull Rect destinationBounds, boolean shouldOffset) {
         mTmpDestinationRectF.set(destinationBounds);
-        return scale(tx, leash, sourceBounds, mTmpDestinationRectF, degrees);
+        return scale(tx, leash, sourceBounds, mTmpDestinationRectF, 0 /* degrees */, shouldOffset);
+    }
+
+    /**
+     * Operates the scale (setMatrix) on a given transaction and leash.
+     *
+     * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+     */
+    public PipSurfaceTransactionHelper scale(@NonNull SurfaceControl.Transaction tx,
+            @NonNull SurfaceControl leash, @NonNull Rect sourceBounds,
+            @NonNull Rect destinationBounds, float degrees) {
+        return scale(tx, leash, sourceBounds, destinationBounds, degrees, true /* shouldOffset */);
+    }
+
+    /**
+     * Operates the scale (setMatrix) on a given transaction and leash.
+     *
+     * @param shouldOffset {@code true} to offset the leash to (0, 0)
+     * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+     */
+    public PipSurfaceTransactionHelper scale(@NonNull SurfaceControl.Transaction tx,
+            @NonNull SurfaceControl leash, @NonNull Rect sourceBounds,
+            @NonNull Rect destinationBounds, float degrees, boolean shouldOffset) {
+        mTmpDestinationRectF.set(destinationBounds);
+        return scale(tx, leash, sourceBounds, mTmpDestinationRectF, degrees, shouldOffset);
     }
 
     /**
      * Operates the scale (setMatrix) on a given transaction and leash, along with a rotation.
+     *
+     * @param shouldOffset {@code true} to offset the leash to (0, 0)
      * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
      */
-    public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
-            Rect sourceBounds, RectF destinationBounds, float degrees) {
+    public PipSurfaceTransactionHelper scale(@NonNull SurfaceControl.Transaction tx,
+            @NonNull SurfaceControl leash, @NonNull Rect sourceBounds,
+            @NonNull RectF destinationBounds, float degrees, boolean shouldOffset) {
         mTmpSourceRectF.set(sourceBounds);
         // We want the matrix to position the surface relative to the screen coordinates so offset
-        // the source to 0,0
-        mTmpSourceRectF.offsetTo(0, 0);
+        // the source to (0, 0) if {@code shouldOffset} is true.
+        if (shouldOffset) {
+            mTmpSourceRectF.offsetTo(0, 0);
+        }
         mTmpDestinationRectF.set(destinationBounds);
         mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
         mTmpTransform.postRotate(degrees,
@@ -123,17 +175,19 @@
     }
 
     /**
-     * Operates the scale (setMatrix) on a given transaction and leash
+     * Operates the scale (setMatrix) on a given transaction and leash.
+     *
+     * @param leashOffset the offset of the leash bounds relative to the screen coordinate
      * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
      */
-    public PipSurfaceTransactionHelper scaleAndCrop(SurfaceControl.Transaction tx,
-            SurfaceControl leash, Rect sourceRectHint,
-            Rect sourceBounds, Rect destinationBounds, Rect insets,
-            boolean isInPipDirection, float fraction) {
+    public PipSurfaceTransactionHelper scaleAndCrop(@NonNull SurfaceControl.Transaction tx,
+            @NonNull SurfaceControl leash, @NonNull Rect sourceRectHint, @NonNull Rect sourceBounds,
+            @NonNull Rect destinationBounds, @NonNull Rect insets, boolean isInPipDirection,
+            float fraction, @NonNull Point leashOffset) {
         mTmpDestinationRect.set(sourceBounds);
         // Similar to {@link #scale}, we want to position the surface relative to the screen
-        // coordinates so offset the bounds to 0,0
-        mTmpDestinationRect.offsetTo(0, 0);
+        // coordinates so offset the bounds relative to the leash.
+        mTmpDestinationRect.offset(-leashOffset.x, -leashOffset.y);
         mTmpDestinationRect.inset(insets);
         // Scale to the bounds no smaller than the destination and offset such that the top/left
         // of the scaled inset source rect aligns with the top/left of the destination bounds
@@ -152,13 +206,13 @@
             scale = Math.max((float) destinationBounds.width() / sourceBounds.width(),
                     (float) destinationBounds.height() / sourceBounds.height());
         }
-        float left = destinationBounds.left - insets.left * scale;
-        float top = destinationBounds.top - insets.top * scale;
+        float left = destinationBounds.left - mTmpDestinationRect.left * scale;
+        float top = destinationBounds.top - mTmpDestinationRect.top * scale;
         if (scale == 1) {
             // Work around the 1 pixel off error by rounding the position down at very beginning.
             // We noticed such error from flicker tests, not visually.
-            left = sourceBounds.left;
-            top = sourceBounds.top;
+            left = leashOffset.x;
+            top = leashOffset.y;
         }
         mTmpTransform.setScale(scale, scale);
         tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index b4e0329..c4e63df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -960,7 +960,7 @@
         final SurfaceControl.Transaction boundsChangeTx =
                 mSurfaceControlTransactionFactory.getTransaction();
         mSurfaceTransactionHelper
-                .crop(boundsChangeTx, mLeash, destinationBounds)
+                .cropAndPosition(boundsChangeTx, mLeash, destinationBounds)
                 .round(boundsChangeTx, mLeash, true /* applyCornerRadius */);
 
         mPipTransitionState.setTransitionState(PipTransitionState.ENTRY_SCHEDULED);
@@ -988,7 +988,7 @@
         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
         mSurfaceTransactionHelper
                 .resetScale(tx, mLeash, destinationBounds)
-                .crop(tx, mLeash, destinationBounds)
+                .cropAndPosition(tx, mLeash, destinationBounds)
                 .round(tx, mLeash, isInPip());
         // The animation is finished in the Launcher and here we directly apply the final touch.
         applyEnterPipSyncTransaction(destinationBounds, () -> {
@@ -1525,7 +1525,7 @@
         mPipBoundsState.setBounds(toBounds);
         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
         mSurfaceTransactionHelper
-                .crop(tx, mLeash, toBounds)
+                .cropAndPosition(tx, mLeash, toBounds)
                 .round(tx, mLeash, mPipTransitionState.isInPip());
         if (shouldSyncPipTransactionWithMenu()) {
             mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
@@ -1628,7 +1628,7 @@
             Rect destinationBounds) {
         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
         mSurfaceTransactionHelper
-                .crop(tx, mLeash, destinationBounds)
+                .cropAndPosition(tx, mLeash, destinationBounds)
                 .resetScale(tx, mLeash, destinationBounds)
                 .round(tx, mLeash, mPipTransitionState.isInPip());
         return tx;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 6da3995..28b91c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -30,7 +30,6 @@
 import static android.view.WindowManager.TRANSIT_PIP;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.transitTypeToString;
 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
 
 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
@@ -45,6 +44,7 @@
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
+import static com.android.wm.shell.transition.Transitions.transitTypeToString;
 
 import android.annotation.IntDef;
 import android.app.ActivityManager;
@@ -551,7 +551,7 @@
                 }
                 // Reset the scale with bounds change synchronously.
                 if (hasValidLeash) {
-                    mSurfaceTransactionHelper.crop(tx, leash, destinationBounds)
+                    mSurfaceTransactionHelper.cropAndPosition(tx, leash, destinationBounds)
                             .resetScale(tx, leash, destinationBounds)
                             .round(tx, leash, true /* applyCornerRadius */);
                     final Rect appBounds = mPipOrganizer.mAppBounds;
@@ -588,7 +588,8 @@
                     ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                             "%s: Destination bounds were changed during animation", TAG);
                     rotateBounds(finishBounds, displayBounds, mEndFixedRotation, displayRotation);
-                    mSurfaceTransactionHelper.crop(mFinishTransaction, leash, finishBounds);
+                    mSurfaceTransactionHelper.cropAndPosition(mFinishTransaction, leash,
+                            finishBounds);
                 }
             }
             mFinishTransaction = null;
@@ -1068,6 +1069,11 @@
         mPipBoundsState.mayUseCachedLauncherShelfHeight();
         final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
         final Rect currentBounds = pipChange.getStartAbsBounds();
+        // The app bounds should offset relative to the task leash to make the center calculation
+        // correctly.
+        final Rect relativeAppBounds = new Rect(taskInfo.topActivityMainWindowFrame != null
+                ? taskInfo.topActivityMainWindowFrame : currentBounds);
+        relativeAppBounds.offset(-currentBounds.left, -currentBounds.top);
 
         int rotationDelta = deltaRotation(startRotation, endRotation);
         Rect sourceHintRect = mPipOrganizer.takeSwipeSourceRectHint();
@@ -1089,6 +1095,8 @@
         if (taskInfo.pictureInPictureParams != null
                 && taskInfo.pictureInPictureParams.isAutoEnterEnabled()
                 && mPipTransitionState.getInSwipePipToHomeTransition()) {
+            // TODO(b/356277166): add support to swipe PIP to home with
+            //  non-match parent activity.
             handleSwipePipToHomeTransition(startTransaction, finishTransaction, leash,
                     sourceHintRect, destinationBounds, taskInfo);
             return;
@@ -1119,8 +1127,8 @@
                 // TODO(b/272819817): cleanup the null-check and extra logging.
                 final boolean hasTopActivityInfo = taskInfo.topActivityInfo != null;
                 if (hasTopActivityInfo && mFixedRotationState != FIXED_ROTATION_TRANSITION) {
-                    animator.setAppIconContentOverlay(
-                            mContext, currentBounds, destinationBounds, taskInfo.topActivityInfo,
+                    animator.setAppIconContentOverlay(mContext, relativeAppBounds,
+                            destinationBounds, taskInfo.topActivityInfo,
                             mPipBoundsState.getLauncherState().getAppIconSizePx());
                 } else {
                     ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
@@ -1149,14 +1157,14 @@
                 animationDuration = 0;
             }
             mSurfaceTransactionHelper
-                    .crop(finishTransaction, leash, destinationBounds)
+                    .cropAndPosition(finishTransaction, leash, destinationBounds)
                     .round(finishTransaction, leash, true /* applyCornerRadius */);
             // Always reset to bounds animation type afterwards.
             setEnterAnimationType(ANIM_TYPE_BOUNDS);
         } else {
             throw new RuntimeException("Unrecognized animation type: " + enterAnimationType);
         }
-        mPipOrganizer.setContentOverlay(animator.getContentOverlayLeash(), currentBounds);
+        mPipOrganizer.setContentOverlay(animator.getContentOverlayLeash(), relativeAppBounds);
         animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
                 .setPipAnimationCallback(mPipAnimationCallback)
                 .setDuration(animationDuration);
@@ -1340,11 +1348,11 @@
                 "%s: Update pip for unhandled transition, change=%s, destBounds=%s, isInPip=%b",
                 TAG, pipChange, destBounds, isInPip);
         mSurfaceTransactionHelper
-                .crop(startTransaction, leash, destBounds)
+                .cropAndPosition(startTransaction, leash, destBounds)
                 .round(startTransaction, leash, isInPip)
                 .shadow(startTransaction, leash, isInPip);
         mSurfaceTransactionHelper
-                .crop(finishTransaction, leash, destBounds)
+                .cropAndPosition(finishTransaction, leash, destBounds)
                 .round(finishTransaction, leash, isInPip)
                 .shadow(finishTransaction, leash, isInPip);
         // Make sure the PiP keeps invisible if it was faded out. If it needs to fade in, that will
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index 7a0e669..d3ae411 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -23,7 +23,6 @@
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_PIP;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.transitTypeToString;
 
 import static com.android.wm.shell.common.pip.PipMenuController.ALPHA_NO_CHANGE;
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
@@ -35,6 +34,7 @@
 import static com.android.wm.shell.pip.PipTransitionState.UNDEFINED;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
+import static com.android.wm.shell.transition.Transitions.transitTypeToString;
 
 import android.animation.AnimationHandler;
 import android.animation.Animator;
@@ -338,7 +338,7 @@
         final Rect pipBounds = mPipBoundsState.getBounds();
         mSurfaceTransactionHelper
                 .resetScale(startTransaction, pipLeash, pipBounds)
-                .crop(startTransaction, pipLeash, pipBounds)
+                .cropAndPosition(startTransaction, pipLeash, pipBounds)
                 .shadow(startTransaction, pipLeash, false);
 
         final SurfaceControl.Transaction transaction = mTransactionFactory.getTransaction();
@@ -420,7 +420,7 @@
 
         mSurfaceTransactionHelper
                 .resetScale(finishTransaction, leash, pipBounds)
-                .crop(finishTransaction, leash, pipBounds)
+                .cropAndPosition(finishTransaction, leash, pipBounds)
                 .shadow(finishTransaction, leash, false);
 
         final Rect currentBounds = pipChange.getStartAbsBounds();
@@ -443,7 +443,7 @@
                 SurfaceControl.Transaction tx = mTransactionFactory.getTransaction();
                 mSurfaceTransactionHelper
                         .resetScale(tx, leash, pipBounds)
-                        .crop(tx, leash, pipBounds)
+                        .cropAndPosition(tx, leash, pipBounds)
                         .shadow(tx, leash, false);
                 mShellTaskOrganizer.applyTransaction(resizePipWct);
                 tx.apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java
index 24077a3..0264820 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java
@@ -24,7 +24,6 @@
 import android.view.SurfaceControl;
 
 import com.android.wm.shell.R;
-import com.android.wm.shell.transition.Transitions;
 
 /**
  * Abstracts the common operations on {@link SurfaceControl.Transaction} for PiP transition.
@@ -180,8 +179,7 @@
         // destination are different.
         final float scale = srcW <= srcH ? (float) destW / srcW : (float) destH / srcH;
         final Rect crop = mTmpDestinationRect;
-        crop.set(0, 0, Transitions.SHELL_TRANSITIONS_ROTATION ? destH
-                : destW, Transitions.SHELL_TRANSITIONS_ROTATION ? destW : destH);
+        crop.set(0, 0, destW, destH);
         // Inverse scale for crop to fit in screen coordinates.
         crop.scale(1 / scale);
         crop.offset(insets.left, insets.top);
@@ -200,8 +198,8 @@
             }
         }
         mTmpTransform.setScale(scale, scale);
-        mTmpTransform.postRotate(degrees);
         mTmpTransform.postTranslate(positionX, positionY);
+        mTmpTransform.postRotate(degrees);
         tx.setMatrix(leash, mTmpTransform, mTmpFloat9).setCrop(leash, crop);
         return this;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
index e513758..eb33ff4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
@@ -20,6 +20,7 @@
 import static android.view.Surface.ROTATION_90;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.RectEvaluator;
 import android.animation.ValueAnimator;
 import android.content.Context;
@@ -34,6 +35,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.pip.PipUtils;
@@ -45,8 +47,7 @@
 /**
  * Animator that handles bounds animations for entering PIP.
  */
-public class PipEnterAnimator extends ValueAnimator
-        implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
+public class PipEnterAnimator extends ValueAnimator {
     @NonNull private final SurfaceControl mLeash;
     private final SurfaceControl.Transaction mStartTransaction;
     private final SurfaceControl.Transaction mFinishTransaction;
@@ -56,49 +57,82 @@
 
     private final RectEvaluator mRectEvaluator;
     private final Rect mEndBounds = new Rect();
-    @Nullable private final Rect mSourceRectHint;
     private final @Surface.Rotation int mRotation;
     @Nullable private Runnable mAnimationStartCallback;
     @Nullable private Runnable mAnimationEndCallback;
 
-    private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+    private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
             mSurfaceControlTransactionFactory;
     Matrix mTransformTensor = new Matrix();
     final float[] mMatrixTmp = new float[9];
     @Nullable private PipContentOverlay mContentOverlay;
 
+    private PipAppIconOverlaySupplier mPipAppIconOverlaySupplier;
 
     // Internal state representing initial transform - cached to avoid recalculation.
     private final PointF mInitScale = new PointF();
     private final PointF mInitPos = new PointF();
     private final Rect mInitCrop = new Rect();
-    private final PointF mInitActivityScale = new PointF();
-    private final PointF mInitActivityPos = new PointF();
+
+    private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationStart(Animator animation) {
+            super.onAnimationStart(animation);
+            if (mAnimationStartCallback != null) {
+                mAnimationStartCallback.run();
+            }
+            if (mStartTransaction != null) {
+                onEnterAnimationUpdate(0f /* fraction */, mStartTransaction);
+                mStartTransaction.apply();
+            }
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            super.onAnimationEnd(animation);
+            if (mFinishTransaction != null) {
+                onEnterAnimationUpdate(1f /* fraction */, mFinishTransaction);
+            }
+            if (mAnimationEndCallback != null) {
+                mAnimationEndCallback.run();
+            }
+        }
+    };
+
+    private final AnimatorUpdateListener mAnimatorUpdateListener = new AnimatorUpdateListener() {
+        @Override
+        public void onAnimationUpdate(@NonNull ValueAnimator animation) {
+            final SurfaceControl.Transaction tx =
+                    mSurfaceControlTransactionFactory.getTransaction();
+            final float fraction = getAnimatedFraction();
+            onEnterAnimationUpdate(fraction, tx);
+            tx.apply();
+        }
+    };
 
     public PipEnterAnimator(Context context,
             @NonNull SurfaceControl leash,
             SurfaceControl.Transaction startTransaction,
             SurfaceControl.Transaction finishTransaction,
             @NonNull Rect endBounds,
-            @Nullable Rect sourceRectHint,
             @Surface.Rotation int rotation) {
         mLeash = leash;
         mStartTransaction = startTransaction;
         mFinishTransaction = finishTransaction;
         mRectEvaluator = new RectEvaluator(mAnimatedRect);
         mEndBounds.set(endBounds);
-        mSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint) : null;
         mRotation = rotation;
         mSurfaceControlTransactionFactory =
                 new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
+        mPipAppIconOverlaySupplier = this::getAppIconOverlay;
 
         final int enterAnimationDuration = context.getResources()
                 .getInteger(R.integer.config_pipEnterAnimationDuration);
         setDuration(enterAnimationDuration);
         setFloatValues(0f, 1f);
         setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-        addListener(this);
-        addUpdateListener(this);
+        addListener(mAnimatorListener);
+        addUpdateListener(mAnimatorUpdateListener);
     }
 
     public void setAnimationStartCallback(@NonNull Runnable runnable) {
@@ -109,35 +143,6 @@
         mAnimationEndCallback = runnable;
     }
 
-    @Override
-    public void onAnimationStart(@NonNull Animator animation) {
-        if (mAnimationStartCallback != null) {
-            mAnimationStartCallback.run();
-        }
-        if (mStartTransaction != null) {
-            onEnterAnimationUpdate(0f /* fraction */, mStartTransaction);
-            mStartTransaction.apply();
-        }
-    }
-
-    @Override
-    public void onAnimationEnd(@NonNull Animator animation) {
-        if (mFinishTransaction != null) {
-            onEnterAnimationUpdate(1f /* fraction */, mFinishTransaction);
-        }
-        if (mAnimationEndCallback != null) {
-            mAnimationEndCallback.run();
-        }
-    }
-
-    @Override
-    public void onAnimationUpdate(@NonNull ValueAnimator animation) {
-        final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
-        final float fraction = getAnimatedFraction();
-        onEnterAnimationUpdate(fraction, tx);
-        tx.apply();
-    }
-
     /**
      * Updates the transaction to reflect the state of PiP leash at a certain fraction during enter.
      *
@@ -177,14 +182,6 @@
         }
     }
 
-    // no-ops
-
-    @Override
-    public void onAnimationCancel(@NonNull Animator animation) {}
-
-    @Override
-    public void onAnimationRepeat(@NonNull Animator animation) {}
-
     /**
      * Caches the initial transform relevant values for the bounds enter animation.
      *
@@ -201,18 +198,13 @@
      */
     public void setAppIconContentOverlay(Context context, Rect appBounds, Rect destinationBounds,
             ActivityInfo activityInfo, int appIconSizePx) {
-        reattachAppIconOverlay(
-                new PipAppIconOverlay(context, appBounds, destinationBounds,
-                        new IconProvider(context).getIcon(activityInfo), appIconSizePx));
-    }
-
-    private void reattachAppIconOverlay(PipAppIconOverlay overlay) {
         final SurfaceControl.Transaction tx =
                 mSurfaceControlTransactionFactory.getTransaction();
         if (mContentOverlay != null) {
             mContentOverlay.detach(tx);
         }
-        mContentOverlay = overlay;
+        mContentOverlay = mPipAppIconOverlaySupplier.get(context, appBounds, destinationBounds,
+                activityInfo, appIconSizePx);
         mContentOverlay.attach(tx, mLeash);
     }
 
@@ -229,6 +221,13 @@
         mContentOverlay = null;
     }
 
+    private PipAppIconOverlay getAppIconOverlay(
+            Context context, Rect appBounds, Rect destinationBounds,
+            ActivityInfo activityInfo, int iconSize) {
+        return new PipAppIconOverlay(context, appBounds, destinationBounds,
+                new IconProvider(context).getIcon(activityInfo), iconSize);
+    }
+
     /**
      * @return the app icon overlay leash; null if no overlay is attached.
      */
@@ -239,4 +238,21 @@
         }
         return mContentOverlay.getLeash();
     }
+
+    @VisibleForTesting
+    void setSurfaceControlTransactionFactory(
+            @NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
+        mSurfaceControlTransactionFactory = factory;
+    }
+
+    @VisibleForTesting
+    interface PipAppIconOverlaySupplier {
+        PipAppIconOverlay get(Context context, Rect appBounds, Rect destinationBounds,
+                ActivityInfo activityInfo, int iconSize);
+    }
+
+    @VisibleForTesting
+    void setPipAppIconOverlaySupplier(@NonNull PipAppIconOverlaySupplier supplier) {
+        mPipAppIconOverlaySupplier = supplier;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java
index 3f9b0c3..fb1aba3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java
@@ -16,6 +16,8 @@
 
 package com.android.wm.shell.pip2.animation;
 
+import static android.view.Surface.ROTATION_90;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.RectEvaluator;
@@ -73,6 +75,7 @@
                 mAnimationStartCallback.run();
             }
             if (mStartTransaction != null) {
+                onExpandAnimationUpdate(mStartTransaction, 0f);
                 mStartTransaction.apply();
             }
         }
@@ -81,13 +84,7 @@
         public void onAnimationEnd(Animator animation) {
             super.onAnimationEnd(animation);
             if (mFinishTransaction != null) {
-                // finishTransaction might override some state (eg. corner radii) so we want to
-                // manually set the state to the end of the animation
-                mPipSurfaceTransactionHelper.scaleAndCrop(mFinishTransaction, mLeash,
-                                mSourceRectHint, mBaseBounds, mAnimatedRect, getInsets(1f),
-                                false /* isInPipDirection */, 1f)
-                        .round(mFinishTransaction, mLeash, false /* applyCornerRadius */)
-                        .shadow(mFinishTransaction, mLeash, false /* applyCornerRadius */);
+                onExpandAnimationUpdate(mFinishTransaction, 1f);
             }
             if (mAnimationEndCallback != null) {
                 mAnimationEndCallback.run();
@@ -102,14 +99,7 @@
                     final SurfaceControl.Transaction tx =
                             mSurfaceControlTransactionFactory.getTransaction();
                     final float fraction = getAnimatedFraction();
-
-                    // TODO (b/350801661): implement fixed rotation
-                    Rect insets = getInsets(fraction);
-                    mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint,
-                                    mBaseBounds, mAnimatedRect,
-                                    insets, false /* isInPipDirection */, fraction)
-                            .round(tx, mLeash, false /* applyCornerRadius */)
-                            .shadow(tx, mLeash, false /* applyCornerRadius */);
+                    onExpandAnimationUpdate(tx, fraction);
                     tx.apply();
                 }
             };
@@ -167,6 +157,32 @@
         mAnimationEndCallback = runnable;
     }
 
+    private void onExpandAnimationUpdate(SurfaceControl.Transaction tx, float fraction) {
+        Rect insets = getInsets(fraction);
+        if (mRotation == Surface.ROTATION_0) {
+            mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint, mBaseBounds,
+                    mAnimatedRect, insets, false /* isInPipDirection */, fraction);
+        } else {
+            // Fixed rotation case.
+            Rect start = mStartBounds;
+            Rect end = mEndBounds;
+            float degrees, x, y;
+            x = fraction * (end.left - start.left) + start.left;
+            y = fraction * (end.top - start.top) + start.top;
+
+            if (mRotation == ROTATION_90) {
+                degrees = 90 * fraction;
+            } else {
+                degrees = -90 * fraction;
+            }
+            mPipSurfaceTransactionHelper.rotateAndScaleWithCrop(tx, mLeash, mBaseBounds,
+                    mAnimatedRect, insets, degrees, x, y,
+                    true /* isExpanding */, mRotation == ROTATION_90);
+        }
+        mPipSurfaceTransactionHelper.round(tx, mLeash, false /* applyCornerRadius */)
+                .shadow(tx, mLeash, false /* applyShadowRadius */);
+    }
+
     private Rect getInsets(float fraction) {
         final Rect startInsets = mSourceRectHintInsets;
         final Rect endInsets = mZeroInsets;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
index d565776..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
@@ -17,6 +17,7 @@
 package com.android.wm.shell.pip2.animation;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.RectEvaluator;
 import android.animation.ValueAnimator;
 import android.content.Context;
@@ -27,13 +28,14 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+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.
  */
-public class PipResizeAnimator extends ValueAnimator
-        implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener{
+public class PipResizeAnimator extends ValueAnimator {
     @NonNull
     private final Context mContext;
     @NonNull
@@ -61,9 +63,47 @@
     private final Rect mAnimatedRect = new Rect();
     private final float mDelta;
 
-    private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+    private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
             mSurfaceControlTransactionFactory;
 
+    private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationStart(Animator animation) {
+            super.onAnimationStart(animation);
+            if (mAnimationStartCallback != null) {
+                mAnimationStartCallback.run();
+            }
+            if (mStartTx != null) {
+                setBoundsAndRotation(mStartTx, mLeash, mBaseBounds, mStartBounds, mDelta);
+                mStartTx.apply();
+            }
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            super.onAnimationEnd(animation);
+            if (mFinishTx != null) {
+                setBoundsAndRotation(mFinishTx, mLeash, mBaseBounds, mEndBounds, 0f);
+            }
+            if (mAnimationEndCallback != null) {
+                mAnimationEndCallback.run();
+            }
+        }
+    };
+
+    private final ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener =
+            new AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(@NonNull ValueAnimator animation) {
+                    final SurfaceControl.Transaction tx =
+                            mSurfaceControlTransactionFactory.getTransaction();
+                    final float fraction = getAnimatedFraction();
+                    final float degrees = (1.0f - fraction) * mDelta;
+                    setBoundsAndRotation(tx, mLeash, mBaseBounds, mAnimatedRect, degrees);
+                    tx.apply();
+                }
+            };
+
     public PipResizeAnimator(@NonNull Context context,
             @NonNull SurfaceControl leash,
             @Nullable SurfaceControl.Transaction startTransaction,
@@ -89,8 +129,9 @@
         mRectEvaluator = new RectEvaluator(mAnimatedRect);
 
         setObjectValues(startBounds, endBounds);
-        addListener(this);
-        addUpdateListener(this);
+        setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        addListener(mAnimatorListener);
+        addUpdateListener(mAnimatorUpdateListener);
         setEvaluator(mRectEvaluator);
         setDuration(duration);
     }
@@ -103,26 +144,6 @@
         mAnimationEndCallback = runnable;
     }
 
-    @Override
-    public void onAnimationStart(@NonNull Animator animation) {
-        if (mAnimationStartCallback != null) {
-            mAnimationStartCallback.run();
-        }
-        if (mStartTx != null) {
-            setBoundsAndRotation(mStartTx, mLeash, mBaseBounds, mStartBounds, mDelta);
-            mStartTx.apply();
-        }
-    }
-
-    @Override
-    public void onAnimationUpdate(@NonNull ValueAnimator animation) {
-        final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
-        final float fraction = getAnimatedFraction();
-        final float degrees = (1.0f - fraction) * mDelta;
-        setBoundsAndRotation(tx, mLeash, mBaseBounds, mAnimatedRect, degrees);
-        tx.apply();
-    }
-
     /**
      * Set a proper transform matrix for a leash to move it to given bounds with a certain rotation.
      *
@@ -130,7 +151,7 @@
      * @param targetBounds bounds to which we are scaling the leash.
      * @param degrees degrees of rotation - counter-clockwise is positive by convention.
      */
-    public static void setBoundsAndRotation(SurfaceControl.Transaction tx, SurfaceControl leash,
+    private static void setBoundsAndRotation(SurfaceControl.Transaction tx, SurfaceControl leash,
             Rect baseBounds, Rect targetBounds, float degrees) {
         Matrix transformTensor = new Matrix();
         final float[] mMatrixTmp = new float[9];
@@ -144,19 +165,9 @@
         tx.setMatrix(leash, transformTensor, mMatrixTmp);
     }
 
-    @Override
-    public void onAnimationEnd(@NonNull Animator animation) {
-        if (mFinishTx != null) {
-            setBoundsAndRotation(mFinishTx, mLeash, mBaseBounds, mEndBounds, 0f);
-        }
-        if (mAnimationEndCallback != null) {
-            mAnimationEndCallback.run();
-        }
+    @VisibleForTesting
+    void setSurfaceControlTransactionFactory(@NonNull
+            PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
+        mSurfaceControlTransactionFactory = factory;
     }
-
-    @Override
-    public void onAnimationCancel(@NonNull Animator animation) {}
-
-    @Override
-    public void onAnimationRepeat(@NonNull Animator animation) {}
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java
index 58d2a85..44900ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java
@@ -573,7 +573,7 @@
             @PipTransitionState.TransitionState int newState, Bundle extra) {
         switch (newState) {
             case PipTransitionState.ENTERED_PIP:
-                attach(mPipTransitionState.mPinnedTaskLeash);
+                attach(mPipTransitionState.getPinnedTaskLeash());
                 break;
             case PipTransitionState.EXITED_PIP:
                 detach();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 9a93371..d3f537b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -376,18 +376,20 @@
     private void setLauncherKeepClearAreaHeight(boolean visible, int height) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "setLauncherKeepClearAreaHeight: visible=%b, height=%d", visible, height);
-        if (visible) {
-            Rect rect = new Rect(
-                    0, mPipDisplayLayoutState.getDisplayBounds().bottom - height,
-                    mPipDisplayLayoutState.getDisplayBounds().right,
-                    mPipDisplayLayoutState.getDisplayBounds().bottom);
-            mPipBoundsState.setNamedUnrestrictedKeepClearArea(
-                    PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, rect);
-        } else {
-            mPipBoundsState.setNamedUnrestrictedKeepClearArea(
-                    PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, null);
-        }
-        mPipTouchHandler.onShelfVisibilityChanged(visible, height);
+        mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> {
+            if (visible) {
+                Rect rect = new Rect(
+                        0, mPipDisplayLayoutState.getDisplayBounds().bottom - height,
+                        mPipDisplayLayoutState.getDisplayBounds().right,
+                        mPipDisplayLayoutState.getDisplayBounds().bottom);
+                mPipBoundsState.setNamedUnrestrictedKeepClearArea(
+                        PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, rect);
+            } else {
+                mPipBoundsState.setNamedUnrestrictedKeepClearArea(
+                        PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, null);
+            }
+            mPipTouchHandler.onShelfVisibilityChanged(visible, height);
+        });
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index 17392bc..3738353 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -785,7 +785,7 @@
 
     private void handleFlingTransition(SurfaceControl.Transaction startTx,
             SurfaceControl.Transaction finishTx, Rect destinationBounds) {
-        startTx.setPosition(mPipTransitionState.mPinnedTaskLeash,
+        startTx.setPosition(mPipTransitionState.getPinnedTaskLeash(),
                 destinationBounds.left, destinationBounds.top);
         startTx.apply();
 
@@ -799,7 +799,7 @@
 
     private void startResizeAnimation(SurfaceControl.Transaction startTx,
             SurfaceControl.Transaction finishTx, Rect destinationBounds, int duration) {
-        SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
+        SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash();
         Preconditions.checkState(pipLeash != null,
                 "No leash cached by mPipTransitionState=" + mPipTransitionState);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
index 751175f..d98be55 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
@@ -531,7 +531,7 @@
                 // If resize transition was scheduled from this component, handle leash updates.
                 mWaitingForBoundsChangeTransition = false;
 
-                SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
+                SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash();
                 Preconditions.checkState(pipLeash != null,
                         "No leash cached by mPipTransitionState=" + mPipTransitionState);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 8b25b11..607de0e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -118,7 +118,7 @@
     public void removePipAfterAnimation() {
         SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
         PipAlphaAnimator animator = new PipAlphaAnimator(mContext,
-                mPipTransitionState.mPinnedTaskLeash, tx, PipAlphaAnimator.FADE_OUT);
+                mPipTransitionState.getPinnedTaskLeash(), tx, PipAlphaAnimator.FADE_OUT);
         animator.setAnimationEndCallback(this::scheduleRemovePipImmediately);
         animator.start();
     }
@@ -203,7 +203,7 @@
                     "%s: Attempted to user resize PIP to empty bounds, aborting.", TAG);
             return;
         }
-        SurfaceControl leash = mPipTransitionState.mPinnedTaskLeash;
+        SurfaceControl leash = mPipTransitionState.getPinnedTaskLeash();
         final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
 
         Matrix transformTensor = new Matrix();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
index 2c7584a..2f93715 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
@@ -29,6 +29,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.ProtoLog;
 import com.android.internal.util.Preconditions;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.ShellExecutor;
@@ -36,6 +38,7 @@
 import com.android.wm.shell.common.pip.PipBoundsState;
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.pip2.animation.PipResizeAnimator;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
 
 import java.util.ArrayList;
@@ -49,7 +52,8 @@
 public class PipTaskListener implements ShellTaskOrganizer.TaskListener,
         PipTransitionState.PipTransitionStateChangedListener {
     private static final int ASPECT_RATIO_CHANGE_DURATION = 250;
-    private static final String ANIMATING_ASPECT_RATIO_CHANGE = "animating_aspect_ratio_change";
+    @VisibleForTesting
+    static final String ANIMATING_ASPECT_RATIO_CHANGE = "animating_aspect_ratio_change";
 
     private final Context mContext;
     private final PipTransitionState mPipTransitionState;
@@ -63,6 +67,8 @@
     private boolean mWaitingForAspectRatioChange = false;
     private final List<PipParamsChangedCallback> mPipParamsChangedListeners = new ArrayList<>();
 
+    private PipResizeAnimatorSupplier mPipResizeAnimatorSupplier;
+
     public PipTaskListener(Context context,
             ShellTaskOrganizer shellTaskOrganizer,
             PipTransitionState pipTransitionState,
@@ -84,6 +90,7 @@
                         ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP);
             });
         }
+        mPipResizeAnimatorSupplier = PipResizeAnimator::new;
     }
 
     void setPictureInPictureParams(@Nullable PictureInPictureParams params) {
@@ -121,6 +128,9 @@
         if (mPictureInPictureParams.equals(params)) {
             return;
         }
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "onTaskInfoChanged: %s, state=%s oldParams=%s newParams=%s",
+                taskInfo.topActivity, mPipTransitionState, mPictureInPictureParams, params);
         setPictureInPictureParams(params);
         float newAspectRatio = mPictureInPictureParams.getAspectRatioFloat();
         if (PipUtils.aspectRatioChanged(newAspectRatio, mPipBoundsState.getAspectRatio())) {
@@ -167,18 +177,18 @@
                 final int duration = extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION,
                         PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION);
 
-                Preconditions.checkNotNull(mPipTransitionState.mPinnedTaskLeash,
+                Preconditions.checkNotNull(mPipTransitionState.getPinnedTaskLeash(),
                         "Leash is null for bounds transition.");
 
                 if (mWaitingForAspectRatioChange) {
-                    PipResizeAnimator animator = new PipResizeAnimator(mContext,
-                            mPipTransitionState.mPinnedTaskLeash, startTx, finishTx,
+                    mWaitingForAspectRatioChange = false;
+                    PipResizeAnimator animator = mPipResizeAnimatorSupplier.get(mContext,
+                            mPipTransitionState.getPinnedTaskLeash(), startTx, finishTx,
                             destinationBounds,
                             mPipBoundsState.getBounds(), destinationBounds, duration,
                             0f /* delta */);
-                    animator.setAnimationEndCallback(() -> {
-                        mPipScheduler.scheduleFinishResizePip(destinationBounds);
-                    });
+                    animator.setAnimationEndCallback(
+                            () -> mPipScheduler.scheduleFinishResizePip(destinationBounds));
                     animator.start();
                 }
                 break;
@@ -192,4 +202,22 @@
         default void onActionsChanged(List<RemoteAction> actions, RemoteAction closeAction) {
         }
     }
+
+    @VisibleForTesting
+    interface PipResizeAnimatorSupplier {
+        PipResizeAnimator get(@NonNull Context context,
+                @NonNull SurfaceControl leash,
+                @Nullable SurfaceControl.Transaction startTx,
+                @Nullable SurfaceControl.Transaction finishTx,
+                @NonNull Rect baseBounds,
+                @NonNull Rect startBounds,
+                @NonNull Rect endBounds,
+                int duration,
+                float delta);
+    }
+
+    @VisibleForTesting
+    void setPipResizeAnimatorSupplier(@NonNull PipResizeAnimatorSupplier supplier) {
+        mPipResizeAnimatorSupplier = supplier;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index 19d293e..65972fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -231,17 +231,15 @@
                 // KCA triggered movement to wait for other transitions (e.g. due to IME changes).
                 return;
             }
-            mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> {
-                boolean hasUserInteracted = (mPipBoundsState.hasUserMovedPip()
-                        || mPipBoundsState.hasUserResizedPip());
-                int delta = mPipBoundsAlgorithm.getEntryDestinationBounds().top
-                        - mPipBoundsState.getBounds().top;
+            boolean hasUserInteracted = (mPipBoundsState.hasUserMovedPip()
+                    || mPipBoundsState.hasUserResizedPip());
+            int delta = mPipBoundsAlgorithm.getEntryDestinationBounds().top
+                    - mPipBoundsState.getBounds().top;
 
-                if (!mIsImeShowing && !hasUserInteracted && delta != 0) {
-                    // If the user hasn't interacted with PiP, we respect the keep clear areas
-                    mMotionHelper.animateToOffset(mPipBoundsState.getBounds(), delta);
-                }
-            });
+            if (!mIsImeShowing && !hasUserInteracted && delta != 0) {
+                // If the user hasn't interacted with PiP, we respect the keep clear areas
+                mMotionHelper.animateToOffset(mPipBoundsState.getBounds(), delta);
+            }
         };
 
         if (PipUtils.isPip2ExperimentEnabled()) {
@@ -877,7 +875,7 @@
             mMovementWithinDismiss = touchState.getDownTouchPosition().y
                     >= mPipBoundsState.getMovementBounds().bottom;
             mMotionHelper.setSpringingToTouch(false);
-            mPipDismissTargetHandler.setTaskLeash(mPipTransitionState.mPinnedTaskLeash);
+            mPipDismissTargetHandler.setTaskLeash(mPipTransitionState.getPinnedTaskLeash());
 
             // If the menu is still visible then just poke the menu
             // so that it will timeout after the user stops touching it
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 64d8887..ea783e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -325,9 +325,7 @@
             return false;
         }
 
-        SurfaceControl pipLeash = pipChange.getLeash();
-        Preconditions.checkNotNull(pipLeash, "Leash is null for swipe-up transition.");
-
+        final SurfaceControl pipLeash = getLeash(pipChange);
         final Rect destinationBounds = pipChange.getEndAbsBounds();
         final SurfaceControl swipePipToHomeOverlay = mPipTransitionState.getSwipePipToHomeOverlay();
         if (swipePipToHomeOverlay != null) {
@@ -349,20 +347,14 @@
                 : startRotation - endRotation;
         if (delta != ROTATION_0) {
             mPipTransitionState.setInFixedRotation(true);
-            handleBoundsTypeFixedRotation(pipChange, pipActivityChange, endRotation);
-        }
-
-        Rect sourceRectHint = null;
-        if (pipChange.getTaskInfo() != null
-                && pipChange.getTaskInfo().pictureInPictureParams != null) {
-            sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint();
+            handleBoundsEnterFixedRotation(pipChange, pipActivityChange, endRotation);
         }
 
         prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange,
                 pipActivityChange);
         startTransaction.merge(finishTransaction);
         PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash,
-                startTransaction, finishTransaction, destinationBounds, sourceRectHint, delta);
+                startTransaction, finishTransaction, destinationBounds, delta);
         animator.setEnterStartState(pipChange);
         animator.onEnterAnimationUpdate(1.0f /* fraction */, startTransaction);
         startTransaction.apply();
@@ -405,7 +397,7 @@
         final Rect adjustedSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint)
                 : PipUtils.getEnterPipWithOverlaySrcRectHint(startBounds, aspectRatio);
 
-        final SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
+        final SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash();
 
         // For opening type transitions, if there is a change of mode TO_FRONT/OPEN,
         // make sure that change has alpha of 1f, since it's init state might be set to alpha=0f
@@ -420,20 +412,20 @@
         }
 
         final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
-        int startRotation = pipChange.getStartRotation();
-        int endRotation = fixedRotationChange != null
+        final int startRotation = pipChange.getStartRotation();
+        final int endRotation = fixedRotationChange != null
                 ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED;
         final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
                 : startRotation - endRotation;
 
         if (delta != ROTATION_0) {
             mPipTransitionState.setInFixedRotation(true);
-            handleBoundsTypeFixedRotation(pipChange, pipActivityChange,
+            handleBoundsEnterFixedRotation(pipChange, pipActivityChange,
                     fixedRotationChange.getEndFixedRotation());
         }
 
         PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash,
-                startTransaction, finishTransaction, endBounds, adjustedSourceRectHint, delta);
+                startTransaction, finishTransaction, endBounds, delta);
         if (sourceRectHint == null) {
             // update the src-rect-hint in params in place, to set up initial animator transform.
             params.getSourceRectHint().set(adjustedSourceRectHint);
@@ -465,7 +457,7 @@
         animator.start();
     }
 
-    private void handleBoundsTypeFixedRotation(TransitionInfo.Change pipTaskChange,
+    private void handleBoundsEnterFixedRotation(TransitionInfo.Change pipTaskChange,
             TransitionInfo.Change pipActivityChange, int endRotation) {
         final Rect endBounds = pipTaskChange.getEndAbsBounds();
         final Rect endActivityBounds = pipActivityChange.getEndAbsBounds();
@@ -498,6 +490,26 @@
                 endBounds.top + activityEndOffset.y);
     }
 
+    private void handleExpandFixedRotation(TransitionInfo.Change pipTaskChange, int endRotation) {
+        final Rect endBounds = pipTaskChange.getEndAbsBounds();
+        final int width = endBounds.width();
+        final int height = endBounds.height();
+        final int left = endBounds.left;
+        final int top = endBounds.top;
+        int newTop, newLeft;
+
+        if (endRotation == Surface.ROTATION_90) {
+            newLeft = top;
+            newTop = -(left + width);
+        } else {
+            newLeft = -(height + top);
+            newTop = left;
+        }
+        // Modify the endBounds, rotating and placing them potentially off-screen, so that
+        // as we translate and rotate around the origin, we place them right into the target.
+        endBounds.set(newLeft, newTop, newLeft + height, newTop + width);
+    }
+
 
     private boolean startAlphaTypeEnterAnimation(@NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
@@ -509,7 +521,7 @@
         }
 
         Rect destinationBounds = pipChange.getEndAbsBounds();
-        SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
+        SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash();
         Preconditions.checkNotNull(pipLeash, "Leash is null for alpha transition.");
 
         // Start transition with 0 alpha at the entry bounds.
@@ -550,33 +562,51 @@
             }
         }
 
-        // for multi activity, we need to manually set the leash layer
-        if (pipChange.getTaskInfo() == null) {
-            TransitionInfo.Change parent = getChangeByToken(info, pipChange.getParent());
-            if (parent != null) {
-                startTransaction.setLayer(parent.getLeash(), Integer.MAX_VALUE - 1);
-            }
+        // The parent change if we were in a multi-activity PiP; null if single activity PiP.
+        final TransitionInfo.Change parentBeforePip = pipChange.getTaskInfo() == null
+                ? getChangeByToken(info, pipChange.getParent()) : null;
+        if (parentBeforePip != null) {
+            // For multi activity, we need to manually set the leash layer
+            startTransaction.setLayer(parentBeforePip.getLeash(), Integer.MAX_VALUE - 1);
         }
 
-        Rect startBounds = pipChange.getStartAbsBounds();
-        Rect endBounds = pipChange.getEndAbsBounds();
-        SurfaceControl pipLeash = pipChange.getLeash();
-        Preconditions.checkNotNull(pipLeash, "Leash is null for exit transition.");
+        final Rect startBounds = pipChange.getStartAbsBounds();
+        final Rect endBounds = pipChange.getEndAbsBounds();
+        final SurfaceControl pipLeash = getLeash(pipChange);
 
-        Rect sourceRectHint = null;
-        if (pipChange.getTaskInfo() != null
-                && pipChange.getTaskInfo().pictureInPictureParams != null) {
+        PictureInPictureParams params = null;
+        if (pipChange.getTaskInfo() != null) {
             // single activity
-            sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint();
-        } else if (mPipTaskListener.getPictureInPictureParams().hasSourceBoundsHint()) {
+            params = pipChange.getTaskInfo().pictureInPictureParams;
+        } else if (parentBeforePip != null && parentBeforePip.getTaskInfo() != null) {
             // multi activity
-            sourceRectHint = mPipTaskListener.getPictureInPictureParams().getSourceRectHint();
+            params = parentBeforePip.getTaskInfo().pictureInPictureParams;
+        }
+        final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, endBounds,
+                startBounds);
+
+        final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
+        final int startRotation = pipChange.getStartRotation();
+        final int endRotation = fixedRotationChange != null
+                ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED;
+        final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
+                : endRotation - startRotation;
+
+        if (delta != ROTATION_0) {
+            handleExpandFixedRotation(pipChange, endRotation);
         }
 
         PipExpandAnimator animator = new PipExpandAnimator(mContext, pipLeash,
                 startTransaction, finishTransaction, endBounds, startBounds, endBounds,
-                sourceRectHint, Surface.ROTATION_0);
-        animator.setAnimationEndCallback(this::finishTransition);
+                sourceRectHint, delta);
+        animator.setAnimationEndCallback(() -> {
+            if (parentBeforePip != null) {
+                // TODO b/377362511: Animate local leash instead to also handle letterbox case.
+                // For multi-activity, set the crop to be null
+                finishTransaction.setCrop(pipLeash, null);
+            }
+            finishTransition();
+        });
         animator.start();
         return true;
     }
@@ -723,6 +753,13 @@
         }
     }
 
+    @NonNull
+    private SurfaceControl getLeash(TransitionInfo.Change change) {
+        SurfaceControl leash = change.getLeash();
+        Preconditions.checkNotNull(leash, "Leash is null for change=" + change);
+        return leash;
+    }
+
     //
     // Miscellaneous callbacks and listeners
     //
@@ -760,17 +797,17 @@
 
                 mPipTransitionState.mPipTaskToken = extra.getParcelable(
                         PIP_TASK_TOKEN, WindowContainerToken.class);
-                mPipTransitionState.mPinnedTaskLeash = extra.getParcelable(
-                        PIP_TASK_LEASH, SurfaceControl.class);
+                mPipTransitionState.setPinnedTaskLeash(extra.getParcelable(
+                        PIP_TASK_LEASH, SurfaceControl.class));
                 boolean hasValidTokenAndLeash = mPipTransitionState.mPipTaskToken != null
-                        && mPipTransitionState.mPinnedTaskLeash != null;
+                        && mPipTransitionState.getPinnedTaskLeash() != null;
 
                 Preconditions.checkState(hasValidTokenAndLeash,
                         "Unexpected bundle for " + mPipTransitionState);
                 break;
             case PipTransitionState.EXITED_PIP:
                 mPipTransitionState.mPipTaskToken = null;
-                mPipTransitionState.mPinnedTaskLeash = null;
+                mPipTransitionState.setPinnedTaskLeash(null);
                 break;
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
index ccdd66b..03e06f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -142,7 +142,7 @@
 
     // pinned PiP task's leash
     @Nullable
-    SurfaceControl mPinnedTaskLeash;
+    private SurfaceControl mPinnedTaskLeash;
 
     // Overlay leash potentially used during swipe PiP to home transition;
     // if null while mInSwipePipToHomeTransition is true, then srcRectHint was invalid.
@@ -304,6 +304,14 @@
         mSwipePipToHomeAppBounds.setEmpty();
     }
 
+    @Nullable SurfaceControl getPinnedTaskLeash() {
+        return mPinnedTaskLeash;
+    }
+
+    void setPinnedTaskLeash(@Nullable SurfaceControl leash) {
+        mPinnedTaskLeash = leash;
+    }
+
     /**
      * @return true if either in swipe or button-nav fixed rotation.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
index 799028a..4a301cc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
@@ -24,7 +24,7 @@
 
 import com.android.wm.shell.recents.IRecentsAnimationRunner;
 import com.android.wm.shell.recents.IRecentTasksListener;
-import com.android.wm.shell.shared.GroupedRecentTaskInfo;
+import com.android.wm.shell.shared.GroupedTaskInfo;
 
 /**
  * Interface that is exposed to remote callers to fetch recent tasks.
@@ -44,7 +44,7 @@
     /**
      * Gets the set of recent tasks.
      */
-    GroupedRecentTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) = 3;
+    GroupedTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) = 3;
 
     /**
      * Gets the set of running tasks.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
index 371bdd5..b58f068 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
@@ -18,6 +18,8 @@
 
 import android.app.ActivityManager.RunningTaskInfo;
 
+import com.android.wm.shell.shared.GroupedTaskInfo;
+
 /**
  * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
  */
@@ -44,8 +46,8 @@
     void onRunningTaskChanged(in RunningTaskInfo taskInfo);
 
     /** A task has moved to front. */
-    oneway void onTaskMovedToFront(in RunningTaskInfo taskInfo);
+    void onTaskMovedToFront(in GroupedTaskInfo[] visibleTasks);
 
     /** A task info has changed. */
-    oneway void onTaskInfoChanged(in RunningTaskInfo taskInfo);
+    void onTaskInfoChanged(in RunningTaskInfo taskInfo);
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index 8c5d1e7..364a087 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -19,7 +19,7 @@
 import android.annotation.Nullable;
 import android.graphics.Color;
 
-import com.android.wm.shell.shared.GroupedRecentTaskInfo;
+import com.android.wm.shell.shared.GroupedTaskInfo;
 import com.android.wm.shell.shared.annotations.ExternalThread;
 
 import java.util.List;
@@ -35,7 +35,7 @@
      * Gets the set of recent tasks.
      */
     default void getRecentTasks(int maxNum, int flags, int userId, Executor callbackExecutor,
-            Consumer<List<GroupedRecentTaskInfo>> callback) {
+            Consumer<List<GroupedTaskInfo>> callback) {
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index faa2015..9911669 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -24,10 +24,12 @@
 import android.Manifest;
 import android.annotation.RequiresPermission;
 import android.app.ActivityManager;
+import android.app.ActivityManager.RecentTaskInfo;
 import android.app.ActivityTaskManager;
 import android.app.IApplicationThread;
 import android.app.KeyguardManager;
 import android.app.PendingIntent;
+import android.app.TaskInfo;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -55,7 +57,7 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.shared.GroupedRecentTaskInfo;
+import com.android.wm.shell.shared.GroupedTaskInfo;
 import com.android.wm.shell.shared.annotations.ExternalThread;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -379,7 +381,8 @@
             return;
         }
         try {
-            mListener.onTaskMovedToFront(taskInfo);
+            GroupedTaskInfo runningTask = GroupedTaskInfo.forFullscreenTasks(taskInfo);
+            mListener.onTaskMovedToFront(new GroupedTaskInfo[]{ runningTask });
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed call onTaskMovedToFront", e);
         }
@@ -407,27 +410,27 @@
     }
 
     @VisibleForTesting
-    ArrayList<GroupedRecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
+    ArrayList<GroupedTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
         // Note: the returned task list is from the most-recent to least-recent order
-        final List<ActivityManager.RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks(
+        final List<RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks(
                 maxNum, flags, userId);
 
         // Make a mapping of task id -> task info
-        final SparseArray<ActivityManager.RecentTaskInfo> rawMapping = new SparseArray<>();
+        final SparseArray<TaskInfo> rawMapping = new SparseArray<>();
         for (int i = 0; i < rawList.size(); i++) {
-            final ActivityManager.RecentTaskInfo taskInfo = rawList.get(i);
+            final TaskInfo taskInfo = rawList.get(i);
             rawMapping.put(taskInfo.taskId, taskInfo);
         }
 
-        ArrayList<ActivityManager.RecentTaskInfo> freeformTasks = new ArrayList<>();
+        ArrayList<TaskInfo> freeformTasks = new ArrayList<>();
         Set<Integer> minimizedFreeformTasks = new HashSet<>();
 
         int mostRecentFreeformTaskIndex = Integer.MAX_VALUE;
 
         // Pull out the pairs as we iterate back in the list
-        ArrayList<GroupedRecentTaskInfo> recentTasks = new ArrayList<>();
+        ArrayList<GroupedTaskInfo> recentTasks = new ArrayList<>();
         for (int i = 0; i < rawList.size(); i++) {
-            final ActivityManager.RecentTaskInfo taskInfo = rawList.get(i);
+            final RecentTaskInfo taskInfo = rawList.get(i);
             if (!rawMapping.contains(taskInfo.taskId)) {
                 // If it's not in the mapping, then it was already paired with another task
                 continue;
@@ -460,20 +463,20 @@
             final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID);
             if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains(
                     pairedTaskId)) {
-                final ActivityManager.RecentTaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId);
+                final TaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId);
                 rawMapping.remove(pairedTaskId);
-                recentTasks.add(GroupedRecentTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
+                recentTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
                         mTaskSplitBoundsMap.get(pairedTaskId)));
             } else {
-                recentTasks.add(GroupedRecentTaskInfo.forSingleTask(taskInfo));
+                recentTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo));
             }
         }
 
         // Add a special entry for freeform tasks
         if (!freeformTasks.isEmpty()) {
             recentTasks.add(mostRecentFreeformTaskIndex,
-                    GroupedRecentTaskInfo.forFreeformTasks(
-                            freeformTasks.toArray(new ActivityManager.RecentTaskInfo[0]),
+                    GroupedTaskInfo.forFreeformTasks(
+                            freeformTasks,
                             minimizedFreeformTasks));
         }
 
@@ -514,16 +517,16 @@
      * {@param ignoreTaskToken} if it is non-null.
      */
     @Nullable
-    public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName,
+    public RecentTaskInfo findTaskInBackground(ComponentName componentName,
             int userId, @Nullable WindowContainerToken ignoreTaskToken) {
         if (componentName == null) {
             return null;
         }
-        List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks(
+        List<RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks(
                 Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE,
                 ActivityManager.getCurrentUser());
         for (int i = 0; i < tasks.size(); i++) {
-            final ActivityManager.RecentTaskInfo task = tasks.get(i);
+            final RecentTaskInfo task = tasks.get(i);
             if (task.isVisible) {
                 continue;
             }
@@ -541,12 +544,12 @@
      * Find the background task that match the given taskId.
      */
     @Nullable
-    public ActivityManager.RecentTaskInfo findTaskInBackground(int taskId) {
-        List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks(
+    public RecentTaskInfo findTaskInBackground(int taskId) {
+        List<RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks(
                 Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE,
                 ActivityManager.getCurrentUser());
         for (int i = 0; i < tasks.size(); i++) {
-            final ActivityManager.RecentTaskInfo task = tasks.get(i);
+            final RecentTaskInfo task = tasks.get(i);
             if (task.isVisible) {
                 continue;
             }
@@ -570,7 +573,7 @@
         pw.println(prefix + TAG);
         pw.println(prefix + " mListener=" + mListener);
         pw.println(prefix + "Tasks:");
-        ArrayList<GroupedRecentTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE,
+        ArrayList<GroupedTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE,
                 ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser());
         for (int i = 0; i < recentTasks.size(); i++) {
             pw.println(innerPrefix + recentTasks.get(i));
@@ -584,9 +587,9 @@
     private class RecentTasksImpl implements RecentTasks {
         @Override
         public void getRecentTasks(int maxNum, int flags, int userId, Executor executor,
-                Consumer<List<GroupedRecentTaskInfo>> callback) {
+                Consumer<List<GroupedTaskInfo>> callback) {
             mMainExecutor.execute(() -> {
-                List<GroupedRecentTaskInfo> tasks =
+                List<GroupedTaskInfo> tasks =
                         RecentTasksController.this.getRecentTasks(maxNum, flags, userId);
                 executor.execute(() -> callback.accept(tasks));
             });
@@ -650,7 +653,7 @@
             }
 
             @Override
-            public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
+            public void onTaskMovedToFront(GroupedTaskInfo[] taskInfo) {
                 mListener.call(l -> l.onTaskMovedToFront(taskInfo));
             }
 
@@ -692,17 +695,20 @@
         }
 
         @Override
-        public GroupedRecentTaskInfo[] getRecentTasks(int maxNum, int flags, int userId)
+        public GroupedTaskInfo[] getRecentTasks(int maxNum, int flags, int userId)
                 throws RemoteException {
             if (mController == null) {
                 // The controller is already invalidated -- just return an empty task list for now
-                return new GroupedRecentTaskInfo[0];
+                return new GroupedTaskInfo[0];
             }
 
-            final GroupedRecentTaskInfo[][] out = new GroupedRecentTaskInfo[][]{null};
+            final GroupedTaskInfo[][] out = new GroupedTaskInfo[][]{null};
             executeRemoteCallWithTaskPermission(mController, "getRecentTasks",
-                    (controller) -> out[0] = controller.getRecentTasks(maxNum, flags, userId)
-                            .toArray(new GroupedRecentTaskInfo[0]),
+                    (controller) -> {
+                        List<GroupedTaskInfo> tasks = controller.getRecentTasks(
+                                maxNum, flags, userId);
+                        out[0] = tasks.toArray(new GroupedTaskInfo[0]);
+                    },
                     true /* blocking */);
             return out[0];
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 6d4d4b4..40065b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -544,10 +544,10 @@
                             .findFirst()
                             .get();
             final RemoteAnimationTarget openingTarget = TransitionUtil.newSyntheticTarget(
-                    homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_OPEN,
+                    homeTask, mShellTaskOrganizer.getHomeTaskSurface(), TRANSIT_OPEN,
                     0, true /* isTranslucent */);
             final RemoteAnimationTarget closingTarget = TransitionUtil.newSyntheticTarget(
-                    homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_CLOSE,
+                    homeTask, mShellTaskOrganizer.getHomeTaskSurface(), TRANSIT_CLOSE,
                     0, true /* isTranslucent */);
             final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
             apps.add(openingTarget);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 839bb4e..cc0e1df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -30,7 +30,6 @@
 import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.transitTypeToString;
 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
 
@@ -73,6 +72,7 @@
 import static com.android.wm.shell.transition.MixedTransitionHelper.getPipReplacingChange;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
+import static com.android.wm.shell.transition.Transitions.transitTypeToString;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index b33f3e9..4407e5b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -245,9 +245,9 @@
                 return;
             }
             mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
-            mVisible = taskInfo.isVisible && taskInfo.isVisibleRequested;
+            mVisible = isStageVisible();
             mCallbacks.onChildTaskStatusChanged(this, taskInfo.taskId, true /* present */,
-                    mVisible);
+                    taskInfo.isVisible && taskInfo.isVisibleRequested);
         } else {
             throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
                     + "\n mRootTaskInfo: " + mRootTaskInfo);
@@ -293,6 +293,19 @@
         t.reparent(sc, findTaskSurface(taskId));
     }
 
+    /**
+     * Checks against all children task info and return true if any are marked as visible.
+     */
+    private boolean isStageVisible() {
+        for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+            if (mChildrenTaskInfo.valueAt(i).isVisible
+                    && mChildrenTaskInfo.valueAt(i).isVisibleRequested) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private SurfaceControl findTaskSurface(int taskId) {
         if (mRootTaskInfo.taskId == taskId) {
             return mRootLeash;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
index a1a9ca9..4ea4613 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
@@ -136,6 +136,7 @@
         @NonNull final Animation mAnim;
         @Nullable final Point mPosition;
         @Nullable final Rect mClipRect;
+        @Nullable private final Rect mAnimClipRect;
         final float mCornerRadius;
         final boolean mIsActivity;
 
@@ -147,6 +148,7 @@
             mPosition = (position != null && (position.x != 0 || position.y != 0))
                     ? position : null;
             mClipRect = (clipRect != null && !clipRect.isEmpty()) ? clipRect : null;
+            mAnimClipRect = mClipRect != null ? new Rect() : null;
             mCornerRadius = cornerRadius;
             mIsActivity = isActivity;
         }
@@ -169,18 +171,26 @@
             t.setAlpha(leash, transformation.getAlpha());
 
             if (mClipRect != null) {
-                Rect clipRect = mClipRect;
+                boolean needCrop = false;
+                mAnimClipRect.set(mClipRect);
+                if (transformation.hasClipRect()
+                        && com.android.window.flags.Flags.respectAnimationClip()) {
+                    mAnimClipRect.intersectUnchecked(transformation.getClipRect());
+                    needCrop = true;
+                }
                 final Insets extensionInsets = Insets.min(transformation.getInsets(), Insets.NONE);
                 if (!extensionInsets.equals(Insets.NONE)) {
                     // Clip out any overflowing edge extension.
-                    clipRect = new Rect(mClipRect);
-                    clipRect.inset(extensionInsets);
-                    t.setCrop(leash, clipRect);
+                    mAnimClipRect.inset(extensionInsets);
+                    needCrop = true;
                 }
                 if (mCornerRadius > 0 && mAnim.hasRoundedCorners()) {
                     // Rounded corner can only be applied if a crop is set.
-                    t.setCrop(leash, clipRect);
                     t.setCornerRadius(leash, mCornerRadius);
+                    needCrop = true;
+                }
+                if (needCrop) {
+                    t.setCrop(leash, mAnimClipRect);
                 }
             }
         }
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/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index a27c14bd..4feb475 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -24,7 +24,6 @@
 import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.transitTypeToString;
 import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW;
 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -34,6 +33,7 @@
 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE;
 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN;
 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
+import static com.android.wm.shell.transition.Transitions.transitTypeToString;
 
 import android.annotation.ColorInt;
 import android.annotation.NonNull;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 7c9cd08..1d456ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -29,7 +29,6 @@
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.fixScale;
-import static android.view.WindowManager.transitTypeToString;
 import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW;
 import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
@@ -725,7 +724,7 @@
             @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
         info.setUnreleasedWarningCallSiteForAllSurfaces("Transitions.onTransitionReady");
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s",
-                info.getDebugId(), transitionToken, info);
+                info.getDebugId(), transitionToken, info.toString("    " /* prefix */));
         int activeIdx = findByToken(mPendingTransitions, transitionToken);
         if (activeIdx < 0) {
             final ActiveTransition existing = mKnownTransitions.get(transitionToken);
@@ -1847,6 +1846,40 @@
         }
     }
 
+    /**
+     * Like WindowManager#transitTypeToString(), but also covers known custom transition types as
+     * well.
+     */
+    public static String transitTypeToString(int transitType) {
+        if (transitType < TRANSIT_FIRST_CUSTOM) {
+            return WindowManager.transitTypeToString(transitType);
+        }
+
+        String typeStr = switch (transitType) {
+            case TRANSIT_EXIT_PIP -> "EXIT_PIP";
+            case TRANSIT_EXIT_PIP_TO_SPLIT -> "EXIT_PIP_TO_SPLIT";
+            case TRANSIT_REMOVE_PIP -> "REMOVE_PIP";
+            case TRANSIT_SPLIT_SCREEN_PAIR_OPEN -> "SPLIT_SCREEN_PAIR_OPEN";
+            case TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE -> "SPLIT_SCREEN_OPEN_TO_SIDE";
+            case TRANSIT_SPLIT_DISMISS_SNAP -> "SPLIT_DISMISS_SNAP";
+            case TRANSIT_SPLIT_DISMISS -> "SPLIT_DISMISS";
+            case TRANSIT_MAXIMIZE -> "MAXIMIZE";
+            case TRANSIT_RESTORE_FROM_MAXIMIZE -> "RESTORE_FROM_MAXIMIZE";
+            case TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP -> "DESKTOP_MODE_START_DRAG_TO_DESKTOP";
+            case TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP -> "DESKTOP_MODE_END_DRAG_TO_DESKTOP";
+            case TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP ->
+                    "DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP";
+            case TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE -> "DESKTOP_MODE_TOGGLE_RESIZE";
+            case TRANSIT_RESIZE_PIP -> "RESIZE_PIP";
+            case TRANSIT_TASK_FRAGMENT_DRAG_RESIZE -> "TASK_FRAGMENT_DRAG_RESIZE";
+            case TRANSIT_SPLIT_PASSTHROUGH -> "SPLIT_PASSTHROUGH";
+            case TRANSIT_CLEANUP_PIP_EXIT -> "CLEANUP_PIP_EXIT";
+            case TRANSIT_MINIMIZE -> "MINIMIZE";
+            default -> "";
+        };
+        return typeStr + "(FIRST_CUSTOM+" + (transitType - TRANSIT_FIRST_CUSTOM) + ")";
+    }
+
     private static boolean getShellTransitEnabled() {
         try {
             if (AppGlobals.getPackageManager().hasSystemFeature(
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/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 3946b61..c954673 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -180,12 +180,13 @@
         // all other cases, it is expected that the transition handler positions and crops the task
         // in order to allow the handler time to animate before the task before the final
         // position and crop are set.
-        final boolean shouldSetTaskPositionAndCrop = mTaskDragResizer.isResizingOrAnimating();
+        final boolean shouldSetTaskVisibilityPositionAndCrop =
+                mTaskDragResizer.isResizingOrAnimating();
         // Use |applyStartTransactionOnDraw| so that the transaction (that applies task crop) is
         // synced with the buffer transaction (that draws the View). Both will be shown on screen
         // at the same, whereas applying them independently causes flickering. See b/270202228.
         relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */,
-                shouldSetTaskPositionAndCrop, hasGlobalFocus);
+                shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus);
     }
 
     @VisibleForTesting
@@ -193,7 +194,7 @@
             RelayoutParams relayoutParams,
             ActivityManager.RunningTaskInfo taskInfo,
             boolean applyStartTransactionOnDraw,
-            boolean setTaskCropAndPosition,
+            boolean shouldSetTaskVisibilityPositionAndCrop,
             boolean isStatusBarVisible,
             boolean isKeyguardVisibleAndOccluded,
             InsetsState displayInsetsState,
@@ -206,7 +207,7 @@
                 ? R.dimen.freeform_decor_shadow_focused_thickness
                 : R.dimen.freeform_decor_shadow_unfocused_thickness;
         relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
-        relayoutParams.mSetTaskPositionAndCrop = setTaskCropAndPosition;
+        relayoutParams.mSetTaskVisibilityPositionAndCrop = shouldSetTaskVisibilityPositionAndCrop;
         relayoutParams.mIsCaptionVisible = taskInfo.isFreeform()
                 || (isStatusBarVisible && !isKeyguardVisibleAndOccluded);
 
@@ -234,7 +235,7 @@
     @SuppressLint("MissingPermission")
     void relayout(RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
-            boolean applyStartTransactionOnDraw, boolean setTaskCropAndPosition,
+            boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
             boolean hasGlobalFocus) {
         final boolean isFreeform =
                 taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -246,7 +247,8 @@
         final WindowContainerTransaction wct = new WindowContainerTransaction();
 
         updateRelayoutParams(mRelayoutParams, taskInfo, applyStartTransactionOnDraw,
-                setTaskCropAndPosition, mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded,
+                shouldSetTaskVisibilityPositionAndCrop, mIsStatusBarVisible,
+                mIsKeyguardVisibleAndOccluded,
                 mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus);
 
         relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
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 29b8ddd..f2d8a78 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -31,6 +31,7 @@
 import static android.view.WindowInsets.Type.statusBars;
 
 import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU;
+import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
 import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing;
 import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger;
 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
@@ -41,7 +42,6 @@
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
@@ -49,6 +49,7 @@
 import android.app.ActivityTaskManager;
 import android.app.IActivityManager;
 import android.app.IActivityTaskManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Point;
@@ -103,6 +104,7 @@
 import com.android.wm.shell.common.MultiInstanceHelper;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIController;
 import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
@@ -113,6 +115,7 @@
 import com.android.wm.shell.desktopmode.DesktopWallpaperActivity;
 import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
 import com.android.wm.shell.desktopmode.education.AppHandleEducationController;
+import com.android.wm.shell.desktopmode.education.AppToWebEducationController;
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
 import com.android.wm.shell.shared.FocusTransitionListener;
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
@@ -120,8 +123,6 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
-import com.android.wm.shell.splitscreen.SplitScreen;
-import com.android.wm.shell.splitscreen.SplitScreen.StageType;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.sysui.KeyguardChangeListener;
 import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -176,6 +177,7 @@
     private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
     private final Optional<DesktopTasksLimiter> mDesktopTasksLimiter;
     private final AppHandleEducationController mAppHandleEducationController;
+    private final AppToWebEducationController mAppToWebEducationController;
     private final AppHeaderViewHolder.Factory mAppHeaderViewHolderFactory;
     private boolean mTransitionDragActive;
 
@@ -249,6 +251,7 @@
             MultiInstanceHelper multiInstanceHelper,
             Optional<DesktopTasksLimiter> desktopTasksLimiter,
             AppHandleEducationController appHandleEducationController,
+            AppToWebEducationController appToWebEducationController,
             WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
             Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
             FocusTransitionObserver focusTransitionObserver,
@@ -282,6 +285,7 @@
                 interactionJankMonitor,
                 desktopTasksLimiter,
                 appHandleEducationController,
+                appToWebEducationController,
                 windowDecorCaptionHandleRepository,
                 activityOrientationChangeHandler,
                 new TaskPositionerFactory(),
@@ -319,6 +323,7 @@
             InteractionJankMonitor interactionJankMonitor,
             Optional<DesktopTasksLimiter> desktopTasksLimiter,
             AppHandleEducationController appHandleEducationController,
+            AppToWebEducationController appToWebEducationController,
             WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
             Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
             TaskPositionerFactory taskPositionerFactory,
@@ -354,6 +359,7 @@
         mInteractionJankMonitor = interactionJankMonitor;
         mDesktopTasksLimiter = desktopTasksLimiter;
         mAppHandleEducationController = appHandleEducationController;
+        mAppToWebEducationController = appToWebEducationController;
         mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository;
         mActivityOrientationChangeHandler = activityOrientationChangeHandler;
         mAssistContentRequester = assistContentRequester;
@@ -439,17 +445,6 @@
     @Override
     public void setSplitScreenController(SplitScreenController splitScreenController) {
         mSplitScreenController = splitScreenController;
-        mSplitScreenController.registerSplitScreenListener(new SplitScreen.SplitScreenListener() {
-            @Override
-            public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
-                if (visible && stage != STAGE_TYPE_UNDEFINED) {
-                    DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
-                    if (decor != null && DesktopModeStatus.canEnterDesktopMode(mContext)) {
-                        mDesktopTasksController.moveToSplit(decor.mTaskInfo);
-                    }
-                }
-            }
-        });
     }
 
     @Override
@@ -474,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));
     }
@@ -1283,6 +1283,7 @@
                 }
                 break;
             }
+            case MotionEvent.ACTION_CANCEL:
             case MotionEvent.ACTION_UP: {
                 if (mTransitionDragActive) {
                     final DesktopModeVisualIndicator.DragStartState dragStartState =
@@ -1297,32 +1298,11 @@
                         // Though this isn't a hover event, we need to update handle's hover state
                         // as it likely will change.
                         relevantDecor.updateHoverAndPressStatus(ev);
-                        DesktopModeVisualIndicator.IndicatorType resultType =
-                                mDesktopTasksController.onDragPositioningEndThroughStatusBar(
-                                        new PointF(ev.getRawX(), ev.getRawY()),
-                                        relevantDecor.mTaskInfo,
-                                        relevantDecor.mTaskSurface);
-                        // If we are entering split select, handle will no longer be visible and
-                        // should not be receiving any input.
-                        if (resultType == TO_SPLIT_LEFT_INDICATOR
-                                || resultType == TO_SPLIT_RIGHT_INDICATOR) {
-                            relevantDecor.disposeStatusBarInputLayer();
-                            // We should also dispose the other split task's input layer if
-                            // applicable.
-                            final int splitPosition = mSplitScreenController
-                                    .getSplitPosition(relevantDecor.mTaskInfo.taskId);
-                            if (splitPosition != SPLIT_POSITION_UNDEFINED) {
-                                final int oppositePosition =
-                                        splitPosition == SPLIT_POSITION_TOP_OR_LEFT
-                                                ? SPLIT_POSITION_BOTTOM_OR_RIGHT
-                                                : SPLIT_POSITION_TOP_OR_LEFT;
-                                final RunningTaskInfo oppositeTaskInfo =
-                                        mSplitScreenController.getTaskInfo(oppositePosition);
-                                if (oppositeTaskInfo != null) {
-                                    mWindowDecorByTaskId.get(oppositeTaskInfo.taskId)
-                                            .disposeStatusBarInputLayer();
-                                }
-                            }
+                        if (ev.getActionMasked() == ACTION_CANCEL) {
+                            mDesktopTasksController.onDragPositioningCancelThroughStatusBar(
+                                    relevantDecor.mTaskInfo);
+                        } else {
+                            endDragToDesktop(ev, relevantDecor);
                         }
                         mMoveToDesktopAnimator = null;
                         return;
@@ -1371,10 +1351,35 @@
                 }
                 break;
             }
+        }
+    }
 
-            case MotionEvent.ACTION_CANCEL: {
-                mTransitionDragActive = false;
-                mMoveToDesktopAnimator = null;
+    private void endDragToDesktop(MotionEvent ev, DesktopModeWindowDecoration relevantDecor) {
+        DesktopModeVisualIndicator.IndicatorType resultType =
+                mDesktopTasksController.onDragPositioningEndThroughStatusBar(
+                        new PointF(ev.getRawX(), ev.getRawY()),
+                        relevantDecor.mTaskInfo,
+                        relevantDecor.mTaskSurface);
+        // If we are entering split select, handle will no longer be visible and
+        // should not be receiving any input.
+        if (resultType == TO_SPLIT_LEFT_INDICATOR
+                || resultType == TO_SPLIT_RIGHT_INDICATOR) {
+            relevantDecor.disposeStatusBarInputLayer();
+            // We should also dispose the other split task's input layer if
+            // applicable.
+            final int splitPosition = mSplitScreenController
+                    .getSplitPosition(relevantDecor.mTaskInfo.taskId);
+            if (splitPosition != SPLIT_POSITION_UNDEFINED) {
+                final int oppositePosition =
+                        splitPosition == SPLIT_POSITION_TOP_OR_LEFT
+                                ? SPLIT_POSITION_BOTTOM_OR_RIGHT
+                                : SPLIT_POSITION_TOP_OR_LEFT;
+                final RunningTaskInfo oppositeTaskInfo =
+                        mSplitScreenController.getTaskInfo(oppositePosition);
+                if (oppositeTaskInfo != null) {
+                    mWindowDecorByTaskId.get(oppositeTaskInfo.taskId)
+                            .disposeStatusBarInputLayer();
+                }
             }
         }
     }
@@ -1474,6 +1479,9 @@
                 && isTopActivityExemptFromDesktopWindowing(mContext, taskInfo)) {
             return false;
         }
+        if (isPartOfDefaultHomePackage(taskInfo)) {
+            return false;
+        }
         return DesktopModeStatus.canEnterDesktopMode(mContext)
                 && !DesktopWallpaperActivity.isWallpaperTask(taskInfo)
                 && taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED
@@ -1481,6 +1489,14 @@
                 && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop();
     }
 
+    private boolean isPartOfDefaultHomePackage(RunningTaskInfo taskInfo) {
+        final ComponentName currentDefaultHome =
+                mContext.getPackageManager().getHomeActivities(new ArrayList<>());
+        return currentDefaultHome != null && taskInfo.baseActivity != null
+                && currentDefaultHome.getPackageName()
+                .equals(taskInfo.baseActivity.getPackageName());
+    }
+
     private void createWindowDecoration(
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
@@ -1566,6 +1582,10 @@
             onManageWindows(windowDecoration);
             return Unit.INSTANCE;
         });
+        windowDecoration.setOnChangeAspectRatioClickListener(() -> {
+            CompatUIController.launchUserAspectRatioSettings(mContext, taskInfo);
+            return Unit.INSTANCE;
+        });
         windowDecoration.setCaptionListeners(
                 touchEventListener, touchEventListener, touchEventListener, touchEventListener);
         windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index f930748..d97632a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -154,6 +154,7 @@
     private Function0<Unit> mOnToSplitscreenClickListener;
     private Function0<Unit> mOnNewWindowClickListener;
     private Function0<Unit> mOnManageWindowsClickListener;
+    private Function0<Unit> mOnChangeAspectRatioClickListener;
     private DragPositioningCallback mDragPositioningCallback;
     private DragResizeInputListener mDragResizeListener;
     private Runnable mCurrentViewHostRunnable = null;
@@ -364,6 +365,11 @@
         mOnManageWindowsClickListener = listener;
     }
 
+    /** Registers a listener to be called when the aspect ratio action is triggered. */
+    void setOnChangeAspectRatioClickListener(Function0<Unit> listener) {
+        mOnChangeAspectRatioClickListener = listener;
+    }
+
     void setCaptionListeners(
             View.OnClickListener onCaptionButtonClickListener,
             View.OnTouchListener onCaptionTouchListener,
@@ -390,18 +396,25 @@
     @Override
     void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus) {
         final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
-        // The crop and position of the task should only be set when a task is fluid resizing. In
-        // all other cases, it is expected that the transition handler positions and crops the task
-        // in order to allow the handler time to animate before the task before the final
-        // position and crop are set.
-        final boolean shouldSetTaskPositionAndCrop = !DesktopModeStatus.isVeiledResizeEnabled()
-                && mTaskDragResizer.isResizingOrAnimating();
+        // The visibility, crop and position of the task should only be set when a task is
+        // fluid resizing. In all other cases, it is expected that the transition handler sets
+        // those task properties to allow the handler time to animate with full control of the task
+        // leash. In general, allowing the window decoration to set any of these is likely to cause
+        // incorrect frames and flickering because relayouts from TaskListener#onTaskInfoChanged
+        // aren't synchronized with shell transition callbacks, so if they come too early it
+        // might show/hide or crop the task at a bad time.
+        // Fluid resizing is exempt from this because it intentionally doesn't use shell
+        // transitions to resize the task, so onTaskInfoChanged relayouts is the only way to make
+        // sure the crop is set correctly.
+        final boolean shouldSetTaskVisibilityPositionAndCrop =
+                !DesktopModeStatus.isVeiledResizeEnabled()
+                        && mTaskDragResizer.isResizingOrAnimating();
         // For headers only (i.e. in freeform): use |applyStartTransactionOnDraw| so that the
         // transaction (that applies task crop) is synced with the buffer transaction (that draws
         // the View). Both will be shown on screen at the same, whereas applying them independently
         // causes flickering. See b/270202228.
         final boolean applyTransactionOnDraw = taskInfo.isFreeform();
-        relayout(taskInfo, t, t, applyTransactionOnDraw, shouldSetTaskPositionAndCrop,
+        relayout(taskInfo, t, t, applyTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop,
                 hasGlobalFocus);
         if (!applyTransactionOnDraw) {
             t.apply();
@@ -428,19 +441,19 @@
 
     void relayout(ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
-            boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop,
+            boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
             boolean hasGlobalFocus) {
         Trace.beginSection("DesktopModeWindowDecoration#relayout");
         if (taskInfo.isFreeform()) {
             // The Task is in Freeform mode -> show its header in sync since it's an integral part
             // of the window itself - a delayed header might cause bad UX.
             relayoutInSync(taskInfo, startT, finishT, applyStartTransactionOnDraw,
-                    shouldSetTaskPositionAndCrop, hasGlobalFocus);
+                    shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus);
         } else {
             // The Task is outside Freeform mode -> allow the handle view to be delayed since the
             // handle is just a small addition to the window.
             relayoutWithDelayedViewHost(taskInfo, startT, finishT, applyStartTransactionOnDraw,
-                    shouldSetTaskPositionAndCrop, hasGlobalFocus);
+                    shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus);
         }
         Trace.endSection();
     }
@@ -448,12 +461,12 @@
     /** Run the whole relayout phase immediately without delay. */
     private void relayoutInSync(ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
-            boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop,
+            boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
             boolean hasGlobalFocus) {
         // Clear the current ViewHost runnable as we will update the ViewHost here
         clearCurrentViewHostRunnable();
         updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT, applyStartTransactionOnDraw,
-                shouldSetTaskPositionAndCrop, hasGlobalFocus);
+                shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus);
         if (mResult.mRootView != null) {
             updateViewHost(mRelayoutParams, startT, mResult);
         }
@@ -475,7 +488,7 @@
      */
     private void relayoutWithDelayedViewHost(ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
-            boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop,
+            boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
             boolean hasGlobalFocus) {
         if (applyStartTransactionOnDraw) {
             throw new IllegalArgumentException(
@@ -484,7 +497,7 @@
         // Clear the current ViewHost runnable as we will update the ViewHost here
         clearCurrentViewHostRunnable();
         updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT,
-                false /* applyStartTransactionOnDraw */, shouldSetTaskPositionAndCrop,
+                false /* applyStartTransactionOnDraw */, shouldSetTaskVisibilityPositionAndCrop,
                 hasGlobalFocus);
         if (mResult.mRootView == null) {
             // This means something blocks the window decor from showing, e.g. the task is hidden.
@@ -499,10 +512,9 @@
     @SuppressLint("MissingPermission")
     private void updateRelayoutParamsAndSurfaces(ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
-            boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop,
+            boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
             boolean hasGlobalFocus) {
         Trace.beginSection("DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces");
-
         if (Flags.enableDesktopWindowingAppToWeb()) {
             setCapturedLink(taskInfo.capturedLink, taskInfo.capturedLinkTimestamp);
         }
@@ -524,9 +536,9 @@
         final boolean inFullImmersive = mDesktopRepository
                 .isTaskInFullImmersiveState(taskInfo.taskId);
         updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw,
-                shouldSetTaskPositionAndCrop, mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded,
-                inFullImmersive, mDisplayController.getInsetsState(taskInfo.displayId),
-                hasGlobalFocus);
+                shouldSetTaskVisibilityPositionAndCrop, mIsStatusBarVisible,
+                mIsKeyguardVisibleAndOccluded, inFullImmersive,
+                mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus);
 
         final WindowDecorLinearLayout oldRootView = mResult.mRootView;
         final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
@@ -544,7 +556,7 @@
         if (mResult.mRootView == null) {
             // This means something blocks the window decor from showing, e.g. the task is hidden.
             // Nothing is set up in this case including the decoration surface.
-            if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) {
+            if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
                 notifyNoCaptionHandle();
             }
             mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
@@ -563,7 +575,7 @@
         if (isAppHandle(mWindowDecorViewHolder)) {
             position.set(determineHandlePosition());
         }
-        if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) {
+        if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
             notifyCaptionStateChanged();
         }
 
@@ -703,7 +715,7 @@
 
     private void notifyCaptionStateChanged() {
         // TODO: b/366159408 - Ensure bounds sent with notification account for RTL mode.
-        if (!canEnterDesktopMode(mContext) || !Flags.enableDesktopWindowingAppHandleEducation()) {
+        if (!canEnterDesktopMode(mContext) || !isEducationEnabled()) {
             return;
         }
         if (!isCaptionVisible()) {
@@ -712,7 +724,7 @@
             // App handle is visible since `mWindowDecorViewHolder` is of type
             // [AppHandleViewHolder].
             final CaptionState captionState = new CaptionState.AppHandle(mTaskInfo,
-                    isHandleMenuActive(), getCurrentAppHandleBounds());
+                    isHandleMenuActive(), getCurrentAppHandleBounds(), isCapturedLinkAvailable());
             mWindowDecorCaptionHandleRepository.notifyCaptionChanged(captionState);
         } else {
             // App header is visible since `mWindowDecorViewHolder` is of type
@@ -725,8 +737,12 @@
         }
     }
 
+    private boolean isCapturedLinkAvailable() {
+        return mCapturedLink != null && !mCapturedLink.mExpired;
+    }
+
     private void notifyNoCaptionHandle() {
-        if (!canEnterDesktopMode(mContext) || !Flags.enableDesktopWindowingAppHandleEducation()) {
+        if (!canEnterDesktopMode(mContext) || !isEducationEnabled()) {
             return;
         }
         mWindowDecorCaptionHandleRepository.notifyCaptionChanged(
@@ -753,7 +769,8 @@
         final CaptionState captionState = new CaptionState.AppHeader(
                 mTaskInfo,
                 isHandleMenuActive(),
-                appChipGlobalPosition);
+                appChipGlobalPosition,
+                isCapturedLinkAvailable());
 
         mWindowDecorCaptionHandleRepository.notifyCaptionChanged(captionState);
     }
@@ -852,7 +869,7 @@
             Context context,
             ActivityManager.RunningTaskInfo taskInfo,
             boolean applyStartTransactionOnDraw,
-            boolean shouldSetTaskPositionAndCrop,
+            boolean shouldSetTaskVisibilityPositionAndCrop,
             boolean isStatusBarVisible,
             boolean isKeyguardVisibleAndOccluded,
             boolean inFullImmersiveMode,
@@ -948,7 +965,7 @@
                     : R.dimen.freeform_decor_shadow_unfocused_thickness;
         }
         relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
-        relayoutParams.mSetTaskPositionAndCrop = shouldSetTaskPositionAndCrop;
+        relayoutParams.mSetTaskVisibilityPositionAndCrop = shouldSetTaskVisibilityPositionAndCrop;
 
         // The configuration used to layout the window decoration. A copy is made instead of using
         // the original reference so that the configuration isn't mutated on config changes and
@@ -1089,13 +1106,13 @@
             if (mAppIconBitmap != null && mAppName != null) {
                 return;
             }
-            final ComponentName baseActivity = mTaskInfo.baseActivity;
-            if (baseActivity == null) {
-                Slog.e(TAG, "Base activity component not found in task");
+            if (mTaskInfo.baseIntent == null) {
+                Slog.e(TAG, "Base intent not found in task");
                 return;
             }
             final PackageManager pm = mUserContext.getPackageManager();
-            final ActivityInfo activityInfo = pm.getActivityInfo(baseActivity, 0 /* flags */);
+            final ActivityInfo activityInfo =
+                    pm.getActivityInfo(mTaskInfo.baseIntent.getComponent(), 0 /* flags */);
             final IconProvider provider = new IconProvider(mContext);
             final Drawable appIconDrawable = provider.getIcon(activityInfo);
             final Drawable badgedAppIconDrawable = pm.getUserBadgedIcon(appIconDrawable,
@@ -1347,6 +1364,8 @@
                 && Flags.enableDesktopWindowingMultiInstanceFeatures();
         final boolean shouldShowManageWindowsButton = supportsMultiInstance
                 && mMinimumInstancesFound;
+        final boolean shouldShowChangeAspectRatioButton = HandleMenu.Companion
+                .shouldShowChangeAspectRatioButton(mTaskInfo);
         final boolean inDesktopImmersive = mDesktopRepository
                 .isTaskInFullImmersiveState(mTaskInfo.taskId);
         mHandleMenu = mHandleMenuFactory.create(
@@ -1359,6 +1378,7 @@
                 canEnterDesktopMode(mContext),
                 supportsMultiInstance,
                 shouldShowManageWindowsButton,
+                shouldShowChangeAspectRatioButton,
                 getBrowserLink(),
                 mResult.mCaptionWidth,
                 mResult.mCaptionHeight,
@@ -1379,9 +1399,13 @@
                 /* onToSplitScreenClickListener= */ mOnToSplitscreenClickListener,
                 /* onNewWindowClickListener= */ mOnNewWindowClickListener,
                 /* onManageWindowsClickListener= */ mOnManageWindowsClickListener,
+                /* onAspectRatioSettingsClickListener= */ mOnChangeAspectRatioClickListener,
                 /* openInBrowserClickListener= */ (intent) -> {
                     mOpenInBrowserClickListener.accept(intent);
                     onCapturedLinkExpired();
+                    if (Flags.enableDesktopWindowingAppToWebEducation()) {
+                        mWindowDecorCaptionHandleRepository.onAppToWebUsage();
+                    }
                     return Unit.INSTANCE;
                 },
                 /* onOpenByDefaultClickListener= */ () -> {
@@ -1400,7 +1424,7 @@
                 },
                 /* forceShowSystemBars= */ inDesktopImmersive
         );
-        if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) {
+        if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
             notifyCaptionStateChanged();
         }
         mMinimumInstancesFound = false;
@@ -1471,7 +1495,7 @@
         mWindowDecorViewHolder.onHandleMenuClosed();
         mHandleMenu.close();
         mHandleMenu = null;
-        if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) {
+        if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
             notifyCaptionStateChanged();
         }
     }
@@ -1626,6 +1650,12 @@
                 && v.getTop() <= y && v.getBottom() >= y;
     }
 
+    /** Returns true if at least one education flag is enabled. */
+    private boolean isEducationEnabled() {
+        return Flags.enableDesktopWindowingAppHandleEducation()
+                || Flags.enableDesktopWindowingAppToWebEducation();
+    }
+
     @Override
     public void close() {
         closeDragResizeListener();
@@ -1635,7 +1665,7 @@
         disposeResizeVeil();
         disposeStatusBarInputLayer();
         clearCurrentViewHostRunnable();
-        if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) {
+        if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
             notifyNoCaptionHandle();
         }
         super.close();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 93bd929..2edc380 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -19,6 +19,7 @@
 import android.annotation.DimenRes
 import android.annotation.SuppressLint
 import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration
 import android.content.Context
 import android.content.Intent
 import android.content.res.ColorStateList
@@ -71,6 +72,7 @@
     private val shouldShowWindowingPill: Boolean,
     private val shouldShowNewWindowButton: Boolean,
     private val shouldShowManageWindowsButton: Boolean,
+    private val shouldShowChangeAspectRatioButton: Boolean,
     private val openInBrowserIntent: Intent?,
     private val captionWidth: Int,
     private val captionHeight: Int,
@@ -111,6 +113,10 @@
     private val shouldShowBrowserPill: Boolean
         get() = openInBrowserIntent != null
 
+    private val shouldShowMoreActionsPill: Boolean
+        get() = SHOULD_SHOW_SCREENSHOT_BUTTON || shouldShowNewWindowButton ||
+            shouldShowManageWindowsButton || shouldShowChangeAspectRatioButton
+
     init {
         updateHandleMenuPillPositions(captionX, captionY)
     }
@@ -121,6 +127,7 @@
         onToSplitScreenClickListener: () -> Unit,
         onNewWindowClickListener: () -> Unit,
         onManageWindowsClickListener: () -> Unit,
+        onChangeAspectRatioClickListener: () -> Unit,
         openInBrowserClickListener: (Intent) -> Unit,
         onOpenByDefaultClickListener: () -> Unit,
         onCloseMenuClickListener: () -> Unit,
@@ -138,6 +145,7 @@
             onToSplitScreenClickListener = onToSplitScreenClickListener,
             onNewWindowClickListener = onNewWindowClickListener,
             onManageWindowsClickListener = onManageWindowsClickListener,
+            onChangeAspectRatioClickListener = onChangeAspectRatioClickListener,
             openInBrowserClickListener = openInBrowserClickListener,
             onOpenByDefaultClickListener = onOpenByDefaultClickListener,
             onCloseMenuClickListener = onCloseMenuClickListener,
@@ -158,6 +166,7 @@
         onToSplitScreenClickListener: () -> Unit,
         onNewWindowClickListener: () -> Unit,
         onManageWindowsClickListener: () -> Unit,
+        onChangeAspectRatioClickListener: () -> Unit,
         openInBrowserClickListener: (Intent) -> Unit,
         onOpenByDefaultClickListener: () -> Unit,
         onCloseMenuClickListener: () -> Unit,
@@ -171,14 +180,16 @@
             shouldShowWindowingPill = shouldShowWindowingPill,
             shouldShowBrowserPill = shouldShowBrowserPill,
             shouldShowNewWindowButton = shouldShowNewWindowButton,
-            shouldShowManageWindowsButton = shouldShowManageWindowsButton
+            shouldShowManageWindowsButton = shouldShowManageWindowsButton,
+            shouldShowChangeAspectRatioButton = shouldShowChangeAspectRatioButton
         ).apply {
-            bind(taskInfo, appIconBitmap, appName)
+            bind(taskInfo, appIconBitmap, appName, shouldShowMoreActionsPill)
             this.onToDesktopClickListener = onToDesktopClickListener
             this.onToFullscreenClickListener = onToFullscreenClickListener
             this.onToSplitScreenClickListener = onToSplitScreenClickListener
             this.onNewWindowClickListener = onNewWindowClickListener
             this.onManageWindowsClickListener = onManageWindowsClickListener
+            this.onChangeAspectRatioClickListener = onChangeAspectRatioClickListener
             this.onOpenInBrowserClickListener = {
                 openInBrowserClickListener.invoke(openInBrowserIntent!!)
             }
@@ -392,8 +403,11 @@
                 R.dimen.desktop_mode_handle_menu_manage_windows_height
             )
         }
-        if (!SHOULD_SHOW_SCREENSHOT_BUTTON && !shouldShowNewWindowButton
-            && !shouldShowManageWindowsButton) {
+        if (!shouldShowChangeAspectRatioButton) {
+            menuHeight -= loadDimensionPixelSize(
+                R.dimen.desktop_mode_handle_menu_change_aspect_ratio_height)
+        }
+        if (!shouldShowMoreActionsPill) {
             menuHeight -= pillTopMargin
         }
         if (!shouldShowBrowserPill) {
@@ -427,7 +441,8 @@
         private val shouldShowWindowingPill: Boolean,
         private val shouldShowBrowserPill: Boolean,
         private val shouldShowNewWindowButton: Boolean,
-        private val shouldShowManageWindowsButton: Boolean
+        private val shouldShowManageWindowsButton: Boolean,
+        private val shouldShowChangeAspectRatioButton: Boolean
     ) {
         val rootView = LayoutInflater.from(context)
             .inflate(R.layout.desktop_mode_window_decor_handle_menu, null /* root */) as View
@@ -454,6 +469,8 @@
         private val newWindowBtn = moreActionsPill.requireViewById<Button>(R.id.new_window_button)
         private val manageWindowBtn = moreActionsPill
             .requireViewById<Button>(R.id.manage_windows_button)
+        private val changeAspectRatioBtn = moreActionsPill
+            .requireViewById<Button>(R.id.change_aspect_ratio_button)
 
         // Open in Browser Pill.
         private val openInBrowserPill = rootView.requireViewById<View>(R.id.open_in_browser_pill)
@@ -472,6 +489,7 @@
         var onToSplitScreenClickListener: (() -> Unit)? = null
         var onNewWindowClickListener: (() -> Unit)? = null
         var onManageWindowsClickListener: (() -> Unit)? = null
+        var onChangeAspectRatioClickListener: (() -> Unit)? = null
         var onOpenInBrowserClickListener: (() -> Unit)? = null
         var onOpenByDefaultClickListener: (() -> Unit)? = null
         var onCloseMenuClickListener: (() -> Unit)? = null
@@ -488,6 +506,7 @@
             collapseMenuButton.setOnClickListener { onCloseMenuClickListener?.invoke() }
             newWindowBtn.setOnClickListener { onNewWindowClickListener?.invoke() }
             manageWindowBtn.setOnClickListener { onManageWindowsClickListener?.invoke() }
+            changeAspectRatioBtn.setOnClickListener { onChangeAspectRatioClickListener?.invoke() }
 
             rootView.setOnTouchListener { _, event ->
                 if (event.actionMasked == ACTION_OUTSIDE) {
@@ -499,7 +518,12 @@
         }
 
         /** Binds the menu views to the new data. */
-        fun bind(taskInfo: RunningTaskInfo, appIconBitmap: Bitmap?, appName: CharSequence?) {
+        fun bind(
+            taskInfo: RunningTaskInfo,
+            appIconBitmap: Bitmap?,
+            appName: CharSequence?,
+            shouldShowMoreActionsPill: Boolean
+        ) {
             this.taskInfo = taskInfo
             this.style = calculateMenuStyle(taskInfo)
 
@@ -507,7 +531,10 @@
             if (shouldShowWindowingPill) {
                 bindWindowingPill(style)
             }
-            bindMoreActionsPill(style)
+            moreActionsPill.isGone = !shouldShowMoreActionsPill
+            if (shouldShowMoreActionsPill) {
+                bindMoreActionsPill(style)
+            }
             bindOpenInBrowserPill(style)
         }
 
@@ -616,27 +643,20 @@
         }
 
         private fun bindMoreActionsPill(style: MenuStyle) {
-            moreActionsPill.apply {
-                isGone = !shouldShowNewWindowButton && !SHOULD_SHOW_SCREENSHOT_BUTTON
-                        && !shouldShowManageWindowsButton
-            }
-            screenshotBtn.apply {
-                isGone = !SHOULD_SHOW_SCREENSHOT_BUTTON
-                background.setTint(style.backgroundColor)
-                setTextColor(style.textColor)
-                compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
-            }
-            newWindowBtn.apply {
-                isGone = !shouldShowNewWindowButton
-                background.setTint(style.backgroundColor)
-                setTextColor(style.textColor)
-                compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
-            }
-            manageWindowBtn.apply {
-                isGone = !shouldShowManageWindowsButton
-                background.setTint(style.backgroundColor)
-                setTextColor(style.textColor)
-                compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
+            arrayOf(
+                screenshotBtn to SHOULD_SHOW_SCREENSHOT_BUTTON,
+                newWindowBtn to shouldShowNewWindowButton,
+                manageWindowBtn to shouldShowManageWindowsButton,
+                changeAspectRatioBtn to shouldShowChangeAspectRatioButton,
+            ).forEach {
+                val button = it.first
+                val shouldShow = it.second
+                button.apply {
+                    isGone = !shouldShow
+                    background.setTint(style.backgroundColor)
+                    setTextColor(style.textColor)
+                    compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
+                }
             }
         }
 
@@ -664,6 +684,14 @@
     companion object {
         private const val TAG = "HandleMenu"
         private const val SHOULD_SHOW_SCREENSHOT_BUTTON = false
+
+        /**
+         * Returns whether the aspect ratio button should be shown for the task. It usually means
+         * that the task is on a large screen with ignore-orientation-request.
+         */
+        fun shouldShowChangeAspectRatioButton(taskInfo: RunningTaskInfo): Boolean =
+            taskInfo.appCompatTaskInfo.eligibleForUserAspectRatioButton() &&
+                taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FULLSCREEN
     }
 }
 
@@ -679,6 +707,7 @@
         shouldShowWindowingPill: Boolean,
         shouldShowNewWindowButton: Boolean,
         shouldShowManageWindowsButton: Boolean,
+        shouldShowChangeAspectRatioButton: Boolean,
         openInBrowserIntent: Intent?,
         captionWidth: Int,
         captionHeight: Int,
@@ -699,6 +728,7 @@
         shouldShowWindowingPill: Boolean,
         shouldShowNewWindowButton: Boolean,
         shouldShowManageWindowsButton: Boolean,
+        shouldShowChangeAspectRatioButton: Boolean,
         openInBrowserIntent: Intent?,
         captionWidth: Int,
         captionHeight: Int,
@@ -715,6 +745,7 @@
             shouldShowWindowingPill,
             shouldShowNewWindowButton,
             shouldShowManageWindowsButton,
+            shouldShowChangeAspectRatioButton,
             openInBrowserIntent,
             captionWidth,
             captionHeight,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index f97dfb89..b016c75 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -249,7 +249,9 @@
 
         if (!mTaskInfo.isVisible) {
             releaseViews(wct);
-            finishT.hide(mTaskSurface);
+            if (params.mSetTaskVisibilityPositionAndCrop) {
+                finishT.hide(mTaskSurface);
+            }
             return;
         }
 
@@ -422,7 +424,7 @@
 
     private void updateTaskSurface(RelayoutParams params, SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT, RelayoutResult<T> outResult) {
-        if (params.mSetTaskPositionAndCrop) {
+        if (params.mSetTaskVisibilityPositionAndCrop) {
             final Point taskPosition = mTaskInfo.positionInParent;
             startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
             finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight)
@@ -437,9 +439,13 @@
             shadowRadius =
                     loadDimension(mDecorWindowContext.getResources(), params.mShadowRadiusId);
         }
-        startT.setShadowRadius(mTaskSurface, shadowRadius).show(mTaskSurface);
+        startT.setShadowRadius(mTaskSurface, shadowRadius);
         finishT.setShadowRadius(mTaskSurface, shadowRadius);
 
+        if (params.mSetTaskVisibilityPositionAndCrop) {
+            startT.show(mTaskSurface);
+        }
+
         if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
             if (!DesktopModeStatus.isVeiledResizeEnabled()) {
                 // When fluid resize is enabled, add a background to freeform tasks
@@ -758,7 +764,7 @@
         Configuration mWindowDecorConfig;
 
         boolean mApplyStartTransactionOnDraw;
-        boolean mSetTaskPositionAndCrop;
+        boolean mSetTaskVisibilityPositionAndCrop;
         boolean mHasGlobalFocus;
 
         void reset() {
@@ -777,7 +783,7 @@
             mIsCaptionVisible = false;
 
             mApplyStartTransactionOnDraw = false;
-            mSetTaskPositionAndCrop = false;
+            mSetTaskVisibilityPositionAndCrop = false;
             mWindowDecorConfig = null;
             mHasGlobalFocus = false;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationPromoController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationPromoController.kt
new file mode 100644
index 0000000..b3489a4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationPromoController.kt
@@ -0,0 +1,231 @@
+/*
+ * 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.windowdecor.education
+
+import android.annotation.ColorInt
+import android.annotation.DimenRes
+import android.annotation.LayoutRes
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Point
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.View
+import android.view.WindowManager
+import android.widget.LinearLayout
+import android.widget.TextView
+import android.window.DisplayAreaInfo
+import android.window.WindowContainerTransaction
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.wm.shell.R
+import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.shared.animation.PhysicsAnimator
+import com.android.wm.shell.windowdecor.WindowManagerWrapper
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
+
+/**
+ * Controls the lifecycle of an education promo, including showing and hiding it.
+ */
+class DesktopWindowingEducationPromoController(
+    private val context: Context,
+    private val additionalSystemViewContainerFactory: AdditionalSystemViewContainer.Factory,
+    private val displayController: DisplayController,
+) : OnDisplayChangingListener {
+    private var educationView: View? = null
+    private var animator: PhysicsAnimator<View>? = null
+    private val springConfig by lazy {
+        PhysicsAnimator.SpringConfig(
+            SpringForce.STIFFNESS_MEDIUM,
+            SpringForce.DAMPING_RATIO_LOW_BOUNCY
+        )
+    }
+    private var popupWindow: AdditionalSystemViewContainer? = null
+
+    override fun onDisplayChange(
+        displayId: Int,
+        fromRotation: Int,
+        toRotation: Int,
+        newDisplayAreaInfo: DisplayAreaInfo?,
+        t: WindowContainerTransaction?
+    ) {
+        // Exit if the rotation hasn't changed or is changed by 180 degrees. [fromRotation] and
+        // [toRotation] can be one of the [@Surface.Rotation] values.
+        if ((fromRotation % 2 == toRotation % 2)) return
+        hideEducation()
+    }
+
+    /**
+     * Shows education promo.
+     *
+     * @param viewConfig features of the education.
+     * @param taskId is used in the title of popup window created for the education view.
+     */
+    fun showEducation(
+        viewConfig: EducationViewConfig,
+        taskId: Int
+    ) {
+        hideEducation()
+        educationView = createEducationView(viewConfig, taskId)
+        animator = createAnimator()
+        animateShowEducationTransition()
+        displayController.addDisplayChangingController(this)
+    }
+
+    /** Hide the current education view if visible */
+    private fun hideEducation() = animateHideEducationTransition { cleanUp() }
+
+    /** Create education view by inflating layout provided. */
+    private fun createEducationView(
+        viewConfig: EducationViewConfig,
+        taskId: Int
+    ): View {
+        val educationView =
+            LayoutInflater.from(context)
+                .inflate(
+                    viewConfig.viewLayout, /* root= */ null, /* attachToRoot= */ false)
+                .apply {
+                    alpha = 0f
+                    scaleX = 0f
+                    scaleY = 0f
+
+                    requireViewById<TextView>(R.id.education_text).apply {
+                        text = viewConfig.educationText
+                    }
+                    setOnTouchListener { _, motionEvent ->
+                        if (motionEvent.action == MotionEvent.ACTION_OUTSIDE) {
+                            hideEducation()
+                            true
+                        } else {
+                            false
+                        }
+                    }
+                    setOnClickListener {
+                        hideEducation()
+                    }
+                    setEducationColorScheme(viewConfig.educationColorScheme)
+                }
+
+        createEducationPopupWindow(
+            taskId,
+            viewConfig.viewGlobalCoordinates,
+            loadDimensionPixelSize(viewConfig.widthId),
+            loadDimensionPixelSize(viewConfig.heightId),
+            educationView = educationView)
+
+        return educationView
+    }
+
+    /** Create animator for education transitions */
+    private fun createAnimator(): PhysicsAnimator<View>? =
+        educationView?.let {
+            PhysicsAnimator.getInstance(it).apply { setDefaultSpringConfig(springConfig) }
+        }
+
+    /** Animate show transition for the education view */
+    private fun animateShowEducationTransition() {
+        animator
+            ?.spring(DynamicAnimation.ALPHA, 1f)
+            ?.spring(DynamicAnimation.SCALE_X, 1f)
+            ?.spring(DynamicAnimation.SCALE_Y, 1f)
+            ?.start()
+    }
+
+    /** Animate hide transition for the education view */
+    private fun animateHideEducationTransition(endActions: () -> Unit) {
+        animator
+            ?.spring(DynamicAnimation.ALPHA, 0f)
+            ?.spring(DynamicAnimation.SCALE_X, 0f)
+            ?.spring(DynamicAnimation.SCALE_Y, 0f)
+            ?.start()
+        endActions()
+    }
+
+    /** Remove education promo and clean up all relative properties */
+    private fun cleanUp() {
+        educationView = null
+        animator = null
+        popupWindow?.releaseView()
+        popupWindow = null
+        displayController.removeDisplayChangingController(this)
+    }
+
+    private fun createEducationPopupWindow(
+        taskId: Int,
+        educationViewGlobalCoordinates: Point,
+        width: Int,
+        height: Int,
+        educationView: View,
+    ) {
+        popupWindow =
+            additionalSystemViewContainerFactory.create(
+                windowManagerWrapper =
+                WindowManagerWrapper(context.getSystemService(WindowManager::class.java)),
+                taskId = taskId,
+                x = educationViewGlobalCoordinates.x,
+                y = educationViewGlobalCoordinates.y,
+                width = width,
+                height = height,
+                flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+                        WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
+                view = educationView)
+    }
+
+    private fun View.setEducationColorScheme(educationColorScheme: EducationColorScheme) {
+        requireViewById<LinearLayout>(R.id.education_container).apply {
+            background.setTint(educationColorScheme.container)
+        }
+        requireViewById<TextView>(R.id.education_text).apply {
+            setTextColor(educationColorScheme.text)
+        }
+    }
+
+    private fun loadDimensionPixelSize(@DimenRes resourceId: Int): Int {
+        if (resourceId == Resources.ID_NULL) return 0
+        return context.resources.getDimensionPixelSize(resourceId)
+    }
+
+    /**
+     * The configuration for education view features:
+     *
+     * @property viewLayout Layout resource ID of the view to be used for education promo.
+     * @property viewGlobalCoordinates Global (screen) coordinates of the education.
+     * @property educationText Text to be added to the TextView of the promo.
+     * @property widthId res Id for education width
+     * @property heightId res Id for education height
+     */
+    data class EducationViewConfig(
+        @LayoutRes val viewLayout: Int,
+        val educationColorScheme: EducationColorScheme,
+        val viewGlobalCoordinates: Point,
+        val educationText: String,
+        @DimenRes val widthId: Int,
+        @DimenRes val heightId: Int
+    )
+
+    /**
+     * Color scheme of education view:
+     *
+     * @property container Color of the container of the education.
+     * @property text Text color of the [TextView] of education promo.
+     */
+    data class EducationColorScheme(
+        @ColorInt val container: Int,
+        @ColorInt val text: Int,
+    )
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt
index c61b31e..4fa2744 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt
@@ -80,7 +80,7 @@
    * @param tooltipViewConfig features of tooltip.
    * @param taskId is used in the title of popup window created for the tooltip view.
    */
-  fun showEducationTooltip(tooltipViewConfig: EducationViewConfig, taskId: Int) {
+  fun showEducationTooltip(tooltipViewConfig: TooltipEducationViewConfig, taskId: Int) {
     hideEducationTooltip()
     tooltipView = createEducationTooltipView(tooltipViewConfig, taskId)
     animator = createAnimator()
@@ -93,7 +93,7 @@
 
   /** Create education view by inflating layout provided. */
   private fun createEducationTooltipView(
-      tooltipViewConfig: EducationViewConfig,
+      tooltipViewConfig: TooltipEducationViewConfig,
       taskId: Int,
   ): View {
     val tooltipView =
@@ -271,7 +271,7 @@
    * @property onEducationClickAction Lambda to be executed when the tooltip is clicked.
    * @property onDismissAction Lambda to be executed when the tooltip is dismissed.
    */
-  data class EducationViewConfig(
+  data class TooltipEducationViewConfig(
       @LayoutRes val tooltipViewLayout: Int,
       val tooltipColorScheme: TooltipColorScheme,
       val tooltipViewGlobalCoordinates: Point,
@@ -299,4 +299,4 @@
     UP,
     LEFT,
   }
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
index e43c3a6..61963cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
@@ -30,6 +30,7 @@
 import com.android.wm.shell.common.DisplayChangeController
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger
 import com.android.wm.shell.desktopmode.DesktopRepository
 import com.android.wm.shell.desktopmode.DesktopTasksController
 import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
@@ -48,6 +49,7 @@
     private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
     private val returnToDragStartAnimator: ReturnToDragStartAnimator,
     private val taskRepository: DesktopRepository,
+    private val desktopModeEventLogger: DesktopModeEventLogger,
 ) : DisplayChangeController.OnDisplayChangingListener {
     @VisibleForTesting
     var tilingTransitionHandlerByDisplayId = SparseArray<DesktopTilingWindowDecoration>()
@@ -80,6 +82,7 @@
                             toggleResizeDesktopTaskTransitionHandler,
                             returnToDragStartAnimator,
                             taskRepository,
+                            desktopModeEventLogger,
                         )
                     tilingTransitionHandlerByDisplayId.put(displayId, newHandler)
                     newHandler
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
index 209eb5e..6cdc517c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
@@ -23,6 +23,7 @@
 import android.graphics.Region
 import android.os.Binder
 import android.view.LayoutInflater
+import android.view.MotionEvent
 import android.view.RoundedCorner
 import android.view.SurfaceControl
 import android.view.SurfaceControlViewHost
@@ -39,6 +40,7 @@
 import android.view.WindowlessWindowManager
 import com.android.wm.shell.R
 import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger
 import java.util.function.Supplier
 
 /**
@@ -141,8 +143,9 @@
         t.setRelativeLayer(leash, relativeLeash, 1)
     }
 
-    override fun onDividerMoveStart(pos: Int) {
+    override fun onDividerMoveStart(pos: Int, motionEvent: MotionEvent) {
         setSlippery(false)
+        transitionHandler.onDividerHandleDragStart(motionEvent)
     }
 
     /**
@@ -161,13 +164,13 @@
      * Notifies the transition handler of tiling operations ending, which might result in resizing
      * WindowContainerTransactions if the sizes of the tiled tasks changed.
      */
-    override fun onDividerMovedEnd(pos: Int) {
+    override fun onDividerMovedEnd(pos: Int, motionEvent: MotionEvent) {
         setSlippery(true)
         val t = transactionSupplier.get()
         t.setPosition(leash, pos.toFloat() - maxRoundedCornerRadius, dividerBounds.top.toFloat())
         val dividerWidth = dividerBounds.width()
         dividerBounds.set(pos, dividerBounds.top, pos + dividerWidth, dividerBounds.bottom)
-        transitionHandler.onDividerHandleDragEnd(dividerBounds, t)
+        transitionHandler.onDividerHandleDragEnd(dividerBounds, t, motionEvent)
     }
 
     private fun getWindowManagerParams(): WindowManager.LayoutParams {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
index c46767c..1c593c03 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
@@ -25,6 +25,7 @@
 import android.os.IBinder
 import android.os.UserHandle
 import android.util.Slog
+import android.view.MotionEvent
 import android.view.SurfaceControl
 import android.view.SurfaceControl.Transaction
 import android.view.WindowManager.TRANSIT_CHANGE
@@ -44,6 +45,8 @@
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.DisplayLayout
 import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
 import com.android.wm.shell.desktopmode.DesktopRepository
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
 import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
@@ -70,6 +73,7 @@
     private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
     private val returnToDragStartAnimator: ReturnToDragStartAnimator,
     private val taskRepository: DesktopRepository,
+    private val desktopModeEventLogger: DesktopModeEventLogger,
     private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() },
 ) :
     Transitions.TransitionHandler,
@@ -218,6 +222,25 @@
         return tilingManager
     }
 
+    fun onDividerHandleDragStart(motionEvent: MotionEvent) {
+        val leftTiledTask = leftTaskResizingHelper ?: return
+        val rightTiledTask = rightTaskResizingHelper ?: return
+
+        desktopModeEventLogger.logTaskResizingStarted(
+            ResizeTrigger.TILING_DIVIDER,
+            motionEvent,
+            leftTiledTask.taskInfo,
+            displayController
+        )
+
+        desktopModeEventLogger.logTaskResizingStarted(
+            ResizeTrigger.TILING_DIVIDER,
+            motionEvent,
+            rightTiledTask.taskInfo,
+            displayController
+        )
+    }
+
     fun onDividerHandleMoved(dividerBounds: Rect, t: SurfaceControl.Transaction): Boolean {
         val leftTiledTask = leftTaskResizingHelper ?: return false
         val rightTiledTask = rightTaskResizingHelper ?: return false
@@ -266,10 +289,32 @@
         return true
     }
 
-    fun onDividerHandleDragEnd(dividerBounds: Rect, t: SurfaceControl.Transaction) {
+    fun onDividerHandleDragEnd(
+        dividerBounds: Rect,
+        t: SurfaceControl.Transaction,
+        motionEvent: MotionEvent,
+    ) {
         val leftTiledTask = leftTaskResizingHelper ?: return
         val rightTiledTask = rightTaskResizingHelper ?: return
 
+        desktopModeEventLogger.logTaskResizingEnded(
+            ResizeTrigger.TILING_DIVIDER,
+            motionEvent,
+            leftTiledTask.taskInfo,
+            leftTiledTask.newBounds.height(),
+            leftTiledTask.newBounds.width(),
+            displayController
+        )
+
+        desktopModeEventLogger.logTaskResizingEnded(
+            ResizeTrigger.TILING_DIVIDER,
+            motionEvent,
+            rightTiledTask.taskInfo,
+            rightTiledTask.newBounds.height(),
+            rightTiledTask.newBounds.width(),
+            displayController
+        )
+
         if (leftTiledTask.newBounds == leftTiledTask.bounds) {
             leftTiledTask.hideVeil()
             rightTiledTask.hideVeil()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt
index b3b30ad..9799d01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt
@@ -15,14 +15,16 @@
  */
 package com.android.wm.shell.windowdecor.tiling
 
+import android.view.MotionEvent
+
 /** Divider move callback to whichever entity that handles the moving logic. */
 interface DividerMoveCallback {
     /** Called on the divider move start gesture. */
-    fun onDividerMoveStart(pos: Int)
+    fun onDividerMoveStart(pos: Int, motionEvent: MotionEvent)
 
     /** Called on the divider moved by dragging it. */
     fun onDividerMove(pos: Int): Boolean
 
     /** Called on divider move gesture end. */
-    fun onDividerMovedEnd(pos: Int)
+    fun onDividerMovedEnd(pos: Int, motionEvent: MotionEvent)
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
index 8922905..111e28e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
@@ -206,7 +206,7 @@
         when (event.actionMasked) {
             MotionEvent.ACTION_DOWN -> {
                 if (!isWithinHandleRegion(yTouchPosInDivider)) return true
-                callback.onDividerMoveStart(touchPos)
+                callback.onDividerMoveStart(touchPos, event)
                 setTouching()
                 canResize = true
             }
@@ -230,7 +230,7 @@
                 if (!canResize) return true
                 if (moving && resized) {
                     dividerBounds.left = dividerBounds.left + lastAcceptedPos - startPos
-                    callback.onDividerMovedEnd(dividerBounds.left)
+                    callback.onDividerMovedEnd(dividerBounds.left, event)
                 }
                 moving = false
                 canResize = false
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MinimizeAppWindowsTest.kt
similarity index 61%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MinimizeAppWindowsTest.kt
index 94b2bdf..e16159c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MinimizeAppWindowsTest.kt
@@ -14,9 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.wm.shell.functional
 
-import com.android.systemui.kosmos.Kosmos
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.MinimizeAppWindows
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+/* Functional test for [MinimizeAppWindows]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class MinimizeAppWindowsTest : MinimizeAppWindows()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt
new file mode 100644
index 0000000..b548363
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * Base scenario test for minimizing all the desktop app windows one-by-one by clicking their
+ * minimize buttons.
+ */
+@Ignore("Test Base Class")
+abstract class MinimizeAppWindows
+constructor(private val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val testApp1 = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+    private val testApp2 = DesktopModeAppHelper(NonResizeableAppHelper(instrumentation))
+    private val testApp3 = DesktopModeAppHelper(NewTasksAppHelper(instrumentation))
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0)
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+        Assume.assumeTrue(Flags.enableMinimizeButton())
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+        ChangeDisplayOrientationRule.setRotation(rotation)
+        testApp1.enterDesktopWithDrag(wmHelper, device)
+        testApp2.launchViaIntent(wmHelper)
+        testApp3.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun minimizeAllAppWindows() {
+        testApp3.minimizeDesktopApp(wmHelper, device)
+        testApp2.minimizeDesktopApp(wmHelper, device)
+        testApp1.minimizeDesktopApp(wmHelper, device)
+    }
+
+    @After
+    fun teardown() {
+        testApp1.exit(wmHelper)
+        testApp2.exit(wmHelper)
+        testApp3.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 72d4dc6..13a8518 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -700,7 +700,7 @@
                 eq(tInfo), eq(st), eq(ft), eq(callback));
 
         mBackTransitionHandler.onAnimationFinished();
-        final TransitionInfo.Change openToClose = createAppChange(openTaskId, TRANSIT_CLOSE,
+        final TransitionInfo.Change openToClose = createAppChangeFromChange(open, TRANSIT_CLOSE,
                 FLAG_BACK_GESTURE_ANIMATED);
         tInfo2 = createTransitionInfo(TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION, openToClose);
         mBackTransitionHandler.mClosePrepareTransition = mock(IBinder.class);
@@ -830,6 +830,16 @@
         return change;
     }
 
+    private TransitionInfo.Change createAppChangeFromChange(
+            TransitionInfo.Change originalChange, @TransitionInfo.TransitionMode int mode,
+            @TransitionInfo.ChangeFlags int flags) {
+        final TransitionInfo.Change change = new TransitionInfo.Change(
+                originalChange.getTaskInfo().token, originalChange.getLeash());
+        change.setMode(mode);
+        change.setFlags(flags);
+        return change;
+    }
+
     private static TransitionInfo createTransitionInfo(
             @WindowManager.TransitionType int type, TransitionInfo.Change ... changes) {
         final TransitionInfo info = new TransitionInfo(type, 0);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
index 266e484..2ed7d07 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -62,6 +62,7 @@
 
     @Before
     public void setUp() throws Exception {
+        mTargetProgressCalled = new CountDownLatch(1);
         mMainThreadHandler = new Handler(Looper.getMainLooper());
         final BackMotionEvent backEvent = backMotionEventFrom(0, 0);
         mMainThreadHandler.post(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
index ecaf970..803e5d4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
@@ -43,38 +43,30 @@
                     .apply {
                         isTopActivityTransparent = true
                         numActivities = 1
-                    }))
-        assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
-            createFreeformTask(/* displayId */ 0)
-                    .apply {
-                        isTopActivityTransparent = true
-                        numActivities = 0
+                        isTopActivityNoDisplay = false
                     }))
     }
 
     @Test
-    fun testIsTopActivityExemptFromDesktopWindowing_singleTopActivity() {
-        assertTrue(isTopActivityExemptFromDesktopWindowing(mContext,
-            createFreeformTask(/* displayId */ 0)
-                    .apply {
-                        isTopActivityTransparent = true
-                        numActivities = 1
-                    }))
+    fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent_multipleActivities() {
         assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
             createFreeformTask(/* displayId */ 0)
-                    .apply {
-                        isTopActivityTransparent = false
-                        numActivities = 1
-                    }))
+                .apply {
+                    isTopActivityTransparent = true
+                    numActivities = 2
+                    isTopActivityNoDisplay = false
+                }))
     }
 
     @Test
-    fun testIsTopActivityExemptFromDesktopWindowing__topActivityStyleFloating() {
+    fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent_notDisplayed() {
         assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
             createFreeformTask(/* displayId */ 0)
-                    .apply {
-                        isTopActivityStyleFloating = true
-                    }))
+                .apply {
+                    isTopActivityTransparent = true
+                    numActivities = 1
+                    isTopActivityNoDisplay = true
+                }))
     }
 
     @Test
@@ -85,6 +77,19 @@
             createFreeformTask(/* displayId */ 0)
                     .apply {
                         baseActivity = baseComponent
+                        isTopActivityNoDisplay = false
                     }))
     }
+
+    @Test
+    fun testIsTopActivityExemptFromDesktopWindowing_systemUiTask_notDisplayed() {
+        val systemUIPackageName = context.resources.getString(R.string.config_systemUi)
+        val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
+        assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
+            createFreeformTask(/* displayId */ 0)
+                .apply {
+                    baseActivity = baseComponent
+                    isTopActivityNoDisplay = true
+                }))
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
new file mode 100644
index 0000000..fea8236
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.content.ContentResolver
+import android.os.Binder
+import android.provider.Settings
+import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+import android.testing.AndroidTestingRunner
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.IWindowManager
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.DisplayAreaInfo
+import android.window.WindowContainerTransaction
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.never
+import com.android.wm.shell.MockToken
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
+
+/**
+ * Test class for [DesktopDisplayEventHandler]
+ *
+ * Usage: atest WMShellUnitTests:DesktopDisplayEventHandlerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopDisplayEventHandlerTest : ShellTestCase() {
+
+  @Mock lateinit var testExecutor: ShellExecutor
+  @Mock lateinit var transitions: Transitions
+  @Mock lateinit var displayController: DisplayController
+  @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+  @Mock private lateinit var mockWindowManager: IWindowManager
+
+  private lateinit var shellInit: ShellInit
+  private lateinit var handler: DesktopDisplayEventHandler
+
+  @Before
+  fun setUp() {
+    shellInit = spy(ShellInit(testExecutor))
+    whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
+    val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+    whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
+    handler =
+        DesktopDisplayEventHandler(
+            context,
+            shellInit,
+            transitions,
+            displayController,
+            rootTaskDisplayAreaOrganizer,
+            mockWindowManager,
+        )
+    shellInit.init()
+  }
+
+  private fun testDisplayWindowingModeSwitch(
+    defaultWindowingMode: Int,
+    extendedDisplayEnabled: Boolean,
+    expectTransition: Boolean
+  ) {
+    val externalDisplayId = 100
+    val captor = ArgumentCaptor.forClass(OnDisplaysChangedListener::class.java)
+    verify(displayController).addDisplayWindowListener(captor.capture())
+    val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+    tda.configuration.windowConfiguration.windowingMode = defaultWindowingMode
+    whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { defaultWindowingMode }
+    val settingsSession = ExtendedDisplaySettingsSession(
+      context.contentResolver, if (extendedDisplayEnabled) 1 else 0)
+
+    settingsSession.use {
+      // The external display connected.
+      whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
+        .thenReturn(intArrayOf(DEFAULT_DISPLAY, externalDisplayId))
+      captor.value.onDisplayAdded(externalDisplayId)
+      tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+      // The external display disconnected.
+      whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
+        .thenReturn(intArrayOf(DEFAULT_DISPLAY))
+      captor.value.onDisplayRemoved(externalDisplayId)
+
+      if (expectTransition) {
+        val arg = argumentCaptor<WindowContainerTransaction>()
+        verify(transitions, times(2)).startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
+        assertThat(arg.firstValue.changes[tda.token.asBinder()]?.windowingMode)
+          .isEqualTo(WINDOWING_MODE_FREEFORM)
+        assertThat(arg.secondValue.changes[tda.token.asBinder()]?.windowingMode)
+          .isEqualTo(defaultWindowingMode)
+      } else {
+        verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull())
+      }
+    }
+  }
+
+  @Test
+  fun displayWindowingModeSwitchOnDisplayConnected_extendedDisplayDisabled() {
+    testDisplayWindowingModeSwitch(
+      defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+      extendedDisplayEnabled = false,
+      expectTransition = false
+    )
+  }
+
+  @Test
+  fun displayWindowingModeSwitchOnDisplayConnected_fullscreenDisplay() {
+    testDisplayWindowingModeSwitch(
+      defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+      extendedDisplayEnabled = true,
+      expectTransition = true
+    )
+  }
+
+  @Test
+  fun displayWindowingModeSwitchOnDisplayConnected_freeformDisplay() {
+    testDisplayWindowingModeSwitch(
+      defaultWindowingMode = WINDOWING_MODE_FREEFORM,
+      extendedDisplayEnabled = true,
+      expectTransition = false
+    )
+  }
+
+  private class ExtendedDisplaySettingsSession(
+    private val contentResolver: ContentResolver,
+      private val overrideValue: Int
+  ) : AutoCloseable {
+    private val settingName = DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+    private val initialValue = Settings.Global.getInt(contentResolver, settingName, 0)
+
+    init { Settings.Global.putInt(contentResolver, settingName, overrideValue) }
+
+    override fun close() {
+      Settings.Global.putInt(contentResolver, settingName, initialValue)
+    }
+  }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
index 868883d..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
@@ -39,9 +39,12 @@
 import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.window.flags.Flags
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler.PendingMixedTransition
 import com.android.wm.shell.freeform.FreeformTaskTransitionHandler
+import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertFalse
@@ -51,7 +54,9 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.kotlin.any
 import org.mockito.kotlin.anyOrNull
@@ -80,6 +85,8 @@
     @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
     @Mock lateinit var mockHandler: Handler
     @Mock lateinit var closingTaskLeash: SurfaceControl
+    @Mock lateinit var shellInit: ShellInit
+    @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
 
     private lateinit var mixedHandler: DesktopMixedTransitionHandler
 
@@ -94,7 +101,9 @@
                 closeDesktopTaskTransitionHandler,
                 desktopImmersiveController,
                 interactionJankMonitor,
-                mockHandler
+                mockHandler,
+                shellInit,
+                rootTaskDisplayAreaOrganizer,
             )
     }
 
@@ -238,8 +247,10 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
-    fun startLaunchTransition_immersiveMixDisabled_doesNotUseMixedHandler() {
+    @DisableFlags(
+        Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP,
+        Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+    fun startLaunchTransition_immersiveAndAppLaunchFlagsDisabled_doesNotUseMixedHandler() {
         val wct = WindowContainerTransaction()
         val task = createTask(WINDOWING_MODE_FREEFORM)
         whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
@@ -274,6 +285,24 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+    fun startLaunchTransition_desktopAppLaunchEnabled_usesMixedHandler() {
+        val wct = WindowContainerTransaction()
+        val task = createTask(WINDOWING_MODE_FREEFORM)
+        whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+            .thenReturn(Binder())
+
+        mixedHandler.startLaunchTransition(
+            transitionType = TRANSIT_OPEN,
+            wct = wct,
+            taskId = task.taskId,
+            exitingImmersiveTask = null
+        )
+
+        verify(transitions).startTransition(TRANSIT_OPEN, wct, mixedHandler)
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
     fun startAndAnimateLaunchTransition_withoutImmersiveChange_dispatchesAllChangesToLeftOver() {
         val wct = WindowContainerTransaction()
@@ -355,6 +384,165 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+    fun startAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() {
+        val wct = WindowContainerTransaction()
+        val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
+        val launchTaskChange = createChange(launchingTask)
+        val transition = Binder()
+        whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+            .thenReturn(transition)
+
+        mixedHandler.startLaunchTransition(
+            transitionType = TRANSIT_OPEN,
+            wct = wct,
+            taskId = launchingTask.taskId,
+            minimizingTaskId = null,
+        )
+        mixedHandler.startAnimation(
+            transition,
+            createTransitionInfo(
+                TRANSIT_OPEN,
+                listOf(launchTaskChange)
+            ),
+            SurfaceControl.Transaction(),
+            SurfaceControl.Transaction(),
+        ) { }
+
+        verify(rootTaskDisplayAreaOrganizer, times(0))
+            .reparentToDisplayArea(anyInt(), any(), any())
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+    fun startAndAnimateLaunchTransition_withMinimizeChange_reparentsMinimizeChange() {
+        val wct = WindowContainerTransaction()
+        val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
+        val minimizingTask = createTask(WINDOWING_MODE_FREEFORM)
+        val launchTaskChange = createChange(launchingTask)
+        val minimizeChange = createChange(minimizingTask)
+        val transition = Binder()
+        whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+            .thenReturn(transition)
+
+        mixedHandler.startLaunchTransition(
+            transitionType = TRANSIT_OPEN,
+            wct = wct,
+            taskId = launchingTask.taskId,
+            minimizingTaskId = minimizingTask.taskId,
+        )
+        mixedHandler.startAnimation(
+            transition,
+            createTransitionInfo(
+                TRANSIT_OPEN,
+                listOf(launchTaskChange, minimizeChange)
+            ),
+            SurfaceControl.Transaction(),
+            SurfaceControl.Transaction(),
+        ) { }
+
+        verify(rootTaskDisplayAreaOrganizer).reparentToDisplayArea(
+            anyInt(), eq(minimizeChange.leash), any())
+    }
+
+    @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)
+        val launchTaskChange = createChange(launchingTask)
+        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,
+            )
+        )
+        mixedHandler.startAnimation(
+            transition,
+            createTransitionInfo(
+                TRANSIT_OPEN,
+                listOf(launchTaskChange)
+            ),
+            SurfaceControl.Transaction(),
+            SurfaceControl.Transaction(),
+        ) { }
+
+        verify(rootTaskDisplayAreaOrganizer, times(0))
+            .reparentToDisplayArea(anyInt(), any(), any())
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+    fun addPendingAndAnimateLaunchTransition_withMinimizeChange_reparentsMinimizeChange() {
+        val wct = WindowContainerTransaction()
+        val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
+        val minimizingTask = createTask(WINDOWING_MODE_FREEFORM)
+        val launchTaskChange = createChange(launchingTask)
+        val minimizeChange = createChange(minimizingTask)
+        val transition = Binder()
+        whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+            .thenReturn(transition)
+
+        mixedHandler.addPendingMixedTransition(
+            PendingMixedTransition.Launch(
+                transition = transition,
+                launchingTask = launchingTask.taskId,
+                minimizingTask = minimizingTask.taskId,
+                exitingImmersiveTask = null,
+            )
+        )
+        mixedHandler.startAnimation(
+            transition,
+            createTransitionInfo(
+                TRANSIT_OPEN,
+                listOf(launchTaskChange, minimizeChange)
+            ),
+            SurfaceControl.Transaction(),
+            SurfaceControl.Transaction(),
+        ) { }
+
+        verify(rootTaskDisplayAreaOrganizer).reparentToDisplayArea(
+            anyInt(), eq(minimizeChange.leash), any())
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
     fun startAndAnimateLaunchTransition_removesPendingMixedTransition() {
         val wct = WindowContainerTransaction()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index fc89b0e..315a46f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -1123,11 +1123,11 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
-  fun moveRunningTaskToDesktop_topActivityTranslucentWithStyleFloating_taskIsMovedToDesktop() {
+  fun moveRunningTaskToDesktop_topActivityTranslucentWithoutDisplay_taskIsMovedToDesktop() {
     val task =
       setUpFullscreenTask().apply {
         isTopActivityTransparent = true
-        isTopActivityStyleFloating = true
+        isTopActivityNoDisplay = true
         numActivities = 1
       }
 
@@ -1139,11 +1139,11 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
-  fun moveRunningTaskToDesktop_topActivityTranslucentWithoutStyleFloating_doesNothing() {
+  fun moveRunningTaskToDesktop_topActivityTranslucentWithDisplay_doesNothing() {
     val task =
       setUpFullscreenTask().apply {
         isTopActivityTransparent = true
-        isTopActivityStyleFloating = false
+        isTopActivityNoDisplay = false
         numActivities = 1
       }
 
@@ -1153,20 +1153,41 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
-  fun moveRunningTaskToDesktop_systemUIActivity_doesNothing() {
-    val task = setUpFullscreenTask()
-
+  fun moveRunningTaskToDesktop_systemUIActivityWithDisplay_doesNothing() {
     // Set task as systemUI package
     val systemUIPackageName = context.resources.getString(
       com.android.internal.R.string.config_systemUi)
     val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
-    task.baseActivity = baseComponent
+    val task =
+      setUpFullscreenTask().apply {
+        baseActivity = baseComponent
+        isTopActivityNoDisplay = false
+      }
 
     controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
     verifyEnterDesktopWCTNotExecuted()
   }
 
   @Test
+  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+  fun moveRunningTaskToDesktop_systemUIActivityWithoutDisplay_doesNothing() {
+    // Set task as systemUI package
+    val systemUIPackageName = context.resources.getString(
+      com.android.internal.R.string.config_systemUi)
+    val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
+    val task =
+      setUpFullscreenTask().apply {
+        baseActivity = baseComponent
+        isTopActivityNoDisplay = true
+      }
+
+    controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+
+    val wct = getLatestEnterDesktopWct()
+    assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
+  }
+
+  @Test
   fun moveRunningTaskToDesktop_deviceSupported_taskIsMovedToDesktop() {
     val task = setUpFullscreenTask()
 
@@ -1413,7 +1434,8 @@
       eq(TRANSIT_TO_FRONT),
       any(),
       eq(freeformTasks[0].taskId),
-      anyOrNull()
+      anyOrNull(),
+      anyOrNull(),
     )).thenReturn(Binder())
 
     controller.moveTaskToFront(freeformTasks[0], remoteTransition = null)
@@ -1471,7 +1493,7 @@
     val task = createTaskInfo(1001)
     whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null)
     whenever(desktopMixedTransitionHandler
-      .startLaunchTransition(eq(TRANSIT_OPEN), any(), eq(task.taskId), anyOrNull()))
+      .startLaunchTransition(eq(TRANSIT_OPEN), any(), eq(task.taskId), anyOrNull(), anyOrNull()))
       .thenReturn(Binder())
 
     controller.moveTaskToFront(task.taskId, remoteTransition = null)
@@ -2222,14 +2244,14 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
-  fun handleRequest_topActivityTransparentWithStyleFloating_returnSwitchToFreeformWCT() {
+  fun handleRequest_topActivityTransparentWithoutDisplay_returnSwitchToFreeformWCT() {
     val freeformTask = setUpFreeformTask()
     markTaskVisible(freeformTask)
 
     val task =
       setUpFullscreenTask().apply {
         isTopActivityTransparent = true
-        isTopActivityStyleFloating = true
+        isTopActivityNoDisplay = true
         numActivities = 1
       }
 
@@ -2240,11 +2262,14 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
-  fun handleRequest_topActivityTransparentWithoutStyleFloating_returnSwitchToFullscreenWCT() {
+  fun handleRequest_topActivityTransparentWithDisplay_returnSwitchToFullscreenWCT() {
+    val freeformTask = setUpFreeformTask()
+    markTaskVisible(freeformTask)
+
     val task =
       setUpFreeformTask().apply {
         isTopActivityTransparent = true
-        isTopActivityStyleFloating = false
+        isTopActivityNoDisplay = false
         numActivities = 1
       }
 
@@ -2255,14 +2280,19 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
-  fun handleRequest_systemUIActivity_returnSwitchToFullscreenWCT() {
-    val task = setUpFreeformTask()
+  fun handleRequest_systemUIActivityWithDisplay_returnSwitchToFullscreenWCT() {
+    val freeformTask = setUpFreeformTask()
+    markTaskVisible(freeformTask)
 
     // Set task as systemUI package
     val systemUIPackageName = context.resources.getString(
       com.android.internal.R.string.config_systemUi)
     val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
-    task.baseActivity = baseComponent
+    val task =
+      setUpFreeformTask().apply {
+        baseActivity = baseComponent
+        isTopActivityNoDisplay = false
+      }
 
     val result = controller.handleRequest(Binder(), createTransition(task))
     assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
@@ -2270,6 +2300,27 @@
   }
 
   @Test
+  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+  fun handleRequest_systemUIActivityWithoutDisplay_returnSwitchToFreeformWCT() {
+    val freeformTask = setUpFreeformTask()
+    markTaskVisible(freeformTask)
+
+    // Set task as systemUI package
+    val systemUIPackageName = context.resources.getString(
+      com.android.internal.R.string.config_systemUi)
+    val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
+    val task =
+      setUpFullscreenTask().apply {
+        baseActivity = baseComponent
+        isTopActivityNoDisplay = true
+      }
+
+    val result = controller.handleRequest(Binder(), createTransition(task))
+    assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+      .isEqualTo(WINDOWING_MODE_FREEFORM)
+  }
+
+  @Test
   @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
   fun handleRequest_backTransition_singleTaskNoToken_noWallpaper_doesNotHandle() {
     val task = setUpFreeformTask()
@@ -3692,7 +3743,8 @@
         runOnTransitionStart = runOnStartTransit,
       ))
     whenever(desktopMixedTransitionHandler
-      .startLaunchTransition(any(), any(), anyInt(), anyOrNull())).thenReturn(transition)
+      .startLaunchTransition(any(), any(), anyInt(), anyOrNull(), anyOrNull()))
+      .thenReturn(transition)
 
     controller.moveTaskToFront(task.taskId, remoteTransition = null)
 
@@ -3713,7 +3765,7 @@
         runOnTransitionStart = runOnStartTransit,
       ))
     whenever(desktopMixedTransitionHandler
-      .startLaunchTransition(any(), any(), eq(task.taskId), anyOrNull()))
+      .startLaunchTransition(any(), any(), eq(task.taskId), anyOrNull(), anyOrNull()))
       .thenReturn(transition)
 
     controller.moveTaskToFront(task.taskId, remoteTransition = null)
@@ -4102,7 +4154,7 @@
     val arg: ArgumentCaptor<WindowContainerTransaction> =
       ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
     verify(desktopMixedTransitionHandler)
-      .startLaunchTransition(eq(type), capture(arg), anyInt(), anyOrNull())
+      .startLaunchTransition(eq(type), capture(arg), anyInt(), anyOrNull(), anyOrNull())
     return arg.value
   }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepositoryTest.kt
index e3caf2e..38c6ed9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepositoryTest.kt
@@ -49,7 +49,10 @@
     val taskInfo = createTaskInfo(WINDOWING_MODE_FULLSCREEN, GMAIL_PACKAGE_NAME)
     val appHandleCaptionState =
         CaptionState.AppHandle(
-            taskInfo, false, Rect(/* left= */ 0, /* top= */ 1, /* right= */ 2, /* bottom= */ 3))
+          runningTaskInfo = taskInfo,
+          isHandleMenuExpanded = false,
+          globalAppHandleBounds = Rect(/* left= */ 0, /* top= */ 1, /* right= */ 2, /* bottom= */ 3),
+          isCapturedLinkAvailable = false)
 
     captionHandleRepository.notifyCaptionChanged(appHandleCaptionState)
 
@@ -61,7 +64,10 @@
     val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM, GMAIL_PACKAGE_NAME)
     val appHeaderCaptionState =
         CaptionState.AppHeader(
-            taskInfo, true, Rect(/* left= */ 0, /* top= */ 1, /* right= */ 2, /* bottom= */ 3))
+          runningTaskInfo = taskInfo,
+          isHeaderMenuExpanded = true,
+          globalAppChipBounds = Rect(/* left= */ 0, /* top= */ 1, /* right= */ 2, /* bottom= */ 3),
+          isCapturedLinkAvailable = false)
 
     captionHandleRepository.notifyCaptionChanged(appHeaderCaptionState)
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
index 5419887..d94186c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
@@ -68,7 +68,6 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @OptIn(ExperimentalCoroutinesApi::class)
-@Ignore("b/374328725")
 class AppHandleEducationControllerTest : ShellTestCase() {
   @JvmField
   @Rule
@@ -85,7 +84,7 @@
   private val testDataStoreFlow = MutableStateFlow(createWindowingEducationProto())
   private val testCaptionStateFlow = MutableStateFlow<CaptionState>(CaptionState.NoCaption)
   private val educationConfigCaptor =
-      argumentCaptor<DesktopWindowingEducationTooltipController.EducationViewConfig>()
+      argumentCaptor<DesktopWindowingEducationTooltipController.TooltipEducationViewConfig>()
   @Mock private lateinit var mockEducationFilter: AppHandleEducationFilter
   @Mock private lateinit var mockDataStoreRepository: AppHandleEducationDatastoreRepository
   @Mock private lateinit var mockCaptionHandleRepository: WindowDecorCaptionHandleRepository
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index e74c804..bcb7461 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -280,7 +280,7 @@
 
     private void preparePipSurfaceTransactionHelper() {
         doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper)
-                .crop(any(), any(), any());
+                .cropAndPosition(any(), any(), any());
         doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper)
                 .resetScale(any(), any(), any());
         doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
new file mode 100644
index 0000000..a4008c1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.animation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip2.phone.PipAppIconOverlay;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test again {@link PipEnterAnimator}.
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipEnterAnimatorTest {
+
+    @Mock private Context mMockContext;
+
+    @Mock private Resources mMockResources;
+
+    @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
+
+    @Mock private SurfaceControl.Transaction mMockAnimateTransaction;
+
+    @Mock private SurfaceControl.Transaction mMockStartTransaction;
+
+    @Mock private SurfaceControl.Transaction mMockFinishTransaction;
+
+    @Mock private Runnable mMockStartCallback;
+
+    @Mock private Runnable mMockEndCallback;
+
+    @Mock private PipAppIconOverlay mMockPipAppIconOverlay;
+
+    @Mock private SurfaceControl mMockAppIconOverlayLeash;
+
+    @Mock private ActivityInfo mMockActivityInfo;
+
+    @Surface.Rotation private int mRotation;
+    private SurfaceControl mTestLeash;
+    private Rect mEndBounds;
+    private PipEnterAnimator mPipEnterAnimator;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mMockContext.getResources()).thenReturn(mMockResources);
+        when(mMockResources.getInteger(anyInt())).thenReturn(0);
+        when(mMockFactory.getTransaction()).thenReturn(mMockAnimateTransaction);
+        when(mMockAnimateTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+                .thenReturn(mMockAnimateTransaction);
+        when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+                .thenReturn(mMockStartTransaction);
+        when(mMockFinishTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+                .thenReturn(mMockFinishTransaction);
+        when(mMockPipAppIconOverlay.getLeash()).thenReturn(mMockAppIconOverlayLeash);
+
+        mTestLeash = new SurfaceControl.Builder()
+                .setContainerLayer()
+                .setName("PipExpandAnimatorTest")
+                .setCallsite("PipExpandAnimatorTest")
+                .build();
+    }
+
+    @Test
+    public void setAnimationStartCallback_enter_callbackStartCallback() {
+        mRotation = Surface.ROTATION_0;
+        mEndBounds = new Rect(100, 100, 500, 500);
+        mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+                mMockStartTransaction, mMockFinishTransaction,
+                mEndBounds, mRotation);
+        mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+        mPipEnterAnimator.setAnimationStartCallback(mMockStartCallback);
+        mPipEnterAnimator.setAnimationEndCallback(mMockEndCallback);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mPipEnterAnimator.start();
+            mPipEnterAnimator.pause();
+        });
+
+        verify(mMockStartCallback).run();
+        verifyZeroInteractions(mMockEndCallback);
+    }
+
+    @Test
+    public void setAnimationEndCallback_enter_callbackStartAndEndCallback() {
+        mRotation = Surface.ROTATION_0;
+        mEndBounds = new Rect(100, 100, 500, 500);
+        mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+                mMockStartTransaction, mMockFinishTransaction,
+                mEndBounds, mRotation);
+        mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+        mPipEnterAnimator.setAnimationStartCallback(mMockStartCallback);
+        mPipEnterAnimator.setAnimationEndCallback(mMockEndCallback);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mPipEnterAnimator.start();
+            mPipEnterAnimator.end();
+        });
+
+        verify(mMockStartCallback).run();
+        verify(mMockEndCallback).run();
+    }
+
+    @Test
+    public void setAppIconContentOverlay_thenGetContentOverlayLeash_returnOverlayLeash() {
+        mRotation = Surface.ROTATION_0;
+        mEndBounds = new Rect(100, 100, 500, 500);
+        mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+                mMockStartTransaction, mMockFinishTransaction,
+                mEndBounds, mRotation);
+        mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+        mPipEnterAnimator.setPipAppIconOverlaySupplier(
+                (context, appBounds, endBounds, icon, iconSize) -> mMockPipAppIconOverlay);
+
+        mPipEnterAnimator.setAppIconContentOverlay(mMockContext, mEndBounds, mEndBounds,
+                mMockActivityInfo, 64 /* iconSize */);
+
+        assertEquals(mPipEnterAnimator.getContentOverlayLeash(), mMockAppIconOverlayLeash);
+    }
+
+    @Test
+    public void setAppIconContentOverlay_thenClearAppIconOverlay_returnNullLeash() {
+        mRotation = Surface.ROTATION_0;
+        mEndBounds = new Rect(100, 100, 500, 500);
+        mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+                mMockStartTransaction, mMockFinishTransaction,
+                mEndBounds, mRotation);
+        mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+        mPipEnterAnimator.setPipAppIconOverlaySupplier(
+                (context, appBounds, endBounds, icon, iconSize) -> mMockPipAppIconOverlay);
+
+        mPipEnterAnimator.setAppIconContentOverlay(mMockContext, mEndBounds, mEndBounds,
+                mMockActivityInfo, 64 /* iconSize */);
+        mPipEnterAnimator.clearAppIconOverlay();
+
+        assertNull(mPipEnterAnimator.getContentOverlayLeash());
+    }
+
+    @Test
+    public void onEnterAnimationUpdate_withContentOverlay_animateOverlay() {
+        mRotation = Surface.ROTATION_0;
+        mEndBounds = new Rect(100, 100, 500, 500);
+        mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+                mMockStartTransaction, mMockFinishTransaction,
+                mEndBounds, mRotation);
+        mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+        mPipEnterAnimator.setPipAppIconOverlaySupplier(
+                (context, appBounds, endBounds, icon, iconSize) -> mMockPipAppIconOverlay);
+
+        float fraction = 0.5f;
+        mPipEnterAnimator.setAppIconContentOverlay(mMockContext, mEndBounds, mEndBounds,
+                mMockActivityInfo, 64 /* iconSize */);
+        mPipEnterAnimator.onEnterAnimationUpdate(fraction, mMockAnimateTransaction);
+
+        verify(mMockPipAppIconOverlay).onAnimationUpdate(
+                eq(mMockAnimateTransaction), anyFloat(), eq(fraction), eq(mEndBounds));
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java
index e19a10a..b816f0e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java
@@ -93,6 +93,17 @@
                 .thenReturn(mMockTransaction);
         when(mMockTransaction.setShadowRadius(any(SurfaceControl.class), anyFloat()))
                 .thenReturn(mMockTransaction);
+        // No-op on the mMockStartTransaction
+        when(mMockStartTransaction.setAlpha(any(SurfaceControl.class), anyFloat()))
+                .thenReturn(mMockFinishTransaction);
+        when(mMockStartTransaction.setCrop(any(SurfaceControl.class), any(Rect.class)))
+                .thenReturn(mMockFinishTransaction);
+        when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+                .thenReturn(mMockFinishTransaction);
+        when(mMockStartTransaction.setCornerRadius(any(SurfaceControl.class), anyFloat()))
+                .thenReturn(mMockFinishTransaction);
+        when(mMockStartTransaction.setShadowRadius(any(SurfaceControl.class), anyFloat()))
+                .thenReturn(mMockFinishTransaction);
         // Do the same for mMockFinishTransaction
         when(mMockFinishTransaction.setAlpha(any(SurfaceControl.class), anyFloat()))
                 .thenReturn(mMockFinishTransaction);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java
new file mode 100644
index 0000000..0adb50b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.animation;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.mockito.kotlin.MatchersKt.eq;
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
+
+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;
+
+/**
+ * Unit test against {@link PipResizeAnimator}.
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipResizeAnimatorTest {
+
+    private static final float FLOAT_COMPARISON_DELTA = 0.001f;
+
+    @Mock private Context mMockContext;
+
+    @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
+
+    @Mock private SurfaceControl.Transaction mMockTransaction;
+
+    @Mock private SurfaceControl.Transaction mMockStartTransaction;
+
+    @Mock private SurfaceControl.Transaction mMockFinishTransaction;
+
+    @Mock private Runnable mMockStartCallback;
+
+    @Mock private Runnable mMockEndCallback;
+
+    private PipResizeAnimator mPipResizeAnimator;
+    private Rect mBaseBounds;
+    private Rect mStartBounds;
+    private Rect mEndBounds;
+    private SurfaceControl mTestLeash;
+    private ArgumentCaptor<Matrix> mArgumentCaptor;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mMockFactory.getTransaction()).thenReturn(mMockTransaction);
+        when(mMockTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+                .thenReturn(mMockTransaction);
+        when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+                .thenReturn(mMockStartTransaction);
+        when(mMockFinishTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+                .thenReturn(mMockFinishTransaction);
+
+        mArgumentCaptor = ArgumentCaptor.forClass(Matrix.class);
+        mTestLeash = new SurfaceControl.Builder()
+                .setContainerLayer()
+                .setName("PipResizeAnimatorTest")
+                .setCallsite("PipResizeAnimatorTest")
+                .build();
+    }
+
+    @Test
+    public void setAnimationStartCallback_resize_callbackStartCallback() {
+        mBaseBounds = new Rect(100, 100, 500, 500);
+        mStartBounds = new Rect(200, 200, 1_000, 1_000);
+        mEndBounds = new Rect(mBaseBounds);
+        final int duration = 10;
+        final float delta = 0;
+        mPipResizeAnimator = new PipResizeAnimator(mMockContext, mTestLeash,
+                mMockStartTransaction, mMockFinishTransaction,
+                mBaseBounds, mStartBounds, mEndBounds,
+                duration, delta);
+
+        mPipResizeAnimator.setAnimationStartCallback(mMockStartCallback);
+        mPipResizeAnimator.setAnimationEndCallback(mMockEndCallback);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mPipResizeAnimator.start();
+            mPipResizeAnimator.pause();
+        });
+
+        verify(mMockStartCallback).run();
+        verifyZeroInteractions(mMockEndCallback);
+    }
+
+    @Test
+    public void setAnimationEndCallback_resize_callbackStartAndEndCallback() {
+        mBaseBounds = new Rect(100, 100, 500, 500);
+        mStartBounds = new Rect(200, 200, 1_000, 1_000);
+        mEndBounds = new Rect(mBaseBounds);
+        final int duration = 10;
+        final float delta = 0;
+        mPipResizeAnimator = new PipResizeAnimator(mMockContext, mTestLeash,
+                mMockStartTransaction, mMockFinishTransaction,
+                mBaseBounds, mStartBounds, mEndBounds,
+                duration, delta);
+
+        mPipResizeAnimator.setAnimationStartCallback(mMockStartCallback);
+        mPipResizeAnimator.setAnimationEndCallback(mMockEndCallback);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mPipResizeAnimator.start();
+            mPipResizeAnimator.end();
+        });
+
+        verify(mMockStartCallback).run();
+        verify(mMockEndCallback).run();
+    }
+
+    @Test
+    public void onAnimationEnd_resizeDown_sizeChanged() {
+        // Resize from 800x800 to 400x400, eg. resize down
+        mBaseBounds = new Rect(100, 100, 500, 500);
+        mStartBounds = new Rect(200, 200, 1_000, 1_000);
+        mEndBounds = new Rect(mBaseBounds);
+        final int duration = 10;
+        final float delta = 0;
+        final float[] matrix = new float[9];
+        mPipResizeAnimator = new PipResizeAnimator(mMockContext, mTestLeash,
+                mMockStartTransaction, mMockFinishTransaction,
+                mBaseBounds, mStartBounds, mEndBounds,
+                duration, delta);
+        mPipResizeAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+        mPipResizeAnimator.setAnimationStartCallback(mMockStartCallback);
+        mPipResizeAnimator.setAnimationEndCallback(mMockEndCallback);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mPipResizeAnimator.start();
+            clearInvocations(mMockTransaction);
+            mPipResizeAnimator.end();
+        });
+
+        // Start transaction scales down from its final state
+        verify(mMockStartTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+        mArgumentCaptor.getValue().getValues(matrix);
+        assertEquals(matrix[Matrix.MSCALE_X],
+                mStartBounds.width() / (float) mEndBounds.width(), FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MSCALE_Y],
+                mStartBounds.height() / (float) mEndBounds.height(), FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_X], mStartBounds.left, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_Y], mStartBounds.top, FLOAT_COMPARISON_DELTA);
+
+        // Final animation transaction scales to 1 and puts the leash at final position
+        verify(mMockTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+        mArgumentCaptor.getValue().getValues(matrix);
+        assertEquals(matrix[Matrix.MSCALE_X], 1f, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MSCALE_Y], 1f, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_X], mEndBounds.left, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_Y], mEndBounds.top, FLOAT_COMPARISON_DELTA);
+
+        // Finish transaction resets scale and puts the leash at final position
+        verify(mMockFinishTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+        mArgumentCaptor.getValue().getValues(matrix);
+        assertEquals(matrix[Matrix.MSCALE_X], 1f, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MSCALE_Y], 1f, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_X], mEndBounds.left, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_Y], mEndBounds.top, FLOAT_COMPARISON_DELTA);
+    }
+
+    @Test
+    public void onAnimationEnd_resizeUp_sizeChanged() {
+        // Resize from 400x400 to 800x800, eg. resize up
+        mBaseBounds = new Rect(200, 200, 1_000, 1_000);
+        mStartBounds = new Rect(100, 100, 500, 500);
+        mEndBounds = new Rect(mBaseBounds);
+        final int duration = 10;
+        final float delta = 0;
+        final float[] matrix = new float[9];
+        mPipResizeAnimator = new PipResizeAnimator(mMockContext, mTestLeash,
+                mMockStartTransaction, mMockFinishTransaction,
+                mBaseBounds, mStartBounds, mEndBounds,
+                duration, delta);
+        mPipResizeAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+        mPipResizeAnimator.setAnimationStartCallback(mMockStartCallback);
+        mPipResizeAnimator.setAnimationEndCallback(mMockEndCallback);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mPipResizeAnimator.start();
+            clearInvocations(mMockTransaction);
+            mPipResizeAnimator.end();
+        });
+
+        // Start transaction scales up from its final state
+        verify(mMockStartTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+        mArgumentCaptor.getValue().getValues(matrix);
+        assertEquals(matrix[Matrix.MSCALE_X],
+                mStartBounds.width() / (float) mEndBounds.width(), FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MSCALE_Y],
+                mStartBounds.height() / (float) mEndBounds.height(), FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_X], mStartBounds.left, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_Y], mStartBounds.top, FLOAT_COMPARISON_DELTA);
+
+        // Final animation transaction scales to 1 and puts the leash at final position
+        verify(mMockTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+        mArgumentCaptor.getValue().getValues(matrix);
+        assertEquals(matrix[Matrix.MSCALE_X], 1f, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MSCALE_Y], 1f, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_X], mEndBounds.left, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_Y], mEndBounds.top, FLOAT_COMPARISON_DELTA);
+
+        // Finish transaction resets scale and puts the leash at final position
+        verify(mMockFinishTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+        mArgumentCaptor.getValue().getValues(matrix);
+        assertEquals(matrix[Matrix.MSCALE_X], 1f, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MSCALE_Y], 1f, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_X], mEndBounds.left, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_Y], mEndBounds.top, FLOAT_COMPARISON_DELTA);
+    }
+
+    @Test
+    public void onAnimationEnd_withInitialDelta_rotateToZeroDegree() {
+        mBaseBounds = new Rect(200, 200, 1_000, 1_000);
+        mStartBounds = new Rect(100, 100, 500, 500);
+        mEndBounds = new Rect(mBaseBounds);
+        final int duration = 10;
+        final float delta = 45;
+        final float[] matrix = new float[9];
+        mPipResizeAnimator = new PipResizeAnimator(mMockContext, mTestLeash,
+                mMockStartTransaction, mMockFinishTransaction,
+                mBaseBounds, mStartBounds, mEndBounds,
+                duration, delta);
+        mPipResizeAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+        mPipResizeAnimator.setAnimationStartCallback(mMockStartCallback);
+        mPipResizeAnimator.setAnimationEndCallback(mMockEndCallback);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mPipResizeAnimator.start();
+            clearInvocations(mMockTransaction);
+            mPipResizeAnimator.end();
+        });
+
+        // Final animation transaction sets skew to zero
+        verify(mMockTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+        mArgumentCaptor.getValue().getValues(matrix);
+        assertEquals(matrix[Matrix.MSKEW_X], 0f, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MSKEW_Y], 0f, FLOAT_COMPARISON_DELTA);
+
+        // Finish transaction sets skew to zero
+        verify(mMockFinishTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+        mArgumentCaptor.getValue().getValues(matrix);
+        assertEquals(matrix[Matrix.MSKEW_X], 0f, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MSKEW_Y], 0f, FLOAT_COMPARISON_DELTA);
+    }
+}
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/util/WindowingEducationTestUtils.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationTestUtils.kt
index 708fadb..99e8295 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationTestUtils.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationTestUtils.kt
@@ -32,11 +32,13 @@
     runningTaskInfo: RunningTaskInfo = createTaskInfo(),
     isHandleMenuExpanded: Boolean = false,
     globalAppHandleBounds: Rect = Rect(),
+    isCapturedLinkAvailable: Boolean = false
 ): CaptionState.AppHandle =
     CaptionState.AppHandle(
         runningTaskInfo = runningTaskInfo,
         isHandleMenuExpanded = isHandleMenuExpanded,
-        globalAppHandleBounds = globalAppHandleBounds)
+        globalAppHandleBounds = globalAppHandleBounds,
+        isCapturedLinkAvailable = isCapturedLinkAvailable)
 
 /**
  * Create an instance of [CaptionState.AppHeader] with parameters as properties.
@@ -47,11 +49,13 @@
     runningTaskInfo: RunningTaskInfo = createTaskInfo(),
     isHeaderMenuExpanded: Boolean = false,
     globalAppChipBounds: Rect = Rect(),
+    isCapturedLinkAvailable: Boolean = false
 ): CaptionState.AppHeader =
     CaptionState.AppHeader(
         runningTaskInfo = runningTaskInfo,
         isHeaderMenuExpanded = isHeaderMenuExpanded,
-        globalAppChipBounds = globalAppChipBounds)
+        globalAppChipBounds = globalAppChipBounds,
+        isCapturedLinkAvailable = isCapturedLinkAvailable)
 
 /**
  * Create an instance of [RunningTaskInfo] with parameters as properties.
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 36c5be1..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
@@ -95,6 +95,7 @@
 import com.android.wm.shell.desktopmode.DesktopTasksLimiter
 import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository
 import com.android.wm.shell.desktopmode.education.AppHandleEducationController
+import com.android.wm.shell.desktopmode.education.AppToWebEducationController
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
@@ -195,6 +196,7 @@
             DesktopModeWindowDecorViewModel.TaskPositionerFactory
     @Mock private lateinit var mockTaskPositioner: TaskPositioner
     @Mock private lateinit var mockAppHandleEducationController: AppHandleEducationController
+    @Mock private lateinit var mockAppToWebEducationController: AppToWebEducationController
     @Mock private lateinit var mockFocusTransitionObserver: FocusTransitionObserver
     @Mock private lateinit var mockCaptionHandleRepository: WindowDecorCaptionHandleRepository
     @Mock private lateinit var motionEvent: MotionEvent
@@ -261,6 +263,7 @@
                 mockInteractionJankMonitor,
                 Optional.of(mockTasksLimiter),
                 mockAppHandleEducationController,
+                mockAppToWebEducationController,
                 mockCaptionHandleRepository,
                 Optional.of(mockActivityOrientationChangeHandler),
                 mockTaskPositionerFactory,
@@ -475,25 +478,10 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
-    fun testDecorationIsCreatedForTopTranslucentActivitiesWithStyleFloating() {
+    fun testDecorationIsNotCreatedForTopTranslucentActivities() {
         val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply {
             isTopActivityTransparent = true
-            isTopActivityStyleFloating = true
-            numActivities = 1
-        }
-        doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) }
-        setUpMockDecorationsForTasks(task)
-
-        onTaskOpening(task)
-        assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
-    fun testDecorationIsNotCreatedForTopTranslucentActivitiesWithoutStyleFloating() {
-        val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply {
-            isTopActivityTransparent = true
-            isTopActivityStyleFloating = false
+            isTopActivityNoDisplay = false
             numActivities = 1
         }
         onTaskOpening(task)
@@ -504,13 +492,14 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
     fun testDecorationIsNotCreatedForSystemUIActivities() {
-        val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN)
-
         // Set task as systemUI package
         val systemUIPackageName = context.resources.getString(
             com.android.internal.R.string.config_systemUi)
         val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
-        task.baseActivity = baseComponent
+        val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply {
+            baseActivity = baseComponent
+            isTopActivityNoDisplay = false
+        }
 
         onTaskOpening(task)
 
@@ -1318,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/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index f6fed29..41f57ae 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -262,8 +262,8 @@
         doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY);
         doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
         when(mMockHandleMenuFactory.create(any(), any(), anyInt(), any(), any(), any(),
-                anyBoolean(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(), anyInt(),
-                anyInt()))
+                anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(),
+                anyInt(), anyInt()))
                 .thenReturn(mMockHandleMenu);
         when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())).thenReturn(false);
         when(mMockAppHeaderViewHolderFactory.create(any(), any(), any(), any(), any(), any(), any(),
@@ -1178,6 +1178,7 @@
                 any(),
                 any(),
                 any(),
+                any(),
                 openInBrowserCaptor.capture(),
                 any(),
                 any(),
@@ -1208,6 +1209,7 @@
                 any(),
                 any(),
                 any(),
+                any(),
                 openInBrowserCaptor.capture(),
                 any(),
                 any(),
@@ -1263,6 +1265,7 @@
                 any(),
                 any(),
                 any(),
+                any(),
                 closeClickListener.capture(),
                 any(),
                 anyBoolean()
@@ -1294,12 +1297,14 @@
                 any(),
                 any(),
                 any(),
+                any(),
                 /* forceShowSystemBars= */ eq(true)
         );
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+    @DisableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION,
+            Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION})
     public void notifyCaptionStateChanged_flagDisabled_doNoNotify() {
         when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
@@ -1437,7 +1442,7 @@
 
     private void verifyHandleMenuCreated(@Nullable Uri uri) {
         verify(mMockHandleMenuFactory).create(any(), any(), anyInt(), any(), any(),
-                any(), anyBoolean(), anyBoolean(), anyBoolean(),
+                any(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(),
                 argThat(intent -> (uri == null && intent == null) || intent.getData().equals(uri)),
                 anyInt(), anyInt(), anyInt(), anyInt());
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index 9544fa8..ade17c6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -266,6 +266,7 @@
             WindowManagerWrapper(mockWindowManager),
             layoutId, appIcon, appName, splitScreenController, shouldShowWindowingPill = true,
             shouldShowNewWindowButton = true, shouldShowManageWindowsButton = false,
+            shouldShowChangeAspectRatioButton = false,
             null /* openInBrowserLink */, captionWidth = HANDLE_WIDTH, captionHeight = 50,
             captionX = captionX,
             captionY = 0,
@@ -276,6 +277,7 @@
             onToSplitScreenClickListener = mock(),
             onNewWindowClickListener = mock(),
             onManageWindowsClickListener = mock(),
+            onChangeAspectRatioClickListener = mock(),
             openInBrowserClickListener = mock(),
             onOpenByDefaultClickListener = mock(),
             onCloseMenuClickListener = mock(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index cb7fade..8e0434c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -218,8 +218,6 @@
         verify(captionContainerSurfaceBuilder, never()).build();
         verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any());
 
-        verify(mMockSurfaceControlFinishT).hide(mMockTaskSurface);
-
         assertNull(mRelayoutResult.mRootView);
     }
 
@@ -281,8 +279,6 @@
 
         verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
         verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
-        verify(mMockSurfaceControlStartT)
-                .show(mMockTaskSurface);
         verify(mMockSurfaceControlStartT).setShadowRadius(mMockTaskSurface, 10);
 
         assertEquals(300, mRelayoutResult.mWidth);
@@ -863,7 +859,7 @@
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
 
 
-        mRelayoutParams.mSetTaskPositionAndCrop = false;
+        mRelayoutParams.mSetTaskVisibilityPositionAndCrop = false;
         windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
 
         verify(mMockSurfaceControlStartT, never()).setWindowCrop(
@@ -891,7 +887,7 @@
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
 
-        mRelayoutParams.mSetTaskPositionAndCrop = true;
+        mRelayoutParams.mSetTaskVisibilityPositionAndCrop = true;
         windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
 
         verify(mMockSurfaceControlStartT).setWindowCrop(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt
index 741dfb8..15f2c7b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt
@@ -285,7 +285,7 @@
       onEducationClickAction: () -> Unit = {},
       onDismissAction: () -> Unit = {}
   ) =
-      DesktopWindowingEducationTooltipController.EducationViewConfig(
+      DesktopWindowingEducationTooltipController.TooltipEducationViewConfig(
           tooltipViewLayout = tooltipViewLayout,
           tooltipColorScheme = tooltipColorScheme,
           tooltipViewGlobalCoordinates = tooltipViewGlobalCoordinates,
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/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index 27817e9..faf84a8 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -3,8 +3,9 @@
 
   public final class AppFunctionManager {
     ctor public AppFunctionManager(android.content.Context);
-    method public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
-    method public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
+    method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+    method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
+    method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
     method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
     field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0
     field public static final int APP_FUNCTION_STATE_DISABLED = 2; // 0x2
@@ -34,6 +35,7 @@
   }
 
   public final class ExecuteAppFunctionResponse {
+    method public int getErrorCategory();
     method @Nullable public String getErrorMessage();
     method @NonNull public android.os.Bundle getExtras();
     method public int getResultCode();
@@ -41,14 +43,19 @@
     method public boolean isSuccess();
     method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle);
     method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle);
+    field public static final int ERROR_CATEGORY_APP = 3; // 0x3
+    field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1
+    field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2
+    field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
     field public static final String PROPERTY_RETURN_VALUE = "returnValue";
-    field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2
-    field public static final int RESULT_CANCELLED = 6; // 0x6
-    field public static final int RESULT_DENIED = 1; // 0x1
-    field public static final int RESULT_DISABLED = 5; // 0x5
-    field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
-    field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
+    field public static final int RESULT_APP_UNKNOWN_ERROR = 3000; // 0xbb8
+    field public static final int RESULT_CANCELLED = 2001; // 0x7d1
+    field public static final int RESULT_DENIED = 1000; // 0x3e8
+    field public static final int RESULT_DISABLED = 1002; // 0x3ea
+    field public static final int RESULT_FUNCTION_NOT_FOUND = 1003; // 0x3eb
+    field public static final int RESULT_INVALID_ARGUMENT = 1001; // 0x3e9
     field public static final int RESULT_OK = 0; // 0x0
+    field public static final int RESULT_SYSTEM_ERROR = 2000; // 0x7d0
   }
 
 }
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
index 6870446..2075104 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
@@ -16,9 +16,11 @@
 
 package com.google.android.appfunctions.sidecar;
 
+import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.UserHandleAware;
 import android.content.Context;
@@ -103,6 +105,12 @@
      * <p>See {@link android.app.appfunctions.AppFunctionManager#executeAppFunction} for the
      * documented behaviour of this method.
      */
+    @RequiresPermission(
+            anyOf = {
+                Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
+                Manifest.permission.EXECUTE_APP_FUNCTIONS
+            },
+            conditional = true)
     public void executeAppFunction(
             @NonNull ExecuteAppFunctionRequest sidecarRequest,
             @NonNull @CallbackExecutor Executor executor,
@@ -131,6 +139,12 @@
      * <p>See {@link android.app.appfunctions.AppFunctionManager#isAppFunctionEnabled} for the
      * documented behaviour of this method.
      */
+    @RequiresPermission(
+            anyOf = {
+                Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
+                Manifest.permission.EXECUTE_APP_FUNCTIONS
+            },
+            conditional = true)
     public void isAppFunctionEnabled(
             @NonNull String functionIdentifier,
             @NonNull String targetPackage,
@@ -140,6 +154,19 @@
     }
 
     /**
+     * Returns a boolean through a callback, indicating whether the app function is enabled.
+     *
+     * <p>See {@link android.app.appfunctions.AppFunctionManager#isAppFunctionEnabled} for the
+     * documented behaviour of this method.
+     */
+    public void isAppFunctionEnabled(
+            @NonNull String functionIdentifier,
+            @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Boolean, Exception> callback) {
+        mManager.isAppFunctionEnabled(functionIdentifier, executor, callback);
+    }
+
+    /**
      * Sets the enabled state of the app function owned by the calling package.
      *
      * <p>See {@link android.app.appfunctions.AppFunctionManager#setAppFunctionEnabled} for the
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
index d5dfaeb..4e88fb0 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
@@ -50,38 +50,102 @@
      */
     public static final String PROPERTY_RETURN_VALUE = "returnValue";
 
-    /** The call was successful. */
+    /**
+     * The call was successful.
+     *
+     * <p>This result code does not belong in an error category.
+     */
     public static final int RESULT_OK = 0;
 
-    /** The caller does not have the permission to execute an app function. */
-    public static final int RESULT_DENIED = 1;
+    /**
+     * The caller does not have the permission to execute an app function.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+     */
+    public static final int RESULT_DENIED = 1000;
+
+    /**
+     * The caller supplied invalid arguments to the execution request.
+     *
+     * <p>This error may be considered similar to {@link IllegalArgumentException}.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+     */
+    public static final int RESULT_INVALID_ARGUMENT = 1001;
+
+    /**
+     * The caller tried to execute a disabled app function.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+     */
+    public static final int RESULT_DISABLED = 1002;
+
+    /**
+     * The caller tried to execute a function that does not exist.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+     */
+    public static final int RESULT_FUNCTION_NOT_FOUND = 1003;
+
+    /**
+     * An internal unexpected error coming from the system.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
+     */
+    public static final int RESULT_SYSTEM_ERROR = 2000;
+
+    /**
+     * The operation was cancelled. Use this error code to report that a cancellation is done after
+     * receiving a cancellation signal.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
+     */
+    public static final int RESULT_CANCELLED = 2001;
 
     /**
      * An unknown error occurred while processing the call in the AppFunctionService.
      *
      * <p>This error is thrown when the service is connected in the remote application but an
      * unexpected error is thrown from the bound application.
-     */
-    public static final int RESULT_APP_UNKNOWN_ERROR = 2;
-
-    /** An internal unexpected error coming from the system. */
-    public static final int RESULT_INTERNAL_ERROR = 3;
-
-    /**
-     * The caller supplied invalid arguments to the call.
      *
-     * <p>This error may be considered similar to {@link IllegalArgumentException}.
+     * <p>This error is in the {@link #ERROR_CATEGORY_APP} category.
      */
-    public static final int RESULT_INVALID_ARGUMENT = 4;
-
-    /** The caller tried to execute a disabled app function. */
-    public static final int RESULT_DISABLED = 5;
+    public static final int RESULT_APP_UNKNOWN_ERROR = 3000;
 
     /**
-     * The operation was cancelled. Use this error code to report that a cancellation is done after
-     * receiving a cancellation signal.
+     * The error category is unknown.
+     *
+     * <p>This is the default value for {@link #getErrorCategory}.
      */
-    public static final int RESULT_CANCELLED = 6;
+    public static final int ERROR_CATEGORY_UNKNOWN = 0;
+
+    /**
+     * The error is caused by the app requesting a function execution.
+     *
+     * <p>For example, the caller provided invalid parameters in the execution request e.g. an
+     * invalid function ID.
+     *
+     * <p>Errors in the category fall in the range 1000-1999 inclusive.
+     */
+    public static final int ERROR_CATEGORY_REQUEST_ERROR = 1;
+
+    /**
+     * The error is caused by an issue in the system.
+     *
+     * <p>For example, the AppFunctionService implementation is not found by the system.
+     *
+     * <p>Errors in the category fall in the range 2000-2999 inclusive.
+     */
+    public static final int ERROR_CATEGORY_SYSTEM = 2;
+
+    /**
+     * The error is caused by the app providing the function.
+     *
+     * <p>For example, the app crashed when the system is executing the request.
+     *
+     * <p>Errors in the category fall in the range 3000-3999 inclusive.
+     */
+    public static final int ERROR_CATEGORY_APP = 3;
 
     /** The result code of the app function execution. */
     @ResultCode private final int mResultCode;
@@ -171,6 +235,36 @@
     }
 
     /**
+     * Returns the error category of the {@link ExecuteAppFunctionResponse}.
+     *
+     * <p>This method categorizes errors based on their underlying cause, allowing developers to
+     * implement targeted error handling and provide more informative error messages to users. It
+     * maps ranges of result codes to specific error categories.
+     *
+     * <p>When constructing a {@link #newFailure} response, use the appropriate result code value to
+     * ensure correct categorization of the failed response.
+     *
+     * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the result code does not belong to
+     * any error category, for example, in the case of a successful result with {@link #RESULT_OK}.
+     *
+     * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding
+     * result code ranges.
+     */
+    @ErrorCategory
+    public int getErrorCategory() {
+        if (mResultCode >= 1000 && mResultCode < 2000) {
+            return ERROR_CATEGORY_REQUEST_ERROR;
+        }
+        if (mResultCode >= 2000 && mResultCode < 3000) {
+            return ERROR_CATEGORY_SYSTEM;
+        }
+        if (mResultCode >= 3000 && mResultCode < 4000) {
+            return ERROR_CATEGORY_APP;
+        }
+        return ERROR_CATEGORY_UNKNOWN;
+    }
+
+    /**
      * Returns a generic document containing the return value of the executed function.
      *
      * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value.
@@ -238,11 +332,28 @@
                 RESULT_OK,
                 RESULT_DENIED,
                 RESULT_APP_UNKNOWN_ERROR,
-                RESULT_INTERNAL_ERROR,
+                RESULT_SYSTEM_ERROR,
+                RESULT_FUNCTION_NOT_FOUND,
                 RESULT_INVALID_ARGUMENT,
                 RESULT_DISABLED,
                 RESULT_CANCELLED
             })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ResultCode {}
+
+    /**
+     * Error categories.
+     *
+     * @hide
+     */
+    @IntDef(
+            prefix = {"ERROR_CATEGORY_"},
+            value = {
+                ERROR_CATEGORY_UNKNOWN,
+                ERROR_CATEGORY_REQUEST_ERROR,
+                ERROR_CATEGORY_APP,
+                ERROR_CATEGORY_SYSTEM
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ErrorCategory {}
 }
diff --git a/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt b/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt
index 1f9fddd..264f842 100644
--- a/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt
+++ b/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt
@@ -105,7 +105,7 @@
         val emptyGd = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "").build()
         val platformResponse =
             ExecuteAppFunctionResponse.newFailure(
-                ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+                ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
                 null,
                 null
             )
@@ -119,7 +119,7 @@
         assertThat(sidecarResponse.resultDocument.id).isEqualTo(emptyGd.id)
         assertThat(sidecarResponse.resultDocument.schemaType).isEqualTo(emptyGd.schemaType)
         assertThat(sidecarResponse.resultCode)
-            .isEqualTo(ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR)
+            .isEqualTo(ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR)
         assertThat(sidecarResponse.errorMessage).isNull()
     }
 
@@ -152,7 +152,7 @@
         val emptyGd = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "").build()
         val sidecarResponse =
             com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse.newFailure(
-                ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+                ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
                 null,
                 null
             )
@@ -166,7 +166,7 @@
         assertThat(platformResponse.resultDocument.id).isEqualTo(emptyGd.id)
         assertThat(platformResponse.resultDocument.schemaType).isEqualTo(emptyGd.schemaType)
         assertThat(platformResponse.resultCode)
-            .isEqualTo(ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR)
+            .isEqualTo(ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR)
         assertThat(platformResponse.errorMessage).isNull()
     }
 }
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index f255967..5ad788c 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -146,3 +146,11 @@
   description: "Whether to have more information in ashmem filenames for bitmaps"
   bug: "369619160"
 }
+
+flag {
+  name: "animated_image_drawable_filter_bitmap"
+  is_exported: true
+  namespace: "core_graphics"
+  description: "API's that enable animated image drawables to use nearest sampling when scaling."
+  bug: "370523334"
+}
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
index 69613c7..5e379aa 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.cpp
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -347,4 +347,26 @@
     return adjustFrameDuration(mSkAnimatedImage->currentFrameDuration());
 }
 
+bool AnimatedImageDrawable::getFilterBitmap() const {
+    const SkFilterMode kFilterBitmap = mSkAnimatedImage->getFilterMode();
+    if (kFilterBitmap == SkFilterMode::kLinear) {
+        return true;
+    }
+    return false;
+}
+
+bool AnimatedImageDrawable::setFilterBitmap(bool filterBitmap) {
+    if (filterBitmap) {
+        if (mSkAnimatedImage->getFilterMode() == SkFilterMode::kLinear) {
+            return false;
+        }
+        mSkAnimatedImage->setFilterMode(SkFilterMode::kLinear);
+    } else {
+        if (mSkAnimatedImage->getFilterMode() == SkFilterMode::kNearest) {
+            return false;
+        }
+        mSkAnimatedImage->setFilterMode(SkFilterMode::kNearest);
+    }
+    return true;
+}
 }  // namespace android
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.h b/libs/hwui/hwui/AnimatedImageDrawable.h
index 1e965ab..2212324 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.h
+++ b/libs/hwui/hwui/AnimatedImageDrawable.h
@@ -87,6 +87,11 @@
     bool isRunning();
     int getRepetitionCount() const { return mSkAnimatedImage->getRepetitionCount(); }
     void setRepetitionCount(int count) { mSkAnimatedImage->setRepetitionCount(count); }
+    // Returns true if the filter mode is set to linear sampling; false if it is
+    // set to nearest neighbor sampling.
+    bool getFilterBitmap() const;
+    // Returns true if the filter mode was changed; false otherwise.
+    bool setFilterBitmap(bool filterBitmap);
 
     void setOnAnimationEndListener(std::unique_ptr<OnAnimationEndListener> listener) {
         mEndListener = std::move(listener);
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/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp
index b01e38d..2c8530d 100644
--- a/libs/hwui/jni/AnimatedImageDrawable.cpp
+++ b/libs/hwui/jni/AnimatedImageDrawable.cpp
@@ -276,6 +276,18 @@
     drawable->setStagingBounds(rect);
 }
 
+static jboolean AnimatedImageDrawable_nSetFilterBitmap(JNIEnv* env, jobject /*clazz*/,
+                                                       jlong nativePtr, jboolean filterBitmap) {
+    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+    return drawable->setFilterBitmap(filterBitmap);
+}
+
+static jboolean AnimatedImageDrawable_nGetFilterBitmap(JNIEnv* env, jobject /*clazz*/,
+                                                       jlong nativePtr) {
+    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+    return drawable->getFilterBitmap();
+}
+
 static const JNINativeMethod gAnimatedImageDrawableMethods[] = {
         {"nCreate", "(JLandroid/graphics/ImageDecoder;IIJZLandroid/graphics/Rect;)J",
          (void*)AnimatedImageDrawable_nCreate},
@@ -294,6 +306,8 @@
         {"nNativeByteSize", "(J)J", (void*)AnimatedImageDrawable_nNativeByteSize},
         {"nSetMirrored", "(JZ)V", (void*)AnimatedImageDrawable_nSetMirrored},
         {"nSetBounds", "(JLandroid/graphics/Rect;)V", (void*)AnimatedImageDrawable_nSetBounds},
+        {"nSetFilterBitmap", "(JZ)Z", (void*)AnimatedImageDrawable_nSetFilterBitmap},
+        {"nGetFilterBitmap", "(J)Z", (void*)AnimatedImageDrawable_nGetFilterBitmap},
 };
 
 int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env) {
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 1024a55..5b90547 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -16,11 +16,15 @@
 
 package android.media;
 
+import static android.media.audio.Flags.FLAG_SPEAKER_CLEANUP_USAGE;
+
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+// TODO switch from HIDL imports to AIDL
 import android.audio.policy.configuration.V7_0.AudioUsage;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.media.audiopolicy.AudioProductStrategy;
@@ -247,6 +251,16 @@
     public static final int USAGE_ANNOUNCEMENT = SYSTEM_USAGE_OFFSET + 3;
 
     /**
+     * @hide
+     * Usage value to use when a system application plays a signal intended to clean up the
+     * speaker transducers and free them of deposits of dust or water.
+     */
+    @FlaggedApi(FLAG_SPEAKER_CLEANUP_USAGE)
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public static final int USAGE_SPEAKER_CLEANUP = SYSTEM_USAGE_OFFSET + 4;
+
+    /**
      * IMPORTANT: when adding new usage types, add them to SDK_USAGES and update SUPPRESSIBLE_USAGES
      *            if applicable, as well as audioattributes.proto.
      *            Also consider adding them to <aaudio/AAudio.h> for the NDK.
@@ -1202,7 +1216,6 @@
                     break;
                 case AudioSystem.STREAM_BLUETOOTH_SCO:
                     mContentType = CONTENT_TYPE_SPEECH;
-                    mFlags |= FLAG_SCO;
                     break;
                 case AudioSystem.STREAM_DTMF:
                     mContentType = CONTENT_TYPE_SONIFICATION;
@@ -1522,6 +1535,8 @@
                 return "USAGE_VEHICLE_STATUS";
             case USAGE_ANNOUNCEMENT:
                 return "USAGE_ANNOUNCEMENT";
+            case USAGE_SPEAKER_CLEANUP:
+                return "USAGE_SPEAKER_CLEANUP";
             default:
                 return "unknown usage " + usage;
         }
@@ -1662,12 +1677,8 @@
     }
 
     /**
-     * @param usage one of {@link AttributeSystemUsage},
-     *     {@link AttributeSystemUsage#USAGE_CALL_ASSISTANT},
-     *     {@link AttributeSystemUsage#USAGE_EMERGENCY},
-     *     {@link AttributeSystemUsage#USAGE_SAFETY},
-     *     {@link AttributeSystemUsage#USAGE_VEHICLE_STATUS},
-     *     {@link AttributeSystemUsage#USAGE_ANNOUNCEMENT}
+     * Returns whether the given usage can only be used by system-privileged components
+     * @param usage one of {@link AttributeSystemUsage}.
      * @return boolean indicating if the usage is a system usage or not
      * @hide
      */
@@ -1677,7 +1688,8 @@
                 || usage == USAGE_EMERGENCY
                 || usage == USAGE_SAFETY
                 || usage == USAGE_VEHICLE_STATUS
-                || usage == USAGE_ANNOUNCEMENT);
+                || usage == USAGE_ANNOUNCEMENT
+                || usage == USAGE_SPEAKER_CLEANUP);
     }
 
     /**
@@ -1750,8 +1762,7 @@
                     AudioSystem.STREAM_SYSTEM : AudioSystem.STREAM_SYSTEM_ENFORCED;
         }
         if ((aa.getAllFlags() & FLAG_SCO) == FLAG_SCO) {
-            return fromGetVolumeControlStream ?
-                    AudioSystem.STREAM_VOICE_CALL : AudioSystem.STREAM_BLUETOOTH_SCO;
+            return AudioSystem.STREAM_VOICE_CALL;
         }
         if ((aa.getAllFlags() & FLAG_BEACON) == FLAG_BEACON) {
             return fromGetVolumeControlStream ?
@@ -1792,6 +1803,7 @@
             case USAGE_SAFETY:
             case USAGE_VEHICLE_STATUS:
             case USAGE_ANNOUNCEMENT:
+            case USAGE_SPEAKER_CLEANUP:
             case USAGE_UNKNOWN:
                 return AudioSystem.STREAM_MUSIC;
             default:
@@ -1831,7 +1843,8 @@
             USAGE_EMERGENCY,
             USAGE_SAFETY,
             USAGE_VEHICLE_STATUS,
-            USAGE_ANNOUNCEMENT
+            USAGE_ANNOUNCEMENT,
+            USAGE_SPEAKER_CLEANUP
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AttributeSystemUsage {}
@@ -1881,6 +1894,7 @@
         USAGE_SAFETY,
         USAGE_VEHICLE_STATUS,
         USAGE_ANNOUNCEMENT,
+        USAGE_SPEAKER_CLEANUP,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AttributeUsage {}
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 39b29d0..2da8eec 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -16,11 +16,15 @@
 
 package android.media;
 
+import static android.media.audio.Flags.FLAG_ENABLE_MULTICHANNEL_GROUP_DEVICE;
+
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.TestApi;
+import android.media.audio.Flags;
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -192,6 +196,15 @@
      */
     public static final int TYPE_DOCK_ANALOG = 31;
 
+    /**
+     * A device type describing a speaker group that supports multichannel contents. The speakers in
+     * the group are connected together using local network based protocols. The speaker group
+     * requires additional input of the physical positions of each individual speaker to provide a
+     * better experience on multichannel contents.
+     */
+    @FlaggedApi(FLAG_ENABLE_MULTICHANNEL_GROUP_DEVICE)
+    public static final int TYPE_MULTICHANNEL_GROUP = 32;
+
     /** @hide */
     @IntDef(flag = false, prefix = "TYPE", value = {
             TYPE_BUILTIN_EARPIECE,
@@ -224,7 +237,8 @@
             TYPE_BLE_SPEAKER,
             TYPE_ECHO_REFERENCE,
             TYPE_BLE_BROADCAST,
-            TYPE_DOCK_ANALOG}
+            TYPE_DOCK_ANALOG,
+            TYPE_MULTICHANNEL_GROUP}
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface AudioDeviceType {}
@@ -285,7 +299,8 @@
             TYPE_BLE_HEADSET,
             TYPE_BLE_SPEAKER,
             TYPE_BLE_BROADCAST,
-            TYPE_DOCK_ANALOG}
+            TYPE_DOCK_ANALOG,
+            TYPE_MULTICHANNEL_GROUP}
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface AudioDeviceTypeOut {}
@@ -321,7 +336,13 @@
             case TYPE_BLE_BROADCAST:
             case TYPE_DOCK_ANALOG:
                 return true;
+
             default:
+                if (Flags.enableMultichannelGroupDevice()) {
+                    if (type == TYPE_MULTICHANNEL_GROUP) {
+                        return true;
+                    }
+                }
                 return false;
         }
     }
@@ -665,6 +686,10 @@
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLE_HEADSET, TYPE_BLE_HEADSET);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLE_SPEAKER, TYPE_BLE_SPEAKER);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLE_BROADCAST, TYPE_BLE_BROADCAST);
+        if (Flags.enableMultichannelGroupDevice()) {
+            INT_TO_EXT_DEVICE_MAPPING.put(
+                    AudioSystem.DEVICE_OUT_MULTICHANNEL_GROUP, TYPE_MULTICHANNEL_GROUP);
+        }
 
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUILTIN_MIC, TYPE_BUILTIN_MIC);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET, TYPE_BLUETOOTH_SCO);
@@ -721,6 +746,10 @@
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_HEADSET, AudioSystem.DEVICE_OUT_BLE_HEADSET);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_SPEAKER, AudioSystem.DEVICE_OUT_BLE_SPEAKER);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_BROADCAST, AudioSystem.DEVICE_OUT_BLE_BROADCAST);
+        if (Flags.enableMultichannelGroupDevice()) {
+            EXT_TO_INT_DEVICE_MAPPING.put(
+                    TYPE_MULTICHANNEL_GROUP, AudioSystem.DEVICE_OUT_MULTICHANNEL_GROUP);
+        }
 
         // privileges mapping to input device
         EXT_TO_INT_INPUT_DEVICE_MAPPING = new SparseIntArray();
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 8250a53..9beeef4 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -21,6 +21,7 @@
 import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.media.audio.Flags.autoPublicVolumeApiHardening;
 import static android.media.audio.Flags.automaticBtDeviceType;
+import static android.media.audio.Flags.FLAG_DEPRECATE_STREAM_BT_SCO;
 import static android.media.audio.Flags.FLAG_FOCUS_EXCLUSIVE_WITH_RECORDING;
 import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API;
 import static android.media.audio.Flags.FLAG_SUPPORTED_DEVICE_TYPES_API;
@@ -406,8 +407,10 @@
     /** Used to identify the volume of audio streams for notifications */
     public static final int STREAM_NOTIFICATION = AudioSystem.STREAM_NOTIFICATION;
     /** @hide Used to identify the volume of audio streams for phone calls when connected
-     *        to bluetooth */
+     *        to bluetooth
+     *  @deprecated use {@link #STREAM_VOICE_CALL} instead */
     @SystemApi
+    @FlaggedApi(FLAG_DEPRECATE_STREAM_BT_SCO)
     public static final int STREAM_BLUETOOTH_SCO = AudioSystem.STREAM_BLUETOOTH_SCO;
     /** @hide Used to identify the volume of audio streams for enforced system sounds
      *        in certain countries (e.g camera in Japan) */
@@ -6156,6 +6159,11 @@
      */
     public static final int DEVICE_OUT_BLE_BROADCAST = AudioSystem.DEVICE_OUT_BLE_BROADCAST;
     /** @hide
+     * The audio output device code for a wireless speaker group supporting multichannel content.
+     */
+    public static final int DEVICE_OUT_MULTICHANNEL_GROUP =
+            AudioSystem.DEVICE_OUT_MULTICHANNEL_GROUP;
+    /** @hide
      * This is not used as a returned value from {@link #getDevicesForStream}, but could be
      *  used in the future in a set method to select whatever default device is chosen by the
      *  platform-specific implementation.
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index bf09cb0..d0d91ba 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -93,7 +93,8 @@
     /** @hide Used to identify the volume of audio streams for notifications */
     public static final int STREAM_NOTIFICATION = 5;
     /** @hide
-     *  Used to identify the volume of audio streams for phone calls when connected on bluetooth */
+     *  Used to identify the volume of audio streams for phone calls when connected on bluetooth
+     *  @deprecated use {@link #STREAM_VOICE_CALL} instead */
     public static final int STREAM_BLUETOOTH_SCO = 6;
     /** @hide Used to identify the volume of audio streams for enforced system sounds in certain
      * countries (e.g camera in Japan) */
@@ -110,7 +111,7 @@
     public static final int STREAM_ASSISTANT = 11;
     /**
      * @hide
-     * @deprecated Use {@link #numStreamTypes() instead}
+     * @deprecated Use {@link #numStreamTypes()} instead
      */
     public static final int NUM_STREAMS = 5;
 
@@ -1066,6 +1067,8 @@
     /** @hide */
     public static final int DEVICE_OUT_IP = 0x800000;
     /** @hide */
+    public static final int DEVICE_OUT_MULTICHANNEL_GROUP = 0x800001;
+    /** @hide */
     public static final int DEVICE_OUT_BUS = 0x1000000;
     /** @hide */
     public static final int DEVICE_OUT_PROXY = 0x2000000;
@@ -1134,6 +1137,7 @@
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_AUX_LINE);
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_SPEAKER_SAFE);
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_IP);
+        DEVICE_OUT_ALL_SET.add(DEVICE_OUT_MULTICHANNEL_GROUP);
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_BUS);
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_PROXY);
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_USB_HEADSET);
@@ -1422,6 +1426,8 @@
     /** @hide */ public static final String DEVICE_OUT_AUX_LINE_NAME = "aux_line";
     /** @hide */ public static final String DEVICE_OUT_SPEAKER_SAFE_NAME = "speaker_safe";
     /** @hide */ public static final String DEVICE_OUT_IP_NAME = "ip";
+    /** @hide */
+    public static final String DEVICE_OUT_MULTICHANNEL_GROUP_NAME = "multichannel_group";
     /** @hide */ public static final String DEVICE_OUT_BUS_NAME = "bus";
     /** @hide */ public static final String DEVICE_OUT_PROXY_NAME = "proxy";
     /** @hide */ public static final String DEVICE_OUT_USB_HEADSET_NAME = "usb_headset";
@@ -1515,6 +1521,8 @@
             return DEVICE_OUT_SPEAKER_SAFE_NAME;
         case DEVICE_OUT_IP:
             return DEVICE_OUT_IP_NAME;
+        case DEVICE_OUT_MULTICHANNEL_GROUP:
+            return DEVICE_OUT_MULTICHANNEL_GROUP_NAME;
         case DEVICE_OUT_BUS:
             return DEVICE_OUT_BUS_NAME;
         case DEVICE_OUT_PROXY:
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/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 3a19f46..96edd63 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -23,6 +23,7 @@
 import static android.media.codec.Flags.FLAG_IN_PROCESS_SW_AUDIO_CODEC;
 import static android.media.codec.Flags.FLAG_NULL_OUTPUT_SURFACE;
 import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST;
+import static android.media.codec.Flags.FLAG_APV_SUPPORT;
 import static android.media.MediaCodec.GetFlag;
 
 import android.annotation.FlaggedApi;
@@ -4496,6 +4497,265 @@
         @SuppressLint("AllUpper")
         public static final int AC4Level4       = 0x10;
 
+        // Profiles and levels/bands for APV Codec, corresponding to the definitions in
+        // "Advanced Professional Video", 10.1.3 Profiles, 10.1.4 Levels and Bands
+        // found at https://www.ietf.org/archive/id/draft-lim-apv-02.html
+
+        /**
+         * APV codec profile 422-10 as per IETF lim-apv-02, 10.1.3.1.1
+         */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVProfile422_10 =  0x01;
+
+        /**
+         * APV codec profile 422-10 as per IETF lim-apv-02, 10.1.3.1.1
+         * with HDR10.
+         */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVProfile422_10HDR10 =  0x1000;
+
+        /**
+         * APV codec profile 422-10 as per IETF lim-apv-02, 10.1.3.1.1
+         * with HDR10Plus.
+         */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVProfile422_10HDR10Plus =  0x2000;
+
+        // For APV Levels, the numerical values are constructed as follows:
+        //   ((0x100 << (level_num - 1)) | (1 << band))
+        // where:
+        //   - "level_num" is the APV Level numbered consecutively
+        //     (i.e., Level 1 == 1, Level 1.1 == 2, etc.)
+        //   - "band" is the APV Band
+
+        /** APV Codec Level 1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel1Band0 =  0x101;
+        /** APV Codec Level 1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel1Band1 =  0x102;
+        /** APV Codec Level 1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel1Band2 =  0x104;
+        /** APV Codec Level 1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel1Band3 =  0x108;
+        /** APV Codec Level 1.1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel11Band0 = 0x201;
+        /** APV Codec Level 1.1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel11Band1 = 0x202;
+        /** APV Codec Level 1.1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel11Band2 = 0x204;
+        /** APV Codec Level 1.1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel11Band3 = 0x208;
+        /** APV Codec Level 2, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel2Band0 =  0x401;
+        /** APV Codec Level 2, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel2Band1 =  0x402;
+        /** APV Codec Level 2, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel2Band2 =  0x404;
+        /** APV Codec Level 2, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel2Band3 =  0x408;
+        /** APV Codec Level 2.1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel21Band0 = 0x801;
+        /** APV Codec Level 2.1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel21Band1 = 0x802;
+        /** APV Codec Level 2.1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel21Band2 = 0x804;
+        /** APV Codec Level 2.1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel21Band3 = 0x808;
+        /** APV Codec Level 3, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel3Band0 =  0x1001;
+        /** APV Codec Level 3, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel3Band1 =  0x1002;
+        /** APV Codec Level 3, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel3Band2 =  0x1004;
+        /** APV Codec Level 3, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel3Band3 =  0x1008;
+        /** APV Codec Level 3.1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel31Band0 = 0x2001;
+        /** APV Codec Level 3.1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel31Band1 = 0x2002;
+        /** APV Codec Level 3.1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel31Band2 = 0x2004;
+        /** APV Codec Level 3.1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel31Band3 = 0x2008;
+        /** APV Codec Level 4, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel4Band0 =  0x4001;
+        /** APV Codec Level 4, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel4Band1 =  0x4002;
+        /** APV Codec Level 4, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel4Band2 =  0x4004;
+        /** APV Codec Level 4, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel4Band3 =  0x4008;
+        /** APV Codec Level 4.1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel41Band0 = 0x8001;
+        /** APV Codec Level 4.1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel41Band1 = 0x8002;
+        /** APV Codec Level 4.1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel41Band2 = 0x8004;
+        /** APV Codec Level 4.1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel41Band3 = 0x8008;
+        /** APV Codec Level 5, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel5Band0 =  0x10001;
+        /** APV Codec Level 5, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel5Band1 =  0x10002;
+        /** APV Codec Level 5, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel5Band2 =  0x10004;
+        /** APV Codec Level 5, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel5Band3 =  0x10008;
+        /** APV Codec Level 5.1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel51Band0 = 0x20001;
+        /** APV Codec Level 5.1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel51Band1 = 0x20002;
+        /** APV Codec Level 5.1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel51Band2 = 0x20004;
+        /** APV Codec Level 5.1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel51Band3 = 0x20008;
+        /** APV Codec Level 6, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel6Band0 =  0x40001;
+        /** APV Codec Level 6, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel6Band1 =  0x40002;
+        /** APV Codec Level 6, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel6Band2 =  0x40004;
+        /** APV Codec Level 6, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel6Band3 =  0x40008;
+        /** APV Codec Level 6.1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel61Band0 = 0x80001;
+        /** APV Codec Level 6.1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel61Band1 = 0x80002;
+        /** APV Codec Level 6.1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel61Band2 = 0x80004;
+        /** APV Codec Level 6.1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel61Band3 = 0x80008;
+        /** APV Codec Level 7, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel7Band0 =  0x100001;
+        /** APV Codec Level 7, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel7Band1 =  0x100002;
+        /** APV Codec Level 7, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel7Band2 =  0x100004;
+        /** APV Codec Level 7, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel7Band3 =  0x100008;
+        /** APV Codec Level 7.1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel71Band0 = 0x200001;
+        /** APV Codec Level 7.1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel71Band1 = 0x200002;
+        /** APV Codec Level 7.1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel71Band2 = 0x200004;
+        /** APV Codec Level 7.1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel71Band3 = 0x200008;
+
         /**
          * The profile of the media content. Depending on the type of media this can be
          * one of the profile values defined in this class.
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index cd0654c..b08a86e 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -18,6 +18,7 @@
 
 import static android.media.codec.Flags.FLAG_IN_PROCESS_SW_AUDIO_CODEC;
 import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST;
+import static android.media.codec.Flags.FLAG_APV_SUPPORT;
 
 import static com.android.media.codec.flags.Flags.FLAG_CODEC_IMPORTANCE;
 import static com.android.media.codec.flags.Flags.FLAG_LARGE_AUDIO_FRAME;
@@ -157,6 +158,8 @@
 public final class MediaFormat {
     public static final String MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8";
     public static final String MIMETYPE_VIDEO_VP9 = "video/x-vnd.on2.vp9";
+    @FlaggedApi(FLAG_APV_SUPPORT)
+    public static final String MIMETYPE_VIDEO_APV = "video/apv";
     public static final String MIMETYPE_VIDEO_AV1 = "video/av01";
     public static final String MIMETYPE_VIDEO_AVC = "video/avc";
     public static final String MIMETYPE_VIDEO_HEVC = "video/hevc";
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index bdfa6301..2d17bf5 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -387,13 +387,13 @@
         /**
          * Audio source for capturing broadcast radio tuner output.
          * Capturing the radio tuner output requires the
-         * {@link android.Manifest.permission#CAPTURE_AUDIO_OUTPUT} permission.
+         * {@link android.Manifest.permission#CAPTURE_TUNER_AUDIO_INPUT} permission.
          * This permission is reserved for use by system components and is not available to
          * third-party applications.
          * @hide
          */
         @SystemApi
-        @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT)
+        @RequiresPermission(android.Manifest.permission.CAPTURE_TUNER_AUDIO_INPUT)
         public static final int RADIO_TUNER = 1998;
 
         /**
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index e048d5c..816729d 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -17,6 +17,7 @@
 package android.media;
 
 import static android.media.MediaRouter2Utils.toUniqueId;
+import static android.media.audio.Flags.FLAG_ENABLE_MULTICHANNEL_GROUP_DEVICE;
 
 import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER;
 import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES;
@@ -272,6 +273,19 @@
     public static final int TYPE_BLE_HEADSET = AudioDeviceInfo.TYPE_BLE_HEADSET;
 
     /**
+     * Indicates the route is a speaker group supporting multichannel contents.
+     *
+     * <p>The speakers in the group are connected together using local network based protocols. The
+     * speaker group requires additional input of the physical positions of each individual speaker
+     * to provide a better experience on multichannel contents.
+     *
+     * @see #getType
+     */
+    @FlaggedApi(FLAG_ENABLE_MULTICHANNEL_GROUP_DEVICE)
+    public static final int TYPE_MULTICHANNEL_SPEAKER_GROUP =
+            AudioDeviceInfo.TYPE_MULTICHANNEL_GROUP;
+
+    /**
      * Indicates the route is a remote TV.
      *
      * <p>A remote device uses a routing protocol managed by the application, as opposed to the
diff --git a/media/java/android/media/flags/editing.aconfig b/media/java/android/media/flags/editing.aconfig
index 185f579..0adc478 100644
--- a/media/java/android/media/flags/editing.aconfig
+++ b/media/java/android/media/flags/editing.aconfig
@@ -15,3 +15,10 @@
   description: "Enable B frames for Stagefright recorder."
   bug: "341121900"
 }
+
+flag {
+  name: "muxer_mp4_enable_apv"
+  namespace: "media_solutions"
+  description: "Enable APV support in mp4 writer."
+  bug: "370061501"
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/media/java/android/media/quality/AmbientBacklightEvent.aidl
similarity index 89%
copy from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
copy to media/java/android/media/quality/AmbientBacklightEvent.aidl
index e21bf8f..174cd46 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/media/java/android/media/quality/AmbientBacklightEvent.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.shared;
+package android.media.quality;
 
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
+parcelable AmbientBacklightEvent;
diff --git a/media/java/android/media/quality/AmbientBacklightEvent.java b/media/java/android/media/quality/AmbientBacklightEvent.java
new file mode 100644
index 0000000..3bc6b86
--- /dev/null
+++ b/media/java/android/media/quality/AmbientBacklightEvent.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.quality;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public final class AmbientBacklightEvent implements Parcelable {
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({AMBIENT_BACKLIGHT_EVENT_ENABLED, AMBIENT_BACKLIGHT_EVENT_DISABLED,
+            AMBIENT_BACKLIGHT_EVENT_METADATA,
+            AMBIENT_BACKLIGHT_EVENT_INTERRUPTED})
+    public @interface AmbientBacklightEventTypes {}
+
+    /**
+     * Event type for ambient backlight events. The ambient backlight is enabled.
+     */
+    public static final int AMBIENT_BACKLIGHT_EVENT_ENABLED = 1;
+
+    /**
+     * Event type for ambient backlight events. The ambient backlight is disabled.
+     */
+    public static final int AMBIENT_BACKLIGHT_EVENT_DISABLED = 2;
+
+    /**
+     * Event type for ambient backlight events. The ambient backlight metadata is
+     * available.
+     */
+    public static final int AMBIENT_BACKLIGHT_EVENT_METADATA = 3;
+
+    /**
+     * Event type for ambient backlight events. The ambient backlight event is preempted by another
+     * application.
+     */
+    public static final int AMBIENT_BACKLIGHT_EVENT_INTERRUPTED = 4;
+
+    private final int mEventType;
+    @Nullable
+    private final AmbientBacklightMetadata mMetadata;
+
+    public AmbientBacklightEvent(int eventType,
+            @Nullable AmbientBacklightMetadata metadata) {
+        mEventType = eventType;
+        mMetadata = metadata;
+    }
+
+    private AmbientBacklightEvent(Parcel in) {
+        mEventType = in.readInt();
+        mMetadata = in.readParcelable(AmbientBacklightMetadata.class.getClassLoader());
+    }
+
+    public int getEventType() {
+        return mEventType;
+    }
+
+    @Nullable
+    public AmbientBacklightMetadata getMetadata() {
+        return mMetadata;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mEventType);
+        dest.writeParcelable(mMetadata, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Parcelable.Creator<AmbientBacklightEvent> CREATOR =
+            new Parcelable.Creator<AmbientBacklightEvent>() {
+                public AmbientBacklightEvent createFromParcel(Parcel in) {
+                    return new AmbientBacklightEvent(in);
+                }
+
+                public AmbientBacklightEvent[] newArray(int size) {
+                    return new AmbientBacklightEvent[size];
+                }
+            };
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (!(obj instanceof AmbientBacklightEvent)) {
+            return false;
+        }
+
+        AmbientBacklightEvent other = (AmbientBacklightEvent) obj;
+        return mEventType == other.mEventType
+                && Objects.equals(mMetadata, other.mMetadata);
+    }
+
+    @Override
+    public int hashCode() {
+        return mEventType * 31 + (mMetadata != null ? mMetadata.hashCode() : 0);
+    }
+
+    @Override
+    public String toString() {
+        return "AmbientBacklightEvent{"
+                + "mEventType=" + mEventType
+                + ", mMetadata=" + mMetadata
+                + '}';
+    }
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/media/java/android/media/quality/AmbientBacklightMetadata.aidl
similarity index 89%
copy from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
copy to media/java/android/media/quality/AmbientBacklightMetadata.aidl
index e21bf8f..b95a474f 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/media/java/android/media/quality/AmbientBacklightMetadata.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.shared;
+package android.media.quality;
 
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
+parcelable AmbientBacklightMetadata;
\ No newline at end of file
diff --git a/media/java/android/media/quality/AmbientBacklightMetadata.java b/media/java/android/media/quality/AmbientBacklightMetadata.java
new file mode 100644
index 0000000..fc77934
--- /dev/null
+++ b/media/java/android/media/quality/AmbientBacklightMetadata.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.quality;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
+
+/**
+ * @hide
+ */
+public class AmbientBacklightMetadata implements Parcelable {
+    @NonNull
+    private final String mPackageName;
+    private final int mCompressAlgorithm;
+    private final int mSource;
+    private final int mColorFormat;
+    private final int mHorizontalZonesNumber;
+    private final int mVerticalZonesNumber;
+    @NonNull
+    private final int[] mZonesColors;
+
+    public AmbientBacklightMetadata(@NonNull String packageName, int compressAlgorithm,
+            int source, int colorFormat, int horizontalZonesNumber, int verticalZonesNumber,
+            @NonNull int[] zonesColors) {
+        mPackageName = packageName;
+        mCompressAlgorithm = compressAlgorithm;
+        mSource = source;
+        mColorFormat = colorFormat;
+        mHorizontalZonesNumber = horizontalZonesNumber;
+        mVerticalZonesNumber = verticalZonesNumber;
+        mZonesColors = zonesColors;
+    }
+
+    private AmbientBacklightMetadata(Parcel in) {
+        mPackageName = in.readString();
+        mCompressAlgorithm = in.readInt();
+        mSource = in.readInt();
+        mColorFormat = in.readInt();
+        mHorizontalZonesNumber = in.readInt();
+        mVerticalZonesNumber = in.readInt();
+        mZonesColors = in.createIntArray();
+    }
+
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    public int getCompressAlgorithm() {
+        return mCompressAlgorithm;
+    }
+
+    public int getSource() {
+        return mSource;
+    }
+
+    public int getColorFormat() {
+        return mColorFormat;
+    }
+
+    public int getHorizontalZonesNumber() {
+        return mHorizontalZonesNumber;
+    }
+
+    public int getVerticalZonesNumber() {
+        return mVerticalZonesNumber;
+    }
+
+    @NonNull
+    public int[] getZonesColors() {
+        return mZonesColors;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mPackageName);
+        dest.writeInt(mCompressAlgorithm);
+        dest.writeInt(mSource);
+        dest.writeInt(mColorFormat);
+        dest.writeInt(mHorizontalZonesNumber);
+        dest.writeInt(mVerticalZonesNumber);
+        dest.writeIntArray(mZonesColors);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Parcelable.Creator<AmbientBacklightMetadata> CREATOR =
+            new Parcelable.Creator<AmbientBacklightMetadata>() {
+                public AmbientBacklightMetadata createFromParcel(Parcel in) {
+                    return new AmbientBacklightMetadata(in);
+                }
+
+                public AmbientBacklightMetadata[] newArray(int size) {
+                    return new AmbientBacklightMetadata[size];
+                }
+            };
+
+    @Override
+    public String toString() {
+        return "AmbientBacklightMetadata{packageName=" + mPackageName
+                + ", compressAlgorithm=" + mCompressAlgorithm + ", source=" + mSource
+                + ", colorFormat=" + mColorFormat + ", horizontalZonesNumber="
+                + mHorizontalZonesNumber + ", verticalZonesNumber=" + mVerticalZonesNumber
+                + ", zonesColors=" + Arrays.toString(mZonesColors) + "}";
+    }
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/media/java/android/media/quality/AmbientBacklightSettings.aidl
similarity index 89%
copy from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
copy to media/java/android/media/quality/AmbientBacklightSettings.aidl
index e21bf8f..e2cdd03 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/media/java/android/media/quality/AmbientBacklightSettings.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.shared;
+package android.media.quality;
 
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
+parcelable AmbientBacklightSettings;
diff --git a/media/java/android/media/quality/AmbientBacklightSettings.java b/media/java/android/media/quality/AmbientBacklightSettings.java
new file mode 100644
index 0000000..391eb22
--- /dev/null
+++ b/media/java/android/media/quality/AmbientBacklightSettings.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.quality;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @hide
+ */
+public class AmbientBacklightSettings implements Parcelable {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({SOURCE_NONE, SOURCE_AUDIO, SOURCE_VIDEO, SOURCE_AUDIO_VIDEO})
+    public @interface Source {}
+
+    /**
+     * The detection is disabled.
+     */
+    public static final int SOURCE_NONE = 0;
+
+    /**
+     * The detection is enabled for audio.
+     */
+    public static final int SOURCE_AUDIO = 1;
+
+    /**
+     * The detection is enabled for video.
+     */
+    public static final int SOURCE_VIDEO = 2;
+
+    /**
+     * The detection is enabled for audio and video.
+     */
+    public static final int SOURCE_AUDIO_VIDEO = 3;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({COLOR_FORMAT_RGB888})
+    public @interface ColorFormat {}
+
+    /**
+     * The color format is RGB888.
+     */
+    public static final int COLOR_FORMAT_RGB888 = 1;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ALGORITHM_NONE, ALGORITHM_RLE})
+    public @interface CompressAlgorithm {}
+
+    /**
+     * The compress algorithm is disabled.
+     */
+    public static final int ALGORITHM_NONE = 0;
+
+    /**
+     * The compress algorithm is RLE.
+     */
+    public static final int ALGORITHM_RLE = 1;
+
+    /**
+     * The source of the ambient backlight.
+     */
+    private final int mSource;
+
+    /**
+     * The maximum framerate for the ambient backlight.
+     */
+    private final int mMaxFps;
+
+    /**
+     * The color format for the ambient backlight.
+     */
+    private final int mColorFormat;
+
+    /**
+     * The number of zones in horizontal direction.
+     */
+    private final int mHorizontalZonesNumber;
+
+    /**
+     * The number of zones in vertical direction.
+     */
+    private final int mVerticalZonesNumber;
+
+    /**
+     * The flag to indicate whether the letterbox is omitted.
+     */
+    private final boolean mIsLetterboxOmitted;
+
+    /**
+     * The color threshold for the ambient backlight.
+     */
+    private final int mThreshold;
+
+    public AmbientBacklightSettings(int source, int maxFps, int colorFormat,
+            int horizontalZonesNumber, int verticalZonesNumber, boolean isLetterboxOmitted,
+            int threshold) {
+        mSource = source;
+        mMaxFps = maxFps;
+        mColorFormat = colorFormat;
+        mHorizontalZonesNumber = horizontalZonesNumber;
+        mVerticalZonesNumber = verticalZonesNumber;
+        mIsLetterboxOmitted = isLetterboxOmitted;
+        mThreshold = threshold;
+    }
+
+    private AmbientBacklightSettings(Parcel in) {
+        mSource = in.readInt();
+        mMaxFps = in.readInt();
+        mColorFormat = in.readInt();
+        mHorizontalZonesNumber = in.readInt();
+        mVerticalZonesNumber = in.readInt();
+        mIsLetterboxOmitted = in.readBoolean();
+        mThreshold = in.readInt();
+    }
+
+    @Source
+    public int getSource() {
+        return mSource;
+    }
+
+    public int getMaxFps() {
+        return mMaxFps;
+    }
+
+    @ColorFormat
+    public int getColorFormat() {
+        return mColorFormat;
+    }
+
+    public int getHorizontalZonesNumber() {
+        return mHorizontalZonesNumber;
+    }
+
+    public int getVerticalZonesNumber() {
+        return mVerticalZonesNumber;
+    }
+
+    public boolean isLetterboxOmitted() {
+        return mIsLetterboxOmitted;
+    }
+
+    public int getThreshold() {
+        return mThreshold;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mSource);
+        dest.writeInt(mMaxFps);
+        dest.writeInt(mColorFormat);
+        dest.writeInt(mHorizontalZonesNumber);
+        dest.writeInt(mVerticalZonesNumber);
+        dest.writeBoolean(mIsLetterboxOmitted);
+        dest.writeInt(mThreshold);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Parcelable.Creator<AmbientBacklightSettings> CREATOR =
+            new Parcelable.Creator<AmbientBacklightSettings>() {
+                public AmbientBacklightSettings createFromParcel(Parcel in) {
+                    return new AmbientBacklightSettings(in);
+                }
+
+                public AmbientBacklightSettings[] newArray(int size) {
+                    return new AmbientBacklightSettings[size];
+                }
+            };
+
+    @Override
+    public String toString() {
+        return "AmbientBacklightSettings{Source=" + mSource + ", MaxFps=" + mMaxFps
+                + ", ColorFormat=" + mColorFormat + ", HorizontalZonesNumber="
+                + mHorizontalZonesNumber + ", VerticalZonesNumber=" + mVerticalZonesNumber
+                + ", IsLetterboxOmitted=" + mIsLetterboxOmitted + ", Threshold=" + mThreshold + "}";
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/media/java/android/media/quality/IAmbientBacklightCallback.aidl
similarity index 74%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to media/java/android/media/quality/IAmbientBacklightCallback.aidl
index 94b2bdf..159f5b7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/media/java/android/media/quality/IAmbientBacklightCallback.aidl
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package android.media.quality;
 
-import com.android.systemui.kosmos.Kosmos
+import android.media.quality.AmbientBacklightEvent;
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+/** @hide */
+oneway interface IAmbientBacklightCallback {
+    void onAmbientBacklightEvent(in AmbientBacklightEvent event);
+}
diff --git a/media/java/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/IMediaQualityManager.aidl
index 4f1210a7..e6c79dd 100644
--- a/media/java/android/media/quality/IMediaQualityManager.aidl
+++ b/media/java/android/media/quality/IMediaQualityManager.aidl
@@ -16,9 +16,49 @@
 
 package android.media.quality;
 
+import android.media.quality.AmbientBacklightSettings;
+import android.media.quality.IAmbientBacklightCallback;
+import android.media.quality.IPictureProfileCallback;
+import android.media.quality.ISoundProfileCallback;
+import android.media.quality.ParamCapability;
+import android.media.quality.PictureProfile;
+import android.media.quality.SoundProfile;
+
 /**
  * Interface for Media Quality Manager
  * @hide
  */
 interface IMediaQualityManager {
-}
\ No newline at end of file
+    PictureProfile createPictureProfile(in PictureProfile pp);
+    void updatePictureProfile(in long id, in PictureProfile pp);
+    void removePictureProfile(in long id);
+    PictureProfile getPictureProfileById(in long id);
+    List<PictureProfile> getPictureProfilesByPackage(in String packageName);
+    List<PictureProfile> getAvailablePictureProfiles();
+    List<PictureProfile> getAllPictureProfiles();
+
+    SoundProfile createSoundProfile(in SoundProfile pp);
+    void updateSoundProfile(in long id, in SoundProfile pp);
+    void removeSoundProfile(in long id);
+    SoundProfile getSoundProfileById(in long id);
+    List<SoundProfile> getSoundProfilesByPackage(in String packageName);
+    List<SoundProfile> getAvailableSoundProfiles();
+    List<SoundProfile> getAllSoundProfiles();
+
+    void registerPictureProfileCallback(in IPictureProfileCallback cb);
+    void registerSoundProfileCallback(in ISoundProfileCallback cb);
+    void registerAmbientBacklightCallback(in IAmbientBacklightCallback cb);
+
+    List<ParamCapability> getParamCapabilities(in List<String> names);
+
+    boolean isSupported();
+    void setAutoPictureQualityEnabled(in boolean enabled);
+    boolean isAutoPictureQualityEnabled();
+    void setSuperResolutionEnabled(in boolean enabled);
+    boolean isSuperResolutionEnabled();
+    void setAutoSoundQualityEnabled(in boolean enabled);
+    boolean isAutoSoundQualityEnabled();
+
+    void setAmbientBacklightSettings(in AmbientBacklightSettings settings);
+    void setAmbientBacklightEnabled(in boolean enabled);
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/media/java/android/media/quality/IPictureProfileCallback.aidl
similarity index 60%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to media/java/android/media/quality/IPictureProfileCallback.aidl
index 94b2bdf..05441cd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/media/java/android/media/quality/IPictureProfileCallback.aidl
@@ -14,9 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
 
-import com.android.systemui.kosmos.Kosmos
+package android.media.quality;
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+import android.media.quality.PictureProfile;
+
+/**
+ * Interface to receive callbacks from IMediaQuality.
+ * @hide
+ */
+oneway interface IPictureProfileCallback {
+    void onPictureProfileAdded(in long id, in PictureProfile p);
+    void onPictureProfileUpdated(in long id, in PictureProfile p);
+    void onPictureProfileRemoved(in long id, in PictureProfile p);
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/media/java/android/media/quality/ISoundProfileCallback.aidl
similarity index 61%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to media/java/android/media/quality/ISoundProfileCallback.aidl
index 94b2bdf..72d1524 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/media/java/android/media/quality/ISoundProfileCallback.aidl
@@ -14,9 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
 
-import com.android.systemui.kosmos.Kosmos
+package android.media.quality;
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+import android.media.quality.SoundProfile;
+
+/**
+ * Interface to receive callbacks from IMediaQuality.
+ * @hide
+ */
+oneway interface ISoundProfileCallback {
+    void onSoundProfileAdded(in long id, in SoundProfile p);
+    void onSoundProfileUpdated(in long id, in SoundProfile p);
+    void onSoundProfileRemoved(in long id, in SoundProfile p);
+}
diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java
new file mode 100644
index 0000000..472d798
--- /dev/null
+++ b/media/java/android/media/quality/MediaQualityContract.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.quality;
+
+
+import android.annotation.FlaggedApi;
+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";
+        String PARAMETER_PACKAGE = "_package";
+        String PARAMETER_INPUT_ID = "_input_id";
+
+    }
+
+    /**
+     * 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";
+        public static final String PARAMETER_TREBLE = "treble";
+    }
+
+    private MediaQualityContract() {
+    }
+}
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index 86dbcdb..38a2025 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -16,23 +16,44 @@
 
 package android.media.quality;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.media.tv.flags.Flags;
+import android.os.RemoteException;
+
+import androidx.annotation.RequiresPermission;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+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)
 @SystemService(Context.MEDIA_QUALITY_SERVICE)
-public class MediaQualityManager {
+public final class MediaQualityManager {
     // TODO: unhide the APIs for api review
     private static final String TAG = "MediaQualityManager";
 
     private final IMediaQualityManager mService;
     private final Context mContext;
+    private final Object mLock = new Object();
+    // @GuardedBy("mLock")
+    private final List<PictureProfileCallbackRecord> mPpCallbackRecords = new ArrayList<>();
+    // @GuardedBy("mLock")
+    private final List<SoundProfileCallbackRecord> mSpCallbackRecords = new ArrayList<>();
+    // @GuardedBy("mLock")
+    private final List<AmbientBacklightCallbackRecord> mAbCallbackRecords = new ArrayList<>();
+
 
     /**
      * @hide
@@ -40,5 +61,684 @@
     public MediaQualityManager(Context context, IMediaQualityManager service) {
         mContext = context;
         mService = service;
+        IPictureProfileCallback ppCallback = new IPictureProfileCallback.Stub() {
+            @Override
+            public void onPictureProfileAdded(long profileId, PictureProfile profile) {
+                synchronized (mLock) {
+                    for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
+                        // TODO: filter callback record
+                        record.postPictureProfileAdded(profileId, profile);
+                    }
+                }
+            }
+            @Override
+            public void onPictureProfileUpdated(long profileId, PictureProfile profile) {
+                synchronized (mLock) {
+                    for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
+                        // TODO: filter callback record
+                        record.postPictureProfileUpdated(profileId, profile);
+                    }
+                }
+            }
+            @Override
+            public void onPictureProfileRemoved(long profileId, PictureProfile profile) {
+                synchronized (mLock) {
+                    for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
+                        // TODO: filter callback record
+                        record.postPictureProfileRemoved(profileId, profile);
+                    }
+                }
+            }
+        };
+        ISoundProfileCallback spCallback = new ISoundProfileCallback.Stub() {
+            @Override
+            public void onSoundProfileAdded(long profileId, SoundProfile profile) {
+                synchronized (mLock) {
+                    for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
+                        // TODO: filter callback record
+                        record.postSoundProfileAdded(profileId, profile);
+                    }
+                }
+            }
+            @Override
+            public void onSoundProfileUpdated(long profileId, SoundProfile profile) {
+                synchronized (mLock) {
+                    for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
+                        // TODO: filter callback record
+                        record.postSoundProfileUpdated(profileId, profile);
+                    }
+                }
+            }
+            @Override
+            public void onSoundProfileRemoved(long profileId, SoundProfile profile) {
+                synchronized (mLock) {
+                    for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
+                        // TODO: filter callback record
+                        record.postSoundProfileRemoved(profileId, profile);
+                    }
+                }
+            }
+        };
+        IAmbientBacklightCallback abCallback = new IAmbientBacklightCallback.Stub() {
+            @Override
+            public void onAmbientBacklightEvent(AmbientBacklightEvent event) {
+                synchronized (mLock) {
+                    for (AmbientBacklightCallbackRecord record : mAbCallbackRecords) {
+                        record.postAmbientBacklightEvent(event);
+                    }
+                }
+            }
+        };
+
+        try {
+            if (mService != null) {
+                mService.registerPictureProfileCallback(ppCallback);
+                mService.registerSoundProfileCallback(spCallback);
+                mService.registerAmbientBacklightCallback(abCallback);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Registers a {@link PictureProfileCallback}.
+     * @hide
+     */
+    public void registerPictureProfileCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull PictureProfileCallback callback) {
+        Preconditions.checkNotNull(callback);
+        Preconditions.checkNotNull(executor);
+        synchronized (mLock) {
+            mPpCallbackRecords.add(new PictureProfileCallbackRecord(callback, executor));
+        }
+    }
+
+    /**
+     * Unregisters the existing {@link PictureProfileCallback}.
+     * @hide
+     */
+    public void unregisterPictureProfileCallback(@NonNull final PictureProfileCallback callback) {
+        Preconditions.checkNotNull(callback);
+        synchronized (mLock) {
+            for (Iterator<PictureProfileCallbackRecord> it = mPpCallbackRecords.iterator();
+                    it.hasNext(); ) {
+                PictureProfileCallbackRecord record = it.next();
+                if (record.getCallback() == callback) {
+                    it.remove();
+                    break;
+                }
+            }
+        }
+    }
+
+
+    /**
+     * 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 {
+            return mService.getPictureProfileById(profileId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
+     * @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 {
+            return mService.getPictureProfilesByPackage(packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Gets profiles that available to the caller.
+     */
+    @NonNull
+    public List<PictureProfile> getAvailablePictureProfiles() {
+        try {
+            return mService.getAvailablePictureProfiles();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @SystemApi all stored picture profiles of all packages
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
+    public List<PictureProfile> getAllPictureProfiles() {
+        try {
+            return mService.getAllPictureProfiles();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
+     * 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 {
+            return mService.createPictureProfile(pp);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
+     * Updates an existing picture profile and store it in the system.
+     * @hide
+     */
+    public void updatePictureProfile(long profileId, PictureProfile pp) {
+        try {
+            mService.updatePictureProfile(profileId, pp);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
+     * Removes a picture profile from the system.
+     * @hide
+     */
+    public void removePictureProfile(long profileId) {
+        try {
+            mService.removePictureProfile(profileId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Registers a {@link SoundProfileCallback}.
+     * @hide
+     */
+    public void registerSoundProfileCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull SoundProfileCallback callback) {
+        Preconditions.checkNotNull(callback);
+        Preconditions.checkNotNull(executor);
+        synchronized (mLock) {
+            mSpCallbackRecords.add(new SoundProfileCallbackRecord(callback, executor));
+        }
+    }
+
+    /**
+     * Unregisters the existing {@link SoundProfileCallback}.
+     * @hide
+     */
+    public void unregisterSoundProfileCallback(@NonNull final SoundProfileCallback callback) {
+        Preconditions.checkNotNull(callback);
+        synchronized (mLock) {
+            for (Iterator<SoundProfileCallbackRecord> it = mSpCallbackRecords.iterator();
+                    it.hasNext(); ) {
+                SoundProfileCallbackRecord record = it.next();
+                if (record.getCallback() == callback) {
+                    it.remove();
+                    break;
+                }
+            }
+        }
+    }
+
+
+    /**
+     * 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 {
+            return mService.getSoundProfileById(profileId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
+     * @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 {
+            return mService.getSoundProfilesByPackage(packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Gets profiles that available to the caller package
+     * @hide
+     */
+    public List<SoundProfile> getAvailableSoundProfiles() {
+        try {
+            return mService.getAvailableSoundProfiles();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @SystemApi all stored sound profiles of all packages
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
+    public List<SoundProfile> getAllSoundProfiles() {
+        try {
+            return mService.getAllSoundProfiles();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
+     * 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 {
+            return mService.createSoundProfile(sp);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
+     * Updates an existing sound profile and store it in the system.
+     * @hide
+     */
+    public void updateSoundProfile(long profileId, SoundProfile sp) {
+        try {
+            mService.updateSoundProfile(profileId, sp);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
+     * Removes a sound profile from the system.
+     * @hide
+     */
+    public void removeSoundProfile(long profileId) {
+        try {
+            mService.removeSoundProfile(profileId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Gets capability information of the given parameters.
+     * @hide
+     */
+    public List<ParamCapability> getParamCapabilities(List<String> names) {
+        try {
+            return mService.getParamCapabilities(names);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns {@code true} if media quality HAL is implemented; {@code false} otherwise.
+     */
+    public boolean isSupported() {
+        try {
+            return mService.isSupported();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Enables or disables auto picture quality.
+     * <p>If enabled, picture quality parameters can be adjusted dynamically by hardware based on
+     * 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) {
+        try {
+            mService.setAutoPictureQualityEnabled(enabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns {@code true} if auto picture quality is enabled; {@code false} otherwise.
+     * @hide
+     */
+    public boolean isAutoPictureQualityEnabled() {
+        try {
+            return mService.isAutoPictureQualityEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Enables or disables super resolution.
+     * <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) {
+        try {
+            mService.setSuperResolutionEnabled(enabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns {@code true} if super resolution is enabled; {@code false} otherwise.
+     * @hide
+     */
+    public boolean isSuperResolutionEnabled() {
+        try {
+            return mService.isSuperResolutionEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Enables or disables auto sound quality.
+     * <p>If enabled, sound quality parameters can be adjusted dynamically by hardware based on
+     * 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) {
+        try {
+            mService.setAutoSoundQualityEnabled(enabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns {@code true} if auto sound quality is enabled; {@code false} otherwise.
+     * @hide
+     */
+    public boolean isAutoSoundQualityEnabled() {
+        try {
+            return mService.isAutoSoundQualityEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Registers a {@link AmbientBacklightCallback}.
+     * @hide
+     */
+    public void registerAmbientBacklightCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull AmbientBacklightCallback callback) {
+        Preconditions.checkNotNull(callback);
+        Preconditions.checkNotNull(executor);
+        synchronized (mLock) {
+            mAbCallbackRecords.add(new AmbientBacklightCallbackRecord(callback, executor));
+        }
+    }
+
+    /**
+     * Unregisters the existing {@link AmbientBacklightCallback}.
+     * @hide
+     */
+    public void unregisterAmbientBacklightCallback(
+            @NonNull final AmbientBacklightCallback callback) {
+        Preconditions.checkNotNull(callback);
+        synchronized (mLock) {
+            for (Iterator<AmbientBacklightCallbackRecord> it = mAbCallbackRecords.iterator();
+                    it.hasNext(); ) {
+                AmbientBacklightCallbackRecord record = it.next();
+                if (record.getCallback() == callback) {
+                    it.remove();
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Set the ambient backlight settings.
+     *
+     * @param settings The settings to use for the backlight detector.
+     * @hide
+     */
+    public void setAmbientBacklightSettings(
+            @NonNull AmbientBacklightSettings settings) {
+        Preconditions.checkNotNull(settings);
+        try {
+            mService.setAmbientBacklightSettings(settings);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Enables or disables the ambient backlight detection.
+     *
+     * @param enabled {@code true} to enable, {@code false} to disable.
+     * @hide
+     */
+    public void setAmbientBacklightEnabled(boolean enabled) {
+        try {
+            mService.setAmbientBacklightEnabled(enabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    private static final class PictureProfileCallbackRecord {
+        private final PictureProfileCallback mCallback;
+        private final Executor mExecutor;
+
+        PictureProfileCallbackRecord(PictureProfileCallback callback, Executor executor) {
+            mCallback = callback;
+            mExecutor = executor;
+        }
+
+        public PictureProfileCallback getCallback() {
+            return mCallback;
+        }
+
+        public void postPictureProfileAdded(final long id, PictureProfile profile) {
+
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onPictureProfileAdded(id, profile);
+                }
+            });
+        }
+
+        public void postPictureProfileUpdated(final long id, PictureProfile profile) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onPictureProfileUpdated(id, profile);
+                }
+            });
+        }
+
+        public void postPictureProfileRemoved(final long id, PictureProfile profile) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onPictureProfileRemoved(id, profile);
+                }
+            });
+        }
+    }
+
+    private static final class SoundProfileCallbackRecord {
+        private final SoundProfileCallback mCallback;
+        private final Executor mExecutor;
+
+        SoundProfileCallbackRecord(SoundProfileCallback callback, Executor executor) {
+            mCallback = callback;
+            mExecutor = executor;
+        }
+
+        public SoundProfileCallback getCallback() {
+            return mCallback;
+        }
+
+        public void postSoundProfileAdded(final long id, SoundProfile profile) {
+
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onSoundProfileAdded(id, profile);
+                }
+            });
+        }
+
+        public void postSoundProfileUpdated(final long id, SoundProfile profile) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onSoundProfileUpdated(id, profile);
+                }
+            });
+        }
+
+        public void postSoundProfileRemoved(final long id, SoundProfile profile) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onSoundProfileRemoved(id, profile);
+                }
+            });
+        }
+    }
+
+    private static final class AmbientBacklightCallbackRecord {
+        private final AmbientBacklightCallback mCallback;
+        private final Executor mExecutor;
+
+        AmbientBacklightCallbackRecord(AmbientBacklightCallback callback, Executor executor) {
+            mCallback = callback;
+            mExecutor = executor;
+        }
+
+        public AmbientBacklightCallback getCallback() {
+            return mCallback;
+        }
+
+        public void postAmbientBacklightEvent(AmbientBacklightEvent event) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onAmbientBacklightEvent(event);
+                }
+            });
+        }
+    }
+
+    /**
+     * Callback used to monitor status of picture profiles.
+     * @hide
+     */
+    public abstract static class PictureProfileCallback {
+        /**
+         * @hide
+         */
+        public void onPictureProfileAdded(long id, PictureProfile profile) {
+        }
+        /**
+         * @hide
+         */
+        public void onPictureProfileUpdated(long id, PictureProfile profile) {
+        }
+        /**
+         * @hide
+         */
+        public void onPictureProfileRemoved(long id, PictureProfile profile) {
+        }
+        /**
+         * @hide
+         */
+        public void onError(int errorCode) {
+        }
+    }
+
+    /**
+     * Callback used to monitor status of sound profiles.
+     * @hide
+     */
+    public abstract static class SoundProfileCallback {
+        /**
+         * @hide
+         */
+        public void onSoundProfileAdded(long id, SoundProfile profile) {
+        }
+        /**
+         * @hide
+         */
+        public void onSoundProfileUpdated(long id, SoundProfile profile) {
+        }
+        /**
+         * @hide
+         */
+        public void onSoundProfileRemoved(long id, SoundProfile profile) {
+        }
+        /**
+         * @hide
+         */
+        public void onError(int errorCode) {
+        }
+    }
+
+    /**
+     * Callback used to monitor status of ambient backlight.
+     * @hide
+     */
+    public abstract static class AmbientBacklightCallback {
+        /**
+         * Called when new ambient backlight event is emitted.
+         * @hide
+         */
+        public void onAmbientBacklightEvent(AmbientBacklightEvent event) {
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/media/java/android/media/quality/ParamCapability.aidl
similarity index 89%
copy from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
copy to media/java/android/media/quality/ParamCapability.aidl
index e21bf8f..b43409d 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/media/java/android/media/quality/ParamCapability.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.shared;
+package android.media.quality;
 
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
+parcelable ParamCapability;
diff --git a/media/java/android/media/quality/ParamCapability.java b/media/java/android/media/quality/ParamCapability.java
new file mode 100644
index 0000000..70e8592
--- /dev/null
+++ b/media/java/android/media/quality/ParamCapability.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.quality;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.StringDef;
+import android.media.tv.flags.Flags;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Capability info of media quality parameters
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
+public class ParamCapability implements Parcelable {
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "TYPE_" }, value = {
+            TYPE_INT,
+            TYPE_LONG,
+            TYPE_DOUBLE,
+            TYPE_STRING,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ParamType {}
+
+    /**
+     * Integer parameter type
+     */
+    public static final int TYPE_INT = 1;
+
+    /**
+     * Long integer parameter type
+     */
+    public static final int TYPE_LONG = 2;
+
+    /**
+     * Double parameter type
+     */
+    public static final int TYPE_DOUBLE = 3;
+
+    /**
+     * String parameter type
+     */
+    public static final int TYPE_STRING = 4;
+
+    /** @hide */
+    @StringDef(prefix = { "CAPABILITY_" }, value = {
+            CAPABILITY_MAX,
+            CAPABILITY_MIN,
+            CAPABILITY_DEFAULT,
+            CAPABILITY_ENUM,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Capability {}
+
+    /**
+     * The key for the max possible value of this parameter.
+     */
+    public static final String CAPABILITY_MAX = "max";
+
+    /**
+     * The key for the min possible value of this parameter.
+     */
+    public static final String CAPABILITY_MIN = "min";
+
+    /**
+     * The key for the default value of this parameter.
+     */
+    public static final String CAPABILITY_DEFAULT = "default";
+
+    /**
+     * The key for the enumeration of this parameter.
+     */
+    public static final String CAPABILITY_ENUM = "enum";
+
+    @NonNull
+    private final String mName;
+    private final boolean mIsSupported;
+    @ParamType
+    private final int mType;
+    @NonNull
+    private final Bundle mCaps;
+
+    protected ParamCapability(Parcel in) {
+        mName = in.readString();
+        mIsSupported = in.readBoolean();
+        mType = in.readInt();
+        mCaps = in.readBundle();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mName);
+        dest.writeBoolean(mIsSupported);
+        dest.writeInt(mType);
+        dest.writeBundle(mCaps);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<ParamCapability> CREATOR = new Creator<ParamCapability>() {
+        @Override
+        public ParamCapability createFromParcel(Parcel in) {
+            return new ParamCapability(in);
+        }
+
+        @Override
+        public ParamCapability[] newArray(int size) {
+            return new ParamCapability[size];
+        }
+    };
+
+
+    /**
+     * Creates a new ParamCapability.
+     *
+     * @hide
+     */
+    public ParamCapability(
+            @NonNull String name,
+            boolean isSupported,
+            int type,
+            @NonNull Bundle caps) {
+        this.mName = name;
+        this.mIsSupported = isSupported;
+        this.mType = type;
+        this.mCaps = caps;
+    }
+
+    /**
+     * Gets parameter name.
+     */
+    @NonNull
+    public String getParamName() {
+        return mName;
+    }
+
+    /**
+     * Returns whether this parameter is supported or not.
+     */
+    public boolean isSupported() {
+        return mIsSupported;
+    }
+
+    /**
+     * Gets parameter type.
+     */
+    @ParamType
+    public int getParamType() {
+        return mType;
+    }
+
+    /**
+     * Gets capability information.
+     * <p>e.g. use the key {@link #CAPABILITY_MAX} to get the max value.
+     */
+    @NonNull
+    public Bundle getCapabilities() {
+        return new Bundle(mCaps);
+    }
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/media/java/android/media/quality/PictureProfile.aidl
similarity index 89%
copy from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
copy to media/java/android/media/quality/PictureProfile.aidl
index e21bf8f..41d018b 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/media/java/android/media/quality/PictureProfile.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.shared;
+package android.media.quality;
 
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
+parcelable PictureProfile;
diff --git a/media/java/android/media/quality/PictureProfile.java b/media/java/android/media/quality/PictureProfile.java
new file mode 100644
index 0000000..8fb5712
--- /dev/null
+++ b/media/java/android/media/quality/PictureProfile.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.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;
+import android.os.Parcelable;
+
+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 final class PictureProfile implements Parcelable {
+    @Nullable
+    private String mId;
+    private final int mType;
+    @NonNull
+    private final String mName;
+    @Nullable
+    private final String mInputId;
+    @NonNull
+    private final String mPackageName;
+    @NonNull
+    private final Bundle mParams;
+
+    /** @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();
+        mParams = in.readBundle();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mId);
+        dest.writeInt(mType);
+        dest.writeString(mName);
+        dest.writeString(mInputId);
+        dest.writeString(mPackageName);
+        dest.writeBundle(mParams);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Creator<PictureProfile> CREATOR = new Creator<PictureProfile>() {
+        @Override
+        public PictureProfile createFromParcel(Parcel in) {
+            return new PictureProfile(in);
+        }
+
+        @Override
+        public PictureProfile[] newArray(int size) {
+            return new PictureProfile[size];
+        }
+    };
+
+
+    /**
+     * Creates a new PictureProfile.
+     *
+     * @hide
+     */
+    public PictureProfile(
+            @Nullable String id,
+            int type,
+            @NonNull String name,
+            @Nullable String inputId,
+            @NonNull String packageName,
+            @NonNull Bundle params) {
+        this.mId = id;
+        this.mType = type;
+        this.mName = 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 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}.
+     * @hide
+     */
+    public static final class Builder {
+        @Nullable
+        private String mId;
+        private int mType = TYPE_APPLICATION;
+        @NonNull
+        private String mName;
+        @Nullable
+        private String mInputId;
+        @NonNull
+        private String mPackageName;
+        @NonNull
+        private Bundle mParams;
+
+        /**
+         * Creates a new Builder.
+         */
+        public Builder(@NonNull String name) {
+            mName = name;
+        }
+
+        /**
+         * 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 */
+
+        /**
+         * Only used by system to assign the ID.
+         * @hide
+         */
+        @NonNull
+        public Builder setProfileId(@Nullable String id) {
+            mId = id;
+            return this;
+        }
+
+        /**
+         * 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;
+            return this;
+        }
+
+        /**
+         * 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;
+            return this;
+        }
+
+        /**
+         * Sets profile parameters.
+         *
+         * @see PictureProfile#getParameters()
+         */
+        @NonNull
+        public Builder setParameters(@NonNull Bundle params) {
+            mParams = new Bundle(params);
+            return this;
+        }
+
+        /**
+         * Builds the instance.
+         */
+        @NonNull
+        public PictureProfile build() {
+
+            PictureProfile o = new PictureProfile(
+                    mId,
+                    mType,
+                    mName,
+                    mInputId,
+                    mPackageName,
+                    mParams);
+            return o;
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/media/java/android/media/quality/SoundProfile.aidl
similarity index 89%
copy from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
copy to media/java/android/media/quality/SoundProfile.aidl
index e21bf8f..e79fcaa 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/media/java/android/media/quality/SoundProfile.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.shared;
+package android.media.quality;
 
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
+parcelable SoundProfile;
diff --git a/media/java/android/media/quality/SoundProfile.java b/media/java/android/media/quality/SoundProfile.java
new file mode 100644
index 0000000..20d117b
--- /dev/null
+++ b/media/java/android/media/quality/SoundProfile.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.quality;
+
+import android.annotation.FlaggedApi;
+import android.media.tv.flags.Flags;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
+public class SoundProfile implements Parcelable {
+    @Nullable
+    private Long mId;
+    @NonNull
+    private final String mName;
+    @Nullable
+    private final String mInputId;
+    @Nullable
+    private final String mPackageName;
+    @NonNull
+    private final Bundle mParams;
+
+    protected SoundProfile(Parcel in) {
+        if (in.readByte() == 0) {
+            mId = null;
+        } else {
+            mId = in.readLong();
+        }
+        mName = in.readString();
+        mInputId = in.readString();
+        mPackageName = in.readString();
+        mParams = in.readBundle();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        if (mId == null) {
+            dest.writeByte((byte) 0);
+        } else {
+            dest.writeByte((byte) 1);
+            dest.writeLong(mId);
+        }
+        dest.writeString(mName);
+        dest.writeString(mInputId);
+        dest.writeString(mPackageName);
+        dest.writeBundle(mParams);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<SoundProfile> CREATOR = new Creator<SoundProfile>() {
+        @Override
+        public SoundProfile createFromParcel(Parcel in) {
+            return new SoundProfile(in);
+        }
+
+        @Override
+        public SoundProfile[] newArray(int size) {
+            return new SoundProfile[size];
+        }
+    };
+
+
+    /**
+     * Creates a new SoundProfile.
+     *
+     * @hide
+     */
+    public SoundProfile(
+            @Nullable Long id,
+            @NonNull String name,
+            @Nullable String inputId,
+            @Nullable String packageName,
+            @NonNull Bundle params) {
+        this.mId = id;
+        this.mName = name;
+        com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, name);
+        this.mInputId = inputId;
+        this.mPackageName = packageName;
+        this.mParams = params;
+    }
+
+    @Nullable
+    public Long getProfileId() {
+        return mId;
+    }
+
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    @Nullable
+    public String getInputId() {
+        return mInputId;
+    }
+
+    @Nullable
+    public String getPackageName() {
+        return mPackageName;
+    }
+    @NonNull
+    public Bundle getParameters() {
+        return new Bundle(mParams);
+    }
+
+    /**
+     * A builder for {@link SoundProfile}
+     */
+    public static class Builder {
+        @Nullable
+        private Long mId;
+        @NonNull
+        private String mName;
+        @Nullable
+        private String mInputId;
+        @Nullable
+        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
+         */
+        public Builder(@NonNull SoundProfile p) {
+            mId = null; // ID needs to be reset
+            mName = p.getName();
+            mPackageName = p.getPackageName();
+            mInputId = p.getInputId();
+        }
+
+        /**
+         * Sets profile ID.
+         * @hide using by MediaQualityService
+         */
+        @NonNull
+        public Builder setProfileId(@Nullable Long id) {
+            mId = id;
+            return this;
+        }
+
+        /**
+         * Sets input ID.
+         */
+        @NonNull
+        public Builder setInputId(@NonNull String value) {
+            mInputId = value;
+            return this;
+        }
+
+        /**
+         * Sets package name of the profile.
+         */
+        @NonNull
+        public Builder setPackageName(@NonNull String value) {
+            mPackageName = value;
+            return this;
+        }
+
+        /**
+         * Sets profile parameters.
+         */
+        @NonNull
+        public Builder setParameters(@NonNull Bundle params) {
+            mParams = new Bundle(params);
+            return this;
+        }
+        /**
+         * Builds the instance.
+         */
+        @NonNull
+        public SoundProfile build() {
+
+            SoundProfile o = new SoundProfile(
+                    mId,
+                    mName,
+                    mInputId,
+                    mPackageName,
+                    mParams);
+            return o;
+        }
+    }
+}
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index b673e03..c2f6896 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -27,6 +27,7 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.annotation.UserIdInt;
 import android.content.AttributionSource;
 import android.content.Context;
 import android.content.Intent;
@@ -760,6 +761,7 @@
      * @hide
      */
     public static final int UNKNOWN_CLIENT_PID = -1;
+
     /**
      * An unknown state of the client userId gets from the TvInputManager. Client gets this value
      * when query through {@link #getClientUserId(String sessionId)} fails.
@@ -2526,9 +2528,10 @@
      *
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
+    @SystemApi
     @FlaggedApi(Flags.FLAG_KIDS_MODE_TVDB_SHARING)
-    public int getClientUserId(@NonNull String sessionId) {
+    @RequiresPermission(android.Manifest.permission.SINGLE_USER_TIS_ACCESS)
+    public @UserIdInt int getClientUserId(@NonNull String sessionId) {
         return getClientUserIdInternal(sessionId);
     }
 
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 6658918..abfc244 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -16,6 +16,8 @@
 
 package android.media.tv;
 
+import static android.media.tv.flags.Flags.tifExtensionStandardization;
+
 import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
@@ -159,6 +161,11 @@
             new RemoteCallbackList<>();
 
     private TvInputManager mTvInputManager;
+    /**
+     * @hide
+     */
+    protected TvInputServiceExtensionManager mTvInputServiceExtensionManager =
+            new TvInputServiceExtensionManager();
 
     @Override
     public final IBinder onBind(Intent intent) {
@@ -211,12 +218,23 @@
             }
 
             @Override
-            public List<String>  getAvailableExtensionInterfaceNames() {
-                return TvInputService.this.getAvailableExtensionInterfaceNames();
+            public List<String> getAvailableExtensionInterfaceNames() {
+                List<String> extensionNames =
+                        TvInputService.this.getAvailableExtensionInterfaceNames();
+                if (tifExtensionStandardization()) {
+                    extensionNames.addAll(
+                            TvInputServiceExtensionManager.getStandardExtensionInterfaceNames());
+                }
+                return extensionNames;
             }
 
             @Override
             public IBinder getExtensionInterface(String name) {
+                if (tifExtensionStandardization() && name != null) {
+                    if (TvInputServiceExtensionManager.checkIsStandardizedInterfaces(name)) {
+                        return mTvInputServiceExtensionManager.getExtensionIBinder(name);
+                    }
+                }
                 return TvInputService.this.getExtensionInterface(name);
             }
 
diff --git a/media/java/android/media/tv/TvInputServiceExtensionManager.java b/media/java/android/media/tv/TvInputServiceExtensionManager.java
new file mode 100644
index 0000000..c514f6e
--- /dev/null
+++ b/media/java/android/media/tv/TvInputServiceExtensionManager.java
@@ -0,0 +1,827 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.media.tv.flags.Flags;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * This class provides a list of available standardized TvInputService extension interface names
+ * and a container storing IBinder objects that implement these interfaces created by SoC/OEMs.
+ * It also provides an API for SoC/OEMs to register implemented IBinder objects.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_TIF_EXTENSION_STANDARDIZATION)
+public final class TvInputServiceExtensionManager {
+    private static final String TAG = "TvInputServiceExtensionManager";
+    private static final String SCAN_PACKAGE = "android.media.tv.extension.scan.";
+    private static final String OAD_PACKAGE = "android.media.tv.extension.oad.";
+    private static final String CAM_PACKAGE = "android.media.tv.extension.cam.";
+    private static final String RATING_PACKAGE = "android.media.tv.extension.rating.";
+    private static final String TIME_PACKAGE = "android.media.tv.extension.time.";
+    private static final String TELETEXT_PACKAGE = "android.media.tv.extension.teletext.";
+    private static final String SCAN_BSU_PACKAGE = "android.media.tv.extension.scanbsu.";
+    private static final String CLIENT_TOKEN_PACKAGE = "android.media.tv.extension.clienttoken.";
+    private static final String SCREEN_MODE_PACKAGE = "android.media.tv.extension.screenmode.";
+    private static final String SIGNAL_PACKAGE = "android.media.tv.extension.signal.";
+    private static final String SERVICE_DATABASE_PACKAGE = "android.media.tv.extension.servicedb.";
+    private static final String PVR_PACKAGE = "android.media.tv.extension.pvr.";
+    private static final String EVENT_PACKAGE = "android.media.tv.extension.event.";
+    private static final String ANALOG_PACKAGE = "android.media.tv.extension.analog.";
+    private static final String TUNE_PACKAGE = "android.media.tv.extension.tune.";
+
+    /** @hide */
+    @IntDef(prefix = {"REGISTER_"}, value = {
+            REGISTER_SUCCESS,
+            REGISTER_FAIL_NAME_NOT_STANDARDIZED,
+            REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED,
+            REGISTER_FAIL_REMOTE_EXCEPTION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RegisterResult {}
+
+    /**
+     * Registering binder returns success when it abides standardized interface structure
+     */
+    public static final int REGISTER_SUCCESS = 0;
+    /**
+     * Registering binder returns failure when the extension name is not in the standardization
+     * list
+     */
+    public static final int REGISTER_FAIL_NAME_NOT_STANDARDIZED = 1;
+    /**
+     * Registering binder returns failure when the IBinder does not implement standardized interface
+     */
+    public static final int REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED = 2;
+    /**
+     * Registering binder returns failure when remote server is not available
+     */
+    public static final int REGISTER_FAIL_REMOTE_EXCEPTION = 3;
+
+    /** @hide */
+    @StringDef({
+            ISCAN_INTERFACE,
+            ISCAN_SESSION,
+            ISCAN_LISTENER,
+            IHDPLUS_INFO,
+            IOPERATOR_DETECTION,
+            IOPERATOR_DETECTION_LISTENER,
+            IREGION_CHANNEL_LIST,
+            IREGION_CHANNEL_LIST_LISTENER,
+            ITARGET_REGION,
+            ITARGET_REGION_LISTENER,
+            ILCN_CONFLICT,
+            ILCN_CONFLICT_LISTENER,
+            ILCNV2_CHANNEL_LIST,
+            ILCNV2_CHANNEL_LIST_LISTENER,
+            IFAVORITE_NETWORK,
+            IFAVORITE_NETWORK_LISTENER,
+            ITKGS_INFO,
+            ITKGS_INFO_LISTENER,
+            ISCAN_SAT_SEARCH,
+            IOAD_UPDATE_INTERFACE,
+            ICAM_APP_INFO_SERVICE,
+            ICAM_APP_INFO_LISTENER,
+            ICAM_MONITORING_SERVICE,
+            ICAM_INFO_LISTENER,
+            ICI_OPERATOR_INTERFACE,
+            ICI_OPERATOR_LISTENER,
+            ICAM_PROFILE_INTERFACE,
+            ICONTENT_CONTROL_SERVICE,
+            ICAM_DRM_INFO_LISTENER,
+            ICAM_PIN_SERVICE,
+            ICAM_PIN_CAPABILITY_LISTENER,
+            ICAM_PIN_STATUS_LISTENER,
+            ICAM_HOST_CONTROL_SERVICE,
+            ICAM_HOST_CONTROL_ASK_RELEASE_REPLY_CALLBACK,
+            ICAM_HOST_CONTROL_INFO_LISTENER,
+            ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG,
+            ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG_LISTENER,
+            IMMI_INTERFACE,
+            IMMI_SESSION,
+            IMMI_STATUS_CALLBACK,
+            IENTER_MENU_ERROR_CALLBACK,
+            IDOWNLOADABLE_RATING_TABLE_MONITOR,
+            IRATING_INTERFACE,
+            IPMT_RATING_INTERFACE,
+            IPMT_RATING_LISTENER,
+            IVBI_RATING_INTERFACE,
+            IVBI_RATING_LISTENER,
+            IPROGRAM_INFO,
+            IPROGRAM_INFO_LISTENER,
+            IBROADCAST_TIME,
+            IDATA_SERVICE_SIGNAL_INFO,
+            IDATA_SERVICE_SIGNAL_INFO_LISTENER,
+            ITELETEXT_PAGE_SUB_CODE,
+            ISCAN_BACKGROUND_SERVICE_UPDATE,
+            ISCAN_BACKGROUND_SERVICE_UPDATE_LISTENER,
+            ICLIENT_TOKEN,
+            ISCREEN_MODE_SETTINGS,
+            IHDMI_SIGNAL_INTERFACE,
+            IHDMI_SIGNAL_INFO_LISTENER,
+            IAUDIO_SIGNAL_INFO,
+            IANALOG_AUDIO_INFO,
+            IAUDIO_SIGNAL_INFO_LISTENER,
+            IVIDEO_SIGNAL_INFO,
+            IVIDEO_SIGNAL_INFO_LISTENER,
+            ISERVICE_LIST_EDIT,
+            ISERVICE_LIST_EDIT_LISTENER,
+            ISERVICE_LIST,
+            ISERVICE_LIST_TRANSFER_INTERFACE,
+            ISERVICE_LIST_EXPORT_SESSION,
+            ISERVICE_LIST_EXPORT_LISTENER,
+            ISERVICE_LIST_IMPORT_SESSION,
+            ISERVICE_LIST_IMPORT_LISTENER,
+            ISERVICE_LIST_SET_CHANNEL_LIST_SESSION,
+            ISERVICE_LIST_SET_CHANNEL_LIST_LISTENER,
+            ICHANNEL_LIST_TRANSFER,
+            IRECORDED_CONTENTS,
+            IDELETE_RECORDED_CONTENTS_CALLBACK,
+            IGET_INFO_RECORDED_CONTENTS_CALLBACK,
+            IEVENT_MONITOR,
+            IEVENT_MONITOR_LISTENER,
+            IEVENT_DOWNLOAD,
+            IEVENT_DOWNLOAD_LISTENER,
+            IEVENT_DOWNLOAD_SESSION,
+            IANALOG_ATTRIBUTE_INTERFACE,
+            ICHANNEL_TUNED_INTERFACE,
+            ICHANNEL_TUNED_LISTENER,
+            ITUNER_FRONTEND_SIGNAL_INFO_INTERFACE,
+            ITUNER_FRONTEND_SIGNAL_INFO_LISTENER,
+            IMUX_TUNE_SESSION,
+            IMUX_TUNE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StandardizedExtensionName {}
+    /**
+     * Interface responsible for creating scan session and obtain parameters.
+     * @hide
+     */
+    public static final String ISCAN_INTERFACE = SCAN_PACKAGE + "IScanInterface";
+    /**
+     * Interface that handles scan session and get/store related information.
+     * @hide
+     */
+    public static final String ISCAN_SESSION = SCAN_PACKAGE + "IScanSession";
+    /**
+     * Interface that notifies changes related to scan session.
+     * @hide
+     */
+    public static final String ISCAN_LISTENER = SCAN_PACKAGE + "IScanListener";
+    /**
+     * Interface for setting HDPlus information.
+     * @hide
+     */
+    public static final String IHDPLUS_INFO = SCAN_PACKAGE + "IHDPlusInfo";
+    /**
+     * Interface for handling operator detection for scanning.
+     * @hide
+     */
+    public static final String IOPERATOR_DETECTION = SCAN_PACKAGE + "IOperatorDetection";
+    /**
+     * Interface for changes related to operator detection searches.
+     * @hide
+     */
+    public static final String IOPERATOR_DETECTION_LISTENER = SCAN_PACKAGE
+            + "IOperatorDetectionListener";
+    /**
+     * Interface for handling region channel list for scanning.
+     * @hide
+     */
+    public static final String IREGION_CHANNEL_LIST = SCAN_PACKAGE + "IRegionChannelList";
+    /**
+     * Interface for changes related to changes in region channel list search.
+     * @hide
+     */
+    public static final String IREGION_CHANNEL_LIST_LISTENER = SCAN_PACKAGE
+            + "IRegionChannelListListener";
+    /**
+     * Interface for handling target region information.
+     * @hide
+     */
+    public static final String ITARGET_REGION = SCAN_PACKAGE + "ITargetRegion";
+    /**
+     * Interface for changes related to target regions during scanning.
+     * @hide
+     */
+    public static final String ITARGET_REGION_LISTENER = SCAN_PACKAGE + "ITargetRegionListener";
+    /**
+     * Interface for handling LCN conflict groups.
+     * @hide
+     */
+    public static final String ILCN_CONFLICT = SCAN_PACKAGE + "ILcnConflict";
+    /**
+     * Interface for detecting LCN conflicts during scanning.
+     * @hide
+     */
+    public static final String ILCN_CONFLICT_LISTENER = SCAN_PACKAGE + "ILcnConflictListener";
+    /**
+     * Interface for handling LCN V2 channel list information.
+     * @hide
+     */
+    public static final String ILCNV2_CHANNEL_LIST = SCAN_PACKAGE + "ILcnV2ChannelList";
+    /**
+     * Interface for detecting LCN V2 channel list during scanning.
+     * @hide
+     */
+    public static final String ILCNV2_CHANNEL_LIST_LISTENER = SCAN_PACKAGE
+            + "ILcnV2ChannelListListener";
+    /**
+     * Interface for handling favorite network related information.
+     * @hide
+     */
+    public static final String IFAVORITE_NETWORK = SCAN_PACKAGE + "IFavoriteNetwork";
+    /**
+     * Interface for detecting favorite network during scanning.
+     * @hide
+     */
+    public static final String IFAVORITE_NETWORK_LISTENER = SCAN_PACKAGE
+            + "IFavoriteNetworkListener";
+    /**
+     * Interface for handling Turksat channel update system service.
+     * @hide
+     */
+    public static final String ITKGS_INFO = SCAN_PACKAGE + "ITkgsInfo";
+    /**
+     * Interface for changes related to TKGS information.
+     * @hide
+     */
+    public static final String ITKGS_INFO_LISTENER = SCAN_PACKAGE + "ITkgsInfoListener";
+    /**
+     * Interface for satellite search related to low noise block downconverter.
+     * @hide
+     */
+    public static final String ISCAN_SAT_SEARCH = SCAN_PACKAGE + "IScanSatSearch";
+    /**
+     * Interface for Over-the-Air Download.
+     * @hide
+     */
+    public static final String IOAD_UPDATE_INTERFACE = OAD_PACKAGE + "IOadUpdateInterface";
+    /**
+     * Interface for handling conditional access module app related information.
+     * @hide
+     */
+    public static final String ICAM_APP_INFO_SERVICE = CAM_PACKAGE + "ICamAppInfoService";
+    /**
+     * Interface for changes on conditional access module app related information.
+     * @hide
+     */
+    public static final String ICAM_APP_INFO_LISTENER = CAM_PACKAGE + "ICamAppInfoListener";
+    /**
+     * Interface for handling conditional access module related information.
+     * @hide
+     */
+    public static final String ICAM_MONITORING_SERVICE = CAM_PACKAGE + "ICamMonitoringService";
+    /**
+     * Interface for changes on conditional access module related information.
+     * @hide
+     */
+    public static final String ICAM_INFO_LISTENER = CAM_PACKAGE + "ICamInfoListener";
+    /**
+     * Interface for handling control of CI+ operations.
+     * @hide
+     */
+    public static final String ICI_OPERATOR_INTERFACE = CAM_PACKAGE + "ICiOperatorInterface";
+    /**
+     * Interfaces for changes on CI+ operations.
+     * @hide
+     */
+    public static final String ICI_OPERATOR_LISTENER = CAM_PACKAGE + "ICiOperatorListener";
+    /**
+     * Interface for handling conditional access module profile related information.
+     * @hide
+     */
+    public static final String ICAM_PROFILE_INTERFACE = CAM_PACKAGE + "ICamProfileInterface";
+    /**
+     * Interface for handling conditional access module DRM related information.
+     * @hide
+     */
+    public static final String ICONTENT_CONTROL_SERVICE = CAM_PACKAGE + "IContentControlService";
+    /**
+     * Interface for changes on DRM.
+     * @hide
+     */
+    public static final String ICAM_DRM_INFO_LISTENER = CAM_PACKAGE + "ICamDrmInfoListener";
+    /**
+     * Interface for handling conditional access module pin related information.
+     * @hide
+     */
+    public static final String ICAM_PIN_SERVICE = CAM_PACKAGE + "ICamPinService";
+    /**
+     * Interface for changes on conditional access module pin capability.
+     * @hide
+     */
+    public static final String ICAM_PIN_CAPABILITY_LISTENER = CAM_PACKAGE
+            + "ICamPinCapabilityListener";
+    /**
+     * Interface for changes on conditional access module pin status.
+     * @hide
+     */
+    public static final String ICAM_PIN_STATUS_LISTENER = CAM_PACKAGE + "ICamPinStatusListener";
+    /**
+     * Interface for handling conditional access module host control service.
+     * @hide
+     */
+    public static final String ICAM_HOST_CONTROL_SERVICE = CAM_PACKAGE + "ICamHostControlService";
+    /**
+     * Interface for handling conditional access module ask release reply.
+     * @hide
+     */
+    public static final String ICAM_HOST_CONTROL_ASK_RELEASE_REPLY_CALLBACK = CAM_PACKAGE
+            + "ICamHostControlAskReleaseReplyCallback";
+    /**
+     * Interface for changes on conditional access module host control service.
+     * @hide
+     */
+    public static final String ICAM_HOST_CONTROL_INFO_LISTENER = CAM_PACKAGE
+            + "ICamHostControlInfoListener";
+    /**
+     * Interface for handling conditional access module host control service tune_quietly_flag.
+     * @hide
+     */
+    public static final String ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG = CAM_PACKAGE
+            + "ICamHostControlTuneQuietlyFlag";
+    /**
+     * Interface for changes on conditional access module host control service tune_quietly_flag.
+     * @hide
+     */
+    public static final String ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG_LISTENER = CAM_PACKAGE
+            + "ICamHostControlTuneQuietlyFlagListener";
+    /**
+     * Interface for handling conditional access module multi media interface.
+     * @hide
+     */
+    public static final String IMMI_INTERFACE = CAM_PACKAGE + "IMmiInterface";
+    /**
+     * Interface for controlling conditional access module multi media session.
+     * @hide
+     */
+    public static final String IMMI_SESSION = CAM_PACKAGE + "IMmiSession";
+    /**
+     * Interface for changes on conditional access module multi media session status.
+     * @hide
+     */
+    public static final String IMMI_STATUS_CALLBACK = CAM_PACKAGE + "IMmiStatusCallback";
+    /**
+     * Interface for changes on conditional access app info related to entering menu.
+     * @hide
+     */
+    public static final String IENTER_MENU_ERROR_CALLBACK = CAM_PACKAGE + "IEnterMenuErrorCallback";
+    /**
+     * Interface for handling RRT downloadable rating data.
+     * @hide
+     */
+    public static final String IDOWNLOADABLE_RATING_TABLE_MONITOR = RATING_PACKAGE
+            + "IDownloadableRatingTableMonitor";
+    /**
+     * Interface for handling RRT rating related information.
+     * @hide
+     */
+    public static final String IRATING_INTERFACE = RATING_PACKAGE + "IRatingInterface";
+    /**
+     * Interface for handling PMT rating related information.
+     * @hide
+     */
+    public static final String IPMT_RATING_INTERFACE = RATING_PACKAGE + "IPmtRatingInterface";
+    /**
+     * Interface for changes on PMT rating related information.
+     * @hide
+     */
+    public static final String IPMT_RATING_LISTENER = RATING_PACKAGE + "IPmtRatingListener";
+    /**
+     * Interface for handling IVBI rating related information.
+     * @hide
+     */
+    public static final String IVBI_RATING_INTERFACE = RATING_PACKAGE + "IVbiRatingInterface";
+    /**
+     * Interface for changes on IVBI rating related information.
+     * @hide
+     */
+    public static final String IVBI_RATING_LISTENER = RATING_PACKAGE + "IVbiRatingListener";
+    /**
+     * Interface for handling program rating related information.
+     * @hide
+     */
+    public static final String IPROGRAM_INFO = RATING_PACKAGE + "IProgramInfo";
+    /**
+     * Interface for changes on program rating related information.
+     * @hide
+     */
+    public static final String IPROGRAM_INFO_LISTENER = RATING_PACKAGE + "IProgramInfoListener";
+    /**
+     * Interface for getting broadcast time related information.
+     * @hide
+     */
+    public static final String IBROADCAST_TIME = TIME_PACKAGE + "BroadcastTime";
+    /**
+     * Interface for handling data service signal information on teletext.
+     * @hide
+     */
+    public static final String IDATA_SERVICE_SIGNAL_INFO = TELETEXT_PACKAGE
+            + "IDataServiceSignalInfo";
+    /**
+     * Interface for changes on data service signal information on teletext.
+     * @hide
+     */
+    public static final String IDATA_SERVICE_SIGNAL_INFO_LISTENER = TELETEXT_PACKAGE
+            + "IDataServiceSignalInfoListener";
+    /**
+     * Interface for handling teletext page information.
+     * @hide
+     */
+    public static final String ITELETEXT_PAGE_SUB_CODE = TELETEXT_PACKAGE + "ITeletextPageSubCode";
+    /**
+     * Interface for handling scan background service update.
+     * @hide
+     */
+    public static final String ISCAN_BACKGROUND_SERVICE_UPDATE = SCAN_BSU_PACKAGE
+            + "IScanBackgroundServiceUpdate";
+    /**
+     * Interface for changes on background service update
+     * @hide
+     */
+    public static final String ISCAN_BACKGROUND_SERVICE_UPDATE_LISTENER = SCAN_BSU_PACKAGE
+            + "IScanBackgroundServiceUpdateListener";
+    /**
+     * Interface for generating client token.
+     * @hide
+     */
+    public static final String ICLIENT_TOKEN = CLIENT_TOKEN_PACKAGE + "IClientToken";
+    /**
+     * Interfaces for handling screen mode information.
+     * @hide
+     */
+    public static final String ISCREEN_MODE_SETTINGS = SCREEN_MODE_PACKAGE + "IScreenModeSettings";
+    /**
+     * Interfaces for handling HDMI signal information update.
+     * @hide
+     */
+    public static final String IHDMI_SIGNAL_INTERFACE = SIGNAL_PACKAGE + "IHdmiSignalInterface";
+    /**
+     * Interfaces for changes on HDMI signal information update.
+     * @hide
+     */
+    public static final String IHDMI_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE
+            + "IHdmiSignalInfoListener";
+    /**
+     * Interfaces for handling audio signal information update.
+     * @hide
+     */
+    public static final String IAUDIO_SIGNAL_INFO = SIGNAL_PACKAGE + "IAudioSignalInfo";
+    /**
+     * Interfaces for handling analog audio signal information update.
+     * @hide
+     */
+    public static final String IANALOG_AUDIO_INFO = SIGNAL_PACKAGE + "IAnalogAudioInfo";
+    /**
+     * Interfaces for change on audio signal information update.
+     * @hide
+     */
+    public static final String IAUDIO_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE
+            + "IAudioSignalInfoListener";
+    /**
+     * Interfaces for handling video signal information update.
+     * @hide
+     */
+    public static final String IVIDEO_SIGNAL_INFO = SIGNAL_PACKAGE + "IVideoSignalInfo";
+    /**
+     * Interfaces for changes on video signal information update.
+     * @hide
+     */
+    public static final String IVIDEO_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE
+            + "IVideoSignalInfoListener";
+    /**
+     * Interfaces for handling service database updates.
+     * @hide
+     */
+    public static final String ISERVICE_LIST_EDIT = SERVICE_DATABASE_PACKAGE + "IServiceListEdit";
+    /**
+     * Interfaces for changes on service database updates.
+     * @hide
+     */
+    public static final String ISERVICE_LIST_EDIT_LISTENER = SERVICE_DATABASE_PACKAGE
+            + "IServiceListEditListener";
+    /**
+     * Interfaces for getting service database related information.
+     * @hide
+     */
+    public static final String ISERVICE_LIST = SERVICE_DATABASE_PACKAGE + "IServiceList";
+    /**
+     * Interfaces for transferring service database related information.
+     * @hide
+     */
+    public static final String ISERVICE_LIST_TRANSFER_INTERFACE = SERVICE_DATABASE_PACKAGE
+            + "IServiceListTransferInterface";
+    /**
+     * Interfaces for exporting service database session.
+     * @hide
+     */
+    public static final String ISERVICE_LIST_EXPORT_SESSION = SERVICE_DATABASE_PACKAGE
+            + "IServiceListExportSession";
+    /**
+     * Interfaces for changes on exporting service database session.
+     * @hide
+     */
+    public static final String ISERVICE_LIST_EXPORT_LISTENER = SERVICE_DATABASE_PACKAGE
+            + "IServiceListExportListener";
+    /**
+     * Interfaces for importing service database session.
+     * @hide
+     */
+    public static final String ISERVICE_LIST_IMPORT_SESSION = SERVICE_DATABASE_PACKAGE
+            + "IServiceListImportSession";
+    /**
+     * Interfaces for changes on importing service database session.
+     * @hide
+     */
+    public static final String ISERVICE_LIST_IMPORT_LISTENER = SERVICE_DATABASE_PACKAGE
+            + "IServiceListImportListener";
+    /**
+     * Interfaces for setting channel list resources.
+     * @hide
+     */
+    public static final String ISERVICE_LIST_SET_CHANNEL_LIST_SESSION = SERVICE_DATABASE_PACKAGE
+            + "IServiceListSetChannelListSession";
+    /**
+     * Interfaces for changes on setting channel list resources.
+     * @hide
+     */
+    public static final String ISERVICE_LIST_SET_CHANNEL_LIST_LISTENER = SERVICE_DATABASE_PACKAGE
+            + "IServiceListSetChannelListListener";
+    /**
+     * Interfaces for transferring channel list resources.
+     * @hide
+     */
+    public static final String ICHANNEL_LIST_TRANSFER = SERVICE_DATABASE_PACKAGE
+            + "IChannelListTransfer";
+    /**
+     * Interfaces for record contents updates.
+     * @hide
+     */
+    public static final String IRECORDED_CONTENTS = PVR_PACKAGE + "IRecordedContents";
+    /**
+     * Interfaces for changes on deleting record contents.
+     * @hide
+     */
+    public static final String IDELETE_RECORDED_CONTENTS_CALLBACK = PVR_PACKAGE
+            + "IDeleteRecordedContentsCallback";
+    /**
+     * Interfaces for changes on getting record contents.
+     * @hide
+     */
+    public static final String IGET_INFO_RECORDED_CONTENTS_CALLBACK = PVR_PACKAGE
+            + "IGetInfoRecordedContentsCallback";
+    /**
+     * Interfaces for monitoring present event information.
+     * @hide
+     */
+    public static final String IEVENT_MONITOR = EVENT_PACKAGE + "IEventMonitor";
+    /**
+     * Interfaces for changes on present event information.
+     * @hide
+     */
+    public static final String IEVENT_MONITOR_LISTENER = EVENT_PACKAGE + "IEventMonitorListener";
+    /**
+     * Interfaces for handling download event information.
+     * @hide
+     */
+    public static final String IEVENT_DOWNLOAD = EVENT_PACKAGE + "IEventDownload";
+    /**
+     * Interfaces for changes on downloading event information.
+     * @hide
+     */
+    public static final String IEVENT_DOWNLOAD_LISTENER = EVENT_PACKAGE + "IEventDownloadListener";
+    /**
+     * Interfaces for handling download event information for DVB and DTMB.
+     * @hide
+     */
+    public static final String IEVENT_DOWNLOAD_SESSION = EVENT_PACKAGE + "IEventDownloadSession";
+    /**
+     * Interfaces for handling analog color system.
+     * @hide
+     */
+    public static final String IANALOG_ATTRIBUTE_INTERFACE = ANALOG_PACKAGE
+            + "IAnalogAttributeInterface";
+    /**
+     * Interfaces for monitoring channel tuned information.
+     * @hide
+     */
+    public static final String ICHANNEL_TUNED_INTERFACE = TUNE_PACKAGE + "IChannelTunedInterface";
+    /**
+     * Interfaces for changes on channel tuned information.
+     * @hide
+     */
+    public static final String ICHANNEL_TUNED_LISTENER = TUNE_PACKAGE + "IChannelTunedListener";
+    /**
+     * Interfaces for handling tuner frontend signal info.
+     * @hide
+     */
+    public static final String ITUNER_FRONTEND_SIGNAL_INFO_INTERFACE = SIGNAL_PACKAGE
+            + "ITunerFrontendSignalInfoInterface";
+    /**
+     * Interfaces for changes on tuner frontend signal info.
+     * @hide
+     */
+    public static final String ITUNER_FRONTEND_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE
+            + "ITunerFrontendSignalInfoListener";
+    /**
+     * Interfaces for handling mux tune operations.
+     * @hide
+     */
+    public static final String IMUX_TUNE_SESSION = TUNE_PACKAGE + "IMuxTuneSession";
+    /**
+     * Interfaces for initing mux tune session.
+     * @hide
+     */
+    public static final String IMUX_TUNE = TUNE_PACKAGE + "IMuxTune";
+
+    // Set of standardized AIDL interface canonical names
+    private static final Set<String> sTisExtensions = new HashSet<>(Set.of(
+            ISCAN_INTERFACE,
+            ISCAN_SESSION,
+            ISCAN_LISTENER,
+            IHDPLUS_INFO,
+            IOPERATOR_DETECTION,
+            IOPERATOR_DETECTION_LISTENER,
+            IREGION_CHANNEL_LIST,
+            IREGION_CHANNEL_LIST_LISTENER,
+            ITARGET_REGION,
+            ITARGET_REGION_LISTENER,
+            ILCN_CONFLICT,
+            ILCN_CONFLICT_LISTENER,
+            ILCNV2_CHANNEL_LIST,
+            ILCNV2_CHANNEL_LIST_LISTENER,
+            IFAVORITE_NETWORK,
+            IFAVORITE_NETWORK_LISTENER,
+            ITKGS_INFO,
+            ITKGS_INFO_LISTENER,
+            ISCAN_SAT_SEARCH,
+            IOAD_UPDATE_INTERFACE,
+            ICAM_APP_INFO_SERVICE,
+            ICAM_APP_INFO_LISTENER,
+            ICAM_MONITORING_SERVICE,
+            ICAM_INFO_LISTENER,
+            ICI_OPERATOR_INTERFACE,
+            ICI_OPERATOR_LISTENER,
+            ICAM_PROFILE_INTERFACE,
+            ICONTENT_CONTROL_SERVICE,
+            ICAM_DRM_INFO_LISTENER,
+            ICAM_PIN_SERVICE,
+            ICAM_PIN_CAPABILITY_LISTENER,
+            ICAM_PIN_STATUS_LISTENER,
+            ICAM_HOST_CONTROL_SERVICE,
+            ICAM_HOST_CONTROL_ASK_RELEASE_REPLY_CALLBACK,
+            ICAM_HOST_CONTROL_INFO_LISTENER,
+            ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG,
+            ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG_LISTENER,
+            IMMI_INTERFACE,
+            IMMI_SESSION,
+            IMMI_STATUS_CALLBACK,
+            IENTER_MENU_ERROR_CALLBACK,
+            IDOWNLOADABLE_RATING_TABLE_MONITOR,
+            IRATING_INTERFACE,
+            IPMT_RATING_INTERFACE,
+            IPMT_RATING_LISTENER,
+            IVBI_RATING_INTERFACE,
+            IVBI_RATING_LISTENER,
+            IPROGRAM_INFO,
+            IPROGRAM_INFO_LISTENER,
+            IBROADCAST_TIME,
+            IDATA_SERVICE_SIGNAL_INFO,
+            IDATA_SERVICE_SIGNAL_INFO_LISTENER,
+            ITELETEXT_PAGE_SUB_CODE,
+            ISCAN_BACKGROUND_SERVICE_UPDATE,
+            ISCAN_BACKGROUND_SERVICE_UPDATE_LISTENER,
+            ICLIENT_TOKEN,
+            ISCREEN_MODE_SETTINGS,
+            IHDMI_SIGNAL_INTERFACE,
+            IHDMI_SIGNAL_INFO_LISTENER,
+            IAUDIO_SIGNAL_INFO,
+            IANALOG_AUDIO_INFO,
+            IAUDIO_SIGNAL_INFO_LISTENER,
+            IVIDEO_SIGNAL_INFO,
+            IVIDEO_SIGNAL_INFO_LISTENER,
+            ISERVICE_LIST_EDIT,
+            ISERVICE_LIST_EDIT_LISTENER,
+            ISERVICE_LIST,
+            ISERVICE_LIST_TRANSFER_INTERFACE,
+            ISERVICE_LIST_EXPORT_SESSION,
+            ISERVICE_LIST_EXPORT_LISTENER,
+            ISERVICE_LIST_IMPORT_SESSION,
+            ISERVICE_LIST_IMPORT_LISTENER,
+            ISERVICE_LIST_SET_CHANNEL_LIST_SESSION,
+            ISERVICE_LIST_SET_CHANNEL_LIST_LISTENER,
+            ICHANNEL_LIST_TRANSFER,
+            IRECORDED_CONTENTS,
+            IDELETE_RECORDED_CONTENTS_CALLBACK,
+            IGET_INFO_RECORDED_CONTENTS_CALLBACK,
+            IEVENT_MONITOR,
+            IEVENT_MONITOR_LISTENER,
+            IEVENT_DOWNLOAD,
+            IEVENT_DOWNLOAD_LISTENER,
+            IEVENT_DOWNLOAD_SESSION,
+            IANALOG_ATTRIBUTE_INTERFACE,
+            ICHANNEL_TUNED_INTERFACE,
+            ICHANNEL_TUNED_LISTENER,
+            ITUNER_FRONTEND_SIGNAL_INFO_INTERFACE,
+            ITUNER_FRONTEND_SIGNAL_INFO_LISTENER,
+            IMUX_TUNE_SESSION,
+            IMUX_TUNE
+    ));
+
+    // Store the mapping between interface names and IBinder
+    private Map<String, IBinder> mExtensionInterfaceIBinderMapping = new HashMap<>();
+
+    TvInputServiceExtensionManager() {
+    }
+
+    /**
+     * Function to return available extension interface names
+     *
+     * @hide
+     */
+    public static @NonNull List<String> getStandardExtensionInterfaceNames() {
+        return new ArrayList<>(sTisExtensions);
+    }
+
+    /**
+     * Function to check if the extension is in the standardization list
+     */
+    static boolean checkIsStandardizedInterfaces(@NonNull String extensionName) {
+        return sTisExtensions.contains(extensionName);
+    }
+
+    /**
+     * This function should be used by OEM to register IBinder objects that implement
+     * standardized AIDL interfaces.
+     *
+     * @param extensionName Extension Interface Name
+     * @param binder        IBinder object to be registered
+     * @return REGISTER_SUCCESS on success of registering IBinder object
+     *         REGISTER_FAIL_NAME_NOT_STANDARDIZED on failure due to registering extension with
+     *              non-standardized name
+     *         REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED on failure due to IBinder not
+     *              implementing standardized AIDL interface
+     *         REGISTER_FAIL_REMOTE_EXCEPTION on failure due to remote exception
+     *
+     * @hide
+     */
+    @RegisterResult
+    public int registerExtensionIBinder(@StandardizedExtensionName @NonNull String extensionName,
+            @NonNull IBinder binder) {
+        if (!checkIsStandardizedInterfaces(extensionName)) {
+            return REGISTER_FAIL_NAME_NOT_STANDARDIZED;
+        }
+        try {
+            if (binder.getInterfaceDescriptor().equals(extensionName)) {
+                mExtensionInterfaceIBinderMapping.put(extensionName, binder);
+                return REGISTER_SUCCESS;
+            } else {
+                return REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Fetching IBinder object failure due to " + e);
+            return REGISTER_FAIL_REMOTE_EXCEPTION;
+        }
+    }
+
+    /**
+     * Function to get corresponding IBinder object
+     */
+    @Nullable IBinder getExtensionIBinder(@NonNull String extensionName) {
+        return mExtensionInterfaceIBinderMapping.get(extensionName);
+    }
+
+}
diff --git a/media/java/android/mtp/OWNERS b/media/java/android/mtp/OWNERS
index 6b5336e..77ed08b 100644
--- a/media/java/android/mtp/OWNERS
+++ b/media/java/android/mtp/OWNERS
@@ -1,10 +1,9 @@
 set noparent
 
-aprasath@google.com
 anothermark@google.com
-kumarashishg@google.com
-sarup@google.com
+febinthattil@google.com
+aprasath@google.com
 jsharkey@android.com
 jameswei@google.com
 rmojumder@google.com
-
+kumarashishg@google.com
diff --git a/media/tests/MtpTests/OWNERS b/media/tests/MtpTests/OWNERS
index 6b5336e..bdb6cdb 100644
--- a/media/tests/MtpTests/OWNERS
+++ b/media/tests/MtpTests/OWNERS
@@ -1,10 +1,9 @@
 set noparent
 
-aprasath@google.com
 anothermark@google.com
-kumarashishg@google.com
-sarup@google.com
+febinthattil@google.com
+aprasath@google.com
 jsharkey@android.com
 jameswei@google.com
 rmojumder@google.com
-
+kumarashishg@google.com
\ No newline at end of file
diff --git a/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/nfc/api/current.txt b/nfc/api/current.txt
index 96b7c13..00812042 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -207,6 +207,7 @@
     method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
     method @FlaggedApi("android.nfc.enable_card_emulation_euicc") public boolean isEuiccSupported();
     method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
+    method @FlaggedApi("android.nfc.nfc_event_listener") public void registerNfcEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.cardemulation.CardEmulation.NfcEventListener);
     method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean);
     method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopPatternFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean);
     method public boolean removeAidsForService(android.content.ComponentName, String);
@@ -216,6 +217,7 @@
     method public boolean setPreferredService(android.app.Activity, android.content.ComponentName);
     method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setShouldDefaultToObserveModeForService(@NonNull android.content.ComponentName, boolean);
     method public boolean supportsAidPrefixRegistration();
+    method @FlaggedApi("android.nfc.nfc_event_listener") public void unregisterNfcEventListener(@NonNull android.nfc.cardemulation.CardEmulation.NfcEventListener);
     method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName);
     method public boolean unsetPreferredService(android.app.Activity);
     field @Deprecated public static final String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
@@ -233,13 +235,16 @@
     field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0
   }
 
+  @FlaggedApi("android.nfc.nfc_event_listener") public static interface CardEmulation.NfcEventListener {
+    method @FlaggedApi("android.nfc.nfc_event_listener") public default void onObserveModeStateChanged(boolean);
+    method @FlaggedApi("android.nfc.nfc_event_listener") public default void onPreferredServiceChanged(boolean);
+  }
+
   public abstract class HostApduService extends android.app.Service {
     ctor public HostApduService();
     method public final void notifyUnhandled();
     method public final android.os.IBinder onBind(android.content.Intent);
     method public abstract void onDeactivated(int);
-    method @FlaggedApi("android.nfc.nfc_event_listener") public void onObserveModeStateChanged(boolean);
-    method @FlaggedApi("android.nfc.nfc_event_listener") public void onPreferredServiceChanged(boolean);
     method public abstract byte[] processCommandApdu(byte[], android.os.Bundle);
     method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.nfc.cardemulation.PollingFrame>);
     method public final void sendResponseApdu(byte[]);
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 24e14e6..6aa8a2b 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -91,6 +91,7 @@
     method public void onDisable(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public void onDisableFinished(int);
     method public void onDisableStarted();
+    method public void onEeListenActivated(boolean);
     method public void onEnable(@NonNull java.util.function.Consumer<java.lang.Boolean>);
     method public void onEnableFinished(int);
     method public void onEnableStarted();
@@ -105,7 +106,7 @@
     method public void onRfFieldActivated(boolean);
     method public void onRoutingChanged();
     method public void onStateUpdated(int);
-    method public void onTagConnected(boolean, @NonNull android.nfc.Tag);
+    method public void onTagConnected(boolean);
     method public void onTagDispatch(@NonNull java.util.function.Consumer<java.lang.Boolean>);
   }
 
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/nfc/java/android/nfc/ComponentNameAndUser.aidl
similarity index 89%
copy from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
copy to nfc/java/android/nfc/ComponentNameAndUser.aidl
index e21bf8f..e677998 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/nfc/java/android/nfc/ComponentNameAndUser.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.shared;
+package android.nfc;
 
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
+parcelable ComponentNameAndUser;
\ No newline at end of file
diff --git a/nfc/java/android/nfc/ComponentNameAndUser.java b/nfc/java/android/nfc/ComponentNameAndUser.java
new file mode 100644
index 0000000..59e6c62
--- /dev/null
+++ b/nfc/java/android/nfc/ComponentNameAndUser.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public class ComponentNameAndUser implements Parcelable {
+    @UserIdInt private final int mUserId;
+    private ComponentName mComponentName;
+
+    public ComponentNameAndUser(@UserIdInt int userId, ComponentName componentName) {
+        mUserId = userId;
+        mComponentName = componentName;
+    }
+
+    /**
+     * @hide
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * @hide
+     */
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mUserId);
+        out.writeParcelable(mComponentName, flags);
+    }
+
+    public static final Parcelable.Creator<ComponentNameAndUser> CREATOR =
+            new Parcelable.Creator<ComponentNameAndUser>() {
+                public ComponentNameAndUser createFromParcel(Parcel in) {
+                    return new ComponentNameAndUser(in);
+                }
+
+                public ComponentNameAndUser[] newArray(int size) {
+                    return new ComponentNameAndUser[size];
+                }
+            };
+
+    private ComponentNameAndUser(Parcel in) {
+        mUserId = in.readInt();
+        mComponentName = in.readParcelable(null, ComponentName.class);
+    }
+
+    @UserIdInt
+    public int getUserId() {
+        return mUserId;
+    }
+
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    @Override
+    public String toString() {
+        return mComponentName + " for user id: " + mUserId;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj != null && obj instanceof ComponentNameAndUser) {
+            ComponentNameAndUser other = (ComponentNameAndUser) obj;
+            return other.getUserId() == mUserId
+                    && Objects.equals(other.getComponentName(), mComponentName);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        if (mComponentName == null) {
+            return mUserId;
+        }
+        return mComponentName.hashCode() + mUserId;
+    }
+}
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
index 8535e4a..5e2e92d 100644
--- a/nfc/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -17,6 +17,8 @@
 package android.nfc;
 
 import android.content.ComponentName;
+import android.nfc.INfcEventListener;
+
 import android.nfc.cardemulation.AidGroup;
 import android.nfc.cardemulation.ApduServiceInfo;
 import android.os.RemoteCallback;
@@ -55,4 +57,7 @@
     boolean isAutoChangeEnabled();
     List<String> getRoutingStatus();
     void overwriteRoutingTable(int userHandle, String emptyAid, String protocol, String tech, String sc);
+
+    void registerNfcEventListener(in INfcEventListener listener);
+    void unregisterNfcEventListener(in INfcEventListener listener);
 }
diff --git a/nfc/java/android/nfc/INfcEventListener.aidl b/nfc/java/android/nfc/INfcEventListener.aidl
new file mode 100644
index 0000000..5162c26
--- /dev/null
+++ b/nfc/java/android/nfc/INfcEventListener.aidl
@@ -0,0 +1,11 @@
+package android.nfc;
+
+import android.nfc.ComponentNameAndUser;
+
+/**
+ * @hide
+ */
+oneway interface INfcEventListener {
+    void onPreferredServiceChanged(in ComponentNameAndUser ComponentNameAndUser);
+    void onObserveModeStateChanged(boolean isEnabled);
+}
\ No newline at end of file
diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
index 48c7ee6..7f1fd15 100644
--- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
+++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
@@ -27,7 +27,7 @@
  * @hide
  */
 interface INfcOemExtensionCallback {
-   void onTagConnected(boolean connected, in Tag tag);
+   void onTagConnected(boolean connected);
    void onStateUpdated(int state);
    void onApplyRouting(in ResultReceiver isSkipped);
    void onNdefRead(in ResultReceiver isSkipped);
@@ -46,6 +46,7 @@
    void onCardEmulationActivated(boolean isActivated);
    void onRfFieldActivated(boolean isActivated);
    void onRfDiscoveryStarted(boolean isDiscoveryStarted);
+   void onEeListenActivated(boolean isActivated);
    void onGetOemAppSearchIntent(in List<String> firstPackage, in ResultReceiver intentConsumer);
    void onNdefMessage(in Tag tag, in NdefMessage message, in ResultReceiver hasOemExecutableContent);
    void onLaunchHceAppChooserActivity(in String selectedAid, in List<ApduServiceInfo> services, in ComponentName failedComponent, in String category);
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index 520ba89..1d2085c 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -80,6 +80,14 @@
     private boolean mCardEmulationActivated = false;
     private boolean mRfFieldActivated = false;
     private boolean mRfDiscoveryStarted = false;
+    private boolean mEeListenActivated = false;
+
+    /**
+     * Broadcast Action: Sent on NFC stack initialization when NFC OEM extensions are enabled.
+     * <p> OEM extension modules should use this intent to start their extension service </p>
+     * @hide
+     */
+    public static final String ACTION_OEM_EXTENSION_INIT = "android.nfc.action.OEM_EXTENSION_INIT";
 
     /**
      * Mode Type for {@link #setControllerAlwaysOnMode(int)}.
@@ -188,9 +196,8 @@
          * ex - if tag is connected  notify cover and Nfctest app if app is in testing mode
          *
          * @param connected status of the tag true if tag is connected otherwise false
-         * @param tag Tag details
          */
-        void onTagConnected(boolean connected, @NonNull Tag tag);
+        void onTagConnected(boolean connected);
 
         /**
          * Update the Nfc Adapter State
@@ -320,6 +327,13 @@
         void onRfDiscoveryStarted(boolean isDiscoveryStarted);
 
         /**
+        * Notifies the NFCEE (NFC Execution Environment) Listen has been activated.
+        *
+        * @param isActivated true, if EE Listen is ON, else EE Listen is OFF.
+        */
+        void onEeListenActivated(boolean isActivated);
+
+        /**
          * Gets the intent to find the OEM package in the OEM App market. If the consumer returns
          * {@code null} or a timeout occurs, the intent from the first available package will be
          * used instead.
@@ -430,6 +444,7 @@
                 callback.onCardEmulationActivated(mCardEmulationActivated);
                 callback.onRfFieldActivated(mRfFieldActivated);
                 callback.onRfDiscoveryStarted(mRfDiscoveryStarted);
+                callback.onEeListenActivated(mEeListenActivated);
             });
         }
     }
@@ -677,9 +692,9 @@
     private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub {
 
         @Override
-        public void onTagConnected(boolean connected, Tag tag) throws RemoteException {
+        public void onTagConnected(boolean connected) throws RemoteException {
             mCallbackMap.forEach((cb, ex) ->
-                    handleVoid2ArgCallback(connected, tag, cb::onTagConnected, ex));
+                    handleVoidCallback(connected, cb::onTagConnected, ex));
         }
 
         @Override
@@ -704,6 +719,13 @@
         }
 
         @Override
+        public void onEeListenActivated(boolean isActivated) throws RemoteException {
+            mEeListenActivated = isActivated;
+            mCallbackMap.forEach((cb, ex) ->
+                    handleVoidCallback(isActivated, cb::onEeListenActivated, ex));
+        }
+
+        @Override
         public void onStateUpdated(int state) throws RemoteException {
             mCallbackMap.forEach((cb, ex) ->
                     handleVoidCallback(state, cb::onStateUpdated, ex));
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
index d75318f..9ff83fe 100644
--- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -52,10 +52,12 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.TreeMap;
 import java.util.regex.Pattern;
 
 /**
@@ -204,7 +206,8 @@
         this(info, onHost, description, staticAidGroups, dynamicAidGroups,
                 requiresUnlock, requiresScreenOn, bannerResource, uid,
                 settingsActivityName, offHost, staticOffHost, isEnabled,
-                new HashMap<String, Boolean>(), new HashMap<Pattern, Boolean>());
+                new HashMap<String, Boolean>(), new TreeMap<>(
+                        Comparator.comparing(Pattern::toString)));
     }
 
     /**
@@ -340,7 +343,8 @@
             mStaticAidGroups = new HashMap<String, AidGroup>();
             mDynamicAidGroups = new HashMap<String, AidGroup>();
             mAutoTransact = new HashMap<String, Boolean>();
-            mAutoTransactPatterns = new HashMap<Pattern, Boolean>();
+            mAutoTransactPatterns = new TreeMap<Pattern, Boolean>(
+                    Comparator.comparing(Pattern::toString));
             mOnHost = onHost;
 
             final int depth = parser.getDepth();
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index d8f04c5..eb28c3b 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -17,6 +17,7 @@
 package android.nfc.cardemulation;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -33,15 +34,18 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.nfc.ComponentNameAndUser;
 import android.nfc.Constants;
 import android.nfc.Flags;
 import android.nfc.INfcCardEmulation;
+import android.nfc.INfcEventListener;
 import android.nfc.NfcAdapter;
 import android.os.Build;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
+import android.util.ArrayMap;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
@@ -50,6 +54,8 @@
 import java.util.HexFormat;
 import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
+import java.util.concurrent.Executor;
 import java.util.regex.Pattern;
 
 /**
@@ -1076,4 +1082,107 @@
             default -> throw new IllegalStateException("Unexpected value: " + route);
         };
     }
+
+    /** Listener for preferred service state changes. */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+    public interface NfcEventListener {
+        /**
+         * This method is called when this package gains or loses preferred Nfc service status,
+         * either the Default Wallet Role holder (see {@link
+         * android.app.role.RoleManager#ROLE_WALLET}) or the preferred service of the foreground
+         * activity set with {@link #setPreferredService(Activity, ComponentName)}
+         *
+         * @param isPreferred true is this service has become the preferred Nfc service, false if it
+         *     is no longer the preferred service
+         */
+        @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+        default void onPreferredServiceChanged(boolean isPreferred) {}
+
+        /**
+         * This method is called when observe mode has been enabled or disabled.
+         *
+         * @param isEnabled true if observe mode has been enabled, false if it has been disabled
+         */
+        @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+        default void onObserveModeStateChanged(boolean isEnabled) {}
+    }
+
+    private final ArrayMap<NfcEventListener, Executor> mNfcEventListeners = new ArrayMap<>();
+
+    final INfcEventListener mINfcEventListener =
+            new INfcEventListener.Stub() {
+                public void onPreferredServiceChanged(ComponentNameAndUser componentNameAndUser) {
+                    if (!android.nfc.Flags.nfcEventListener()) {
+                        return;
+                    }
+                    boolean isPreferred =
+                            componentNameAndUser != null
+                                    && componentNameAndUser.getUserId()
+                                            == mContext.getUser().getIdentifier()
+                                    && componentNameAndUser.getComponentName() != null
+                                    && Objects.equals(
+                                            mContext.getPackageName(),
+                                            componentNameAndUser.getComponentName()
+                                                    .getPackageName());
+                    synchronized (mNfcEventListeners) {
+                        mNfcEventListeners.forEach(
+                                (listener, executor) -> {
+                                    executor.execute(
+                                            () -> listener.onPreferredServiceChanged(isPreferred));
+                                });
+                    }
+                }
+
+                public void onObserveModeStateChanged(boolean isEnabled) {
+                    if (!android.nfc.Flags.nfcEventListener()) {
+                        return;
+                    }
+                    synchronized (mNfcEventListeners) {
+                        mNfcEventListeners.forEach(
+                                (listener, executor) -> {
+                                    executor.execute(
+                                            () -> listener.onObserveModeStateChanged(isEnabled));
+                                });
+                    }
+                }
+            };
+
+    /**
+     * Register a listener for NFC Events.
+     *
+     * @param executor The Executor to run the call back with
+     * @param listener The listener to register
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+    public void registerNfcEventListener(
+            @NonNull @CallbackExecutor Executor executor, @NonNull NfcEventListener listener) {
+        if (!android.nfc.Flags.nfcEventListener()) {
+            return;
+        }
+        synchronized (mNfcEventListeners) {
+            mNfcEventListeners.put(listener, executor);
+            if (mNfcEventListeners.size() == 1) {
+                callService(() -> sService.registerNfcEventListener(mINfcEventListener));
+            }
+        }
+    }
+
+    /**
+     * Unregister a preferred service listener that was previously registered with {@link
+     * #registerNfcEventListener(Executor, NfcEventListener)}
+     *
+     * @param listener The previously registered listener to unregister
+     */
+    @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+    public void unregisterNfcEventListener(@NonNull NfcEventListener listener) {
+        if (!android.nfc.Flags.nfcEventListener()) {
+            return;
+        }
+        synchronized (mNfcEventListeners) {
+            mNfcEventListeners.remove(listener);
+            if (mNfcEventListeners.size() == 0) {
+                callService(() -> sService.unregisterNfcEventListener(mINfcEventListener));
+            }
+        }
+    }
 }
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
index cd8e19c..4f601f0 100644
--- a/nfc/java/android/nfc/cardemulation/HostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -239,15 +239,6 @@
      */
     public static final int MSG_POLLING_LOOP = 4;
 
-    /**
-     * @hide
-     */
-    public static final int MSG_OBSERVE_MODE_CHANGE = 5;
-
-    /**
-     * @hide
-     */
-    public static final int MSG_PREFERRED_SERVICE_CHANGED = 6;
 
     /**
      * @hide
@@ -343,16 +334,6 @@
                         processPollingFrames(pollingFrames);
                     }
                     break;
-                case MSG_OBSERVE_MODE_CHANGE:
-                    if (android.nfc.Flags.nfcEventListener()) {
-                        onObserveModeStateChanged(msg.arg1 == 1);
-                    }
-                    break;
-                case MSG_PREFERRED_SERVICE_CHANGED:
-                    if (android.nfc.Flags.nfcEventListener()) {
-                        onPreferredServiceChanged(msg.arg1 == 1);
-                    }
-                    break;
                 default:
                 super.handleMessage(msg);
             }
@@ -462,25 +443,4 @@
      */
     public abstract void onDeactivated(int reason);
 
-
-    /**
-     * This method is called when this service is the preferred Nfc service and
-     * Observe mode has been enabled or disabled.
-     *
-     * @param isEnabled true if observe mode has been enabled, false if it has been disabled
-     */
-    @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
-    public void onObserveModeStateChanged(boolean isEnabled) {
-
-    }
-
-    /**
-     * This method is called when this service gains or loses preferred Nfc service status.
-     *
-     * @param isPreferred true is this service has become the preferred Nfc service,
-     * false if it is no longer the preferred service
-     */
-    @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
-    public void onPreferredServiceChanged(boolean isPreferred) {
-    }
 }
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index 6a7e693..34f0200 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -165,3 +165,11 @@
     description: "Enabling security log for nfc state change"
     bug: "319934052"
 }
+
+flag {
+    name: "nfc_associated_role_services"
+    is_exported: true
+    namespace: "nfc"
+    description: "Share wallet role routing priority with associated services"
+    bug: "366243361"
+}
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/Graph/graph.proto b/packages/SettingsLib/Graph/graph.proto
index cbe602e..6b93cd7 100644
--- a/packages/SettingsLib/Graph/graph.proto
+++ b/packages/SettingsLib/Graph/graph.proto
@@ -77,6 +77,8 @@
   // Intent to show and locate the preference (might have highlight animation on
   // the preference).
   optional IntentProto launch_intent = 14;
+  // Descriptor of the preference value.
+  optional PreferenceValueDescriptorProto value_descriptor = 15;
 
   // Target of an Intent
   message ActionTarget {
@@ -103,9 +105,28 @@
 message PreferenceValueProto {
   oneof value {
     bool boolean_value = 1;
+    int32 int_value = 2;
   }
 }
 
+// Proto of preference value descriptor.
+message PreferenceValueDescriptorProto {
+  oneof type {
+    bool boolean_type = 1;
+    RangeValueProto range_value = 2;
+  }
+}
+
+// Proto of preference value that is between a range.
+message RangeValueProto {
+  // The lower bound (inclusive) of the range.
+  optional int32 min = 1;
+  // The upper bound (inclusive) of the range.
+  optional int32 max = 2;
+  // The increment step within the range. 0 means unset, which implies step size is 1.
+  optional int32 step = 3;
+}
+
 // Proto of android.content.Intent
 message IntentProto {
   // The action of the Intent.
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
index fdffe5d..5ceee6d 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
@@ -65,6 +65,7 @@
     val visitedScreens: Set<String> = setOf(),
     val locale: Locale? = null,
     val includeValue: Boolean = true,
+    val includeValueDescriptor: Boolean = true,
 )
 
 object GetPreferenceGraphRequestCodec : MessageCodec<GetPreferenceGraphRequest> {
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
index 9cb872a..2256bb3 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
@@ -49,6 +49,7 @@
 import com.android.settingslib.metadata.PreferenceScreenRegistry
 import com.android.settingslib.metadata.PreferenceSummaryProvider
 import com.android.settingslib.metadata.PreferenceTitleProvider
+import com.android.settingslib.metadata.RangeValue
 import com.android.settingslib.preference.PreferenceScreenFactory
 import com.android.settingslib.preference.PreferenceScreenProvider
 import java.util.Locale
@@ -66,6 +67,7 @@
     private val builder by lazy { PreferenceGraphProto.newBuilder() }
     private val visitedScreens = mutableSetOf<String>().apply { addAll(request.visitedScreens) }
     private val includeValue = request.includeValue
+    private val includeValueDescriptor = request.includeValueDescriptor
 
     private suspend fun init() {
         for (key in request.screenKeys) {
@@ -270,7 +272,8 @@
                 summary = textProto { string = it.toString() }
             }
         }
-        if (metadata.icon != 0) icon = metadata.icon
+        val metadataIcon = metadata.getPreferenceIcon(context)
+        if (metadataIcon != 0) icon = metadataIcon
         if (metadata.keywords != 0) keywords = metadata.keywords
         val preferenceExtras = metadata.extras(context)
         preferenceExtras?.let { extras = it.toProto() }
@@ -283,14 +286,37 @@
             restricted = metadata.isRestricted(context)
         }
         persistent = metadata.isPersistent(context)
-        if (
-            includeValue &&
-                persistent &&
-                metadata is BooleanValue &&
-                metadata is PersistentPreference<*>
-        ) {
-            metadata.storage(context).getValue(metadata.key, Boolean::class.javaObjectType)?.let {
-                value = preferenceValueProto { booleanValue = it }
+        if (persistent) {
+            if (includeValue && metadata is PersistentPreference<*>) {
+                value = preferenceValueProto {
+                    when (metadata) {
+                        is BooleanValue ->
+                            metadata
+                                .storage(context)
+                                .getValue(metadata.key, Boolean::class.javaObjectType)
+                                ?.let { booleanValue = it }
+                        is RangeValue -> {
+                            metadata
+                                .storage(context)
+                                .getValue(metadata.key, Int::class.javaObjectType)
+                                ?.let { intValue = it }
+                        }
+                        else -> {}
+                    }
+                }
+            }
+            if (includeValueDescriptor) {
+                valueDescriptor = preferenceValueDescriptorProto {
+                    when (metadata) {
+                        is BooleanValue -> booleanType = true
+                        is RangeValue -> rangeValue = rangeValueProto {
+                                min = metadata.getMinValue(context)
+                                max = metadata.getMaxValue(context)
+                                step = metadata.getIncrementStep(context)
+                            }
+                        else -> {}
+                    }
+                }
             }
         }
         if (metadata is PreferenceScreenMetadata) {
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
index d8db1bb..6e4db1d 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
@@ -27,8 +27,10 @@
 import com.android.settingslib.metadata.BooleanValue
 import com.android.settingslib.metadata.PersistentPreference
 import com.android.settingslib.metadata.PreferenceAvailabilityProvider
+import com.android.settingslib.metadata.PreferenceMetadata
 import com.android.settingslib.metadata.PreferenceRestrictionProvider
 import com.android.settingslib.metadata.PreferenceScreenRegistry
+import com.android.settingslib.metadata.RangeValue
 import com.android.settingslib.metadata.ReadWritePermit
 
 /** Request to set preference value. */
@@ -114,27 +116,39 @@
         if (metadata is PreferenceAvailabilityProvider && !metadata.isAvailable(application)) {
             return PreferenceSetterResult.UNAVAILABLE
         }
+
+        fun <T> PreferenceMetadata.checkWritePermit(value: T): Int {
+            @Suppress("UNCHECKED_CAST") val preference = (this as PersistentPreference<T>)
+            return when (preference.getWritePermit(application, value, myUid, callingUid)) {
+                ReadWritePermit.ALLOW -> PreferenceSetterResult.OK
+                ReadWritePermit.DISALLOW -> PreferenceSetterResult.DISALLOW
+                ReadWritePermit.REQUIRE_APP_PERMISSION ->
+                    PreferenceSetterResult.REQUIRE_APP_PERMISSION
+                ReadWritePermit.REQUIRE_USER_AGREEMENT ->
+                    PreferenceSetterResult.REQUIRE_USER_AGREEMENT
+                else -> PreferenceSetterResult.INTERNAL_ERROR
+            }
+        }
+
         val storage = metadata.storage(application)
         val value = request.value
         try {
             if (value.hasBooleanValue()) {
                 if (metadata !is BooleanValue) return PreferenceSetterResult.INVALID_REQUEST
                 val booleanValue = value.booleanValue
-                @Suppress("UNCHECKED_CAST")
-                val booleanPreference = metadata as PersistentPreference<Boolean>
-                when (
-                    booleanPreference.getWritePermit(application, booleanValue, myUid, callingUid)
-                ) {
-                    ReadWritePermit.ALLOW -> {}
-                    ReadWritePermit.DISALLOW -> return PreferenceSetterResult.DISALLOW
-                    ReadWritePermit.REQUIRE_APP_PERMISSION ->
-                        return PreferenceSetterResult.REQUIRE_APP_PERMISSION
-                    ReadWritePermit.REQUIRE_USER_AGREEMENT ->
-                        return PreferenceSetterResult.REQUIRE_USER_AGREEMENT
-                    else -> return PreferenceSetterResult.INTERNAL_ERROR
-                }
+                val resultCode = metadata.checkWritePermit(booleanValue)
+                if (resultCode != PreferenceSetterResult.OK) return resultCode
                 storage.setValue(key, Boolean::class.javaObjectType, booleanValue)
                 return PreferenceSetterResult.OK
+            } else if (value.hasIntValue()) {
+                val intValue = value.intValue
+                val resultCode = metadata.checkWritePermit(intValue)
+                if (resultCode != PreferenceSetterResult.OK) return resultCode
+                if (metadata is RangeValue && !metadata.isValidValue(application, intValue)) {
+                    return PreferenceSetterResult.INVALID_REQUEST
+                }
+                storage.setValue(key, Int::class.javaObjectType, intValue)
+                return PreferenceSetterResult.OK
             }
         } catch (e: Exception) {
             return PreferenceSetterResult.INTERNAL_ERROR
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
index d7dae77..dee32d9 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
@@ -24,7 +24,9 @@
 import com.android.settingslib.graph.proto.PreferenceProto
 import com.android.settingslib.graph.proto.PreferenceProto.ActionTarget
 import com.android.settingslib.graph.proto.PreferenceScreenProto
+import com.android.settingslib.graph.proto.PreferenceValueDescriptorProto
 import com.android.settingslib.graph.proto.PreferenceValueProto
+import com.android.settingslib.graph.proto.RangeValueProto
 import com.android.settingslib.graph.proto.TextProto
 
 /** Returns root or null. */
@@ -89,6 +91,16 @@
 inline fun preferenceValueProto(init: PreferenceValueProto.Builder.() -> Unit) =
     PreferenceValueProto.newBuilder().also(init).build()
 
+/** Kotlin DSL-style builder for [PreferenceValueDescriptorProto]. */
+@JvmSynthetic
+inline fun preferenceValueDescriptorProto(init: PreferenceValueDescriptorProto.Builder.() -> Unit) =
+    PreferenceValueDescriptorProto.newBuilder().also(init).build()
+
+/** Kotlin DSL-style builder for [RangeValueProto]. */
+@JvmSynthetic
+inline fun rangeValueProto(init: RangeValueProto.Builder.() -> Unit) =
+    RangeValueProto.newBuilder().also(init).build()
+
 /** Kotlin DSL-style builder for [TextProto]. */
 @JvmSynthetic
 inline fun textProto(init: TextProto.Builder.() -> Unit) = TextProto.newBuilder().also(init).build()
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
index 72cf403..49acc1d 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
@@ -115,7 +115,7 @@
     fun isFlagEnabled(context: Context): Boolean = true
 
     val preferenceBindingFactory: PreferenceBindingFactory
-        get() = DefaultPreferenceBindingFactory
+        get() = PreferenceBindingFactory.defaultFactory
 
     override fun createPreferenceScreen(factory: PreferenceScreenFactory) =
         factory.getOrCreatePreferenceScreen().apply {
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
index 87c289f..51b9aac 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindingFactory.kt
@@ -45,10 +45,15 @@
 
     /** Returns the [PreferenceBinding] associated with the [PreferenceMetadata]. */
     fun getPreferenceBinding(metadata: PreferenceMetadata): PreferenceBinding?
+
+    companion object {
+        /** Default preference binding factory. */
+        @JvmStatic var defaultFactory: PreferenceBindingFactory = DefaultPreferenceBindingFactory()
+    }
 }
 
 /** Default [PreferenceBindingFactory]. */
-object DefaultPreferenceBindingFactory : PreferenceBindingFactory {
+open class DefaultPreferenceBindingFactory : PreferenceBindingFactory {
 
     override fun getPreferenceBinding(metadata: PreferenceMetadata) =
         metadata as? PreferenceBinding
@@ -66,5 +71,6 @@
     PreferenceBindingFactory {
 
     override fun getPreferenceBinding(metadata: PreferenceMetadata) =
-        bindings[metadata.key] ?: DefaultPreferenceBindingFactory.getPreferenceBinding(metadata)
+        bindings[metadata.key]
+            ?: PreferenceBindingFactory.defaultFactory.getPreferenceBinding(metadata)
 }
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
index d40a6f6..762f5ea 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
@@ -23,7 +23,6 @@
 import androidx.preference.SwitchPreferenceCompat
 import androidx.preference.TwoStatePreference
 import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_KEY
-import com.android.settingslib.metadata.PersistentPreference
 import com.android.settingslib.metadata.PreferenceMetadata
 import com.android.settingslib.metadata.PreferenceScreenMetadata
 import com.android.settingslib.metadata.PreferenceTitleProvider
@@ -71,10 +70,10 @@
 
     override fun bind(preference: Preference, metadata: PreferenceMetadata) {
         super.bind(preference, metadata)
-        (metadata as? PersistentPreference<*>)
-            ?.storage(preference.context)
-            ?.getValue(metadata.key, Boolean::class.javaObjectType)
-            ?.let { (preference as TwoStatePreference).isChecked = it }
+        (preference as TwoStatePreference).apply {
+            // "false" is kind of placeholder, metadata datastore should provide the default value
+            isChecked = preferenceDataStore!!.getBoolean(key, false)
+        }
     }
 }
 
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt
index c26ba18..657f69a 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt
@@ -40,11 +40,11 @@
             addPreference(preferenceGroup)
             preferenceGroup.inflatePreferenceHierarchy(preferenceBindingFactory, it)
         } else {
-            preferenceBindingFactory.bind(widget, it, preferenceBinding)
             (metadata as? PersistentPreference<*>)?.storage(context)?.let { storage ->
                 widget.preferenceDataStore =
                     storages.getOrPut(storage) { PreferenceDataStoreAdapter(storage) }
             }
+            preferenceBindingFactory.bind(widget, it, preferenceBinding)
             // MUST add preference after binding for persistent preference to get initial value
             // (preference key is set within bind method)
             addPreference(widget)
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
index d75f3e8..022fb1d 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
@@ -236,12 +236,12 @@
                     preference.bindRecursively(preferenceBindingFactory, preferences, storages)
                 } else {
                     preferences[preference.key]?.let {
-                        preferenceBindingFactory.bind(preference, it)
                         val metadata = it.metadata
                         (metadata as? PersistentPreference<*>)?.storage(context)?.let { storage ->
                             preference.preferenceDataStore =
                                 storages.getOrPut(storage) { PreferenceDataStoreAdapter(storage) }
                         }
+                        preferenceBindingFactory.bind(preference, it)
                     }
                 }
             }
diff --git a/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt
new file mode 100644
index 0000000..00bad52
--- /dev/null
+++ b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.preference
+
+import android.content.Context
+import androidx.annotation.VisibleForTesting
+import androidx.preference.Preference
+import com.android.settingslib.metadata.PersistentPreference
+import com.android.settingslib.metadata.PreferenceMetadata
+
+/** Creates [Preference] widget and binds with metadata. */
+@VisibleForTesting
+fun <P : Preference> PreferenceMetadata.createAndBindWidget(context: Context): P {
+    val binding = PreferenceBindingFactory.defaultFactory.getPreferenceBinding(this)!!
+    return (binding.createWidget(context) as P).also {
+        if (this is PersistentPreference<*>) {
+            storage(context)?.let { keyValueStore ->
+                it.preferenceDataStore = PreferenceDataStoreAdapter(keyValueStore)
+            }
+        }
+        binding.bind(it, this)
+    }
+}
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
index cc42dab..944bef6 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
@@ -21,6 +21,7 @@
     android:layout_height="wrap_content"
     android:layout_weight="1"
     android:paddingVertical="@dimen/settingslib_expressive_space_small1"
+    android:paddingHorizontal="@dimen/settingslib_expressive_space_small1"
     android:filterTouchesWhenObscured="false">
 
     <TextView
@@ -40,7 +41,6 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_below="@android:id/title"
-        android:layout_alignLeft="@android:id/title"
         android:layout_alignStart="@android:id/title"
         android:layout_gravity="start"
         android:textAlignment="viewStart"
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index b76b028..4d33466 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -82,7 +82,7 @@
     <string name="speed_label_very_fast" msgid="8215718029533182439">"Veoma brzo"</string>
     <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Isteklo"</string>
     <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
-    <string name="bluetooth_disconnected" msgid="7739366554710388701">"Isključen"</string>
+    <string name="bluetooth_disconnected" msgid="7739366554710388701">"Nije povezano"</string>
     <string name="bluetooth_disconnecting" msgid="7638892134401574338">"Prekidanje veze…"</string>
     <string name="bluetooth_connecting" msgid="5871702668260192755">"Povezivanje…"</string>
     <string name="bluetooth_connected" msgid="8065345572198502293">"Povezano<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-in/arrays.xml b/packages/SettingsLib/res/values-in/arrays.xml
index 038800c..00ea04d 100644
--- a/packages/SettingsLib/res/values-in/arrays.xml
+++ b/packages/SettingsLib/res/values-in/arrays.xml
@@ -29,7 +29,7 @@
     <item msgid="4613015005934755724">"Terhubung"</item>
     <item msgid="3763530049995655072">"Ditangguhkan"</item>
     <item msgid="7852381437933824454">"Memutus sambungan..."</item>
-    <item msgid="5046795712175415059">"Sambungan terputus"</item>
+    <item msgid="5046795712175415059">"Tidak terhubung"</item>
     <item msgid="2473654476624070462">"Gagal"</item>
     <item msgid="9146847076036105115">"Diblokir"</item>
     <item msgid="4543924085816294893">"Menghindari sambungan buruk untuk sementara"</item>
@@ -43,7 +43,7 @@
     <item msgid="1043944043827424501">"Terhubung ke <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</item>
     <item msgid="7445993821842009653">"Ditangguhkan"</item>
     <item msgid="1175040558087735707">"Diputus dari <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item>
-    <item msgid="699832486578171722">"Sambungan terputus"</item>
+    <item msgid="699832486578171722">"Tidak terhubung"</item>
     <item msgid="522383512264986901">"Gagal"</item>
     <item msgid="3602596701217484364">"Diblokir"</item>
     <item msgid="1999413958589971747">"Menghindari sambungan buruk untuk sementara"</item>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 3b265f8..bfd8f39 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -50,7 +50,7 @@
     <string name="wifi_security_owe" msgid="3343421403561657809">"Enhanced Open"</string>
     <string name="wifi_security_eap_suiteb" msgid="415842785991698142">"WPA3-Enterprise 192-bit"</string>
     <string name="wifi_remembered" msgid="3266709779723179188">"Disimpan"</string>
-    <string name="wifi_disconnected" msgid="7054450256284661757">"Terputus"</string>
+    <string name="wifi_disconnected" msgid="7054450256284661757">"Tidak terhubung"</string>
     <string name="wifi_disabled_generic" msgid="2651916945380294607">"Nonaktif"</string>
     <string name="wifi_disabled_network_failure" msgid="2660396183242399585">"Kegagalan Konfigurasi IP"</string>
     <string name="wifi_disabled_password_failure" msgid="6892387079613226738">"Masalah autentikasi"</string>
@@ -82,7 +82,7 @@
     <string name="speed_label_very_fast" msgid="8215718029533182439">"Sangat Cepat"</string>
     <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Sudah tidak berlaku"</string>
     <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
-    <string name="bluetooth_disconnected" msgid="7739366554710388701">"Sambungan terputus"</string>
+    <string name="bluetooth_disconnected" msgid="7739366554710388701">"Tidak terhubung"</string>
     <string name="bluetooth_disconnecting" msgid="7638892134401574338">"Memutus sambungan..."</string>
     <string name="bluetooth_connecting" msgid="5871702668260192755">"Menghubungkan…"</string>
     <string name="bluetooth_connected" msgid="8065345572198502293">"Terhubung<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 2fb036f..785bf43 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -258,7 +258,7 @@
     <string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"QR кодунун сканерин колдонуп, жаңы түзмөктөрдү жупташтырыңыз"</string>
     <string name="adb_pair_method_code_title" msgid="1122590300445142904">"Түзмөктү атайын код аркылуу жупташтыруу"</string>
     <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Жаңы түзмөктөрдү алты сандан турган код аркылуу жупташтырасыз"</string>
-    <string name="adb_paired_devices_title" msgid="5268997341526217362">"Жупташтырылган түзмөктөр"</string>
+    <string name="adb_paired_devices_title" msgid="5268997341526217362">"Байланышкан түзмөктөр"</string>
     <string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"Учурда туташып турган түзмөктөр"</string>
     <string name="adb_wireless_device_details_title" msgid="7129369670526565786">"Түзмөктүн чоо-жайы"</string>
     <string name="adb_device_forget" msgid="193072400783068417">"Унутулсун"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index f5e84a2..b9d970d 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -85,7 +85,7 @@
     <string name="bluetooth_disconnected" msgid="7739366554710388701">"Не е поврзано"</string>
     <string name="bluetooth_disconnecting" msgid="7638892134401574338">"Се исклучува..."</string>
     <string name="bluetooth_connecting" msgid="5871702668260192755">"Се поврзува..."</string>
-    <string name="bluetooth_connected" msgid="8065345572198502293">"Поврзан со <xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
+    <string name="bluetooth_connected" msgid="8065345572198502293">"Поврзано<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
     <string name="bluetooth_pairing" msgid="4269046942588193600">"Се спарува..."</string>
     <string name="bluetooth_connected_no_headset" msgid="2224101138659967604">"Поврзан со <xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g> (без телефон)"</string>
     <string name="bluetooth_connected_no_a2dp" msgid="8566874395813947092">"Поврзан со <xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g> (без аудиовизуелни содржини)"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 73b0996..ae1df21 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -82,7 +82,7 @@
     <string name="speed_label_very_fast" msgid="8215718029533182439">"ଅତି ଦ୍ରୁତ"</string>
     <string name="wifi_passpoint_expired" msgid="6540867261754427561">"ମିଆଦ ଶେଷ ହୋଇଯାଇଛି"</string>
     <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
-    <string name="bluetooth_disconnected" msgid="7739366554710388701">"ବିଛିନ୍ନ ହେଲା"</string>
+    <string name="bluetooth_disconnected" msgid="7739366554710388701">"ଡିସକନେକ୍ଟ ହୋଇଛି"</string>
     <string name="bluetooth_disconnecting" msgid="7638892134401574338">"ବିଚ୍ଛିନ୍ନ କରୁଛି…"</string>
     <string name="bluetooth_connecting" msgid="5871702668260192755">"କନେକ୍ଟ ହେଉଛି…"</string>
     <string name="bluetooth_connected" msgid="8065345572198502293">"ସଂଯୁକ୍ତ ହେଲା<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 74c294b..4c41b7d 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -82,7 +82,7 @@
     <string name="speed_label_very_fast" msgid="8215718029533182439">"Veľmi rýchla"</string>
     <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Vypršalo"</string>
     <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
-    <string name="bluetooth_disconnected" msgid="7739366554710388701">"Odpojený"</string>
+    <string name="bluetooth_disconnected" msgid="7739366554710388701">"Odpojené"</string>
     <string name="bluetooth_disconnecting" msgid="7638892134401574338">"Prebieha odpájanie..."</string>
     <string name="bluetooth_connecting" msgid="5871702668260192755">"Prebieha pripájanie…"</string>
     <string name="bluetooth_connected" msgid="8065345572198502293">"Pripojené k zariadeniu <xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 1c1117d..1d7f62a 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -82,7 +82,7 @@
     <string name="speed_label_very_fast" msgid="8215718029533182439">"Mycket snabb"</string>
     <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Har upphört att gälla"</string>
     <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
-    <string name="bluetooth_disconnected" msgid="7739366554710388701">"Kopplas ifrån"</string>
+    <string name="bluetooth_disconnected" msgid="7739366554710388701">"Frånkopplad"</string>
     <string name="bluetooth_disconnecting" msgid="7638892134401574338">"Kopplar ifrån…"</string>
     <string name="bluetooth_connecting" msgid="5871702668260192755">"Ansluter…"</string>
     <string name="bluetooth_connected" msgid="8065345572198502293">"Ansluten<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 57b8f59..060b7b2 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -82,7 +82,7 @@
     <string name="speed_label_very_fast" msgid="8215718029533182439">"เร็วมาก"</string>
     <string name="wifi_passpoint_expired" msgid="6540867261754427561">"หมดอายุแล้ว"</string>
     <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
-    <string name="bluetooth_disconnected" msgid="7739366554710388701">"ตัดการเชื่อมต่อ"</string>
+    <string name="bluetooth_disconnected" msgid="7739366554710388701">"ยกเลิกการเชื่อมต่อแล้ว"</string>
     <string name="bluetooth_disconnecting" msgid="7638892134401574338">"กำลังตัดการเชื่อมต่อ..."</string>
     <string name="bluetooth_connecting" msgid="5871702668260192755">"กำลังเชื่อมต่อ…"</string>
     <string name="bluetooth_connected" msgid="8065345572198502293">"เชื่อมต่อแล้ว<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java
index 6578eb7..c36ade9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java
@@ -22,14 +22,20 @@
 import androidx.preference.DropDownPreference;
 import androidx.preference.PreferenceViewHolder;
 
-public class RestrictedDropDownPreference extends DropDownPreference {
-    RestrictedPreferenceHelper mHelper;
+public class RestrictedDropDownPreference extends DropDownPreference implements
+        RestrictedPreferenceHelperProvider {
+    private final RestrictedPreferenceHelper mHelper;
 
     public RestrictedDropDownPreference(@NonNull Context context) {
         super(context);
         mHelper = new RestrictedPreferenceHelper(context, this, null);
     }
 
+    @Override
+    public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() {
+        return mHelper;
+    }
+
     /**
      * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
      * package. Marks the preference as disabled if so.
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedInterface.kt b/packages/SettingsLib/src/com/android/settingslib/RestrictedInterface.kt
deleted file mode 100644
index 14f9a19..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedInterface.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib
-
-import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
-
-interface RestrictedInterface {
-    fun useAdminDisabledSummary(useSummary: Boolean)
-
-    fun checkRestrictionAndSetDisabled(userRestriction: String)
-
-    fun checkRestrictionAndSetDisabled(userRestriction: String, userId: Int)
-
-    /**
-     * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
-     * package. Marks the preference as disabled if so.
-     *
-     * @param settingIdentifier The key identifying the setting
-     * @param packageName       the package to check the settingIdentifier for
-     */
-    fun checkEcmRestrictionAndSetDisabled(
-        settingIdentifier: String,
-        packageName: String
-    )
-
-    val isDisabledByAdmin: Boolean
-
-    fun setDisabledByAdmin(admin: EnforcedAdmin?)
-
-    val isDisabledByEcm: Boolean
-
-    val uid: Int
-
-    val packageName: String?
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
index 495410b..332042a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
@@ -34,7 +34,9 @@
  * Preference class that supports being disabled by a user restriction
  * set by a device admin.
  */
-public class RestrictedPreference extends TwoTargetPreference {
+public class RestrictedPreference extends TwoTargetPreference implements
+        RestrictedPreferenceHelperProvider {
+
     RestrictedPreferenceHelper mHelper;
 
     public RestrictedPreference(Context context, AttributeSet attrs,
@@ -67,6 +69,11 @@
     }
 
     @Override
+    public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() {
+        return mHelper;
+    }
+
+    @Override
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
         mHelper.onBindViewHolder(holder);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelperProvider.kt
similarity index 68%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelperProvider.kt
index 94b2bdf..f286084 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelperProvider.kt
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.settingslib
 
-import com.android.systemui.kosmos.Kosmos
+/** Provider of [RestrictedPreferenceHelper]. */
+interface RestrictedPreferenceHelperProvider {
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+    /** Returns the [RestrictedPreferenceHelper] applied to the preference. */
+    fun getRestrictedPreferenceHelper(): RestrictedPreferenceHelper
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java
index c52c7ea..573869d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSelectorWithWidgetPreference.java
@@ -32,8 +32,9 @@
 /**
  * Selector with widget preference that can be disabled by a device admin using a user restriction.
  */
-public class RestrictedSelectorWithWidgetPreference extends SelectorWithWidgetPreference {
-    private RestrictedPreferenceHelper mHelper;
+public class RestrictedSelectorWithWidgetPreference extends SelectorWithWidgetPreference implements
+        RestrictedPreferenceHelperProvider {
+    private final RestrictedPreferenceHelper mHelper;
 
     /**
      * Perform inflation from XML and apply a class-specific base style.
@@ -82,8 +83,11 @@
      */
     public RestrictedSelectorWithWidgetPreference(@NonNull Context context) {
         this(context, null);
-        mHelper =
-                new RestrictedPreferenceHelper(context, /* preference= */ this, /* attrs= */ null);
+    }
+
+    @Override
+    public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() {
+        return mHelper;
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSliderPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSliderPreference.java
index 1dc5281..b690816 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSliderPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSliderPreference.java
@@ -19,7 +19,6 @@
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.os.Process;
-import android.os.UserHandle;
 import android.util.AttributeSet;
 
 import androidx.annotation.NonNull;
@@ -34,8 +33,10 @@
  * Slide Preference that supports being disabled by a user restriction
  * set by a device admin.
  */
-public class RestrictedSliderPreference extends SliderPreference implements RestrictedInterface {
-    RestrictedPreferenceHelper mHelper;
+public class RestrictedSliderPreference extends SliderPreference implements
+        RestrictedPreferenceHelperProvider {
+
+    private final RestrictedPreferenceHelper mHelper;
 
     public RestrictedSliderPreference(@NonNull Context context, @Nullable AttributeSet attrs,
             int defStyleAttr, @Nullable String packageName, int uid) {
@@ -66,6 +67,11 @@
     }
 
     @Override
+    public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() {
+        return mHelper;
+    }
+
+    @Override
     public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
         mHelper.onBindViewHolder(holder);
@@ -81,12 +87,12 @@
 
     @Override
     public void setEnabled(boolean enabled) {
-        if (enabled && isDisabledByAdmin()) {
+        if (enabled && mHelper.isDisabledByAdmin()) {
             mHelper.setDisabledByAdmin(null);
             return;
         }
 
-        if (enabled && isDisabledByEcm()) {
+        if (enabled && mHelper.isDisabledByEcm()) {
             mHelper.setDisabledByEcm(null);
             return;
         }
@@ -95,55 +101,6 @@
     }
 
     @Override
-    public void useAdminDisabledSummary(boolean useSummary) {
-        mHelper.useAdminDisabledSummary(useSummary);
-    }
-
-    @Override
-    public void checkRestrictionAndSetDisabled(@NonNull String userRestriction) {
-        mHelper.checkRestrictionAndSetDisabled(userRestriction, UserHandle.myUserId());
-    }
-
-    @Override
-    public void checkRestrictionAndSetDisabled(@NonNull String userRestriction, int userId) {
-        mHelper.checkRestrictionAndSetDisabled(userRestriction, userId);
-    }
-
-    @Override
-    public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier,
-            @NonNull String packageName) {
-        mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName);
-    }
-
-    @Override
-    public void setDisabledByAdmin(@Nullable RestrictedLockUtils.EnforcedAdmin admin) {
-        if (mHelper.setDisabledByAdmin(admin)) {
-            notifyChanged();
-        }
-    }
-
-    @Override
-    public boolean isDisabledByAdmin() {
-        return mHelper.isDisabledByAdmin();
-    }
-
-    @Override
-    public boolean isDisabledByEcm() {
-        return mHelper.isDisabledByEcm();
-    }
-
-    @Override
-    public int getUid() {
-        return mHelper != null ? mHelper.uid : Process.INVALID_UID;
-    }
-
-    @Override
-    @Nullable
-    public String getPackageName() {
-        return mHelper != null ? mHelper.packageName : null;
-    }
-
-    @Override
     protected void onAttachedToHierarchy(@NonNull PreferenceManager preferenceManager) {
         mHelper.onAttachedToHierarchy();
         super.onAttachedToHierarchy(preferenceManager);
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index fffbb54..727dbe1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -45,8 +45,9 @@
  * Version of SwitchPreferenceCompat that can be disabled by a device admin
  * using a user restriction.
  */
-public class RestrictedSwitchPreference extends SwitchPreferenceCompat {
-    RestrictedPreferenceHelper mHelper;
+public class RestrictedSwitchPreference extends SwitchPreferenceCompat implements
+        RestrictedPreferenceHelperProvider {
+    private final RestrictedPreferenceHelper mHelper;
     AppOpsManager mAppOpsManager;
     boolean mUseAdditionalSummary = false;
     CharSequence mRestrictedSwitchSummary;
@@ -98,6 +99,11 @@
         this(context, null);
     }
 
+    @Override
+    public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() {
+        return mHelper;
+    }
+
     @VisibleForTesting
     public void setAppOps(AppOpsManager appOps) {
         mAppOpsManager = appOps;
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedTopLevelPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedTopLevelPreference.java
index 0096015..34e4d8f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedTopLevelPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedTopLevelPreference.java
@@ -22,14 +22,16 @@
 import android.os.UserHandle;
 import android.util.AttributeSet;
 
+import androidx.annotation.NonNull;
 import androidx.core.content.res.TypedArrayUtils;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceManager;
 import androidx.preference.PreferenceViewHolder;
 
 /** Top level preference that can be disabled by a device admin using a user restriction. */
-public class RestrictedTopLevelPreference extends Preference {
-    private RestrictedPreferenceHelper mHelper;
+public class RestrictedTopLevelPreference extends Preference implements
+        RestrictedPreferenceHelperProvider {
+    private final RestrictedPreferenceHelper mHelper;
 
     public RestrictedTopLevelPreference(Context context, AttributeSet attrs,
             int defStyleAttr, int defStyleRes) {
@@ -51,6 +53,11 @@
     }
 
     @Override
+    public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() {
+        return mHelper;
+    }
+
+    @Override
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
         mHelper.onBindViewHolder(holder);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
index e7c7476..f4b79db 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
@@ -120,4 +120,10 @@
 
     /** Device setting ID for ANC. */
     int DEVICE_SETTING_ID_ANC = 1001;
+
+    /** Device setting expandable ID 1. */
+    int DEVICE_SETTING_ID_EXPANDABLE_1 = 3001;
+
+    /** Device setting expandable ID 2. */
+    int DEVICE_SETTING_ID_EXPANDABLE_2 = 3100;
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPendingIntentAction.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPendingIntentAction.java
index 3d4282c..b997e4d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPendingIntentAction.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPendingIntentAction.java
@@ -17,7 +17,6 @@
 package com.android.settingslib.bluetooth.devicesettings;
 
 import android.app.PendingIntent;
-import android.content.Intent;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -47,7 +46,7 @@
     /** Read a {@link DeviceSettingPendingIntentAction} instance from {@link Parcel} */
     @NonNull
     public static DeviceSettingPendingIntentAction readFromParcel(@NonNull Parcel in) {
-        PendingIntent pendingIntent = in.readParcelable(Intent.class.getClassLoader());
+        PendingIntent pendingIntent = in.readParcelable(PendingIntent.class.getClassLoader());
         Bundle extras = in.readBundle(Bundle.class.getClassLoader());
         return new DeviceSettingPendingIntentAction(pendingIntent, extras);
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
index 8537897..c68dbee 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
@@ -106,26 +106,40 @@
 
     private fun DeviceSettingsConfig.toModel(): DeviceSettingConfigModel =
         DeviceSettingConfigModel(
-            mainItems = mainContentItems.map { it.toModel() },
-            moreSettingsItems = moreSettingsItems.map { it.toModel() },
+            mainItems = mainContentItems.toModel(),
+            moreSettingsItems = moreSettingsItems.toModel(),
             moreSettingsHelpItem = moreSettingsHelpItem?.toModel(),
         )
 
-    private fun DeviceSettingItem.toModel(): DeviceSettingConfigItemModel {
+    private fun List<DeviceSettingItem>.toModel(): List<DeviceSettingConfigItemModel> {
+        return this.flatMap { item ->
+            if (item.settingId in EXPANDABLE_SETTING_IDS) {
+                IntRange(item.settingId, item.settingId + SETTING_ID_EXPAND_LIMIT - 1).map {
+                    item.toModel(overrideSettingId = it)
+                }
+            } else {
+                listOf(item.toModel())
+            }
+        }
+    }
+
+    private fun DeviceSettingItem.toModel(
+        overrideSettingId: Int? = null
+    ): DeviceSettingConfigItemModel {
         return if (!TextUtils.isEmpty(preferenceKey)) {
             if (settingId == DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_PROFILES) {
                 BluetoothProfilesItem(
-                    settingId,
+                    overrideSettingId ?: settingId,
                     highlighted,
                     preferenceKey!!,
                     extras.getStringArrayList(DeviceSettingContract.INVISIBLE_PROFILES)
                         ?: emptyList(),
                 )
             } else {
-                CommonBuiltinItem(settingId, highlighted, preferenceKey!!)
+                CommonBuiltinItem(overrideSettingId ?: settingId, highlighted, preferenceKey!!)
             }
         } else {
-            AppProvidedItem(settingId, highlighted)
+            AppProvidedItem(overrideSettingId ?: settingId, highlighted)
         }
     }
 
@@ -134,6 +148,7 @@
             is DeviceSettingIntentAction -> DeviceSettingActionModel.IntentAction(this.intent)
             is DeviceSettingPendingIntentAction ->
                 DeviceSettingActionModel.PendingIntentAction(this.pendingIntent)
+
             else -> null
         }
 
@@ -150,7 +165,7 @@
                     summary = pref.summary,
                     icon = pref.icon?.let { DeviceSettingIcon.BitmapIcon(it) },
                     isAllowedChangingState = pref.isAllowedChangingState,
-                    action = pref.action?.toModel(),
+                    action = pref.action.toModel(),
                     switchState =
                         if (pref.hasSwitch()) {
                             DeviceSettingStateModel.ActionSwitchPreferenceState(pref.checked)
@@ -163,6 +178,7 @@
                         }
                     },
                 )
+
             is MultiTogglePreference ->
                 DeviceSettingModel.MultiTogglePreference(
                     cachedDevice = cachedDevice,
@@ -178,21 +194,33 @@
                         }
                     },
                 )
+
             is DeviceSettingFooterPreference ->
                 DeviceSettingModel.FooterPreference(
                     cachedDevice = cachedDevice,
                     id = settingId,
                     footerText = pref.footerText,
                 )
+
             is DeviceSettingHelpPreference ->
                 DeviceSettingModel.HelpPreference(
                     cachedDevice = cachedDevice,
                     id = settingId,
                     intent = pref.intent,
                 )
+
             else -> DeviceSettingModel.Unknown(cachedDevice, settingId)
         }
 
     private fun ToggleInfo.toModel(): ToggleModel =
         ToggleModel(label, DeviceSettingIcon.BitmapIcon(icon))
+
+    companion object {
+        private val EXPANDABLE_SETTING_IDS =
+            listOf(
+                DeviceSettingId.DEVICE_SETTING_ID_EXPANDABLE_1,
+                DeviceSettingId.DEVICE_SETTING_ID_EXPANDABLE_2,
+            )
+        private const val SETTING_ID_EXPAND_LIMIT = 15
+    }
 }
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/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
index 4e62fd3b..3d4e449 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
@@ -167,6 +167,26 @@
     }
 
     @Test
+    fun getDeviceSettingsConfig_expandable_success() {
+        testScope.runTest {
+            setUpConfigService(true, DEVICE_SETTING_CONFIG_EXPANDABLE)
+            `when`(settingProviderService1.serviceStatus)
+                .thenReturn(DeviceSettingsProviderServiceStatus(true))
+            `when`(settingProviderService2.serviceStatus)
+                .thenReturn(DeviceSettingsProviderServiceStatus(true))
+
+            val config = underTest.getDeviceSettingsConfig(cachedDevice)!!
+
+            assertThat(config.mainItems.map { it.settingId }).isEqualTo(
+                IntRange(
+                    DeviceSettingId.DEVICE_SETTING_ID_EXPANDABLE_1,
+                    DeviceSettingId.DEVICE_SETTING_ID_EXPANDABLE_1 + 14
+                ).toList()
+            )
+        }
+    }
+
+    @Test
     fun getDeviceSettingsConfig_noMetadata_returnNull() {
         testScope.runTest {
             `when`(
@@ -510,6 +530,13 @@
                 SETTING_PROVIDER_SERVICE_CLASS_NAME_2,
                 SETTING_PROVIDER_SERVICE_INTENT_ACTION_2,
             )
+        val DEVICE_SETTING_APP_PROVIDED_ITEM_EXPANDABLE =
+            DeviceSettingItem(
+                DeviceSettingId.DEVICE_SETTING_ID_EXPANDABLE_1,
+                SETTING_PROVIDER_SERVICE_PACKAGE_NAME_1,
+                SETTING_PROVIDER_SERVICE_CLASS_NAME_1,
+                SETTING_PROVIDER_SERVICE_INTENT_ACTION_1,
+            )
         val DEVICE_SETTING_BUILT_IN_ITEM =
             DeviceSettingItem(
                 DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_AUDIO_DEVICE_TYPE_GROUP,
@@ -581,5 +608,13 @@
                 listOf(DEVICE_SETTING_APP_PROVIDED_ITEM_2),
                 DEVICE_SETTING_HELP_ITEM,
             )
+        val DEVICE_SETTING_CONFIG_EXPANDABLE =
+            DeviceSettingsConfig(
+                listOf(
+                    DEVICE_SETTING_APP_PROVIDED_ITEM_EXPANDABLE,
+                ),
+                listOf(),
+                DEVICE_SETTING_HELP_ITEM,
+            )
     }
 }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 05c5e5d..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" />
 
@@ -951,6 +954,9 @@
     <!-- Permission required for CTS test - CtsAppTestCases -->
     <uses-permission android:name="android.permission.KILL_UID" />
 
+    <!-- Permission required for CTS test - CtsTelephonyTestCases -->
+    <uses-permission android:name="android.permission.READ_BASIC_PHONE_STATE" />
+
     <application
         android:label="@string/app_label"
         android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/StatementService/Android.bp b/packages/StatementService/Android.bp
index ff1a756..90e1808 100644
--- a/packages/StatementService/Android.bp
+++ b/packages/StatementService/Android.bp
@@ -35,6 +35,7 @@
     privileged: true,
     certificate: "platform",
     static_libs: [
+        "StatementServiceParser",
         "androidx.appcompat_appcompat",
         "androidx.collection_collection-ktx",
         "androidx.work_work-runtime",
diff --git a/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt b/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt
index 455e8085..ad137400 100644
--- a/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt
+++ b/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt
@@ -28,6 +28,8 @@
 import java.util.ArrayList
 import com.android.statementservice.retriever.WebAsset
 import com.android.statementservice.retriever.AndroidAppAsset
+import com.android.statementservice.retriever.DynamicAppLinkComponent
+import org.json.JSONObject
 
 /**
  * Parses JSON from the Digital Asset Links specification. For examples, see [WebAsset],
@@ -97,13 +99,45 @@
                 FIELD_NOT_ARRAY_FORMAT_STRING.format(StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION)
             )
         val target = AssetFactory.create(targetObject)
+        val dynamicAppLinkComponents = parseDynamicAppLinkComponents(
+            statement.optJSONObject(StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION_EXTENSIONS)
+        )
 
         val statements = (0 until relations.length())
             .map { relations.getString(it) }
             .map(Relation::create)
-            .map { Statement.create(source, target, it) }
+            .map { Statement.create(source, target, it, dynamicAppLinkComponents) }
         return Result.Success(ParsedStatement(statements, listOfNotNull(delegate)))
     }
 
+    private fun parseDynamicAppLinkComponents(
+        statement: JSONObject?
+    ): List<DynamicAppLinkComponent> {
+        val relationExtensions = statement?.optJSONObject(
+            StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION_EXTENSIONS
+        ) ?: return emptyList()
+        val handleAllUrlsRelationExtension = relationExtensions.optJSONObject(
+            StatementUtils.RELATION.toString()
+        ) ?: return emptyList()
+        val components = handleAllUrlsRelationExtension.optJSONArray(
+            StatementUtils.RELATION_EXTENSION_FIELD_DAL_COMPONENTS
+        ) ?: return emptyList()
+
+        return (0 until components.length())
+            .map { components.getJSONObject(it) }
+            .map { parseComponent(it) }
+    }
+
+    private fun parseComponent(component: JSONObject): DynamicAppLinkComponent {
+        val query = component.optJSONObject("?")
+        return DynamicAppLinkComponent.create(
+            component.optBoolean("exclude", false),
+            component.optString("#"),
+            component.optString("/"),
+            query?.keys()?.asSequence()?.associateWith { query.getString(it) },
+            component.optString("comments")
+        )
+    }
+
     data class ParsedStatement(val statements: List<Statement>, val delegates: List<String>)
 }
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java b/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java
new file mode 100644
index 0000000..dc27e12
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java
@@ -0,0 +1,145 @@
+/*
+ * 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.statementservice.retriever;
+
+import android.annotation.Nullable;
+
+import java.util.Map;
+
+/**
+ * A immutable value type representing a dynamic app link component
+ */
+public final class DynamicAppLinkComponent {
+    private final boolean mExclude;
+    private final String mFragment;
+    private final String mPath;
+    private final Map<String, String> mQuery;
+    private final String mComments;
+
+    private DynamicAppLinkComponent(boolean exclude, String fragment, String path,
+                                    Map<String, String> query, String comments) {
+        mExclude = exclude;
+        mFragment = fragment;
+        mPath = path;
+        mQuery = query;
+        mComments = comments;
+    }
+
+    /**
+     * Returns true or false indicating whether this rule should be a exclusion rule.
+     */
+    public boolean getExclude() {
+        return mExclude;
+    }
+
+    /**
+     * Returns a optional pattern string for matching URL fragments.
+     */
+    @Nullable
+    public String getFragment() {
+        return mFragment;
+    }
+
+    /**
+     * Returns a optional pattern string for matching URL paths.
+     */
+    @Nullable
+    public String getPath() {
+        return mPath;
+    }
+
+    /**
+     * Returns a optional pattern string for matching a single key-value pair in the URL query
+     * params.
+     */
+    @Nullable
+    public Map<String, String> getQuery() {
+        return mQuery;
+    }
+
+    /**
+     * Returns a optional comment string for this component.
+     */
+    @Nullable
+    public String getComments() {
+        return mComments;
+    }
+
+    /**
+     * Creates a new DynamicAppLinkComponent object.
+     */
+    public static DynamicAppLinkComponent create(boolean exclude, String fragment, String path,
+                                                 Map<String, String> query, String comments) {
+        return new DynamicAppLinkComponent(exclude, fragment, path, query, comments);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DynamicAppLinkComponent rule = (DynamicAppLinkComponent) o;
+
+        if (mExclude != rule.mExclude) {
+            return false;
+        }
+        if (!mFragment.equals(rule.mFragment)) {
+            return false;
+        }
+        if (!mPath.equals(rule.mPath)) {
+            return false;
+        }
+        if (!mQuery.equals(rule.mQuery)) {
+            return false;
+        }
+        if (!mComments.equals(rule.mComments)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = Boolean.hashCode(mExclude);
+        result = 31 * result + mFragment.hashCode();
+        result = 31 * result + mPath.hashCode();
+        result = 31 * result + mQuery.hashCode();
+        result = 31 * result + mComments.hashCode();
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder statement = new StringBuilder();
+        statement.append("HandleAllUriRule: ");
+        statement.append(mExclude);
+        statement.append(", ");
+        statement.append(mFragment);
+        statement.append(", ");
+        statement.append(mPath);
+        statement.append(", ");
+        statement.append(mQuery);
+        statement.append(", ");
+        statement.append(mComments);
+        return statement.toString();
+    }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java b/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java
index ce063ea..7635e82 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java
@@ -46,12 +46,6 @@
         while (reader.hasNext()) {
             String fieldName = reader.nextName();
 
-            if (output.has(fieldName)) {
-                errorMsg = "Duplicate field name.";
-                reader.skipValue();
-                continue;
-            }
-
             JsonToken token = reader.peek();
             if (token.equals(JsonToken.BEGIN_ARRAY)) {
                 output.put(fieldName, new JSONArray(parseArray(reader)));
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/Statement.java b/packages/StatementService/src/com/android/statementservice/retriever/Statement.java
index f8bab3e..b5e2046 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/Statement.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/Statement.java
@@ -23,6 +23,10 @@
 
 import kotlin.coroutines.Continuation;
 
+import java.util.Collections;
+import java.util.List;
+
+
 /**
  * An immutable value type representing a statement, consisting of a source, target, and relation.
  * This reflects an assertion that the relation holds for the source, target pair. For example, if a
@@ -32,7 +36,21 @@
  * {
  * "relation": ["delegate_permission/common.handle_all_urls"],
  * "target"  : {"namespace": "android_app", "package_name": "com.example.app",
- *              "sha256_cert_fingerprints": ["00:11:22:33"] }
+ *              "sha256_cert_fingerprints": ["00:11:22:33"] },
+ * "relation_extensions": {
+ *     "delegate_permission/common_handle_all_urls": {
+ *         "dynamic_app_link_components": [
+ *             {
+ *                 "/": "/foo*",
+ *                 "exclude": true,
+ *                 "comments": "App should not handle paths that start with foo"
+ *             },
+ *             {
+ *                 "/": "*",
+ *                 "comments": "Catch all other paths"
+ *             }
+ *         ]
+ *     }
  * }
  * </pre>
  *
@@ -40,7 +58,7 @@
  * return a {@link Statement} with {@link #getSource} equal to the input parameter,
  * {@link #getRelation} equal to
  *
- * <pre>Relation.create("delegate_permission", "common.get_login_creds");</pre>
+ * <pre>Relation.create("delegate_permission", "common.handle_all_urls");</pre>
  *
  * and with {@link #getTarget} equal to
  *
@@ -48,17 +66,23 @@
  *                           + "\"package_name\": \"com.example.app\"}"
  *                           + "\"sha256_cert_fingerprints\": \"[\"00:11:22:33\"]\"}");
  * </pre>
+ *
+ * If extensions exist for the handle_all_urls relation then {@link #getDynamicAppLinkComponents}
+ * will return a list of parsed {@link DynamicAppLinkComponent}s.
  */
 public final class Statement {
 
     private final AbstractAsset mTarget;
     private final Relation mRelation;
     private final AbstractAsset mSource;
+    private final List<DynamicAppLinkComponent> mDynamicAppLinkComponents;
 
-    private Statement(AbstractAsset source, AbstractAsset target, Relation relation) {
+    private Statement(AbstractAsset source, AbstractAsset target, Relation relation,
+                      List<DynamicAppLinkComponent> components) {
         mSource = source;
         mTarget = target;
         mRelation = relation;
+        mDynamicAppLinkComponents = Collections.unmodifiableList(components);
     }
 
     /**
@@ -86,6 +110,14 @@
     }
 
     /**
+     * Returns the relation matching rules of the statement.
+     */
+    @NonNull
+    public List<DynamicAppLinkComponent> getDynamicAppLinkComponents() {
+        return mDynamicAppLinkComponents;
+    }
+
+    /**
      * Creates a new Statement object for the specified target asset and relation. For example:
      * <pre>
      *   Asset asset = Asset.Factory.create(
@@ -95,8 +127,9 @@
      * </pre>
      */
     public static Statement create(@NonNull AbstractAsset source, @NonNull AbstractAsset target,
-                                   @NonNull Relation relation) {
-        return new Statement(source, target, relation);
+                                   @NonNull Relation relation,
+                                   @NonNull List<DynamicAppLinkComponent> components) {
+        return new Statement(source, target, relation, components);
     }
 
     @Override
@@ -119,6 +152,9 @@
         if (!mSource.equals(statement.mSource)) {
             return false;
         }
+        if (!mDynamicAppLinkComponents.equals(statement.mDynamicAppLinkComponents)) {
+            return false;
+        }
 
         return true;
     }
@@ -128,6 +164,7 @@
         int result = mTarget.hashCode();
         result = 31 * result + mRelation.hashCode();
         result = 31 * result + mSource.hashCode();
+        result = 31 * result + mDynamicAppLinkComponents.hashCode();
         return result;
     }
 
@@ -140,6 +177,10 @@
         statement.append(mTarget);
         statement.append(", ");
         statement.append(mRelation);
+        if (!mDynamicAppLinkComponents.isEmpty()) {
+            statement.append(", ");
+            statement.append(mDynamicAppLinkComponents);
+        }
         return statement.toString();
     }
 }
diff --git a/packages/StatementService/src/com/android/statementservice/utils/StatementUtils.kt b/packages/StatementService/src/com/android/statementservice/utils/StatementUtils.kt
index 4837aad..47c69b4 100644
--- a/packages/StatementService/src/com/android/statementservice/utils/StatementUtils.kt
+++ b/packages/StatementService/src/com/android/statementservice/utils/StatementUtils.kt
@@ -17,8 +17,17 @@
 package com.android.statementservice.utils
 
 import android.content.Context
+import android.content.UriRelativeFilter
+import android.content.UriRelativeFilter.FRAGMENT
+import android.content.UriRelativeFilter.PATH
+import android.content.UriRelativeFilter.QUERY
+import android.content.UriRelativeFilterGroup
+import android.content.UriRelativeFilterGroup.ACTION_ALLOW
+import android.content.UriRelativeFilterGroup.ACTION_BLOCK
 import android.content.pm.PackageManager
 import android.util.Patterns
+import com.android.statementservice.parser.parseMatchingExpression
+import com.android.statementservice.retriever.DynamicAppLinkComponent
 import com.android.statementservice.retriever.Relation
 import java.net.URL
 import java.security.MessageDigest
@@ -52,7 +61,9 @@
      */
     const val ASSET_DESCRIPTOR_FIELD_RELATION = "relation"
     const val ASSET_DESCRIPTOR_FIELD_TARGET = "target"
+    const val ASSET_DESCRIPTOR_FIELD_RELATION_EXTENSIONS = "relation_extensions"
     const val DELEGATE_FIELD_DELEGATE = "include"
+    const val RELATION_EXTENSION_FIELD_DAL_COMPONENTS = "dynamic_app_link_components"
 
     val HEX_DIGITS =
         charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
@@ -160,4 +171,23 @@
     // Hosts with *. for wildcard subdomain support are verified against their root domain
     fun createWebAssetString(host: String) =
         WEB_ASSET_FORMAT.format(URL("https", host.removePrefix("*."), "").toString())
+
+    fun createUriRelativeFilterGroup(component: DynamicAppLinkComponent): UriRelativeFilterGroup {
+        val group = UriRelativeFilterGroup(if (component.exclude) ACTION_BLOCK else ACTION_ALLOW)
+        component.fragment?.let {
+            val (type, filter) = parseMatchingExpression(it)
+            group.addUriRelativeFilter(UriRelativeFilter(FRAGMENT, type, filter))
+        }
+        component.path?.let {
+            val (type, filter) = parseMatchingExpression(it)
+            group.addUriRelativeFilter(UriRelativeFilter(PATH, type, filter))
+        }
+        component.query?.let {
+            for ((k, v) in it) {
+                val (type, filter) = parseMatchingExpression(k + "=" + v)
+                group.addUriRelativeFilter(UriRelativeFilter(QUERY, type, filter))
+            }
+        }
+        return group
+    }
 }
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index a18b6c1..bffda8b 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -536,6 +536,8 @@
         "androidx.room_room-runtime",
         "androidx.room_room-ktx",
         "androidx.datastore_datastore-preferences",
+        "androidx.media3.media3-common",
+        "androidx.media3.media3-session",
         "com.google.android.material_material",
         "device_state_flags_lib",
         "kotlinx_coroutines_android",
@@ -703,6 +705,8 @@
         "androidx.room_room-testing",
         "androidx.room_room-ktx",
         "androidx.datastore_datastore-preferences",
+        "androidx.media3.media3-common",
+        "androidx.media3.media3-session",
         "device_state_flags_lib",
         "kotlinx-coroutines-android",
         "kotlinx-coroutines-core",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 0537b14..e011736 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -1013,9 +1013,14 @@
             android:autoRemoveFromRecents="true"
             android:launchMode="singleTop"
             android:showForAllUsers="true"
+            android:turnScreenOn="true"
             android:exported="false">
         </activity>
 
+        <service
+            android:name="com.android.systemui.communal.widgets.GlanceableHubWidgetManagerService"
+            android:exported="false" />
+
         <!-- Doze with notifications, run in main sysui process for every user  -->
         <service
             android:name=".doze.DozeService"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 83b7566..3bf3e24 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -26,6 +26,23 @@
 }
 
 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."
+   bug: "376450983"
+}
+
+flag {
    name: "priority_people_section"
    namespace: "systemui"
    description: "Add a new section for priority people (aka important conversations)."
@@ -461,6 +478,15 @@
 }
 
 flag {
+    name: "status_bar_notification_chips_test"
+    namespace: "systemui"
+    description: "Flag to enable certain features that let us test the status bar notification "
+        "chips with teamfooders. This flag should *never* be released to trunkfood or nextfood."
+    bug: "361346412"
+}
+
+
+flag {
     name: "compose_bouncer"
     namespace: "systemui"
     description: "Use the new compose bouncer in SystemUI"
@@ -1508,6 +1534,16 @@
 }
 
 flag {
+  namespace: "systemui"
+  name: "user_aware_settings_repositories"
+  description: "Provide user-aware versions of SecureSettingsRepository and SystemSettingsRepository in SystemUI modules (see doc linked from b/356099784)."
+  bug: "356099784"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
     name: "notify_password_text_view_user_activity_in_background"
     namespace: "systemui"
     description: "Decide whether to notify the user activity in password text view, to power manager in the background thread."
@@ -1552,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"
@@ -1700,4 +1743,3 @@
     description: "An implementation of shortcut customizations through shortcut helper."
     bug: "365064144"
 }
-
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/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/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 6fc51e4..e78862e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -53,6 +53,7 @@
 import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
 import com.android.systemui.res.R
 import com.android.systemui.shade.LargeScreenHeaderHelper
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
@@ -84,7 +85,7 @@
     stackScrollLayout: NotificationStackScrollLayout,
     sharedNotificationContainerBinder: SharedNotificationContainerBinder,
     private val keyguardRootViewModel: KeyguardRootViewModel,
-    private val configurationState: ConfigurationState,
+    @ShadeDisplayAware private val configurationState: ConfigurationState,
     private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
     private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
     private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
@@ -127,7 +128,7 @@
         }
         val burnIn = rememberBurnIn(clockInteractor)
         AnimatedVisibility(
-            visibleState  = transitionState,
+            visibleState = transitionState,
             enter = fadeIn(),
             exit = fadeOut(),
             modifier =
@@ -150,7 +151,7 @@
                             )
                         }
                     }
-                },
+                }
             )
         }
     }
@@ -172,7 +173,7 @@
         areNotificationsVisible: Boolean,
         isShadeLayoutWide: Boolean,
         burnInParams: BurnInParameters?,
-        modifier: Modifier = Modifier
+        modifier: Modifier = Modifier,
     ) {
         if (!areNotificationsVisible) {
             return
@@ -192,10 +193,7 @@
                         if (burnInParams == null) {
                             it
                         } else {
-                            it.burnInAware(
-                                viewModel = aodBurnInViewModel,
-                                params = burnInParams,
-                            )
+                            it.burnInAware(viewModel = aodBurnInViewModel, params = burnInParams)
                         }
                     },
         )
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
index 5cb45e5..94c18cd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
@@ -16,11 +16,13 @@
 
 package com.android.systemui.notifications.ui.composable
 
+import androidx.compose.foundation.gestures.FlingBehavior
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.util.fastCoerceAtLeast
 import androidx.compose.ui.util.fastCoerceAtMost
+import com.android.compose.nestedscroll.OnStopScope
 import com.android.compose.nestedscroll.PriorityNestedScrollConnection
 import com.android.compose.nestedscroll.ScrollController
 
@@ -43,6 +45,7 @@
     isCurrentGestureOverscroll: () -> Boolean,
     onStart: (Float) -> Unit = {},
     onStop: (Float) -> Unit = {},
+    flingBehavior: FlingBehavior,
 ): PriorityNestedScrollConnection {
     return PriorityNestedScrollConnection(
         orientation = Orientation.Vertical,
@@ -77,8 +80,9 @@
                     return amountConsumed
                 }
 
-                override suspend fun onStop(initialVelocity: Float): Float {
-                    onStop(initialVelocity)
+                override suspend fun OnStopScope.onStop(initialVelocity: Float): Float {
+                    val consumedByScroll = flingToScroll(initialVelocity, flingBehavior)
+                    onStop(initialVelocity - consumedByScroll)
                     if (scrimOffset() < minScrimOffset()) {
                         animateScrimOffset(minScrimOffset())
                     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
index e1b74a9..d8abfd7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
@@ -18,7 +18,9 @@
 
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.tween
+import androidx.compose.foundation.gestures.FlingBehavior
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.ScrollableDefaults
 import androidx.compose.foundation.layout.offset
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
@@ -30,6 +32,7 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.fastCoerceAtLeast
+import com.android.compose.nestedscroll.OnStopScope
 import com.android.compose.nestedscroll.PriorityNestedScrollConnection
 import com.android.compose.nestedscroll.ScrollController
 import kotlin.math.max
@@ -46,32 +49,35 @@
     val screenHeight =
         with(LocalDensity.current) { LocalConfiguration.current.screenHeightDp.dp.toPx() }
     val overscrollOffset = remember { Animatable(0f) }
-    val stackNestedScrollConnection = remember {
-        NotificationStackNestedScrollConnection(
-            stackOffset = { overscrollOffset.value },
-            canScrollForward = canScrollForward,
-            onScroll = { offsetAvailable ->
-                coroutineScope.launch {
-                    val maxProgress = screenHeight * 0.2f
-                    val tilt = 3f
-                    var offset =
-                        overscrollOffset.value +
-                            maxProgress * tanh(x = offsetAvailable / (maxProgress * tilt))
-                    offset = max(offset, -1f * maxProgress)
-                    overscrollOffset.snapTo(offset)
-                }
-            },
-            onStop = { velocityAvailable ->
-                coroutineScope.launch {
-                    overscrollOffset.animateTo(
-                        targetValue = 0f,
-                        initialVelocity = velocityAvailable,
-                        animationSpec = tween(),
-                    )
-                }
-            },
-        )
-    }
+    val flingBehavior = ScrollableDefaults.flingBehavior()
+    val stackNestedScrollConnection =
+        remember(flingBehavior) {
+            NotificationStackNestedScrollConnection(
+                stackOffset = { overscrollOffset.value },
+                canScrollForward = canScrollForward,
+                onScroll = { offsetAvailable ->
+                    coroutineScope.launch {
+                        val maxProgress = screenHeight * 0.2f
+                        val tilt = 3f
+                        var offset =
+                            overscrollOffset.value +
+                                maxProgress * tanh(x = offsetAvailable / (maxProgress * tilt))
+                        offset = max(offset, -1f * maxProgress)
+                        overscrollOffset.snapTo(offset)
+                    }
+                },
+                onStop = { velocityAvailable ->
+                    coroutineScope.launch {
+                        overscrollOffset.animateTo(
+                            targetValue = 0f,
+                            initialVelocity = velocityAvailable,
+                            animationSpec = tween(),
+                        )
+                    }
+                },
+                flingBehavior = flingBehavior,
+            )
+        }
 
     return this.then(
         Modifier.nestedScroll(stackNestedScrollConnection).offset {
@@ -86,6 +92,7 @@
     onStart: (Float) -> Unit = {},
     onScroll: (Float) -> Unit,
     onStop: (Float) -> Unit = {},
+    flingBehavior: FlingBehavior,
 ): PriorityNestedScrollConnection {
     return PriorityNestedScrollConnection(
         orientation = Orientation.Vertical,
@@ -106,8 +113,9 @@
                     return consumed
                 }
 
-                override suspend fun onStop(initialVelocity: Float): Float {
-                    onStop(initialVelocity)
+                override suspend fun OnStopScope.onStop(initialVelocity: Float): Float {
+                    val consumedByScroll = flingToScroll(initialVelocity, flingBehavior)
+                    onStop(initialVelocity - consumedByScroll)
                     return initialVelocity
                 }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 4fa1984..ae273d8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -25,6 +25,7 @@
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.ScrollableDefaults
 import androidx.compose.foundation.gestures.animateScrollBy
 import androidx.compose.foundation.gestures.rememberScrollableState
 import androidx.compose.foundation.gestures.scrollBy
@@ -451,12 +452,14 @@
         }
     }
 
+    val flingBehavior = ScrollableDefaults.flingBehavior()
     val scrimNestedScrollConnection =
         shadeSession.rememberSession(
             scrimOffset,
             maxScrimTop,
             minScrimTop,
             isCurrentGestureOverscroll,
+            flingBehavior,
         ) {
             NotificationScrimNestedScrollConnection(
                 scrimOffset = { scrimOffset.value },
@@ -469,6 +472,7 @@
                 contentHeight = { stackHeight.intValue.toFloat() },
                 minVisibleScrimHeight = minVisibleScrimHeight,
                 isCurrentGestureOverscroll = { isCurrentGestureOverscroll.value },
+                flingBehavior = flingBehavior,
             )
         }
 
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/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
index 2a91bd8..26c827a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
@@ -43,6 +43,7 @@
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
 import com.android.systemui.qs.composefragment.ui.GridAnchor
 import com.android.systemui.qs.panels.ui.compose.EditMode
 import com.android.systemui.qs.panels.ui.compose.TileGrid
@@ -53,8 +54,11 @@
 import com.android.systemui.scene.ui.composable.Overlay
 import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
 import com.android.systemui.shade.ui.composable.OverlayShade
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import com.android.systemui.statusbar.phone.ui.StatusBarIconController
 import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 
@@ -67,6 +71,8 @@
     private val tintedIconManagerFactory: TintedIconManager.Factory,
     private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
     private val statusBarIconController: StatusBarIconController,
+    private val notificationStackScrollView: Lazy<NotificationScrollView>,
+    private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
 ) : Overlay {
 
     override val key = Overlays.QuickSettingsShade
@@ -98,6 +104,14 @@
 
                 ShadeBody(viewModel = viewModel.quickSettingsContainerViewModel)
             }
+
+            SnoozeableHeadsUpNotificationSpace(
+                stackScrollView = notificationStackScrollView.get(),
+                viewModel =
+                    rememberViewModel("QuickSettingsShadeOverlay") {
+                        notificationsPlaceholderViewModelFactory.create()
+                    },
+            )
         }
     }
 }
diff --git a/packages/SystemUI/compose/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/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index 9d4408a..46f5ecd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -121,14 +121,15 @@
     val systemBars = WindowInsets.systemBarsIgnoringVisibility
     val displayCutout = WindowInsets.displayCutout
     val waterfall = WindowInsets.waterfall
-    val contentPadding = PaddingValues(all = OverlayShade.Dimensions.ScrimContentPadding)
+    val horizontalPadding =
+        PaddingValues(horizontal = dimensionResource(id = R.dimen.shade_panel_margin_horizontal))
 
     val combinedPadding =
         combinePaddings(
             systemBars.asPaddingValues(),
             displayCutout.asPaddingValues(),
             waterfall.asPaddingValues(),
-            contentPadding,
+            horizontalPadding,
         )
 
     return if (widthSizeClass == WindowWidthSizeClass.Compact) {
@@ -174,7 +175,6 @@
     }
 
     object Dimensions {
-        val ScrimContentPadding = 16.dp
         val PanelCornerRadius = 46.dp
         val OverscrollLimit = 32.dp
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index 28a12f8..581fb9d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -92,7 +92,7 @@
                 onValueChangeFinished = { sliderViewModel.onValueChangeFinished() },
                 onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
                 sliderColors = sliderColors,
-                hapticsViewModelFactory = sliderViewModel.hapticsViewModelFactory,
+                hapticsViewModelFactory = sliderViewModel.getSliderHapticsViewModelFactory(),
             )
 
             ExpandButton(
@@ -142,7 +142,8 @@
                             onValueChangeFinished = { sliderViewModel.onValueChangeFinished() },
                             onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
                             sliderColors = sliderColors,
-                            hapticsViewModelFactory = sliderViewModel.hapticsViewModelFactory,
+                            hapticsViewModelFactory =
+                                sliderViewModel.getSliderHapticsViewModelFactory(),
                         )
                     }
                 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
index a0e46d5..3718b10 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
@@ -49,7 +49,7 @@
                 onValueChangeFinished = { sliderViewModel.onValueChangeFinished() },
                 onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
                 sliderColors = sliderColors,
-                hapticsViewModelFactory = sliderViewModel.hapticsViewModelFactory,
+                hapticsViewModelFactory = sliderViewModel.getSliderHapticsViewModelFactory(),
             )
         }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index eb79b90..97ce429 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -48,7 +48,6 @@
 import androidx.compose.ui.unit.dp
 import com.android.compose.PlatformSlider
 import com.android.compose.PlatformSliderColors
-import com.android.systemui.Flags
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.compose.modifiers.sysuiResTag
@@ -66,15 +65,15 @@
     onIconTapped: () -> Unit,
     modifier: Modifier = Modifier,
     sliderColors: PlatformSliderColors,
-    hapticsViewModelFactory: SliderHapticsViewModel.Factory,
+    hapticsViewModelFactory: SliderHapticsViewModel.Factory?,
 ) {
     val value by valueState(state)
     val interactionSource = remember { MutableInteractionSource() }
     val sliderStepSize = 1f / (state.valueRange.endInclusive - state.valueRange.start)
     val hapticsViewModel: SliderHapticsViewModel? =
-        if (Flags.hapticsForComposeSliders()) {
+        hapticsViewModelFactory?.let {
             rememberViewModel(traceName = "SliderHapticsViewModel") {
-                hapticsViewModelFactory.create(
+                it.create(
                     interactionSource,
                     state.valueRange,
                     Orientation.Horizontal,
@@ -93,8 +92,6 @@
                     ),
                 )
             }
-        } else {
-            null
         }
 
     // Perform haptics due to UI composition
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 7c7202a..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
@@ -27,6 +27,7 @@
 import com.android.compose.animation.scene.content.Content
 import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
+import com.android.compose.nestedscroll.OnStopScope
 import com.android.compose.nestedscroll.PriorityNestedScrollConnection
 import com.android.compose.nestedscroll.ScrollController
 import kotlin.math.absoluteValue
@@ -35,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
 }
 
 /**
@@ -95,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) {
@@ -106,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
@@ -123,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) {
@@ -152,7 +148,7 @@
             return updateDragController(swipes, swipeAnimation)
         }
 
-        val swipes = computeSwipes(startedPosition, pointersDown)
+        val swipes = computeSwipes(pointersInfo)
         val fromContent = layoutImpl.contentForUserActions()
 
         swipes.updateSwipesResults(fromContent)
@@ -189,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(),
@@ -199,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. */
@@ -497,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
     }
 
@@ -568,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) },
         )
     }
 
@@ -581,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
@@ -594,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
@@ -618,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
@@ -635,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
@@ -661,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) {
@@ -703,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) {
@@ -717,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,
@@ -741,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
             }
@@ -749,7 +710,7 @@
             return dragController.onDrag(delta = deltaScroll)
         }
 
-        override suspend fun onStop(initialVelocity: Float): Float {
+        override suspend fun OnStopScope.onStop(initialVelocity: Float): Float {
             return dragController
                 .onStop(velocity = initialVelocity, canChangeContent = canChangeScene)
                 .invoke()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 63c5d7a..e7b66c5 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -52,6 +52,7 @@
 import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.animation.scene.transformation.PropertyTransformation
 import com.android.compose.animation.scene.transformation.SharedElementTransformation
+import com.android.compose.animation.scene.transformation.TransformationWithRange
 import com.android.compose.modifiers.thenIf
 import com.android.compose.ui.graphics.drawInContainer
 import com.android.compose.ui.util.lerp
@@ -187,6 +188,7 @@
                 state.transformationSpec
                     .transformations(key, content.key)
                     .shared
+                    ?.transformation
                     ?.elevateInContent == content.key &&
                 isSharedElement(stateByContent, state) &&
                 isSharedElementEnabled(key, state) &&
@@ -901,7 +903,7 @@
     }
 
     val sharedTransformation = sharedElementTransformation(element.key, transition)
-    if (sharedTransformation?.enabled == false) {
+    if (sharedTransformation?.transformation?.enabled == false) {
         return true
     }
 
@@ -954,13 +956,13 @@
     element: ElementKey,
     transition: TransitionState.Transition,
 ): Boolean {
-    return sharedElementTransformation(element, transition)?.enabled ?: true
+    return sharedElementTransformation(element, transition)?.transformation?.enabled ?: true
 }
 
 internal fun sharedElementTransformation(
     element: ElementKey,
     transition: TransitionState.Transition,
-): SharedElementTransformation? {
+): TransformationWithRange<SharedElementTransformation>? {
     val transformationSpec = transition.transformationSpec
     val sharedInFromContent =
         transformationSpec.transformations(element, transition.fromContent).shared
@@ -1244,7 +1246,7 @@
     element: Element,
     transition: TransitionState.Transition?,
     contentValue: (Element.State) -> T,
-    transformation: (ElementTransformations) -> PropertyTransformation<T>?,
+    transformation: (ElementTransformations) -> TransformationWithRange<PropertyTransformation<T>>?,
     currentValue: () -> T,
     isSpecified: (T) -> Boolean,
     lerp: (T, T, Float) -> T,
@@ -1280,7 +1282,7 @@
                 checkNotNull(if (currentContent == toContent) toState else fromState)
             val idleValue = contentValue(overscrollState)
             val targetValue =
-                with(propertySpec) {
+                with(propertySpec.transformation) {
                     layoutImpl.propertyTransformationScope.transform(
                         currentContent,
                         element.key,
@@ -1375,7 +1377,7 @@
         val idleValue = contentValue(contentState)
         val isEntering = content == toContent
         val previewTargetValue =
-            with(previewTransformation) {
+            with(previewTransformation.transformation) {
                 layoutImpl.propertyTransformationScope.transform(
                     content,
                     element.key,
@@ -1386,7 +1388,7 @@
 
         val targetValueOrNull =
             transformation?.let { transformation ->
-                with(transformation) {
+                with(transformation.transformation) {
                     layoutImpl.propertyTransformationScope.transform(
                         content,
                         element.key,
@@ -1461,7 +1463,7 @@
 
     val idleValue = contentValue(contentState)
     val targetValue =
-        with(transformation) {
+        with(transformation.transformation) {
             layoutImpl.propertyTransformationScope.transform(
                 content,
                 element.key,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 8613f6d..ab2324a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -33,6 +33,7 @@
 import androidx.compose.ui.input.pointer.PointerId
 import androidx.compose.ui.input.pointer.PointerInputChange
 import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.input.pointer.PointerType
 import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
 import androidx.compose.ui.input.pointer.changedToDown
 import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
@@ -52,6 +53,7 @@
 import androidx.compose.ui.util.fastAny
 import androidx.compose.ui.util.fastFilter
 import androidx.compose.ui.util.fastFirstOrNull
+import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastSumBy
 import com.android.compose.ui.util.SpaceVectorConverter
 import kotlin.coroutines.cancellation.CancellationException
@@ -78,8 +80,8 @@
 @Stable
 internal fun Modifier.multiPointerDraggable(
     orientation: Orientation,
-    startDragImmediately: (startedPosition: Offset) -> Boolean,
-    onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+    startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
+    onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
     onFirstPointerDown: () -> Unit = {},
     swipeDetector: SwipeDetector = DefaultSwipeDetector,
     dispatcher: NestedScrollDispatcher,
@@ -97,9 +99,8 @@
 
 private data class MultiPointerDraggableElement(
     private val orientation: Orientation,
-    private val startDragImmediately: (startedPosition: Offset) -> Boolean,
-    private val onDragStarted:
-        (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+    private val startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
+    private val onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
     private val onFirstPointerDown: () -> Unit,
     private val swipeDetector: SwipeDetector,
     private val dispatcher: NestedScrollDispatcher,
@@ -125,9 +126,8 @@
 
 internal class MultiPointerDraggableNode(
     orientation: Orientation,
-    var startDragImmediately: (startedPosition: Offset) -> Boolean,
-    var onDragStarted:
-        (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+    var startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
+    var onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
     var onFirstPointerDown: () -> Unit,
     swipeDetector: SwipeDetector = DefaultSwipeDetector,
     private val dispatcher: NestedScrollDispatcher,
@@ -183,17 +183,22 @@
         pointerInput.onPointerEvent(pointerEvent, pass, bounds)
     }
 
+    private var lastPointerEvent: PointerEvent? = null
     private var startedPosition: Offset? = null
     private var pointersDown: Int = 0
-    private var isMouseWheel: Boolean = false
 
-    internal fun pointersInfo(): PointersInfo {
-        return PointersInfo(
+    internal fun pointersInfo(): PointersInfo? {
+        val startedPosition = startedPosition
+        val lastPointerEvent = lastPointerEvent
+        if (startedPosition == null || lastPointerEvent == null) {
             // This may be null, i.e. when the user uses TalkBack
+            return null
+        }
+
+        return PointersInfo(
             startedPosition = startedPosition,
-            // We could have 0 pointers during fling or for other reasons.
-            pointersDown = pointersDown.coerceAtLeast(1),
-            isMouseWheel = isMouseWheel,
+            pointersDown = pointersDown,
+            lastPointerEvent = lastPointerEvent,
         )
     }
 
@@ -212,8 +217,8 @@
                 if (pointerEvent.type == PointerEventType.Enter) continue
 
                 val changes = pointerEvent.changes
+                lastPointerEvent = pointerEvent
                 pointersDown = changes.countDown()
-                isMouseWheel = pointerEvent.type == PointerEventType.Scroll
 
                 when {
                     // There are no more pointers down.
@@ -285,8 +290,8 @@
                     detectDragGestures(
                         orientation = orientation,
                         startDragImmediately = startDragImmediately,
-                        onDragStart = { startedPosition, overSlop, pointersDown ->
-                            onDragStarted(startedPosition, overSlop, pointersDown)
+                        onDragStart = { pointersInfo, overSlop ->
+                            onDragStarted(pointersInfo, overSlop)
                         },
                         onDrag = { controller, amount ->
                             dispatchScrollEvents(
@@ -435,9 +440,8 @@
      */
     private suspend fun AwaitPointerEventScope.detectDragGestures(
         orientation: Orientation,
-        startDragImmediately: (startedPosition: Offset) -> Boolean,
-        onDragStart:
-            (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+        startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
+        onDragStart: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
         onDrag: (controller: DragController, dragAmount: Float) -> Unit,
         onDragEnd: (controller: DragController) -> Unit,
         onDragCancel: (controller: DragController) -> Unit,
@@ -462,8 +466,13 @@
                 .first()
 
         var overSlop = 0f
+        var lastPointersInfo =
+            checkNotNull(pointersInfo()) {
+                "We should have pointers down, last event: $currentEvent"
+            }
+
         val drag =
-            if (startDragImmediately(consumablePointer.position)) {
+            if (startDragImmediately(lastPointersInfo)) {
                 consumablePointer.consume()
                 consumablePointer
             } else {
@@ -488,14 +497,18 @@
                                 consumablePointer.id,
                                 onSlopReached,
                             )
-                    }
+                    } ?: return
 
+                lastPointersInfo =
+                    checkNotNull(pointersInfo()) {
+                        "We should have pointers down, last event: $currentEvent"
+                    }
                 // Make sure that overSlop is not 0f. This can happen when the user drags by exactly
                 // the touch slop. However, the overSlop we pass to onDragStarted() is used to
                 // compute the direction we are dragging in, so overSlop should never be 0f unless
                 // we intercept an ongoing swipe transition (i.e. startDragImmediately() returned
                 // true).
-                if (drag != null && overSlop == 0f) {
+                if (overSlop == 0f) {
                     val delta = (drag.position - consumablePointer.position).toFloat()
                     check(delta != 0f) { "delta is equal to 0" }
                     overSlop = delta.sign
@@ -503,49 +516,38 @@
                 drag
             }
 
-        if (drag != null) {
-            val controller =
-                onDragStart(
-                    // The startedPosition is the starting position when a gesture begins (when the
-                    // first pointer touches the screen), not the point where we begin dragging.
-                    // For example, this could be different if one of our children intercepts the
-                    // gesture first and then we do.
-                    requireNotNull(startedPosition),
-                    overSlop,
-                    pointersDown,
+        val controller = onDragStart(lastPointersInfo, overSlop)
+
+        val successful: Boolean
+        try {
+            onDrag(controller, overSlop)
+
+            successful =
+                drag(
+                    initialPointerId = drag.id,
+                    hasDragged = { it.positionChangeIgnoreConsumed().toFloat() != 0f },
+                    onDrag = {
+                        onDrag(controller, it.positionChange().toFloat())
+                        it.consume()
+                    },
+                    onIgnoredEvent = {
+                        // We are still dragging an object, but this event is not of interest to the
+                        // caller.
+                        // This event will not trigger the onDrag event, but we will consume the
+                        // event to prevent another pointerInput from interrupting the current
+                        // gesture just because the event was ignored.
+                        it.consume()
+                    },
                 )
+        } catch (t: Throwable) {
+            onDragCancel(controller)
+            throw t
+        }
 
-            val successful: Boolean
-            try {
-                onDrag(controller, overSlop)
-
-                successful =
-                    drag(
-                        initialPointerId = drag.id,
-                        hasDragged = { it.positionChangeIgnoreConsumed().toFloat() != 0f },
-                        onDrag = {
-                            onDrag(controller, it.positionChange().toFloat())
-                            it.consume()
-                        },
-                        onIgnoredEvent = {
-                            // We are still dragging an object, but this event is not of interest to
-                            // the caller.
-                            // This event will not trigger the onDrag event, but we will consume the
-                            // event to prevent another pointerInput from interrupting the current
-                            // gesture just because the event was ignored.
-                            it.consume()
-                        },
-                    )
-            } catch (t: Throwable) {
-                onDragCancel(controller)
-                throw t
-            }
-
-            if (successful) {
-                onDragEnd(controller)
-            } else {
-                onDragCancel(controller)
-            }
+        if (successful) {
+            onDragEnd(controller)
+        } else {
+            onDragCancel(controller)
         }
     }
 
@@ -655,11 +657,57 @@
 }
 
 internal fun interface PointersInfoOwner {
-    fun pointersInfo(): PointersInfo
+    /**
+     * Provides information about the pointers interacting with this composable.
+     *
+     * @return A [PointersInfo] object containing details about the pointers, including the starting
+     *   position and the number of pointers down, or `null` if there are no pointers down.
+     */
+    fun pointersInfo(): PointersInfo?
 }
 
+/**
+ * Holds information about pointer interactions within a composable.
+ *
+ * This class stores details such as the starting position of a gesture, the number of pointers
+ * down, and whether the last pointer event was a mouse wheel scroll.
+ *
+ * @param startedPosition The starting position of the gesture. This is the position where the first
+ *   pointer touched the screen, not necessarily the point where dragging begins. This may be
+ *   different from the initial touch position if a child composable intercepts the gesture before
+ *   this one.
+ * @param pointersDown The number of pointers currently down.
+ * @param isMouseWheel Indicates whether the last pointer event was a mouse wheel scroll.
+ * @param pointersDownByType Provide a map of pointer types to the count of pointers of that type
+ *   currently down/pressed.
+ */
 internal data class PointersInfo(
-    val startedPosition: Offset?,
+    val startedPosition: Offset,
     val pointersDown: Int,
     val isMouseWheel: Boolean,
-)
+    val pointersDownByType: Map<PointerType, Int>,
+) {
+    init {
+        check(pointersDown > 0) { "We should have at least 1 pointer down, $pointersDown instead" }
+    }
+}
+
+private fun PointersInfo(
+    startedPosition: Offset,
+    pointersDown: Int,
+    lastPointerEvent: PointerEvent,
+): PointersInfo {
+    return PointersInfo(
+        startedPosition = startedPosition,
+        pointersDown = pointersDown,
+        isMouseWheel = lastPointerEvent.type == PointerEventType.Scroll,
+        pointersDownByType =
+            buildMap {
+                lastPointerEvent.changes.fastForEach { change ->
+                    if (!change.pressed) return@fastForEach
+                    val newValue = (get(change.type) ?: 0) + 1
+                    put(change.type, newValue)
+                }
+            },
+    )
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
index 077927d..5bf77ae 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
@@ -233,6 +233,12 @@
             (to == null || this.toContent == to)
     }
 
+    fun isTransitioningSets(from: Set<ContentKey>? = null, to: Set<ContentKey>? = null): Boolean {
+        return this is Transition &&
+            (from == null || from.contains(this.fromContent)) &&
+            (to == null || to.contains(this.toContent))
+    }
+
     /** Whether we are transitioning from [content] to [other], or from [other] to [content]. */
     fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean {
         return isTransitioning(from = content, to = other) ||
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
index b00c8ad..8a6a0d6 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
@@ -70,6 +70,7 @@
             // The predictive back APIs will automatically animate the progress for us in this case
             // so there is no need to animate it.
             cancelSpec = snap(),
+            animationScope = layoutImpl.animationScope,
         )
     }
 }
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/animation/scene/transition/Seek.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/Seek.kt
index 715d979..2b33224 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/Seek.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/Seek.kt
@@ -30,6 +30,8 @@
 import com.android.compose.animation.scene.UserActionResult
 import com.android.compose.animation.scene.createSwipeAnimation
 import kotlin.coroutines.cancellation.CancellationException
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.collectLatest
@@ -141,6 +143,7 @@
     progress: Flow<Float>,
     commitSpec: AnimationSpec<Float>?,
     cancelSpec: AnimationSpec<Float>?,
+    animationScope: CoroutineScope? = null,
 ) {
     fun animateOffset(targetContent: T, spec: AnimationSpec<Float>?) {
         if (state.transitionState != animation.contentTransition || animation.isAnimatingOffset()) {
@@ -176,12 +179,20 @@
         }
 
         // Start the transition.
-        state.startTransition(animation.contentTransition)
+        animationScope?.launch { startTransition(state, animation, collectionJob) }
+            ?: startTransition(state, animation, collectionJob)
+    }
+}
 
-        // The transition is done. Cancel the collection in case the transition was finished because
-        // it was interrupted by another transition.
-        if (collectionJob.isActive) {
-            collectionJob.cancel()
-        }
+private suspend fun <T : ContentKey> startTransition(
+    state: MutableSceneTransitionLayoutStateImpl,
+    animation: SwipeAnimation<T>,
+    progressCollectionJob: Job,
+) {
+    state.startTransition(animation.contentTransition)
+    // The transition is done. Cancel the collection in case the transition was finished
+    // because it was interrupted by another transition.
+    if (progressCollectionJob.isActive) {
+        progressCollectionJob.cancel()
     }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
index 255da31..a5be4dc 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
@@ -16,6 +16,7 @@
 
 package com.android.compose.nestedscroll
 
+import androidx.compose.foundation.gestures.FlingBehavior
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
@@ -41,6 +42,7 @@
     onHeightChanged: (Float) -> Unit,
     minHeight: () -> Float,
     maxHeight: () -> Float,
+    flingBehavior: FlingBehavior,
 ): PriorityNestedScrollConnection {
     return PriorityNestedScrollConnection(
         orientation = Orientation.Vertical,
@@ -55,7 +57,15 @@
             offsetAvailable > 0 && height() < maxHeight()
         },
         canStartPostFling = { false },
-        onStart = { LargeTopAppBarScrollController(height, maxHeight, minHeight, onHeightChanged) },
+        onStart = {
+            LargeTopAppBarScrollController(
+                height = height,
+                maxHeight = maxHeight,
+                minHeight = minHeight,
+                onHeightChanged = onHeightChanged,
+                flingBehavior = flingBehavior,
+            )
+        },
     )
 }
 
@@ -64,6 +74,7 @@
     val maxHeight: () -> Float,
     val minHeight: () -> Float,
     val onHeightChanged: (Float) -> Unit,
+    val flingBehavior: FlingBehavior,
 ) : ScrollController {
     override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float {
         val currentHeight = height()
@@ -79,9 +90,8 @@
         return amountConsumed
     }
 
-    override suspend fun onStop(initialVelocity: Float): Float {
-        // Don't consume the velocity on pre/post fling
-        return 0f
+    override suspend fun OnStopScope.onStop(initialVelocity: Float): Float {
+        return flingToScroll(initialVelocity, flingBehavior)
     }
 
     override fun onCancel() {
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 ca44a5c..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
@@ -16,7 +16,9 @@
 
 package com.android.compose.nestedscroll
 
+import androidx.compose.foundation.gestures.FlingBehavior
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.ScrollScope
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
@@ -83,7 +85,16 @@
      * @param initialVelocity The initial velocity of the scroll when stopping.
      * @return The consumed [initialVelocity] when the animation completes.
      */
-    suspend fun onStop(initialVelocity: Float): Float
+    suspend fun OnStopScope.onStop(initialVelocity: Float): Float
+}
+
+interface OnStopScope {
+    /**
+     * Emits scroll events by using the [initialVelocity] and the [FlingBehavior].
+     *
+     * @return consumed velocity
+     */
+    suspend fun flingToScroll(initialVelocity: Float, flingBehavior: FlingBehavior): Float
 }
 
 /**
@@ -196,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.
@@ -208,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
 
@@ -304,10 +321,15 @@
      * @return The consumed velocity.
      */
     suspend fun stop(velocity: Float): Velocity {
+        if (isStopping) return Velocity.Zero
         val controller = requireController(isStopping = false)
         return coroutineScope {
             try {
-                async { controller.onStop(velocity) }
+                async {
+                        with(controller) {
+                            OnStopScopeImpl(controller = controller).onStop(velocity)
+                        }
+                    }
                     // Allows others to interrupt the job.
                     .also { stoppingJob = it }
                     // Note: this can be cancelled by [interruptStopping]
@@ -336,3 +358,19 @@
         offsetScrolledBeforePriorityMode = 0f
     }
 }
+
+private class OnStopScopeImpl(private val controller: ScrollController) : OnStopScope {
+    override suspend fun flingToScroll(
+        initialVelocity: Float,
+        flingBehavior: FlingBehavior,
+    ): Float {
+        return with(flingBehavior) {
+            object : ScrollScope {
+                    override fun scrollBy(pixels: Float): Float {
+                        return controller.onScroll(pixels, NestedScrollSource.SideEffect)
+                    }
+                }
+                .performFling(initialVelocity)
+        }
+    }
+}
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/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
index a406e13..e27f9b5 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.compose.nestedscroll
 
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.ScrollScope
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
@@ -29,6 +31,13 @@
     val scrollSource = testCase.scrollSource
 
     private var height = 0f
+    private val customFlingBehavior =
+        object : FlingBehavior {
+            override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
+                scrollBy(initialVelocity)
+                return initialVelocity / 2f
+            }
+        }
 
     private fun buildScrollConnection(heightRange: ClosedFloatingPointRange<Float>) =
         LargeTopAppBarNestedScrollConnection(
@@ -36,6 +45,7 @@
             onHeightChanged = { height = it },
             minHeight = { heightRange.start },
             maxHeight = { heightRange.endInclusive },
+            flingBehavior = customFlingBehavior,
         )
 
     private fun NestedScrollConnection.scroll(
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 0364cdc..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
@@ -18,14 +18,19 @@
 
 package com.android.compose.nestedscroll
 
+import androidx.compose.foundation.gestures.FlingBehavior
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.ScrollScope
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.UserInput
 import androidx.compose.ui.unit.Velocity
 import androidx.test.ext.junit.runners.AndroidJUnit4
+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
@@ -41,7 +46,16 @@
     private var consumeScroll = true
     private var lastStop: Float? = null
     private var isCancelled: Boolean = false
-    private var consumeStop = true
+    private var onStopConsumeFlingToScroll = false
+    private var onStopConsumeAll = true
+
+    private val customFlingBehavior =
+        object : FlingBehavior {
+            override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
+                scrollBy(initialVelocity)
+                return initialVelocity / 2f
+            }
+        }
 
     private val scrollConnection =
         PriorityNestedScrollConnection(
@@ -57,9 +71,16 @@
                         return if (consumeScroll) deltaScroll else 0f
                     }
 
-                    override suspend fun onStop(initialVelocity: Float): Float {
+                    override suspend fun OnStopScope.onStop(initialVelocity: Float): Float {
                         lastStop = initialVelocity
-                        return if (consumeStop) initialVelocity else 0f
+                        var velocityConsumed = 0f
+                        if (onStopConsumeFlingToScroll) {
+                            velocityConsumed = flingToScroll(initialVelocity, customFlingBehavior)
+                        }
+                        if (onStopConsumeAll) {
+                            velocityConsumed = initialVelocity
+                        }
+                        return velocityConsumed
                     }
 
                     override fun onCancel() {
@@ -178,6 +199,22 @@
     }
 
     @Test
+    fun onStopScrollUsingFlingToScroll() = runMonotonicClockTest {
+        startPriorityModePostScroll()
+        onStopConsumeFlingToScroll = true
+        onStopConsumeAll = false
+        lastScroll = Float.NaN
+
+        val consumed = scrollConnection.onPreFling(available = Velocity(2f, 2f))
+
+        assertThat(lastStop).isEqualTo(2f)
+        // flingToScroll should try to scroll the content, customFlingBehavior uses the velocity.
+        assertThat(lastScroll).isEqualTo(2f)
+        // customFlingBehavior returns half of the vertical velocity.
+        assertThat(consumed).isEqualTo(Velocity(0f, 1f))
+    }
+
+    @Test
     fun ifCannotStopOnPreFling_shouldStopOnPostFling() = runTest {
         startPriorityModePostScroll()
         canStopOnPreFling = false
@@ -227,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/res/drawable/clock_default_thumbnail.xml b/packages/SystemUI/customization/res/drawable/clock_default_thumbnail.xml
index be72d0b..3951e4c 100644
--- a/packages/SystemUI/customization/res/drawable/clock_default_thumbnail.xml
+++ b/packages/SystemUI/customization/res/drawable/clock_default_thumbnail.xml
@@ -14,7 +14,22 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle">
-    <solid android:color="#FFFF00FF" />
-</shape>
+  <vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="36dp"
+    android:height="50dp"
+    android:viewportWidth="36"
+    android:viewportHeight="50">
+
+    <path
+        android:pathData="M8.592,22.712C11.052,22.712 13.063,21.635 14.628,19.48C16.193,17.326 16.976,14.623 16.976,11.372C16.976,8.059 16.198,5.346 14.644,3.232C13.099,1.109 11.082,0.047 8.592,0.047C6.113,0.047 4.096,1.109 2.541,3.232C0.997,5.346 0.225,8.059 0.225,11.372C0.225,14.664 0.997,17.377 2.541,19.511C4.096,21.645 6.113,22.712 8.592,22.712ZM8.592,18.901C7.322,18.901 6.301,18.225 5.529,16.874C4.757,15.523 4.37,13.699 4.37,11.402C4.37,9.045 4.757,7.206 5.529,5.885C6.301,4.553 7.322,3.888 8.592,3.888C9.873,3.888 10.899,4.543 11.671,5.854C12.444,7.155 12.83,9.004 12.83,11.402C12.83,13.78 12.449,15.624 11.686,16.935C10.924,18.246 9.893,18.901 8.592,18.901Z"
+        android:fillColor="#FFFFFF"/>
+    <path
+        android:pathData="M8.141,39.462C9.289,39.462 10.168,39.742 10.778,40.301C11.387,40.849 11.692,41.627 11.692,42.633C11.692,43.7 11.377,44.538 10.747,45.148C10.117,45.747 9.254,46.047 8.156,46.047C7.14,46.047 6.353,45.778 5.794,45.239C5.245,44.701 4.859,44.132 4.635,43.532C4.483,43.115 4.249,42.831 3.934,42.679C3.629,42.516 3.182,42.521 2.593,42.694C2.034,42.856 1.638,43.136 1.404,43.532C1.18,43.918 1.155,44.375 1.328,44.904C1.694,46.113 2.486,47.2 3.706,48.166C4.925,49.121 6.49,49.598 8.4,49.598C10.585,49.598 12.343,48.948 13.674,47.647C15.005,46.336 15.67,44.695 15.67,42.724C15.67,41.383 15.279,40.209 14.497,39.203C13.714,38.197 12.703,37.583 11.464,37.359V37.298C12.51,36.973 13.323,36.373 13.902,35.5C14.482,34.616 14.771,33.574 14.771,32.375C14.771,30.597 14.192,29.225 13.034,28.26C11.885,27.284 10.336,26.796 8.385,26.796C6.789,26.796 5.443,27.172 4.346,27.924C3.258,28.666 2.496,29.57 2.059,30.637C1.826,31.135 1.795,31.587 1.968,31.994C2.151,32.4 2.501,32.715 3.02,32.939C3.579,33.183 4.015,33.244 4.33,33.122C4.656,32.99 4.92,32.741 5.123,32.375C5.469,31.704 5.87,31.196 6.327,30.851C6.784,30.495 7.409,30.317 8.202,30.317H8.217C9.152,30.317 9.879,30.581 10.397,31.11C10.925,31.638 11.189,32.38 11.189,33.335C11.189,34.209 10.89,34.915 10.29,35.454C9.691,35.992 8.923,36.262 7.989,36.262H6.906C6.378,36.262 5.971,36.389 5.687,36.643C5.402,36.887 5.26,37.278 5.26,37.816C5.26,38.334 5.402,38.741 5.687,39.036C5.971,39.32 6.378,39.462 6.906,39.462H8.141Z"
+        android:fillColor="#FFFFFF"/>
+    <path
+        android:pathData="M23.263,7.531C23.263,6.372 23.644,5.432 24.406,4.711C25.178,3.979 26.153,3.613 27.332,3.613C28.531,3.613 29.507,3.984 30.259,4.726C31.021,5.458 31.402,6.393 31.402,7.531C31.402,8.679 30.99,9.639 30.167,10.411C29.354,11.184 28.404,11.57 27.317,11.57C26.169,11.57 25.203,11.194 24.421,10.442C23.649,9.68 23.263,8.709 23.263,7.531ZM26.936,22.026C27.271,21.558 27.759,20.867 28.399,19.953C29.049,19.038 30.04,17.595 31.371,15.624C32.144,14.476 32.956,13.236 33.81,11.905C34.664,10.574 35.09,9.116 35.09,7.531C35.09,5.468 34.333,3.705 32.819,2.242C31.315,0.778 29.466,0.047 27.271,0.047C25.198,0.047 23.4,0.778 21.875,2.242C20.361,3.705 19.604,5.498 19.604,7.622C19.604,9.756 20.331,11.524 21.784,12.926C23.237,14.318 24.959,15.014 26.951,15.014C27.754,15.014 28.303,14.959 28.597,14.847C28.892,14.725 29.151,14.603 29.375,14.481L28.536,13.292L28.277,13.643C27.932,14.12 27.545,14.659 27.119,15.258C26.702,15.858 26.285,16.468 25.869,17.087C25.351,17.829 24.929,18.429 24.604,18.886C24.289,19.343 24.06,19.678 23.918,19.892C23.573,20.4 23.456,20.872 23.567,21.309C23.689,21.736 24.004,22.107 24.512,22.422C25.01,22.767 25.457,22.905 25.854,22.834C26.26,22.773 26.621,22.503 26.936,22.026Z"
+        android:fillColor="#FFFFFF"/>
+    <path
+        android:pathData="M27.406,49.537C29.865,49.537 31.877,48.46 33.442,46.306C35.007,44.152 35.789,41.449 35.789,38.197C35.789,34.885 35.012,32.172 33.457,30.058C31.912,27.934 29.895,26.873 27.406,26.873C24.927,26.873 22.91,27.934 21.355,30.058C19.81,32.172 19.038,34.885 19.038,38.197C19.038,41.49 19.81,44.203 21.355,46.336C22.91,48.47 24.927,49.537 27.406,49.537ZM27.406,45.727C26.136,45.727 25.115,45.051 24.342,43.7C23.57,42.348 23.184,40.524 23.184,38.228C23.184,35.87 23.57,34.031 24.342,32.71C25.115,31.379 26.136,30.713 27.406,30.713C28.686,30.713 29.712,31.369 30.485,32.68C31.257,33.98 31.643,35.83 31.643,38.228C31.643,40.605 31.262,42.45 30.5,43.761C29.738,45.071 28.707,45.727 27.406,45.727Z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt
index f5e8432..bcf055b 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt
@@ -166,12 +166,6 @@
     // A color literal like `#FF00FF` or a color resource like `@android:color/system_accent1_100`
     val fillColorDark: String? = null,
     override val fontSizeScale: Float? = null,
-    /**
-     * use `wdth` for width, `wght` for weight, 'opsz' for optical size single quote for tag name,
-     * and no quote for value separate different axis with `,` e.g. "'wght' 1000, 'wdth' 108, 'opsz'
-     * 90"
-     */
-    var fontVariation: String? = null,
     // used when alternate in one font file is needed
     var fontFeatureSettings: String? = null,
     val renderType: RenderType = RenderType.STROKE_TEXT,
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 9da3022..12b20a5 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -44,6 +44,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
+import org.json.JSONObject
 
 private val KEY_TIMESTAMP = "appliedTimestamp"
 private val KNOWN_PLUGINS =
@@ -299,7 +300,7 @@
                         )
                     }
 
-                ClockSettings.deserialize(json)
+                ClockSettings.fromJson(JSONObject(json))
             } catch (ex: Exception) {
                 logger.e("Failed to parse clock settings", ex)
                 null
@@ -312,21 +313,24 @@
         assert.isNotMainThread()
 
         try {
-            value?.metadata?.put(KEY_TIMESTAMP, System.currentTimeMillis())
+            val json =
+                value?.let {
+                    it.metadata.put(KEY_TIMESTAMP, System.currentTimeMillis())
+                    ClockSettings.toJson(it)
+                } ?: ""
 
-            val json = ClockSettings.serialize(value)
             if (handleAllUsers) {
                 Settings.Secure.putStringForUser(
                     context.contentResolver,
                     Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
-                    json,
+                    json.toString(),
                     ActivityManager.getCurrentUser(),
                 )
             } else {
                 Settings.Secure.putString(
                     context.contentResolver,
                     Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
-                    json,
+                    json.toString(),
                 )
             }
         } catch (ex: Exception) {
@@ -557,14 +561,15 @@
     }
 
     fun getClocks(): List<ClockMetadata> {
-        if (!isEnabled) {
-            return listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata)
-        }
+        if (!isEnabled) return listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata)
         return availableClocks.map { (_, clock) -> clock.metadata }
     }
 
-    fun getClockPickerConfig(clockId: ClockId): ClockPickerConfig? =
-        availableClocks[clockId]?.provider?.getClockPickerConfig(clockId)
+    fun getClockPickerConfig(clockId: ClockId): ClockPickerConfig? {
+        val clockSettings =
+            settings?.let { if (clockId == it.clockId) it else null } ?: ClockSettings(clockId)
+        return availableClocks[clockId]?.provider?.getClockPickerConfig(clockSettings)
+    }
 
     fun createExampleClock(clockId: ClockId): ClockController? = createClock(clockId)
 
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index d70d61c..e9b58b0 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -19,10 +19,9 @@
 import com.android.systemui.customization.R
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.core.LogcatOnlyMessageBuffer
-import com.android.systemui.plugins.clocks.AxisType
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockFontAxis
-import com.android.systemui.plugins.clocks.ClockId
+import com.android.systemui.plugins.clocks.ClockFontAxisSetting
 import com.android.systemui.plugins.clocks.ClockMessageBuffers
 import com.android.systemui.plugins.clocks.ClockMetadata
 import com.android.systemui.plugins.clocks.ClockPickerConfig
@@ -61,7 +60,15 @@
                 messageBuffers?.infraMessageBuffer ?: LogcatOnlyMessageBuffer(LogLevel.INFO)
             val assets = AssetLoader(ctx, ctx, "clocks/", buffer)
             assets.setSeedColor(settings.seedColor, null)
-            FlexClockController(ctx, resources, assets, FLEX_DESIGN, messageBuffers)
+            val fontAxes = ClockFontAxis.merge(FlexClockController.FONT_AXES, settings.axes)
+            FlexClockController(
+                ctx,
+                resources,
+                settings.copy(axes = fontAxes.map { it.toSetting() }),
+                assets,
+                FLEX_DESIGN,
+                messageBuffers,
+            )
         } else {
             DefaultClockController(
                 ctx,
@@ -75,64 +82,42 @@
         }
     }
 
-    override fun getClockPickerConfig(id: ClockId): ClockPickerConfig {
-        if (id != DEFAULT_CLOCK_ID) {
-            throw IllegalArgumentException("$id is unsupported by $TAG")
+    override fun getClockPickerConfig(settings: ClockSettings): ClockPickerConfig {
+        if (settings.clockId != DEFAULT_CLOCK_ID) {
+            throw IllegalArgumentException("${settings.clockId} is unsupported by $TAG")
         }
 
+        val fontAxes =
+            if (!isClockReactiveVariantsEnabled) listOf()
+            else ClockFontAxis.merge(FlexClockController.FONT_AXES, settings.axes)
         return ClockPickerConfig(
             DEFAULT_CLOCK_ID,
             resources.getString(R.string.clock_default_name),
             resources.getString(R.string.clock_default_description),
-            // TODO(b/352049256): Update placeholder to actual resource
             resources.getDrawable(R.drawable.clock_default_thumbnail, null),
             isReactiveToTone = true,
-            // TODO(b/364673969): Populate the rest of this
-            axes =
-                if (isClockReactiveVariantsEnabled)
-                    listOf(
-                        ClockFontAxis(
-                            key = "wght",
-                            type = AxisType.Float,
-                            minValue = 1f,
-                            currentValue = 400f,
-                            maxValue = 1000f,
-                            name = "Weight",
-                            description = "Glyph Weight",
-                        ),
-                        ClockFontAxis(
-                            key = "wdth",
-                            type = AxisType.Float,
-                            minValue = 25f,
-                            currentValue = 100f,
-                            maxValue = 151f,
-                            name = "Width",
-                            description = "Glyph Width",
-                        ),
-                        ClockFontAxis(
-                            key = "ROND",
-                            type = AxisType.Boolean,
-                            minValue = 0f,
-                            currentValue = 0f,
-                            maxValue = 100f,
-                            name = "Round",
-                            description = "Glyph Roundness",
-                        ),
-                        ClockFontAxis(
-                            key = "slnt",
-                            type = AxisType.Boolean,
-                            minValue = 0f,
-                            currentValue = 0f,
-                            maxValue = -10f,
-                            name = "Slant",
-                            description = "Glyph Slant",
-                        ),
-                    )
-                else listOf(),
+            axes = fontAxes,
         )
     }
 
     companion object {
+        // TODO(b/364681643): Variations for retargetted DIGITAL_CLOCK_FLEX
+        val LEGACY_FLEX_LS_VARIATION =
+            listOf(
+                ClockFontAxisSetting("wght", 600f),
+                ClockFontAxisSetting("wdth", 100f),
+                ClockFontAxisSetting("ROND", 100f),
+                ClockFontAxisSetting("slnt", 0f),
+            )
+
+        val LEGACY_FLEX_AOD_VARIATION =
+            listOf(
+                ClockFontAxisSetting("wght", 74f),
+                ClockFontAxisSetting("wdth", 43f),
+                ClockFontAxisSetting("ROND", 100f),
+                ClockFontAxisSetting("slnt", 0f),
+            )
+
         val FLEX_DESIGN = run {
             val largeLayer =
                 listOf(
@@ -144,16 +129,9 @@
                                 DigitalHandLayer(
                                     layerBounds = LayerBounds.FIT,
                                     timespec = DigitalTimespec.FIRST_DIGIT,
-                                    style =
-                                        FontTextStyle(
-                                            lineHeight = 147.25f,
-                                            fontVariation =
-                                                "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100, 'slnt' 0",
-                                        ),
+                                    style = FontTextStyle(lineHeight = 147.25f),
                                     aodStyle =
                                         FontTextStyle(
-                                            fontVariation =
-                                                "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100, 'slnt' 0",
                                             fillColorLight = "#FFFFFFFF",
                                             outlineColor = "#00000000",
                                             renderType = RenderType.CHANGE_WEIGHT,
@@ -170,16 +148,9 @@
                                 DigitalHandLayer(
                                     layerBounds = LayerBounds.FIT,
                                     timespec = DigitalTimespec.SECOND_DIGIT,
-                                    style =
-                                        FontTextStyle(
-                                            lineHeight = 147.25f,
-                                            fontVariation =
-                                                "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100, 'slnt' 0",
-                                        ),
+                                    style = FontTextStyle(lineHeight = 147.25f),
                                     aodStyle =
                                         FontTextStyle(
-                                            fontVariation =
-                                                "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100, 'slnt' 0",
                                             fillColorLight = "#FFFFFFFF",
                                             outlineColor = "#00000000",
                                             renderType = RenderType.CHANGE_WEIGHT,
@@ -196,16 +167,9 @@
                                 DigitalHandLayer(
                                     layerBounds = LayerBounds.FIT,
                                     timespec = DigitalTimespec.FIRST_DIGIT,
-                                    style =
-                                        FontTextStyle(
-                                            lineHeight = 147.25f,
-                                            fontVariation =
-                                                "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100, 'slnt' 0",
-                                        ),
+                                    style = FontTextStyle(lineHeight = 147.25f),
                                     aodStyle =
                                         FontTextStyle(
-                                            fontVariation =
-                                                "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100, 'slnt' 0",
                                             fillColorLight = "#FFFFFFFF",
                                             outlineColor = "#00000000",
                                             renderType = RenderType.CHANGE_WEIGHT,
@@ -222,16 +186,9 @@
                                 DigitalHandLayer(
                                     layerBounds = LayerBounds.FIT,
                                     timespec = DigitalTimespec.SECOND_DIGIT,
-                                    style =
-                                        FontTextStyle(
-                                            lineHeight = 147.25f,
-                                            fontVariation =
-                                                "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100, 'slnt' 0",
-                                        ),
+                                    style = FontTextStyle(lineHeight = 147.25f),
                                     aodStyle =
                                         FontTextStyle(
-                                            fontVariation =
-                                                "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100, 'slnt' 0",
                                             fillColorLight = "#FFFFFFFF",
                                             outlineColor = "#00000000",
                                             renderType = RenderType.CHANGE_WEIGHT,
@@ -254,16 +211,9 @@
                     DigitalHandLayer(
                         layerBounds = LayerBounds.FIT,
                         timespec = DigitalTimespec.TIME_FULL_FORMAT,
-                        style =
-                            FontTextStyle(
-                                fontVariation =
-                                    "'wght' 600, 'wdth' 100, 'opsz' 144, 'ROND' 100, 'slnt' 0",
-                                fontSizeScale = 0.98f,
-                            ),
+                        style = FontTextStyle(fontSizeScale = 0.98f),
                         aodStyle =
                             FontTextStyle(
-                                fontVariation =
-                                    "'wght' 133, 'wdth' 43, 'opsz' 144, 'ROND' 100, 'slnt' 0",
                                 fillColorLight = "#FFFFFFFF",
                                 outlineColor = "#00000000",
                                 renderType = RenderType.CHANGE_WEIGHT,
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
index d8fd776..6c627e2 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
@@ -20,11 +20,14 @@
 import android.content.res.Resources
 import com.android.systemui.customization.R
 import com.android.systemui.plugins.clocks.AlarmData
+import com.android.systemui.plugins.clocks.AxisType
 import com.android.systemui.plugins.clocks.ClockConfig
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockEvents
+import com.android.systemui.plugins.clocks.ClockFontAxis
 import com.android.systemui.plugins.clocks.ClockFontAxisSetting
 import com.android.systemui.plugins.clocks.ClockMessageBuffers
+import com.android.systemui.plugins.clocks.ClockSettings
 import com.android.systemui.plugins.clocks.ThemeConfig
 import com.android.systemui.plugins.clocks.WeatherData
 import com.android.systemui.plugins.clocks.ZenData
@@ -37,6 +40,7 @@
 class FlexClockController(
     private val ctx: Context,
     private val resources: Resources,
+    private val settings: ClockSettings,
     private val assets: AssetLoader, // TODO(b/364680879): Remove and replace w/ resources
     val design: ClockDesign, // TODO(b/364680879): Remove when done inlining
     val messageBuffers: ClockMessageBuffers?,
@@ -114,13 +118,16 @@
             }
 
             override fun onFontAxesChanged(axes: List<ClockFontAxisSetting>) {
-                smallClock.events.onFontAxesChanged(axes)
-                largeClock.events.onFontAxesChanged(axes)
+                val fontAxes = ClockFontAxis.merge(FONT_AXES, axes).map { it.toSetting() }
+                smallClock.events.onFontAxesChanged(fontAxes)
+                largeClock.events.onFontAxesChanged(fontAxes)
             }
         }
 
     override fun initialize(isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float) {
         val theme = ThemeConfig(isDarkTheme, assets.seedColor)
+        events.onFontAxesChanged(settings.axes)
+
         smallClock.run {
             events.onThemeChanged(theme)
             animations.doze(dozeFraction)
@@ -137,4 +144,46 @@
     }
 
     override fun dump(pw: PrintWriter) {}
+
+    companion object {
+        val FONT_AXES =
+            listOf(
+                ClockFontAxis(
+                    key = "wght",
+                    type = AxisType.Float,
+                    minValue = 1f,
+                    currentValue = 400f,
+                    maxValue = 1000f,
+                    name = "Weight",
+                    description = "Glyph Weight",
+                ),
+                ClockFontAxis(
+                    key = "wdth",
+                    type = AxisType.Float,
+                    minValue = 25f,
+                    currentValue = 100f,
+                    maxValue = 151f,
+                    name = "Width",
+                    description = "Glyph Width",
+                ),
+                ClockFontAxis(
+                    key = "ROND",
+                    type = AxisType.Boolean,
+                    minValue = 0f,
+                    currentValue = 0f,
+                    maxValue = 100f,
+                    name = "Round",
+                    description = "Glyph Roundness",
+                ),
+                ClockFontAxis(
+                    key = "slnt",
+                    type = AxisType.Boolean,
+                    minValue = 0f,
+                    currentValue = 0f,
+                    maxValue = -10f,
+                    name = "Slant",
+                    description = "Glyph Slant",
+                ),
+            )
+    }
 }
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 edcee9d..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
@@ -19,7 +19,6 @@
 import android.content.Context
 import android.graphics.Canvas
 import android.graphics.Point
-import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
 import android.widget.RelativeLayout
@@ -28,7 +27,6 @@
 import com.android.systemui.log.core.MessageBuffer
 import com.android.systemui.shared.clocks.AssetLoader
 import com.android.systemui.shared.clocks.DigitTranslateAnimator
-import com.android.systemui.shared.clocks.FontTextStyle
 import kotlin.math.abs
 import kotlin.math.max
 import kotlin.math.min
@@ -41,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)
@@ -52,65 +50,6 @@
             )
     }
 
-    private var prevX = 0f
-    private var prevY = 0f
-    private var isDown = false
-
-    private var wght = 603f
-    private var wdth = 100f
-
-    private val MAX_WGHT = 950f
-    private val MIN_WGHT = 50f
-    private val WGHT_SCALE = 0.5f
-
-    private val MAX_WDTH = 150f
-    private val MIN_WDTH = 0f
-    private val WDTH_SCALE = 0.2f
-
-    override fun onTouchEvent(evt: MotionEvent): Boolean {
-        if (!isReactiveTouchInteractionEnabled) {
-            return super.onTouchEvent(evt)
-        }
-
-        when (evt.action) {
-            MotionEvent.ACTION_DOWN -> {
-                isDown = true
-                prevX = evt.x
-                prevY = evt.y
-                return true
-            }
-
-            MotionEvent.ACTION_MOVE -> {
-                if (!isDown) {
-                    return super.onTouchEvent(evt)
-                }
-
-                wdth = clamp(wdth + (evt.x - prevX) * WDTH_SCALE, MIN_WDTH, MAX_WDTH)
-                wght = clamp(wght + (evt.y - prevY) * WGHT_SCALE, MIN_WGHT, MAX_WGHT)
-                prevX = evt.x
-                prevY = evt.y
-
-                val fvar = "'wght' $wght, 'wdth' $wdth, 'opsz' 144, 'ROND' 100"
-                digitalClockTextViewMap.forEach { (_, view) ->
-                    val textStyle = view.textStyle as FontTextStyle
-                    textStyle.fontVariation = fvar
-                    view.applyStyles(assets, textStyle, view.aodStyle)
-                }
-
-                requestLayout()
-                invalidate()
-                return true
-            }
-
-            MotionEvent.ACTION_UP -> {
-                isDown = false
-                return true
-            }
-        }
-
-        return super.onTouchEvent(evt)
-    }
-
     override fun addView(child: View?) {
         super.addView(child)
         (child as SimpleDigitalClockTextView).digitTranslateAnimator =
@@ -125,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),
@@ -223,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 0dacce1..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
@@ -65,6 +65,9 @@
     val lockScreenPaint = TextPaint()
     override lateinit var textStyle: FontTextStyle
     lateinit var aodStyle: FontTextStyle
+
+    private var lsFontVariation = ClockFontAxisSetting.toFVar(DEFAULT_LS_VARIATION)
+    private var aodFontVariation = ClockFontAxisSetting.toFVar(DEFAULT_AOD_VARIATION)
     private val parser = DimensionParser(ctx)
     var maxSingleDigitHeight = -1
     var maxSingleDigitWidth = -1
@@ -142,18 +145,14 @@
     }
 
     override fun updateAxes(axes: List<ClockFontAxisSetting>) {
-        val sb = StringBuilder()
-        sb.append("'opsz' 144")
-
-        for (axis in axes) {
-            if (sb.length > 0) sb.append(", ")
-            sb.append("'${axis.key}' ${axis.value.toInt()}")
-        }
-
-        val fvar = sb.toString()
-        lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
+        lsFontVariation = ClockFontAxisSetting.toFVar(axes + OPTICAL_SIZE_AXIS)
+        lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(lsFontVariation)
         typeface = lockScreenPaint.typeface
-        textAnimator.setTextStyle(fvar = fvar, 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()
@@ -206,7 +205,7 @@
                     } else {
                         textBounds.height() + 2 * lockScreenPaint.strokeWidth.toInt()
                     },
-                    MeasureSpec.getMode(measuredHeight),
+                    MeasureSpec.getMode(measuredHeightAndState),
                 )
         }
 
@@ -220,10 +219,10 @@
                     } else {
                         max(
                             textBounds.width() + 2 * lockScreenPaint.strokeWidth.toInt(),
-                            MeasureSpec.getSize(measuredWidth),
+                            MeasureSpec.getSize(measuredWidthAndState),
                         )
                     },
-                    MeasureSpec.getMode(measuredWidth),
+                    MeasureSpec.getMode(measuredWidthAndState),
                 )
         }
 
@@ -287,15 +286,12 @@
     }
 
     override fun animateDoze(isDozing: Boolean, isAnimated: Boolean) {
-        if (!this::textAnimator.isInitialized) {
-            return
-        }
-        val fvar = if (isDozing) aodStyle.fontVariation else textStyle.fontVariation
+        if (!this::textAnimator.isInitialized) return
         textAnimator.setTextStyle(
             animate = isAnimated && isAnimationEnabled,
             color = if (isDozing) AOD_COLOR else lockscreenColor,
             textSize = if (isDozing) aodFontSizePx else lockScreenPaint.textSize,
-            fvar = fvar,
+            fvar = if (isDozing) aodFontVariation else lsFontVariation,
             duration = aodStyle.transitionDuration,
             interpolator = aodDozingInterpolator,
         )
@@ -308,14 +304,15 @@
             return
         }
         logger.d("animateCharge()")
-        val middleFvar = if (dozeFraction == 0F) aodStyle.fontVariation else textStyle.fontVariation
-        val endFvar = if (dozeFraction == 0F) textStyle.fontVariation else aodStyle.fontVariation
         val startAnimPhase2 = Runnable {
-            textAnimator.setTextStyle(fvar = endFvar, animate = isAnimationEnabled)
+            textAnimator.setTextStyle(
+                fvar = if (dozeFraction == 0F) lsFontVariation else aodFontVariation,
+                animate = isAnimationEnabled,
+            )
             updateTextBoundsForTextAnimator()
         }
         textAnimator.setTextStyle(
-            fvar = middleFvar,
+            fvar = if (dozeFraction == 0F) aodFontVariation else lsFontVariation,
             animate = isAnimationEnabled,
             onAnimationEnd = startAnimPhase2,
         )
@@ -444,7 +441,7 @@
         val typefaceName = "fonts/" + textStyle.fontFamily
         setTypefaceCache(assets.typefaceCache.getVariantCache(typefaceName))
         lockScreenPaint.strokeJoin = Paint.Join.ROUND
-        lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(textStyle.fontVariation)
+        lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(lsFontVariation)
         textStyle.fontFeatureSettings?.let {
             lockScreenPaint.fontFeatureSettings = it
             fontFeatureSettings = it
@@ -515,7 +512,7 @@
             textAnimator.textInterpolator.targetPaint.set(lockScreenPaint)
             textAnimator.textInterpolator.onTargetPaintModified()
             textAnimator.setTextStyle(
-                fvar = textStyle.fontVariation,
+                fvar = lsFontVariation,
                 textSize = lockScreenPaint.textSize,
                 color = lockscreenColor,
                 animate = false,
@@ -555,5 +552,22 @@
     companion object {
         val AOD_STROKE_WIDTH = "2dp"
         val AOD_COLOR = Color.WHITE
+        val OPTICAL_SIZE_AXIS = ClockFontAxisSetting("opsz", 144f)
+        val DEFAULT_LS_VARIATION =
+            listOf(
+                OPTICAL_SIZE_AXIS,
+                ClockFontAxisSetting("wght", 400f),
+                ClockFontAxisSetting("wdth", 100f),
+                ClockFontAxisSetting("ROND", 0f),
+                ClockFontAxisSetting("slnt", 0f),
+            )
+        val DEFAULT_AOD_VARIATION =
+            listOf(
+                OPTICAL_SIZE_AXIS,
+                ClockFontAxisSetting("wght", 200f),
+                ClockFontAxisSetting("wdth", 100f),
+                ClockFontAxisSetting("ROND", 0f),
+                ClockFontAxisSetting("slnt", 0f),
+            )
     }
 }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
index 2a87452..ae18aac 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
@@ -16,102 +16,19 @@
 
 package com.android.systemui.shared.settings.data.repository
 
-import android.content.ContentResolver
-import android.database.ContentObserver
 import android.provider.Settings
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.withContext
 
-/**
- * Defines interface for classes that can provide access to data from [Settings.Secure].
- * This repository doesn't guarantee to provide value across different users. For that
- * see: [UserAwareSecureSettingsRepository]
- */
+/** Defines interface for classes that can provide access to data from [Settings.Secure]. */
 interface SecureSettingsRepository {
 
     /** Returns a [Flow] tracking the value of a setting as an [Int]. */
-    fun intSetting(
-        name: String,
-        defaultValue: Int = 0,
-    ): Flow<Int>
+    fun intSetting(name: String, defaultValue: Int = 0): Flow<Int>
 
     /** Updates the value of the setting with the given name. */
-    suspend fun setInt(
-        name: String,
-        value: Int,
-    )
+    suspend fun setInt(name: String, value: Int)
 
-    suspend fun getInt(
-        name: String,
-        defaultValue: Int = 0,
-    ): Int
+    suspend fun getInt(name: String, defaultValue: Int = 0): Int
 
     suspend fun getString(name: String): String?
 }
-
-class SecureSettingsRepositoryImpl(
-    private val contentResolver: ContentResolver,
-    private val backgroundDispatcher: CoroutineDispatcher,
-) : SecureSettingsRepository {
-
-    override fun intSetting(
-        name: String,
-        defaultValue: Int,
-    ): Flow<Int> {
-        return callbackFlow {
-                val observer =
-                    object : ContentObserver(null) {
-                        override fun onChange(selfChange: Boolean) {
-                            trySend(Unit)
-                        }
-                    }
-
-                contentResolver.registerContentObserver(
-                    Settings.Secure.getUriFor(name),
-                    /* notifyForDescendants= */ false,
-                    observer,
-                )
-                send(Unit)
-
-                awaitClose { contentResolver.unregisterContentObserver(observer) }
-            }
-            .map { Settings.Secure.getInt(contentResolver, name, defaultValue) }
-            // The above work is done on the background thread (which is important for accessing
-            // settings through the content resolver).
-            .flowOn(backgroundDispatcher)
-    }
-
-    override suspend fun setInt(name: String, value: Int) {
-        withContext(backgroundDispatcher) {
-            Settings.Secure.putInt(
-                contentResolver,
-                name,
-                value,
-            )
-        }
-    }
-
-    override suspend fun getInt(name: String, defaultValue: Int): Int {
-        return withContext(backgroundDispatcher) {
-            Settings.Secure.getInt(
-                contentResolver,
-                name,
-                defaultValue,
-            )
-        }
-    }
-
-    override suspend fun getString(name: String): String? {
-        return withContext(backgroundDispatcher) {
-            Settings.Secure.getString(
-                contentResolver,
-                name,
-            )
-        }
-    }
-}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt
new file mode 100644
index 0000000..8b9fcb4
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.settings.data.repository
+
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.provider.Settings
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+
+/**
+ * Simple implementation of [SecureSettingsRepository].
+ *
+ * This repository doesn't guarantee to provide value across different users, and therefore
+ * shouldn't be used in SystemUI. For that see: [UserAwareSecureSettingsRepository]
+ */
+// TODO: b/377244768 - Move to Theme/WallpaperPicker, away from SystemUI.
+class SecureSettingsRepositoryImpl(
+    private val contentResolver: ContentResolver,
+    private val backgroundDispatcher: CoroutineDispatcher,
+) : SecureSettingsRepository {
+
+    override fun intSetting(name: String, defaultValue: Int): Flow<Int> {
+        return callbackFlow {
+                val observer =
+                    object : ContentObserver(null) {
+                        override fun onChange(selfChange: Boolean) {
+                            trySend(Unit)
+                        }
+                    }
+
+                contentResolver.registerContentObserver(
+                    Settings.Secure.getUriFor(name),
+                    /* notifyForDescendants= */ false,
+                    observer,
+                )
+                send(Unit)
+
+                awaitClose { contentResolver.unregisterContentObserver(observer) }
+            }
+            .map { Settings.Secure.getInt(contentResolver, name, defaultValue) }
+            // The above work is done on the background thread (which is important for accessing
+            // settings through the content resolver).
+            .flowOn(backgroundDispatcher)
+    }
+
+    override suspend fun setInt(name: String, value: Int) {
+        withContext(backgroundDispatcher) { Settings.Secure.putInt(contentResolver, name, value) }
+    }
+
+    override suspend fun getInt(name: String, defaultValue: Int): Int {
+        return withContext(backgroundDispatcher) {
+            Settings.Secure.getInt(contentResolver, name, defaultValue)
+        }
+    }
+
+    override suspend fun getString(name: String): String? {
+        return withContext(backgroundDispatcher) {
+            Settings.Secure.getString(contentResolver, name)
+        }
+    }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt
index afe82fb..8cda9b3 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt
@@ -16,102 +16,19 @@
 
 package com.android.systemui.shared.settings.data.repository
 
-import android.content.ContentResolver
-import android.database.ContentObserver
 import android.provider.Settings
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.withContext
 
-/**
- * Defines interface for classes that can provide access to data from [Settings.System]. This
- * repository doesn't guarantee to provide value across different users. For that see:
- * [UserAwareSecureSettingsRepository] which does that for secure settings.
- */
+/** Interface for classes that can provide access to data from [Settings.System]. */
 interface SystemSettingsRepository {
 
     /** Returns a [Flow] tracking the value of a setting as an [Int]. */
-    fun intSetting(
-        name: String,
-        defaultValue: Int = 0,
-    ): Flow<Int>
+    fun intSetting(name: String, defaultValue: Int = 0): Flow<Int>
 
     /** Updates the value of the setting with the given name. */
-    suspend fun setInt(
-        name: String,
-        value: Int,
-    )
+    suspend fun setInt(name: String, value: Int)
 
-    suspend fun getInt(
-        name: String,
-        defaultValue: Int = 0,
-    ): Int
+    suspend fun getInt(name: String, defaultValue: Int = 0): Int
 
     suspend fun getString(name: String): String?
 }
-
-class SystemSettingsRepositoryImpl(
-    private val contentResolver: ContentResolver,
-    private val backgroundDispatcher: CoroutineDispatcher,
-) : SystemSettingsRepository {
-
-    override fun intSetting(
-        name: String,
-        defaultValue: Int,
-    ): Flow<Int> {
-        return callbackFlow {
-                val observer =
-                    object : ContentObserver(null) {
-                        override fun onChange(selfChange: Boolean) {
-                            trySend(Unit)
-                        }
-                    }
-
-                contentResolver.registerContentObserver(
-                    Settings.System.getUriFor(name),
-                    /* notifyForDescendants= */ false,
-                    observer,
-                )
-                send(Unit)
-
-                awaitClose { contentResolver.unregisterContentObserver(observer) }
-            }
-            .map { Settings.System.getInt(contentResolver, name, defaultValue) }
-            // The above work is done on the background thread (which is important for accessing
-            // settings through the content resolver).
-            .flowOn(backgroundDispatcher)
-    }
-
-    override suspend fun setInt(name: String, value: Int) {
-        withContext(backgroundDispatcher) {
-            Settings.System.putInt(
-                contentResolver,
-                name,
-                value,
-            )
-        }
-    }
-
-    override suspend fun getInt(name: String, defaultValue: Int): Int {
-        return withContext(backgroundDispatcher) {
-            Settings.System.getInt(
-                contentResolver,
-                name,
-                defaultValue,
-            )
-        }
-    }
-
-    override suspend fun getString(name: String): String? {
-        return withContext(backgroundDispatcher) {
-            Settings.System.getString(
-                contentResolver,
-                name,
-            )
-        }
-    }
-}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt
new file mode 100644
index 0000000..b039a32
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.settings.data.repository
+
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.provider.Settings
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+
+/**
+ * Defines interface for classes that can provide access to data from [Settings.System].
+ *
+ * This repository doesn't guarantee to provide value across different users. For that see:
+ * [UserAwareSystemSettingsRepository].
+ */
+// TODO: b/377244768 - Move to Theme/WallpaperPicker, away from SystemUI.
+class SystemSettingsRepositoryImpl(
+    private val contentResolver: ContentResolver,
+    private val backgroundDispatcher: CoroutineDispatcher,
+) : SystemSettingsRepository {
+
+    override fun intSetting(name: String, defaultValue: Int): Flow<Int> {
+        return callbackFlow {
+                val observer =
+                    object : ContentObserver(null) {
+                        override fun onChange(selfChange: Boolean) {
+                            trySend(Unit)
+                        }
+                    }
+
+                contentResolver.registerContentObserver(
+                    Settings.System.getUriFor(name),
+                    /* notifyForDescendants= */ false,
+                    observer,
+                )
+                send(Unit)
+
+                awaitClose { contentResolver.unregisterContentObserver(observer) }
+            }
+            .map { Settings.System.getInt(contentResolver, name, defaultValue) }
+            // The above work is done on the background thread (which is important for accessing
+            // settings through the content resolver).
+            .flowOn(backgroundDispatcher)
+    }
+
+    override suspend fun setInt(name: String, value: Int) {
+        withContext(backgroundDispatcher) { Settings.System.putInt(contentResolver, name, value) }
+    }
+
+    override suspend fun getInt(name: String, defaultValue: Int): Int {
+        return withContext(backgroundDispatcher) {
+            Settings.System.getInt(contentResolver, name, defaultValue)
+        }
+    }
+
+    override suspend fun getString(name: String): String? {
+        return withContext(backgroundDispatcher) {
+            Settings.System.getString(contentResolver, name)
+        }
+    }
+}
diff --git a/packages/SystemUI/docs/demo_mode.md b/packages/SystemUI/docs/demo_mode.md
index ade5171..c18d1f1 100644
--- a/packages/SystemUI/docs/demo_mode.md
+++ b/packages/SystemUI/docs/demo_mode.md
@@ -35,6 +35,7 @@
 |                      | ```fully```                |                  | Sets MCS state to fully connected (```true```, ```false```)
 |                      | ```wifi```                 |                  | ```show``` to show icon, any other value to hide
 |                      |                            | ```level```      | Sets wifi level (null or 0-4)
+|                      |                            | ```hotspot```    | Sets the wifi to be from an Instant Hotspot. Values: ```none```, ```unknown```, ```phone```, ```tablet```, ```laptop```, ```watch```, ```auto```. (See `DemoModeWifiDataSource.kt`.)
 |                      | ```mobile```               |                  | ```show``` to show icon, any other value to hide
 |                      |                            | ```datatype```   | Values: ```1x```, ```3g```, ```4g```, ```e```, ```g```, ```h```, ```lte```, ```roam```, any other value to hide
 |                      |                            | ```level```      | Sets mobile signal strength level (null or 0-4)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt
new file mode 100644
index 0000000..8635bb0
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.JUnitCore
+
+@Suppress("JUnitMalformedDeclaration")
+@SmallTest
+class OnTeardownRuleTest : SysuiTestCase() {
+    // None of these inner classes should be run except as part of this utilities-testing test
+    class HasTeardown {
+        @get:Rule val teardownRule = OnTeardownRule()
+
+        @Before
+        fun setUp() {
+            teardownWasRun = false
+            teardownRule.onTeardown { teardownWasRun = true }
+        }
+
+        @Test fun doTest() {}
+
+        companion object {
+            var teardownWasRun = false
+        }
+    }
+
+    @Test
+    fun teardownRuns() {
+        val result = JUnitCore().run(HasTeardown::class.java)
+        assertThat(result.failures).isEmpty()
+        assertThat(HasTeardown.teardownWasRun).isTrue()
+    }
+
+    class FirstTeardownFails {
+        @get:Rule val teardownRule = OnTeardownRule()
+
+        @Before
+        fun setUp() {
+            teardownWasRun = false
+            teardownRule.onTeardown { fail("One fails") }
+            teardownRule.onTeardown { teardownWasRun = true }
+        }
+
+        @Test fun doTest() {}
+
+        companion object {
+            var teardownWasRun = false
+        }
+    }
+
+    @Test
+    fun allTeardownsRun() {
+        val result = JUnitCore().run(FirstTeardownFails::class.java)
+        assertThat(result.failures.map { it.message }).isEqualTo(listOf("One fails"))
+        assertThat(FirstTeardownFails.teardownWasRun).isTrue()
+    }
+
+    class ThreeTeardowns {
+        @get:Rule val teardownRule = OnTeardownRule()
+
+        @Before
+        fun setUp() {
+            messages.clear()
+        }
+
+        @Test
+        fun doTest() {
+            teardownRule.onTeardown { messages.add("A") }
+            teardownRule.onTeardown { messages.add("B") }
+            teardownRule.onTeardown { messages.add("C") }
+        }
+
+        companion object {
+            val messages = mutableListOf<String>()
+        }
+    }
+
+    @Test
+    fun reverseOrder() {
+        val result = JUnitCore().run(ThreeTeardowns::class.java)
+        assertThat(result.failures).isEmpty()
+        assertThat(ThreeTeardowns.messages).isEqualTo(listOf("C", "B", "A"))
+    }
+
+    class TryToDoABadThing {
+        @get:Rule val teardownRule = OnTeardownRule()
+
+        @Test
+        fun doTest() {
+            teardownRule.onTeardown {
+                teardownRule.onTeardown {
+                    // do nothing
+                }
+            }
+        }
+    }
+
+    @Test
+    fun prohibitTeardownDuringTeardown() {
+        val result = JUnitCore().run(TryToDoABadThing::class.java)
+        assertThat(result.failures.map { it.message })
+            .isEqualTo(listOf("Cannot add new teardown routines after test complete."))
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index c74d340..b087ecf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -19,7 +19,6 @@
 import static com.android.systemui.accessibility.MagnificationImpl.DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -190,8 +189,7 @@
 
     @Test
     public void showMagnificationButton_delayedShowButton() throws RemoteException {
-        // magnification settings panel should not be showing
-        assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY));
+        when(mMagnificationSettingsController.isMagnificationSettingsShowing()).thenReturn(false);
 
         mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY,
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
@@ -237,8 +235,7 @@
     @Test
     public void removeMagnificationButton_delayingShowButton_doNotShowButtonAfterTimeout()
             throws RemoteException {
-        // magnification settings panel should not be showing
-        assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY));
+        when(mMagnificationSettingsController.isMagnificationSettingsShowing()).thenReturn(false);
 
         mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY,
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/OWNERS b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/OWNERS
new file mode 100644
index 0000000..a2001e6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/view/accessibility/OWNERS
\ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 25696bf..f41d5c8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
-import static android.view.Choreographer.FrameCallback;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.WindowInsets.Type.systemGestures;
@@ -28,14 +27,8 @@
 
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
 
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.hasItems;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.AdditionalAnswers.returnsSecondArg;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
@@ -45,13 +38,17 @@
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static java.util.Arrays.asList;
+
 import android.animation.ValueAnimator;
 import android.annotation.IdRes;
 import android.annotation.Nullable;
@@ -63,38 +60,36 @@
 import android.graphics.Insets;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.RegionIterator;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.provider.Settings;
 import android.testing.TestableLooper;
 import android.testing.TestableResources;
-import android.text.TextUtils;
 import android.util.Size;
+import android.view.AttachedSurfaceControl;
 import android.view.Display;
-import android.view.IWindowSession;
+import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
 import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewRootImpl;
 import android.view.WindowInsets;
 import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
+import android.widget.FrameLayout;
+import android.window.InputTransferToken;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
 
-import com.android.app.viewcapture.ViewCapture;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
-import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.AnimatorTestRule;
@@ -109,8 +104,6 @@
 
 import com.google.common.util.concurrent.AtomicDouble;
 
-import kotlin.Lazy;
-
 import org.junit.After;
 import org.junit.Assume;
 import org.junit.Before;
@@ -118,31 +111,27 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Answers;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
 
 @LargeTest
 @TestableLooper.RunWithLooper
 @RunWith(AndroidJUnit4.class)
-@RequiresFlagsDisabled(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER)
 public class WindowMagnificationControllerTest extends SysuiTestCase {
 
     @Rule
     // NOTE: pass 'null' to allow this test advances time on the main thread.
-    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(null);
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(/* test= */ null);
 
     private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000;
     @Mock
-    private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
-    @Mock
     private MirrorWindowControl mMirrorWindowControl;
     @Mock
     private WindowMagnifierCallback mWindowMagnifierCallback;
@@ -150,12 +139,10 @@
     IRemoteMagnificationAnimationCallback mAnimationCallback;
     @Mock
     IRemoteMagnificationAnimationCallback mAnimationCallback2;
-    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
-    private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
+    private SurfaceControl.Transaction mTransaction;
     @Mock
     private SecureSettings mSecureSettings;
-    @Mock
-    private Lazy<ViewCapture> mLazyViewCapture;
 
     private long mWaitAnimationDuration;
     private long mWaitBounceEffectDuration;
@@ -170,11 +157,17 @@
     private final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 1.0f).setDuration(0);
     private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
 
-    private IWindowSession mWindowSessionSpy;
-
     private View mSpyView;
     private View.OnTouchListener mTouchListener;
+
     private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
+
+    // This list contains all SurfaceControlViewHosts created during a given test. If the
+    // magnification window is recreated during a test, the list will contain more than a single
+    // element.
+    private List<SurfaceControlViewHost> mSurfaceControlViewHosts = new ArrayList<>();
+    // The most recently created SurfaceControlViewHost.
+    private SurfaceControlViewHost mSurfaceControlViewHost;
     private KosmosJavaAdapter mKosmos;
     private FakeSharedPreferences mSharedPreferences;
 
@@ -196,15 +189,7 @@
         final WindowManager wm = mContext.getSystemService(WindowManager.class);
         mWindowManager = spy(new TestableWindowManager(wm));
 
-        mWindowSessionSpy = spy(WindowManagerGlobal.getWindowSession());
-
         mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
-        doAnswer(invocation -> {
-            FrameCallback callback = invocation.getArgument(0);
-            callback.doFrame(0);
-            return null;
-        }).when(mSfVsyncFrameProvider).postFrameCallback(
-                any(FrameCallback.class));
         mSysUiState = new SysUiState(mDisplayTracker, mKosmos.getSceneContainerPlugin());
         mSysUiState.addCallback(Mockito.mock(SysUiState.SysUiStateCallback.class));
         when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).then(
@@ -228,13 +213,20 @@
 
         mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
                 mContext, mValueAnimator);
+        Supplier<SurfaceControlViewHost> scvhSupplier = () -> {
+            mSurfaceControlViewHost = spy(new SurfaceControlViewHost(
+                    mContext, mContext.getDisplay(), new InputTransferToken(),
+                    "WindowMagnification"));
+            ViewRootImpl viewRoot = mock(ViewRootImpl.class);
+            when(mSurfaceControlViewHost.getRootSurfaceControl()).thenReturn(viewRoot);
+            mSurfaceControlViewHosts.add(mSurfaceControlViewHost);
+            return mSurfaceControlViewHost;
+        };
+        mTransaction = spy(new SurfaceControl.Transaction());
         mSharedPreferences = new FakeSharedPreferences();
         when(mContext.getSharedPreferences(
                 eq("window_magnification_preferences"), anyInt()))
                 .thenReturn(mSharedPreferences);
-        ViewCaptureAwareWindowManager viewCaptureAwareWindowManager = new
-                ViewCaptureAwareWindowManager(mWindowManager, mLazyViewCapture,
-                /* isViewCaptureEnabled= */ false);
         mWindowMagnificationController =
                 new WindowMagnificationController(
                         mContext,
@@ -245,10 +237,7 @@
                         mWindowMagnifierCallback,
                         mSysUiState,
                         mSecureSettings,
-                        /* scvhSupplier= */ () -> null,
-                        mSfVsyncFrameProvider,
-                        /* globalWindowSessionSupplier= */ () -> mWindowSessionSpy,
-                        viewCaptureAwareWindowManager);
+                        scvhSupplier);
 
         verify(mMirrorWindowControl).setWindowDelegate(
                 any(MirrorWindowControl.MirrorWindowDelegate.class));
@@ -277,7 +266,7 @@
         verify(mSecureSettings).getIntForUser(
                 eq(Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING),
                 /* def */ eq(1), /* userHandle= */ anyInt());
-        assertTrue(mWindowMagnificationController.isDiagonalScrollingEnabled());
+        assertThat(mWindowMagnificationController.isDiagonalScrollingEnabled()).isTrue();
     }
 
     @Test
@@ -325,7 +314,8 @@
         });
         advanceTimeBy(LAYOUT_CHANGE_TIMEOUT_MS);
 
-        verify(mSfVsyncFrameProvider, atLeast(2)).postFrameCallback(any());
+        verify(mTransaction, atLeastOnce()).setGeometry(any(), any(), any(),
+                eq(Surface.ROTATION_0));
     }
 
     @Test
@@ -342,10 +332,10 @@
         final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
         verify(mWindowMagnifierCallback, atLeast(2)).onSourceBoundsChanged(
                 (eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        assertEquals(mWindowMagnificationController.getCenterX(),
-                sourceBoundsCaptor.getValue().exactCenterX(), 0);
-        assertEquals(mWindowMagnificationController.getCenterY(),
-                sourceBoundsCaptor.getValue().exactCenterY(), 0);
+        assertThat(mWindowMagnificationController.getCenterX())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
+        assertThat(mWindowMagnificationController.getCenterY())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
     }
 
     @Test
@@ -357,8 +347,8 @@
         // Wait for Rects updated.
         waitForIdleSync();
 
-        List<Rect> rects = mWindowManager.getAttachedView().getSystemGestureExclusionRects();
-        assertFalse(rects.isEmpty());
+        List<Rect> rects = mSurfaceControlViewHost.getView().getSystemGestureExclusionRects();
+        assertThat(rects).isNotEmpty();
     }
 
     @Ignore("The default window size should be constrained after fixing b/288056772")
@@ -373,11 +363,11 @@
         });
 
         final int halfScreenSize = screenSize / 2;
-        WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView();
+        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
         // The frame size should be the half of smaller value of window height/width unless it
         //exceed the max frame size.
-        assertTrue(params.width < halfScreenSize);
-        assertTrue(params.height < halfScreenSize);
+        assertThat(params.width).isLessThan(halfScreenSize);
+        assertThat(params.height).isLessThan(halfScreenSize);
     }
 
     @Test
@@ -411,7 +401,7 @@
         });
 
         verify(mMirrorWindowControl).destroyControl();
-        assertFalse(hasMagnificationOverlapFlag());
+        assertThat(hasMagnificationOverlapFlag()).isFalse();
     }
 
     @Test
@@ -435,10 +425,14 @@
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
-            mWindowMagnificationController.moveWindowMagnifier(100f, 100f);
         });
 
-        verify(mSfVsyncFrameProvider, atLeastOnce()).postFrameCallback(any());
+        waitForIdleSync();
+        mInstrumentation.runOnMainSync(
+                () -> mWindowMagnificationController.moveWindowMagnifier(100f, 100f));
+
+        verify(mTransaction, atLeastOnce()).setGeometry(any(), any(), any(),
+                eq(Surface.ROTATION_0));
     }
 
     @Test
@@ -455,6 +449,7 @@
         final float targetCenterX = sourceBoundsCaptor.getValue().exactCenterX() + 10;
         final float targetCenterY = sourceBoundsCaptor.getValue().exactCenterY() + 10;
 
+        reset(mWindowMagnifierCallback);
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.moveWindowMagnifierToPosition(
                     targetCenterX, targetCenterY, mAnimationCallback);
@@ -465,12 +460,12 @@
         verify(mAnimationCallback, never()).onResult(eq(false));
         verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
                 .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        assertEquals(mWindowMagnificationController.getCenterX(),
-                sourceBoundsCaptor.getValue().exactCenterX(), 0);
-        assertEquals(mWindowMagnificationController.getCenterY(),
-                sourceBoundsCaptor.getValue().exactCenterY(), 0);
-        assertEquals(mWindowMagnificationController.getCenterX(), targetCenterX, 0);
-        assertEquals(mWindowMagnificationController.getCenterY(), targetCenterY, 0);
+        assertThat(mWindowMagnificationController.getCenterX())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
+        assertThat(mWindowMagnificationController.getCenterY())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
+        assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(targetCenterX);
+        assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(targetCenterY);
     }
 
     @Test
@@ -487,6 +482,7 @@
         final float centerX = sourceBoundsCaptor.getValue().exactCenterX();
         final float centerY = sourceBoundsCaptor.getValue().exactCenterY();
 
+        reset(mWindowMagnifierCallback);
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.moveWindowMagnifierToPosition(
                     centerX + 10, centerY + 10, mAnimationCallback);
@@ -505,12 +501,12 @@
         verify(mAnimationCallback, times(3)).onResult(eq(false));
         verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
                 .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        assertEquals(mWindowMagnificationController.getCenterX(),
-                sourceBoundsCaptor.getValue().exactCenterX(), 0);
-        assertEquals(mWindowMagnificationController.getCenterY(),
-                sourceBoundsCaptor.getValue().exactCenterY(), 0);
-        assertEquals(mWindowMagnificationController.getCenterX(), centerX + 40, 0);
-        assertEquals(mWindowMagnificationController.getCenterY(), centerY + 40, 0);
+        assertThat(mWindowMagnificationController.getCenterX())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
+        assertThat(mWindowMagnificationController.getCenterY())
+                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
+        assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(centerX + 40);
+        assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(centerY + 40);
     }
 
     @Test
@@ -521,10 +517,10 @@
 
         mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f));
 
-        assertEquals(3.0f, mWindowMagnificationController.getScale(), 0);
-        final View mirrorView = mWindowManager.getAttachedView();
-        assertNotNull(mirrorView);
-        assertThat(mirrorView.getStateDescription().toString(), containsString("300"));
+        assertThat(mWindowMagnificationController.getScale()).isEqualTo(3.0f);
+        final View mirrorView = mSurfaceControlViewHost.getView();
+        assertThat(mirrorView).isNotNull();
+        assertThat(mirrorView.getStateDescription().toString()).contains("300");
     }
 
     @Test
@@ -563,12 +559,12 @@
         mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged(
                 ActivityInfo.CONFIG_ORIENTATION));
 
-        assertEquals(newRotation, mWindowMagnificationController.mRotation);
+        assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
         final PointF expectedCenter = new PointF(magnifiedCenter.y,
                 displayWidth - magnifiedCenter.x);
         final PointF actualCenter = new PointF(mWindowMagnificationController.getCenterX(),
                 mWindowMagnificationController.getCenterY());
-        assertEquals(expectedCenter, actualCenter);
+        assertThat(actualCenter).isEqualTo(expectedCenter);
     }
 
     @Test
@@ -583,7 +579,7 @@
         mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged(
                 ActivityInfo.CONFIG_ORIENTATION));
 
-        assertEquals(newRotation, mWindowMagnificationController.mRotation);
+        assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
     }
 
     @Test
@@ -610,14 +606,13 @@
         });
 
         // The ratio of center to window size should be the same.
-        assertEquals(expectedRatio,
-                mWindowMagnificationController.getCenterX() / testWindowBounds.width(),
-                0);
-        assertEquals(expectedRatio,
-                mWindowMagnificationController.getCenterY() / testWindowBounds.height(),
-                0);
+        assertThat(mWindowMagnificationController.getCenterX() / testWindowBounds.width())
+                .isEqualTo(expectedRatio);
+        assertThat(mWindowMagnificationController.getCenterY() / testWindowBounds.height())
+                .isEqualTo(expectedRatio);
     }
 
+    @DisableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
     @Test
     public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierWindow() {
         int newSmallestScreenWidthDp =
@@ -635,7 +630,7 @@
                     Float.NaN);
         });
 
-        // Change screen density and size to trigger restoring the preferred window size
+        // Screen density and size change
         mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp;
         final Rect testWindowBounds = new Rect(
                 mWindowManager.getCurrentWindowMetrics().getBounds());
@@ -648,12 +643,56 @@
 
         // wait for rect update
         waitForIdleSync();
-        WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView();
+        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
         final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
                 R.dimen.magnification_mirror_surface_margin);
         // The width and height of the view include the magnification frame and the margins.
-        assertTrue(params.width == (windowFrameSize + 2 * mirrorSurfaceMargin));
-        assertTrue(params.height == (windowFrameSize + 2 * mirrorSurfaceMargin));
+        assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
+        assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
+    }
+
+    @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
+    @Test
+    public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierIndexAndWindow() {
+        int newSmallestScreenWidthDp =
+                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
+        int windowFrameSize = mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+        Size preferredWindowSize = new Size(windowFrameSize, windowFrameSize);
+        mSharedPreferences
+                .edit()
+                .putString(String.valueOf(newSmallestScreenWidthDp),
+                        WindowMagnificationFrameSpec.serialize(
+                                WindowMagnificationSettings.MagnificationSize.CUSTOM,
+                                preferredWindowSize))
+                .commit();
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+        });
+
+        // Screen density and size change
+        mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp;
+        final Rect testWindowBounds = new Rect(
+                mWindowManager.getCurrentWindowMetrics().getBounds());
+        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
+                testWindowBounds.right + 100, testWindowBounds.bottom + 100);
+        mWindowManager.setWindowBounds(testWindowBounds);
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
+        });
+
+        // wait for rect update
+        waitForIdleSync();
+        verify(mWindowMagnifierCallback).onWindowMagnifierBoundsRestored(
+                eq(mContext.getDisplayId()),
+                eq(WindowMagnificationSettings.MagnificationSize.CUSTOM));
+        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
+        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
+                R.dimen.magnification_mirror_surface_margin);
+        // The width and height of the view include the magnification frame and the margins.
+        assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
+        assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
     }
 
     @Test
@@ -675,10 +714,10 @@
         final int defaultWindowSize =
                 mWindowMagnificationController.getMagnificationWindowSizeFromIndex(
                         WindowMagnificationSettings.MagnificationSize.MEDIUM);
-        WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView();
+        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
 
-        assertTrue(params.width == defaultWindowSize);
-        assertTrue(params.height == defaultWindowSize);
+        assertThat(params.width).isEqualTo(defaultWindowSize);
+        assertThat(params.height).isEqualTo(defaultWindowSize);
     }
 
     @Test
@@ -695,9 +734,9 @@
         });
 
         verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt());
-        verify(mWindowManager).removeView(any());
+        verify(mSurfaceControlViewHosts.get(0)).release();
         verify(mMirrorWindowControl).destroyControl();
-        verify(mWindowManager).addView(any(), any());
+        verify(mSurfaceControlViewHosts.get(1)).setView(any(), any());
         verify(mMirrorWindowControl).showControl();
     }
 
@@ -716,21 +755,30 @@
             mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN,
                     Float.NaN);
         });
-        final View mirrorView = mWindowManager.getAttachedView();
-        assertNotNull(mirrorView);
+        final View mirrorView = mSurfaceControlViewHost.getView();
+        assertThat(mirrorView).isNotNull();
         final AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
 
         mirrorView.onInitializeAccessibilityNodeInfo(nodeInfo);
 
-        assertNotNull(nodeInfo.getContentDescription());
-        assertThat(nodeInfo.getStateDescription().toString(), containsString("250"));
-        assertThat(nodeInfo.getActionList(),
-                hasItems(new AccessibilityAction(R.id.accessibility_action_zoom_in, null),
-                        new AccessibilityAction(R.id.accessibility_action_zoom_out, null),
-                        new AccessibilityAction(R.id.accessibility_action_move_right, null),
-                        new AccessibilityAction(R.id.accessibility_action_move_left, null),
-                        new AccessibilityAction(R.id.accessibility_action_move_down, null),
-                        new AccessibilityAction(R.id.accessibility_action_move_up, null)));
+        assertThat(nodeInfo.getContentDescription()).isNotNull();
+        assertThat(nodeInfo.getStateDescription().toString()).contains("250");
+        assertThat(nodeInfo.getActionList()).containsExactlyElementsIn(asList(
+                new AccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(),
+                        mContext.getResources().getString(
+                                R.string.magnification_open_settings_click_label)),
+                new AccessibilityAction(R.id.accessibility_action_zoom_in,
+                        mContext.getString(R.string.accessibility_control_zoom_in)),
+                new AccessibilityAction(R.id.accessibility_action_zoom_out,
+                        mContext.getString(R.string.accessibility_control_zoom_out)),
+                new AccessibilityAction(R.id.accessibility_action_move_right,
+                        mContext.getString(R.string.accessibility_control_move_right)),
+                new AccessibilityAction(R.id.accessibility_action_move_left,
+                        mContext.getString(R.string.accessibility_control_move_left)),
+                new AccessibilityAction(R.id.accessibility_action_move_down,
+                        mContext.getString(R.string.accessibility_control_move_down)),
+                new AccessibilityAction(R.id.accessibility_action_move_up,
+                        mContext.getString(R.string.accessibility_control_move_up))));
     }
 
     @Test
@@ -741,29 +789,34 @@
                     Float.NaN);
         });
 
-        final View mirrorView = mWindowManager.getAttachedView();
-        assertTrue(
-                mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null));
+        final View mirrorView = mSurfaceControlViewHost.getView();
+        assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null))
+                .isTrue();
         // Minimum scale is 1.0.
         verify(mWindowMagnifierCallback).onPerformScaleAction(
                 eq(displayId), /* scale= */ eq(1.0f), /* updatePersistence= */ eq(true));
 
-        assertTrue(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null));
+        assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null))
+                .isTrue();
         verify(mWindowMagnifierCallback).onPerformScaleAction(
                 eq(displayId), /* scale= */ eq(2.5f), /* updatePersistence= */ eq(true));
 
         // TODO: Verify the final state when the mirror surface is visible.
-        assertTrue(mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null));
-        assertTrue(
-                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null));
-        assertTrue(
-                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null));
-        assertTrue(
-                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null));
+        assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null))
+                .isTrue();
+        assertThat(
+                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null))
+                .isTrue();
+        assertThat(
+                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null))
+                .isTrue();
+        assertThat(
+                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null))
+                .isTrue();
         verify(mWindowMagnifierCallback, times(4)).onMove(eq(displayId));
 
-        assertTrue(mirrorView.performAccessibilityAction(
-                AccessibilityAction.ACTION_CLICK.getId(), null));
+        assertThat(mirrorView.performAccessibilityAction(
+                AccessibilityAction.ACTION_CLICK.getId(), null)).isTrue();
         verify(mWindowMagnifierCallback).onClickSettingsButton(eq(displayId));
     }
 
@@ -775,7 +828,7 @@
                     Float.NaN);
         });
 
-        final View mirrorView = mWindowManager.getAttachedView();
+        final View mirrorView = mSurfaceControlViewHost.getView();
         mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null);
 
         verify(mWindowMagnifierCallback).onAccessibilityActionPerformed(eq(displayId));
@@ -795,20 +848,22 @@
         View topRightCorner = getInternalView(R.id.top_right_corner);
         View topLeftCorner = getInternalView(R.id.top_left_corner);
 
-        assertEquals(View.VISIBLE, closeButton.getVisibility());
-        assertEquals(View.VISIBLE, bottomRightCorner.getVisibility());
-        assertEquals(View.VISIBLE, bottomLeftCorner.getVisibility());
-        assertEquals(View.VISIBLE, topRightCorner.getVisibility());
-        assertEquals(View.VISIBLE, topLeftCorner.getVisibility());
+        assertThat(closeButton.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(topRightCorner.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(topLeftCorner.getVisibility()).isEqualTo(View.VISIBLE);
 
-        final View mirrorView = mWindowManager.getAttachedView();
-        mirrorView.performAccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(), null);
+        final View mirrorView = mSurfaceControlViewHost.getView();
+        mInstrumentation.runOnMainSync(() ->
+                mirrorView.performAccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(),
+                        null));
 
-        assertEquals(View.GONE, closeButton.getVisibility());
-        assertEquals(View.GONE, bottomRightCorner.getVisibility());
-        assertEquals(View.GONE, bottomLeftCorner.getVisibility());
-        assertEquals(View.GONE, topRightCorner.getVisibility());
-        assertEquals(View.GONE, topLeftCorner.getVisibility());
+        assertThat(closeButton.getVisibility()).isEqualTo(View.GONE);
+        assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.GONE);
+        assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.GONE);
+        assertThat(topRightCorner.getVisibility()).isEqualTo(View.GONE);
+        assertThat(topLeftCorner.getVisibility()).isEqualTo(View.GONE);
     }
 
     @Test
@@ -828,7 +883,7 @@
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
         });
 
-        final View mirrorView = mWindowManager.getAttachedView();
+        final View mirrorView = mSurfaceControlViewHost.getView();
         final AtomicInteger actualWindowHeight = new AtomicInteger();
         final AtomicInteger actualWindowWidth = new AtomicInteger();
 
@@ -836,8 +891,10 @@
                 () -> {
                     mirrorView.performAccessibilityAction(
                             R.id.accessibility_action_increase_window_width, null);
-                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
-                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                    actualWindowHeight.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().height);
+                    actualWindowWidth.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().width);
                 });
 
         final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
@@ -847,8 +904,8 @@
         int newWindowWidth =
                 (int) ((startingWidth - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
                         + 2 * mirrorSurfaceMargin;
-        assertEquals(newWindowWidth, actualWindowWidth.get());
-        assertEquals(startingHeight, actualWindowHeight.get());
+        assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth);
+        assertThat(actualWindowHeight.get()).isEqualTo(startingHeight);
     }
 
     @Test
@@ -868,7 +925,7 @@
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
         });
 
-        final View mirrorView = mWindowManager.getAttachedView();
+        final View mirrorView = mSurfaceControlViewHost.getView();
         final AtomicInteger actualWindowHeight = new AtomicInteger();
         final AtomicInteger actualWindowWidth = new AtomicInteger();
 
@@ -876,8 +933,10 @@
                 () -> {
                     mirrorView.performAccessibilityAction(
                             R.id.accessibility_action_increase_window_height, null);
-                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
-                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                    actualWindowHeight.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().height);
+                    actualWindowWidth.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().width);
                 });
 
         final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
@@ -887,8 +946,8 @@
         int newWindowHeight =
                 (int) ((startingHeight - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
                         + 2 * mirrorSurfaceMargin;
-        assertEquals(startingWidth, actualWindowWidth.get());
-        assertEquals(newWindowHeight, actualWindowHeight.get());
+        assertThat(actualWindowWidth.get()).isEqualTo(startingWidth);
+        assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight);
     }
 
     @Test
@@ -904,11 +963,14 @@
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
         });
 
-        final View mirrorView = mWindowManager.getAttachedView();
+        final View mirrorView = mSurfaceControlViewHost.getView();
         final AccessibilityNodeInfo accessibilityNodeInfo =
                 mirrorView.createAccessibilityNodeInfo();
-        assertFalse(accessibilityNodeInfo.getActionList().contains(
-                new AccessibilityAction(R.id.accessibility_action_increase_window_width, null)));
+        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
+                new AccessibilityAction(
+                        R.id.accessibility_action_increase_window_width,
+                        mContext.getString(
+                                R.string.accessibility_control_increase_window_width)));
     }
 
     @Test
@@ -924,11 +986,12 @@
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
         });
 
-        final View mirrorView = mWindowManager.getAttachedView();
+        final View mirrorView = mSurfaceControlViewHost.getView();
         final AccessibilityNodeInfo accessibilityNodeInfo =
                 mirrorView.createAccessibilityNodeInfo();
-        assertFalse(accessibilityNodeInfo.getActionList().contains(
-                new AccessibilityAction(R.id.accessibility_action_increase_window_height, null)));
+        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
+                new AccessibilityAction(
+                        R.id.accessibility_action_increase_window_height, null));
     }
 
     @Test
@@ -947,7 +1010,7 @@
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
         });
 
-        final View mirrorView = mWindowManager.getAttachedView();
+        final View mirrorView = mSurfaceControlViewHost.getView();
         final AtomicInteger actualWindowHeight = new AtomicInteger();
         final AtomicInteger actualWindowWidth = new AtomicInteger();
 
@@ -955,8 +1018,10 @@
                 () -> {
                     mirrorView.performAccessibilityAction(
                             R.id.accessibility_action_decrease_window_width, null);
-                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
-                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                    actualWindowHeight.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().height);
+                    actualWindowWidth.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().width);
                 });
 
         final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
@@ -966,8 +1031,8 @@
         int newWindowWidth =
                 (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
                         + 2 * mirrorSurfaceMargin;
-        assertEquals(newWindowWidth, actualWindowWidth.get());
-        assertEquals(startingSize, actualWindowHeight.get());
+        assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth);
+        assertThat(actualWindowHeight.get()).isEqualTo(startingSize);
     }
 
     @Test
@@ -987,7 +1052,7 @@
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
         });
 
-        final View mirrorView = mWindowManager.getAttachedView();
+        final View mirrorView = mSurfaceControlViewHost.getView();
         final AtomicInteger actualWindowHeight = new AtomicInteger();
         final AtomicInteger actualWindowWidth = new AtomicInteger();
 
@@ -995,8 +1060,10 @@
                 () -> {
                     mirrorView.performAccessibilityAction(
                             R.id.accessibility_action_decrease_window_height, null);
-                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
-                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                    actualWindowHeight.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().height);
+                    actualWindowWidth.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().width);
                 });
 
         final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
@@ -1006,8 +1073,8 @@
         int newWindowHeight =
                 (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
                         + 2 * mirrorSurfaceMargin;
-        assertEquals(startingSize, actualWindowWidth.get());
-        assertEquals(newWindowHeight, actualWindowHeight.get());
+        assertThat(actualWindowWidth.get()).isEqualTo(startingSize);
+        assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight);
     }
 
     @Test
@@ -1023,15 +1090,16 @@
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
         });
 
-        final View mirrorView = mWindowManager.getAttachedView();
+        final View mirrorView = mSurfaceControlViewHost.getView();
         final AccessibilityNodeInfo accessibilityNodeInfo =
                 mirrorView.createAccessibilityNodeInfo();
-        assertFalse(accessibilityNodeInfo.getActionList().contains(
-                new AccessibilityAction(R.id.accessibility_action_decrease_window_width, null)));
+        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
+                new AccessibilityAction(
+                        R.id.accessibility_action_decrease_window_width, null));
     }
 
     @Test
-    public void windowHeightIsMin_noDecreaseWindowHeightA11yAcyion() {
+    public void windowHeightIsMin_noDecreaseWindowHeightA11yAction() {
         int mMinWindowSize = mResources.getDimensionPixelSize(
                 com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
         final int startingSize = mMinWindowSize;
@@ -1043,11 +1111,12 @@
             mWindowMagnificationController.setEditMagnifierSizeMode(true);
         });
 
-        final View mirrorView = mWindowManager.getAttachedView();
+        final View mirrorView = mSurfaceControlViewHost.getView();
         final AccessibilityNodeInfo accessibilityNodeInfo =
                 mirrorView.createAccessibilityNodeInfo();
-        assertFalse(accessibilityNodeInfo.getActionList().contains(
-                new AccessibilityAction(R.id.accessibility_action_decrease_window_height, null)));
+        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
+                new AccessibilityAction(
+                        R.id.accessibility_action_decrease_window_height, null));
     }
 
     @Test
@@ -1057,8 +1126,8 @@
                     Float.NaN);
         });
 
-        assertEquals(getContext().getResources().getString(
-                com.android.internal.R.string.android_system_label), getAccessibilityWindowTitle());
+        assertThat(getAccessibilityWindowTitle()).isEqualTo(getContext().getResources().getString(
+                com.android.internal.R.string.android_system_label));
     }
 
     @Test
@@ -1073,14 +1142,14 @@
                     Float.NaN);
         });
 
-        assertEquals(Float.NaN, mWindowMagnificationController.getScale(), 0);
+        assertThat(mWindowMagnificationController.getScale()).isEqualTo(Float.NaN);
     }
 
     @Test
     public void enableWindowMagnification_rotationIsChanged_updateRotationValue() {
         // the config orientation should not be undefined, since it would cause config.diff
         // returning 0 and thus the orientation changed would not be detected
-        assertNotEquals(ORIENTATION_UNDEFINED, mResources.getConfiguration().orientation);
+        assertThat(mResources.getConfiguration().orientation).isNotEqualTo(ORIENTATION_UNDEFINED);
 
         final Configuration config = mResources.getConfiguration();
         config.orientation = config.orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT
@@ -1091,7 +1160,7 @@
                 () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                         Float.NaN, Float.NaN));
 
-        assertEquals(newRotation, mWindowMagnificationController.mRotation);
+        assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
     }
 
     @Test
@@ -1119,7 +1188,7 @@
             mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE);
         });
 
-        assertTrue(TextUtils.equals(newA11yWindowTitle, getAccessibilityWindowTitle()));
+        assertThat(getAccessibilityWindowTitle()).isEqualTo(newA11yWindowTitle);
     }
 
     @Ignore("it's flaky in presubmit but works in abtd, filter for now. b/305654925")
@@ -1134,7 +1203,7 @@
             mWindowMagnificationController.onSingleTap(mSpyView);
         });
 
-        final View mirrorView = mWindowManager.getAttachedView();
+        final View mirrorView = mSurfaceControlViewHost.getView();
 
         final AtomicDouble maxScaleX = new AtomicDouble();
         advanceTimeBy(mWaitBounceEffectDuration, /* runnableOnEachRefresh= */ () -> {
@@ -1142,10 +1211,10 @@
             // maxScaleX.getAndAccumulate(mirrorView.getScaleX(), Math::max);
             final double oldMax = maxScaleX.get();
             final double newMax = Math.max(mirrorView.getScaleX(), oldMax);
-            assertTrue(maxScaleX.compareAndSet(oldMax, newMax));
+            assertThat(maxScaleX.compareAndSet(oldMax, newMax)).isTrue();
         });
 
-        assertTrue(maxScaleX.get() > 1.0);
+        assertThat(maxScaleX.get()).isGreaterThan(1.0);
     }
 
     @Test
@@ -1174,30 +1243,23 @@
                     mWindowMagnificationController.updateWindowMagnificationInternal(
                             Float.NaN, Float.NaN, Float.NaN);
                 });
+        // Wait for Region updated.
+        waitForIdleSync();
 
         mInstrumentation.runOnMainSync(
                 () -> {
                     mWindowMagnificationController.moveWindowMagnifier(bounds.width(), 0);
                 });
-
         // Wait for Region updated.
         waitForIdleSync();
 
-        final ArgumentCaptor<Region> tapExcludeRegionCapturer =
-                ArgumentCaptor.forClass(Region.class);
-        verify(mWindowSessionSpy, times(2))
-                .updateTapExcludeRegion(any(), tapExcludeRegionCapturer.capture());
-        Region tapExcludeRegion = tapExcludeRegionCapturer.getValue();
-        RegionIterator iterator = new RegionIterator(tapExcludeRegion);
+        AttachedSurfaceControl viewRoot = mSurfaceControlViewHost.getRootSurfaceControl();
+        // Verifying two times in: (1) enable window magnification (2) reposition drag handle
+        verify(viewRoot, times(2)).setTouchableRegion(any());
 
-        final Rect topRect = new Rect();
-        final Rect bottomRect = new Rect();
-        assertTrue(iterator.next(topRect));
-        assertTrue(iterator.next(bottomRect));
-        assertFalse(iterator.next(new Rect()));
-
-        assertEquals(topRect.right, bottomRect.right);
-        assertNotEquals(topRect.left, bottomRect.left);
+        View dragButton = getInternalView(R.id.drag_handle);
+        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams();
+        assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.LEFT);
     }
 
     @Test
@@ -1210,29 +1272,23 @@
                     mWindowMagnificationController.updateWindowMagnificationInternal(
                             Float.NaN, Float.NaN, Float.NaN);
                 });
+        // Wait for Region updated.
+        waitForIdleSync();
 
         mInstrumentation.runOnMainSync(
                 () -> {
                     mWindowMagnificationController.moveWindowMagnifier(-bounds.width(), 0);
                 });
-
         // Wait for Region updated.
         waitForIdleSync();
 
-        final ArgumentCaptor<Region> tapExcludeRegionCapturer =
-                ArgumentCaptor.forClass(Region.class);
-        verify(mWindowSessionSpy).updateTapExcludeRegion(any(), tapExcludeRegionCapturer.capture());
-        Region tapExcludeRegion = tapExcludeRegionCapturer.getValue();
-        RegionIterator iterator = new RegionIterator(tapExcludeRegion);
+        AttachedSurfaceControl viewRoot = mSurfaceControlViewHost.getRootSurfaceControl();
+        // Verifying one times in: (1) enable window magnification
+        verify(viewRoot).setTouchableRegion(any());
 
-        final Rect topRect = new Rect();
-        final Rect bottomRect = new Rect();
-        assertTrue(iterator.next(topRect));
-        assertTrue(iterator.next(bottomRect));
-        assertFalse(iterator.next(new Rect()));
-
-        assertEquals(topRect.left, bottomRect.left);
-        assertNotEquals(topRect.right, bottomRect.right);
+        View dragButton = getInternalView(R.id.drag_handle);
+        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams();
+        assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.RIGHT);
     }
 
     @Test
@@ -1249,13 +1305,13 @@
         final AtomicInteger actualWindowWidth = new AtomicInteger();
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight);
-            actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
-            actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
+            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
 
         });
 
-        assertEquals(expectedWindowHeight, actualWindowHeight.get());
-        assertEquals(expectedWindowWidth, actualWindowWidth.get());
+        assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
+        assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
     }
 
     @Test
@@ -1271,12 +1327,12 @@
             mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight);
             mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
                     Float.NaN, Float.NaN);
-            actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
-            actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
+            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
         });
 
-        assertEquals(expectedWindowHeight, actualWindowHeight.get());
-        assertEquals(expectedWindowWidth, actualWindowWidth.get());
+        assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
+        assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
     }
 
     @Test
@@ -1292,12 +1348,12 @@
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.setWindowSize(minimumWindowSize - 10,
                     minimumWindowSize - 10);
-            actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
-            actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
+            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
         });
 
-        assertEquals(minimumWindowSize, actualWindowHeight.get());
-        assertEquals(minimumWindowSize, actualWindowWidth.get());
+        assertThat(actualWindowHeight.get()).isEqualTo(minimumWindowSize);
+        assertThat(actualWindowWidth.get()).isEqualTo(minimumWindowSize);
     }
 
     @Test
@@ -1311,12 +1367,12 @@
         final AtomicInteger actualWindowWidth = new AtomicInteger();
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.setWindowSize(bounds.width() + 10, bounds.height() + 10);
-            actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
-            actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
+            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
         });
 
-        assertEquals(bounds.height(), actualWindowHeight.get());
-        assertEquals(bounds.width(), actualWindowWidth.get());
+        assertThat(actualWindowHeight.get()).isEqualTo(bounds.height());
+        assertThat(actualWindowWidth.get()).isEqualTo(bounds.width());
     }
 
     @Test
@@ -1342,12 +1398,14 @@
                 () -> {
                     mWindowMagnificationController.changeMagnificationSize(
                             WindowMagnificationSettings.MagnificationSize.LARGE);
-                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
-                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                    actualWindowHeight.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().height);
+                    actualWindowWidth.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().width);
                 });
 
-        assertEquals(expectedWindowHeight, actualWindowHeight.get());
-        assertEquals(expectedWindowWidth, actualWindowWidth.get());
+        assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
+        assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
     }
 
     @Test
@@ -1376,12 +1434,14 @@
                 () -> {
                     mWindowMagnificationController
                             .onDrag(getInternalView(R.id.bottom_right_corner), 2f, 1f);
-                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
-                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                    actualWindowHeight.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().height);
+                    actualWindowWidth.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().width);
                 });
 
-        assertEquals(startingSize + 1, actualWindowHeight.get());
-        assertEquals(startingSize + 2, actualWindowWidth.get());
+        assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1);
+        assertThat(actualWindowWidth.get()).isEqualTo(startingSize + 2);
     }
 
     @Test
@@ -1404,11 +1464,13 @@
                     mWindowMagnificationController.setEditMagnifierSizeMode(true);
                     mWindowMagnificationController
                             .onDrag(getInternalView(R.id.bottom_handle), 2f, 1f);
-                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
-                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                    actualWindowHeight.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().height);
+                    actualWindowWidth.set(
+                            mSurfaceControlViewHost.getView().getLayoutParams().width);
                 });
-        assertEquals(startingSize + 1, actualWindowHeight.get());
-        assertEquals(startingSize, actualWindowWidth.get());
+        assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1);
+        assertThat(actualWindowWidth.get()).isEqualTo(startingSize);
     }
 
     @Test
@@ -1430,8 +1492,8 @@
             magnificationCenterY.set((int) mWindowMagnificationController.getCenterY());
         });
 
-        assertTrue(magnificationCenterX.get() < bounds.right);
-        assertTrue(magnificationCenterY.get() < bounds.bottom);
+        assertThat(magnificationCenterX.get()).isLessThan(bounds.right);
+        assertThat(magnificationCenterY.get()).isLessThan(bounds.bottom);
     }
 
     @Test
@@ -1451,13 +1513,13 @@
         dragButton.dispatchTouchEvent(
                 obtainMotionEvent(downTime, downTime, ACTION_UP, 100, 100));
 
-        verify(mWindowManager).addView(any(View.class), any());
+        verify(mSurfaceControlViewHost).setView(any(View.class), any());
     }
 
     private <T extends View> T getInternalView(@IdRes int idRes) {
-        View mirrorView = mWindowManager.getAttachedView();
+        View mirrorView = mSurfaceControlViewHost.getView();
         T view = mirrorView.findViewById(idRes);
-        assertNotNull(view);
+        assertThat(view).isNotNull();
         return view;
     }
 
@@ -1466,14 +1528,14 @@
         return mMotionEventHelper.obtainMotionEvent(downTime, eventTime, action, x, y);
     }
 
-    private CharSequence getAccessibilityWindowTitle() {
-        final View mirrorView = mWindowManager.getAttachedView();
+    private String getAccessibilityWindowTitle() {
+        final View mirrorView = mSurfaceControlViewHost.getView();
         if (mirrorView == null) {
             return null;
         }
         WindowManager.LayoutParams layoutParams =
                 (WindowManager.LayoutParams) mirrorView.getLayoutParams();
-        return layoutParams.accessibilityTitle;
+        return layoutParams.accessibilityTitle.toString();
     }
 
     private boolean hasMagnificationOverlapFlag() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
index 176c3ac..2594472 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
@@ -22,12 +22,13 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
 import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -40,8 +41,6 @@
 @RunWith(AndroidJUnit4::class)
 class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() {
     private val kosmos = testKosmos()
-    private val testDispatcher = kosmos.testDispatcher
-    private val testScope = kosmos.testScope
     private val secureSettings = kosmos.fakeSettings
 
     @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
@@ -55,8 +54,8 @@
                 return UserA11yQsShortcutsRepository(
                     userId,
                     secureSettings,
-                    testScope.backgroundScope,
-                    testDispatcher,
+                    kosmos.testScope.backgroundScope,
+                    kosmos.testDispatcher,
                 )
             }
         }
@@ -69,13 +68,13 @@
             AccessibilityQsShortcutsRepositoryImpl(
                 a11yManager,
                 userA11yQsShortcutsRepositoryFactory,
-                testDispatcher
+                kosmos.testDispatcher,
             )
     }
 
     @Test
     fun a11yQsShortcutTargetsForCorrectUsers() =
-        testScope.runTest {
+        kosmos.runTest {
             val user0 = 0
             val targetsForUser0 = setOf("a", "b", "c")
             val user1 = 1
@@ -94,7 +93,7 @@
         secureSettings.putStringForUser(
             SETTING_NAME,
             a11yQsTargets.joinToString(separator = ":"),
-            forUser
+            forUser,
         )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index 8c7cd61..cdda9cc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -47,8 +47,7 @@
 import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -61,7 +60,6 @@
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.runCurrent
 import org.junit.Before
 import org.junit.Rule
@@ -95,9 +93,6 @@
     @Mock private lateinit var onBackInvokedDispatcher: WindowOnBackInvokedDispatcher
     @Mock private lateinit var iStatusBarService: IStatusBarService
     @Mock private lateinit var headsUpManager: HeadsUpManager
-    private val activeNotificationsRepository = ActiveNotificationListRepository()
-    private val activeNotificationsInteractor =
-        ActiveNotificationsInteractor(activeNotificationsRepository, StandardTestDispatcher())
 
     private val keyguardRepository = FakeKeyguardRepository()
     private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy {
@@ -107,7 +102,7 @@
             keyguardRepository,
             headsUpManager,
             powerInteractor,
-            activeNotificationsInteractor,
+            kosmos.activeNotificationsInteractor,
             kosmos::sceneInteractor,
         )
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/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/common/ui/domain/interactor/ConfigurationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
index 72e0726..5994afa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
@@ -55,7 +55,7 @@
         testableResources.overrideConfiguration(configuration)
         configurationRepository = FakeConfigurationRepository()
         testScope = TestScope()
-        underTest = ConfigurationInteractor(configurationRepository)
+        underTest = ConfigurationInteractorImpl(configurationRepository)
     }
 
     @Test
@@ -207,7 +207,7 @@
             updateDisplay(
                 width = DISPLAY_HEIGHT,
                 height = DISPLAY_WIDTH,
-                rotation = Surface.ROTATION_90
+                rotation = Surface.ROTATION_90,
             )
             runCurrent()
 
@@ -217,7 +217,7 @@
     private fun updateDisplay(
         width: Int = DISPLAY_WIDTH,
         height: Int = DISPLAY_HEIGHT,
-        @Surface.Rotation rotation: Int = Surface.ROTATION_0
+        @Surface.Rotation rotation: Int = Surface.ROTATION_0,
     ) {
         configuration.windowConfiguration.maxBounds.set(Rect(0, 0, width, height))
         configuration.windowConfiguration.displayRotation = rotation
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryLocalImplTest.kt
similarity index 98%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryLocalImplTest.kt
index 8ae9d2e..55d7d08 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryLocalImplTest.kt
@@ -73,7 +73,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
+class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
     @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
     @Mock private lateinit var providerInfoA: AppWidgetProviderInfo
     @Mock private lateinit var providerInfoB: AppWidgetProviderInfo
@@ -105,14 +105,14 @@
             "com.android.fake/WidgetProviderC",
         )
 
-    private lateinit var underTest: CommunalWidgetRepositoryImpl
+    private lateinit var underTest: CommunalWidgetRepositoryLocalImpl
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         fakeWidgets = MutableStateFlow(emptyMap())
         fakeProviders = MutableStateFlow(emptyMap())
-        logBuffer = logcatLogBuffer(name = "CommunalWidgetRepoImplTest")
+        logBuffer = logcatLogBuffer(name = "CommunalWidgetRepoLocalImplTest")
         backupUtils = CommunalBackupUtils(kosmos.applicationContext)
 
         setAppWidgetIds(emptyList())
@@ -126,7 +126,7 @@
         restoreUser(mainUser)
 
         underTest =
-            CommunalWidgetRepositoryImpl(
+            CommunalWidgetRepositoryLocalImpl(
                 appWidgetHost,
                 testScope.backgroundScope,
                 kosmos.testDispatcher,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModelTest.kt
new file mode 100644
index 0000000..c210154
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModelTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.shared.model
+
+import android.appwidget.AppWidgetProviderInfo
+import android.content.ComponentName
+import android.os.Parcel
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalWidgetContentModelTest : SysuiTestCase() {
+    @Test
+    fun testParcelizeAvailableWidget() {
+        val widgetToParcelize =
+            CommunalWidgetContentModel.Available(
+                appWidgetId = 1,
+                providerInfo =
+                    AppWidgetProviderInfo().apply { provider = ComponentName("pkg", "cls") },
+                rank = 2,
+                spanY = 3,
+            )
+
+        val parcel = Parcel.obtain()
+        widgetToParcelize.writeToParcel(parcel, flags = 0)
+
+        parcel.setDataPosition(0)
+
+        // Only checking fields are equal and not complete equality because not all fields are
+        // specified in the fake AppWidgetProviderInfo
+        val widgetFromParcel =
+            CommunalWidgetContentModel.createFromParcel(parcel)
+                as CommunalWidgetContentModel.Available
+        assertThat(widgetFromParcel.appWidgetId).isEqualTo(widgetToParcelize.appWidgetId)
+        assertThat(widgetFromParcel.rank).isEqualTo(widgetToParcelize.rank)
+        assertThat(widgetFromParcel.spanY).isEqualTo(widgetToParcelize.spanY)
+        assertThat(widgetFromParcel.providerInfo.provider)
+            .isEqualTo(widgetToParcelize.providerInfo.provider)
+    }
+
+    @Test
+    fun testParcelizePendingWidget() {
+        val widgetToParcelize =
+            CommunalWidgetContentModel.Pending(
+                appWidgetId = 2,
+                rank = 3,
+                componentName = ComponentName("pkg", "cls"),
+                icon = null,
+                user = UserHandle(0),
+                spanY = 6,
+            )
+
+        val parcel = Parcel.obtain()
+        widgetToParcelize.writeToParcel(parcel, flags = 0)
+
+        parcel.setDataPosition(0)
+
+        val widgetFromParcel = CommunalWidgetContentModel.createFromParcel(parcel)
+        assertThat(widgetFromParcel).isEqualTo(widgetToParcelize)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt
index 7816d3b..bea1010 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt
@@ -367,6 +367,39 @@
         }
 
     @Test
+    fun testMaxSpansLessThanCurrentSpan() =
+        testScope.runTest {
+            // heightPerSpan =
+            // (viewportHeightPx - verticalContentPaddingPx - (totalSpans - 1)
+            // * verticalItemSpacingPx) / totalSpans
+            // = 145.3333
+            // maxSpans = (maxHeightPx + verticalItemSpacing) /
+            // (heightPerSpanPx + verticalItemSpacingPx)
+            // = 4.72
+            // This is invalid because the max span calculation comes out to be less than
+            // the current span. Ensure we handle this case correctly.
+            val layout =
+                GridLayout(
+                    verticalItemSpacingPx = 100f,
+                    currentRow = 0,
+                    minHeightPx = 480,
+                    maxHeightPx = 1060,
+                    currentSpan = 6,
+                    resizeMultiple = 3,
+                    totalSpans = 6,
+                    viewportHeightPx = 1600,
+                    verticalContentPaddingPx = 228f,
+                )
+            updateGridLayout(layout)
+
+            val topState = underTest.topDragState
+            val bottomState = underTest.bottomDragState
+
+            assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
+            assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, -3 to -736f)
+        }
+
+    @Test
     fun testCanExpand_atTopPosition_withMultipleAnchors_returnsTrue() =
         testScope.runTest {
             val twoRowGrid = singleSpanGrid.copy(totalSpans = 2, currentSpan = 1, currentRow = 0)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
index 1e79112..18513fc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper
 import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -61,6 +62,7 @@
                 backgroundScope = kosmos.applicationCoroutineScope,
                 hostId = 116,
                 logBuffer = logcatLogBuffer("CommunalAppWidgetHostTest"),
+                glanceableHubMultiUserHelper = kosmos.fakeGlanceableHubMultiUserHelper,
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
index c9f3f14..9ef2b19 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
@@ -24,10 +24,14 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
 import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.shared.model.FakeGlanceableHubMultiUserHelper
+import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
@@ -58,6 +62,9 @@
     @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
     @Mock private lateinit var communalWidgetHost: CommunalWidgetHost
 
+    private lateinit var widgetManager: GlanceableHubWidgetManager
+    private lateinit var helper: FakeGlanceableHubMultiUserHelper
+
     private lateinit var appWidgetIdToRemove: MutableSharedFlow<Int>
 
     private lateinit var underTest: CommunalAppWidgetHostStartable
@@ -69,17 +76,23 @@
         kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
         mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
 
+        widgetManager = kosmos.mockGlanceableHubWidgetManager
+        helper = kosmos.fakeGlanceableHubMultiUserHelper
         appWidgetIdToRemove = MutableSharedFlow()
         whenever(appWidgetHost.appWidgetIdToRemove).thenReturn(appWidgetIdToRemove)
 
         underTest =
             CommunalAppWidgetHostStartable(
-                appWidgetHost,
-                communalWidgetHost,
-                kosmos.communalInteractor,
-                kosmos.fakeUserTracker,
+                { appWidgetHost },
+                { communalWidgetHost },
+                { kosmos.communalInteractor },
+                { kosmos.communalSettingsInteractor },
+                { kosmos.keyguardInteractor },
+                { kosmos.fakeUserTracker },
                 kosmos.applicationCoroutineScope,
                 kosmos.testDispatcher,
+                { widgetManager },
+                helper,
             )
     }
 
@@ -211,7 +224,7 @@
                 fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
                 fakeCommunalWidgetRepository.addPendingWidget(
                     appWidgetId = 2,
-                    userId = USER_INFO_WORK.id
+                    userId = USER_INFO_WORK.id,
                 )
                 fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
 
@@ -246,16 +259,42 @@
             }
         }
 
-    private suspend fun setCommunalAvailable(available: Boolean) =
+    @Test
+    fun onStartHeadlessSystemUser_registerWidgetManager_whenCommunalIsAvailable() =
+        with(kosmos) {
+            testScope.runTest {
+                helper.setIsInHeadlessSystemUser(true)
+                underTest.start()
+                runCurrent()
+                verify(widgetManager, never()).register()
+                verify(widgetManager, never()).unregister()
+
+                // Binding to the service does not require keyguard showing
+                setCommunalAvailable(true, setKeyguardShowing = false)
+                runCurrent()
+                verify(widgetManager).register()
+
+                setCommunalAvailable(false)
+                runCurrent()
+                verify(widgetManager).unregister()
+            }
+        }
+
+    private suspend fun setCommunalAvailable(
+        available: Boolean,
+        setKeyguardShowing: Boolean = true,
+    ) =
         with(kosmos) {
             fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
             fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
-            fakeKeyguardRepository.setKeyguardShowing(true)
+            if (setKeyguardShowing) {
+                fakeKeyguardRepository.setKeyguardShowing(true)
+            }
             val settingsValue = if (available) 1 else 0
             fakeSettings.putIntForUser(
                 Settings.Secure.GLANCEABLE_HUB_ENABLED,
                 settingsValue,
-                MAIN_USER_INFO.id
+                MAIN_USER_INFO.id,
             )
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt
index 054e516..017c778 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt
@@ -26,6 +26,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -84,7 +85,7 @@
                     any<Int>(),
                     any<UserHandle>(),
                     any<ComponentName>(),
-                    any<Bundle>()
+                    any<Bundle>(),
                 )
             )
             .thenReturn(true)
@@ -96,6 +97,7 @@
                 appWidgetHost,
                 selectedUserInteractor,
                 logcatLogBuffer("CommunalWidgetHostTest"),
+                glanceableHubMultiUserHelper = kosmos.fakeGlanceableHubMultiUserHelper,
             )
     }
 
@@ -162,7 +164,7 @@
                         any<Int>(),
                         any<UserHandle>(),
                         any<ComponentName>(),
-                        any<Bundle>()
+                        any<Bundle>(),
                     )
                 )
                 .thenReturn(false)
@@ -283,12 +285,7 @@
             // all providers are emitted at once
             assertThat(providerInfoValues).hasSize(2)
             assertThat(providerInfoValues[1])
-                .containsExactlyEntriesIn(
-                    mapOf(
-                        Pair(1, providerInfo1),
-                        Pair(2, providerInfo2),
-                    )
-                )
+                .containsExactlyEntriesIn(mapOf(Pair(1, providerInfo1), Pair(2, providerInfo2)))
         }
 
     @Test
@@ -304,12 +301,7 @@
             // Assert that the provider info map is populated
             val providerInfo by collectLastValue(underTest.appWidgetProviders)
             assertThat(providerInfo)
-                .containsExactlyEntriesIn(
-                    mapOf(
-                        Pair(1, providerInfo1),
-                        Pair(2, providerInfo2),
-                    )
-                )
+                .containsExactlyEntriesIn(mapOf(Pair(1, providerInfo1), Pair(2, providerInfo2)))
 
             // Host stop listening
             observer.onHostStopListening()
@@ -332,12 +324,7 @@
 
             // Assert that the provider info map is populated
             assertThat(providerInfo)
-                .containsExactlyEntriesIn(
-                    mapOf(
-                        Pair(1, providerInfo1),
-                        Pair(2, providerInfo2),
-                    )
-                )
+                .containsExactlyEntriesIn(mapOf(Pair(1, providerInfo1), Pair(2, providerInfo2)))
 
             // Provider info for widget 1 updated
             val listener =
@@ -349,12 +336,7 @@
 
             // Assert that the update is reflected in the flow
             assertThat(providerInfo)
-                .containsExactlyEntriesIn(
-                    mapOf(
-                        Pair(1, providerInfo3),
-                        Pair(2, providerInfo2),
-                    )
-                )
+                .containsExactlyEntriesIn(mapOf(Pair(1, providerInfo3), Pair(2, providerInfo2)))
         }
 
     @Test
@@ -371,12 +353,7 @@
 
             // Assert that the provider info map is populated
             assertThat(providerInfo)
-                .containsExactlyEntriesIn(
-                    mapOf(
-                        Pair(1, providerInfo1),
-                        Pair(2, providerInfo2),
-                    )
-                )
+                .containsExactlyEntriesIn(mapOf(Pair(1, providerInfo1), Pair(2, providerInfo2)))
 
             // Bind a new widget
             whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(3)
@@ -388,11 +365,7 @@
             // Assert that the new provider is reflected in the flow
             assertThat(providerInfo)
                 .containsExactlyEntriesIn(
-                    mapOf(
-                        Pair(1, providerInfo1),
-                        Pair(2, providerInfo2),
-                        Pair(3, providerInfo3),
-                    )
+                    mapOf(Pair(1, providerInfo1), Pair(2, providerInfo2), Pair(3, providerInfo3))
                 )
         }
 
@@ -410,31 +383,21 @@
 
             // Assert that the provider info map is populated
             assertThat(providerInfo)
-                .containsExactlyEntriesIn(
-                    mapOf(
-                        Pair(1, providerInfo1),
-                        Pair(2, providerInfo2),
-                    )
-                )
+                .containsExactlyEntriesIn(mapOf(Pair(1, providerInfo1), Pair(2, providerInfo2)))
 
             // Remove widget 1
             observer.onDeleteAppWidgetId(1)
             runCurrent()
 
             // Assert that provider info for widget 1 is removed
-            assertThat(providerInfo)
-                .containsExactlyEntriesIn(
-                    mapOf(
-                        Pair(2, providerInfo2),
-                    )
-                )
+            assertThat(providerInfo).containsExactlyEntriesIn(mapOf(Pair(2, providerInfo2)))
         }
 
     private fun selectUser() {
         kosmos.fakeUserRepository.selectedUser.value =
             SelectedUserModel(
                 userInfo = UserInfo(0, "Current user", 0),
-                selectionStatus = SelectionStatus.SELECTION_COMPLETE
+                selectionStatus = SelectionStatus.SELECTION_COMPLETE,
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceTest.kt
new file mode 100644
index 0000000..c3c958c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceTest.kt
@@ -0,0 +1,415 @@
+/*
+ * 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.communal.widgets
+
+import android.appwidget.AppWidgetHost
+import android.appwidget.AppWidgetProviderInfo
+import android.content.ComponentName
+import android.content.Intent
+import android.content.IntentSender
+import android.os.Binder
+import android.os.UserHandle
+import android.testing.TestableLooper
+import android.widget.RemoteViews
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper
+import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IConfigureWidgetCallback
+import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IGlanceableHubWidgetsListener
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.testKosmos
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidJUnit4::class)
+class GlanceableHubWidgetManagerServiceTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private val appWidgetHostListenerCaptor = argumentCaptor<AppWidgetHost.AppWidgetHostListener>()
+
+    private val widgetRepository = kosmos.fakeCommunalWidgetRepository
+    private val appWidgetHost = mock<CommunalAppWidgetHost>()
+    private val communalWidgetHost = mock<CommunalWidgetHost>()
+    private val multiUserHelper = kosmos.fakeGlanceableHubMultiUserHelper
+
+    private lateinit var underTest: GlanceableHubWidgetManagerService
+
+    @Before
+    fun setup() {
+        underTest =
+            GlanceableHubWidgetManagerService(
+                widgetRepository,
+                appWidgetHost,
+                communalWidgetHost,
+                multiUserHelper,
+                logcatLogBuffer("GlanceableHubWidgetManagerServiceTest"),
+            )
+    }
+
+    @Test
+    fun appWidgetHost_listenWhenServiceIsBound() {
+        underTest.onCreate()
+        verify(appWidgetHost).startListening()
+        verify(communalWidgetHost).startObservingHost()
+        verify(appWidgetHost, never()).stopListening()
+        verify(communalWidgetHost, never()).stopObservingHost()
+
+        underTest.onDestroy()
+        verify(appWidgetHost).stopListening()
+        verify(communalWidgetHost).stopObservingHost()
+    }
+
+    @Test
+    fun widgetsListener_getWidgetUpdates() =
+        testScope.runTest {
+            setupWidgets()
+
+            // Bind service
+            val binder = underTest.onBind(Intent())
+            val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+            // Verify the update is as expected
+            val widgets by collectLastValue(service.listenForWidgetUpdates())
+            assertThat(widgets).hasSize(3)
+            assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+        }
+
+    @Test
+    fun widgetsListener_multipleListeners_eachGetsWidgetUpdates() =
+        testScope.runTest {
+            setupWidgets()
+
+            // Bind service
+            val binder = underTest.onBind(Intent())
+            val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+            // Verify the update for the first listener is as expected
+            val widgets1 by collectLastValue(service.listenForWidgetUpdates())
+            assertThat(widgets1).hasSize(3)
+            assertThat(widgets1?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets1?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets1?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+
+            // Verify the update for the second listener is as expected
+            val widgets2 by collectLastValue(service.listenForWidgetUpdates())
+            assertThat(widgets2).hasSize(3)
+            assertThat(widgets2?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets2?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets2?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+        }
+
+    @Test
+    fun setAppWidgetHostListener_getUpdates() =
+        testScope.runTest {
+            // Bind service
+            val binder = underTest.onBind(Intent())
+            val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+            // Set listener
+            val listener = mock<IGlanceableHubWidgetManagerService.IAppWidgetHostListener>()
+            service.setAppWidgetHostListener(1, listener)
+
+            // Verify a listener is set on the host
+            verify(appWidgetHost).setListener(eq(1), appWidgetHostListenerCaptor.capture())
+            val appWidgetHostListener = appWidgetHostListenerCaptor.firstValue
+
+            // Each update should be passed to the listener
+            val providerInfo = mock<AppWidgetProviderInfo>()
+            appWidgetHostListener.onUpdateProviderInfo(providerInfo)
+            verify(listener).onUpdateProviderInfo(providerInfo)
+
+            val remoteViews = mock<RemoteViews>()
+            appWidgetHostListener.updateAppWidget(remoteViews)
+            verify(listener).updateAppWidget(remoteViews)
+
+            appWidgetHostListener.updateAppWidgetDeferred("pkg", 1)
+            verify(listener).updateAppWidgetDeferred("pkg", 1)
+
+            appWidgetHostListener.onViewDataChanged(1)
+            verify(listener).onViewDataChanged(1)
+        }
+
+    @Test
+    fun addWidget_noConfigurationCallback_getWidgetUpdate() =
+        testScope.runTest {
+            setupWidgets()
+
+            // Bind service
+            val binder = underTest.onBind(Intent())
+            val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+            // Verify the update is as expected
+            val widgets by collectLastValue(service.listenForWidgetUpdates())
+            assertThat(widgets).hasSize(3)
+            assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+
+            // Add a widget
+            service.addWidget(ComponentName("pkg_4", "cls_4"), UserHandle.of(0), 3, null)
+            runCurrent()
+
+            // Verify an update pushed with widget 4 added
+            assertThat(widgets).hasSize(4)
+            assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+            assertThat(widgets?.get(3)?.has(4, "pkg_4/cls_4", 3, 3)).isTrue()
+        }
+
+    @Test
+    fun addWidget_withConfigurationCallback_configurationFails_doNotAddWidget() =
+        testScope.runTest {
+            setupWidgets()
+
+            // Bind service
+            val binder = underTest.onBind(Intent())
+            val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+            // Verify the update is as expected
+            val widgets by collectLastValue(service.listenForWidgetUpdates())
+            assertThat(widgets).hasSize(3)
+            assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+
+            // Add a widget with a configuration callback that fails
+            service.addWidget(
+                ComponentName("pkg_4", "cls_4"),
+                UserHandle.of(0),
+                3,
+                createConfigureWidgetCallback(success = false),
+            )
+            runCurrent()
+
+            // Verify that widget 4 is not added
+            assertThat(widgets).hasSize(3)
+            assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+        }
+
+    @Test
+    fun addWidget_withConfigurationCallback_configurationSucceeds_addWidget() =
+        testScope.runTest {
+            setupWidgets()
+
+            // Bind service
+            val binder = underTest.onBind(Intent())
+            val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+            // Verify the update is as expected
+            val widgets by collectLastValue(service.listenForWidgetUpdates())
+            assertThat(widgets).hasSize(3)
+            assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+
+            // Add a widget with a configuration callback that fails
+            service.addWidget(
+                ComponentName("pkg_4", "cls_4"),
+                UserHandle.of(0),
+                3,
+                createConfigureWidgetCallback(success = true),
+            )
+            runCurrent()
+
+            // Verify that widget 4 is added
+            assertThat(widgets).hasSize(4)
+            assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+            assertThat(widgets?.get(3)?.has(4, "pkg_4/cls_4", 3, 3)).isTrue()
+        }
+
+    @Test
+    fun deleteWidget_getWidgetUpdate() =
+        testScope.runTest {
+            setupWidgets()
+
+            // Bind service
+            val binder = underTest.onBind(Intent())
+            val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+            // Verify the update is as expected
+            val widgets by collectLastValue(service.listenForWidgetUpdates())
+            assertThat(widgets).hasSize(3)
+            assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+
+            // Delete a widget
+            service.deleteWidget(1)
+            runCurrent()
+
+            // Verify an update pushed with widget 1 removed
+            assertThat(widgets).hasSize(2)
+            assertThat(widgets?.get(0)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(1)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+        }
+
+    @Test
+    fun updateWidgetOrder_getWidgetUpdate() =
+        testScope.runTest {
+            setupWidgets()
+
+            // Bind service
+            val binder = underTest.onBind(Intent())
+            val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+            // Verify the update is as expected
+            val widgets by collectLastValue(service.listenForWidgetUpdates())
+            assertThat(widgets).hasSize(3)
+            assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+
+            // Update widget order
+            service.updateWidgetOrder(intArrayOf(1, 2, 3), intArrayOf(2, 1, 0))
+            runCurrent()
+
+            // Verify an update pushed with the new order
+            assertThat(widgets).hasSize(3)
+            assertThat(widgets?.get(0)?.has(3, "pkg_3/cls_3", 0, 6)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(1, "pkg_1/cls_1", 2, 3)).isTrue()
+        }
+
+    @Test
+    fun resizeWidget_getWidgetUpdate() =
+        testScope.runTest {
+            setupWidgets()
+
+            // Bind service
+            val binder = underTest.onBind(Intent())
+            val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+            // Verify the update is as expected
+            val widgets by collectLastValue(service.listenForWidgetUpdates())
+            assertThat(widgets).hasSize(3)
+            assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+
+            // Resize widget 1 from spanY 3 to 6
+            service.resizeWidget(1, 6, intArrayOf(1, 2, 3), intArrayOf(0, 1, 2))
+            runCurrent()
+
+            // Verify an update pushed with the new size for widget 1
+            assertThat(widgets).hasSize(3)
+            assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 6)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+        }
+
+    @Test
+    fun getIntentSenderForConfigureActivity() =
+        testScope.runTest {
+            val expected = IntentSender(Binder())
+            whenever(appWidgetHost.getIntentSenderForConfigureActivity(anyInt(), anyInt()))
+                .thenReturn(expected)
+
+            // Bind service
+            val binder = underTest.onBind(Intent())
+            val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+            val actual = service.getIntentSenderForConfigureActivity(1)
+            assertThat(actual).isEqualTo(expected)
+        }
+
+    private fun setupWidgets() {
+        widgetRepository.addWidget(
+            appWidgetId = 1,
+            componentName = "pkg_1/cls_1",
+            rank = 0,
+            spanY = 3,
+        )
+        widgetRepository.addWidget(
+            appWidgetId = 2,
+            componentName = "pkg_2/cls_2",
+            rank = 1,
+            spanY = 3,
+        )
+        widgetRepository.addWidget(
+            appWidgetId = 3,
+            componentName = "pkg_3/cls_3",
+            rank = 2,
+            spanY = 6,
+        )
+    }
+
+    private fun IGlanceableHubWidgetManagerService.listenForWidgetUpdates() =
+        conflatedCallbackFlow {
+            val listener =
+                object : IGlanceableHubWidgetsListener.Stub() {
+                    override fun onWidgetsUpdated(widgets: List<CommunalWidgetContentModel>) {
+                        trySend(widgets)
+                    }
+                }
+            addWidgetsListener(listener)
+            awaitClose { removeWidgetsListener(listener) }
+        }
+
+    private fun CommunalWidgetContentModel.has(
+        appWidgetId: Int,
+        componentName: String,
+        rank: Int,
+        spanY: Int,
+    ): Boolean {
+        return this is CommunalWidgetContentModel.Available &&
+            this.appWidgetId == appWidgetId &&
+            this.providerInfo.provider.flattenToString() == componentName &&
+            this.rank == rank &&
+            this.spanY == spanY
+    }
+
+    private fun createConfigureWidgetCallback(success: Boolean): IConfigureWidgetCallback {
+        return object : IConfigureWidgetCallback.Stub() {
+            override fun onConfigureWidget(
+                appWidgetId: Int,
+                resultReceiver: IConfigureWidgetCallback.IResultReceiver?,
+            ) {
+                resultReceiver?.onResult(success)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt
index 55fafdf..e1bdf1c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt
@@ -18,16 +18,18 @@
 
 import android.app.Activity
 import android.content.ActivityNotFoundException
+import android.content.IntentSender
+import android.os.Binder
+import android.os.OutcomeReceiver
 import androidx.activity.ComponentActivity
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper
+import com.android.systemui.concurrency.fakeExecutor
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.async
@@ -37,15 +39,22 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class WidgetConfigurationControllerTest : SysuiTestCase() {
-    @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
-    @Mock private lateinit var ownerActivity: ComponentActivity
+    private val appWidgetHost = mock<CommunalAppWidgetHost>()
+    private val ownerActivity = mock<ComponentActivity>()
+
+    private val outcomeReceiverCaptor = argumentCaptor<OutcomeReceiver<IntentSender?, Throwable>>()
 
     private val kosmos = testKosmos()
 
@@ -53,13 +62,19 @@
 
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
         underTest =
-            WidgetConfigurationController(ownerActivity, appWidgetHost, kosmos.testDispatcher)
+            WidgetConfigurationController(
+                ownerActivity,
+                { appWidgetHost },
+                kosmos.testDispatcher,
+                kosmos.fakeGlanceableHubMultiUserHelper,
+                { kosmos.mockGlanceableHubWidgetManager },
+                kosmos.fakeExecutor,
+            )
     }
 
     @Test
-    fun configurationFailsWhenActivityNotFound() =
+    fun configureWidget_activityNotFound_returnsFalse() =
         with(kosmos) {
             testScope.runTest {
                 whenever(
@@ -68,7 +83,7 @@
                             eq(123),
                             anyInt(),
                             eq(WidgetConfigurationController.REQUEST_CODE),
-                            any()
+                            any(),
                         )
                     )
                     .thenThrow(ActivityNotFoundException())
@@ -78,7 +93,7 @@
         }
 
     @Test
-    fun configurationFails() =
+    fun configureWidget_configurationFails_returnsFalse() =
         with(kosmos) {
             testScope.runTest {
                 val result = async { underTest.configureWidget(123) }
@@ -94,7 +109,7 @@
         }
 
     @Test
-    fun configurationSuccessful() =
+    fun configureWidget_configurationSucceeds_returnsTrue() =
         with(kosmos) {
             testScope.runTest {
                 val result = async { underTest.configureWidget(123) }
@@ -108,4 +123,116 @@
                 result.cancel()
             }
         }
+
+    @Test
+    fun configureWidget_headlessSystemUser_activityNotFound_returnsFalse() =
+        with(kosmos) {
+            testScope.runTest {
+                fakeGlanceableHubMultiUserHelper.setIsInHeadlessSystemUser(true)
+
+                // Activity not found
+                whenever(
+                        mockGlanceableHubWidgetManager.getIntentSenderForConfigureActivity(
+                            anyInt(),
+                            outcomeReceiverCaptor.capture(),
+                            any(),
+                        )
+                    )
+                    .then { outcomeReceiverCaptor.firstValue.onError(ActivityNotFoundException()) }
+
+                val result = async { underTest.configureWidget(123) }
+                runCurrent()
+
+                assertThat(result.await()).isFalse()
+                result.cancel()
+            }
+        }
+
+    @Test
+    fun configureWidget_headlessSystemUser_intentSenderNull_returnsFalse() =
+        with(kosmos) {
+            testScope.runTest {
+                fakeGlanceableHubMultiUserHelper.setIsInHeadlessSystemUser(true)
+
+                prepareIntentSender(null)
+
+                assertThat(underTest.configureWidget(123)).isFalse()
+            }
+        }
+
+    @Test
+    fun configureWidget_headlessSystemUser_configurationFails_returnsFalse() =
+        with(kosmos) {
+            testScope.runTest {
+                fakeGlanceableHubMultiUserHelper.setIsInHeadlessSystemUser(true)
+
+                val intentSender = IntentSender(Binder())
+                prepareIntentSender(intentSender)
+
+                val result = async { underTest.configureWidget(123) }
+                runCurrent()
+                assertThat(result.isCompleted).isFalse()
+
+                verify(ownerActivity)
+                    .startIntentSenderForResult(
+                        eq(intentSender),
+                        eq(WidgetConfigurationController.REQUEST_CODE),
+                        anyOrNull(),
+                        anyInt(),
+                        anyInt(),
+                        anyInt(),
+                        any(),
+                    )
+
+                underTest.setConfigurationResult(Activity.RESULT_CANCELED)
+                runCurrent()
+
+                assertThat(result.await()).isFalse()
+                result.cancel()
+            }
+        }
+
+    @Test
+    fun configureWidget_headlessSystemUser_configurationSucceeds_returnsTrue() =
+        with(kosmos) {
+            testScope.runTest {
+                fakeGlanceableHubMultiUserHelper.setIsInHeadlessSystemUser(true)
+
+                val intentSender = IntentSender(Binder())
+                prepareIntentSender(intentSender)
+
+                val result = async { underTest.configureWidget(123) }
+                runCurrent()
+                assertThat(result.isCompleted).isFalse()
+
+                verify(ownerActivity)
+                    .startIntentSenderForResult(
+                        eq(intentSender),
+                        eq(WidgetConfigurationController.REQUEST_CODE),
+                        anyOrNull(),
+                        anyInt(),
+                        anyInt(),
+                        anyInt(),
+                        any(),
+                    )
+
+                underTest.setConfigurationResult(Activity.RESULT_OK)
+                runCurrent()
+
+                assertThat(result.await()).isTrue()
+                result.cancel()
+            }
+        }
+
+    private fun prepareIntentSender(intentSender: IntentSender?) =
+        with(kosmos) {
+            whenever(
+                    mockGlanceableHubWidgetManager.getIntentSenderForConfigureActivity(
+                        anyInt(),
+                        outcomeReceiverCaptor.capture(),
+                        any(),
+                    )
+                )
+                .then { outcomeReceiverCaptor.firstValue.onResult(intentSender) }
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
index ed45e8c..ef02817 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
@@ -23,88 +23,86 @@
 import android.content.pm.UserInfo
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
 import android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_HOME_CONTROLS_DREAM_HSUM
+import com.android.systemui.Flags.homeControlsDreamHsum
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.ControlsServiceInfo
-import com.android.systemui.controls.dagger.ControlsComponent
-import com.android.systemui.controls.management.ControlsListingController
-import com.android.systemui.controls.panels.AuthorizedPanelsRepository
 import com.android.systemui.controls.panels.SelectedComponentRepository
 import com.android.systemui.controls.panels.authorizedPanelsRepository
 import com.android.systemui.controls.panels.selectedComponentRepository
 import com.android.systemui.dreams.homecontrols.system.HomeControlsDreamStartable
-import com.android.systemui.dreams.homecontrols.system.domain.interactor.HomeControlsComponentInteractor
 import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsComponent
 import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsListingController
 import com.android.systemui.dreams.homecontrols.system.domain.interactor.homeControlsComponentInteractor
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.fakeUserTracker
 import com.android.systemui.settings.userTracker
 import com.android.systemui.testKosmos
-import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
 import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class HomeControlsDreamStartableTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class HomeControlsDreamStartableTest(flags: FlagsParameterization) : SysuiTestCase() {
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
 
     private val kosmos = testKosmos()
-
-    @Mock private lateinit var packageManager: PackageManager
-
-    private lateinit var homeControlsComponentInteractor: HomeControlsComponentInteractor
-    private lateinit var selectedComponentRepository: SelectedComponentRepository
-    private lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
-    private lateinit var userRepository: FakeUserRepository
-    private lateinit var controlsComponent: ControlsComponent
-    private lateinit var controlsListingController: ControlsListingController
-
-    private lateinit var startable: HomeControlsDreamStartable
-    private val componentName = ComponentName(context, HomeControlsDreamService::class.java)
     private val testScope = kosmos.testScope
 
+    private val systemUserPackageManager = mock<PackageManager>()
+    private val userPackageManager = mock<PackageManager>()
+
+    private val selectedComponentRepository = kosmos.selectedComponentRepository
+    private val userRepository =
+        kosmos.fakeUserRepository.apply { setUserInfos(listOf(PRIMARY_USER)) }
+    private val controlsListingController =
+        kosmos.controlsListingController.stub {
+            on { getCurrentServices() } doReturn
+                listOf(buildControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+        }
+    private val controlsComponent =
+        kosmos.controlsComponent.stub {
+            on { getControlsListingController() } doReturn Optional.of(controlsListingController)
+        }
+
+    private val underTest by lazy {
+        HomeControlsDreamStartable(
+            mContext,
+            systemUserPackageManager,
+            kosmos.userTracker,
+            kosmos.homeControlsComponentInteractor,
+            kosmos.applicationCoroutineScope,
+        )
+    }
+    private val componentName = ComponentName(context, HomeControlsDreamService::class.java)
+
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        selectedComponentRepository = kosmos.selectedComponentRepository
-        authorizedPanelsRepository = kosmos.authorizedPanelsRepository
-        userRepository = kosmos.fakeUserRepository
-        controlsComponent = kosmos.controlsComponent
-        controlsListingController = kosmos.controlsListingController
-
-        userRepository.setUserInfos(listOf(PRIMARY_USER))
-
-        authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE_PANEL))
-
+        kosmos.authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE_PANEL))
         whenever(controlsComponent.getControlsListingController())
             .thenReturn(Optional.of(controlsListingController))
-        whenever(controlsListingController.getCurrentServices())
-            .thenReturn(listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true)))
-
-        homeControlsComponentInteractor = kosmos.homeControlsComponentInteractor
-
-        startable =
-            HomeControlsDreamStartable(
-                mContext,
-                packageManager,
-                kosmos.userTracker,
-                homeControlsComponentInteractor,
-                kosmos.applicationCoroutineScope,
-            )
+        whenever(kosmos.fakeUserTracker.userContext.packageManager).thenReturn(userPackageManager)
     }
 
     @Test
@@ -113,13 +111,19 @@
         testScope.runTest {
             userRepository.setSelectedUserInfo(PRIMARY_USER)
             selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
-            startable.start()
+            underTest.start()
             runCurrent()
+            val packageManager =
+                if (homeControlsDreamHsum()) {
+                    userPackageManager
+                } else {
+                    systemUserPackageManager
+                }
             verify(packageManager)
                 .setComponentEnabledSetting(
-                    eq(componentName),
-                    eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
-                    eq(PackageManager.DONT_KILL_APP),
+                    componentName,
+                    PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+                    PackageManager.DONT_KILL_APP,
                 )
         }
 
@@ -128,13 +132,19 @@
     fun testStartDisablesHomeControlsDreamServiceWhenPanelComponentIsNull() =
         testScope.runTest {
             selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
-            startable.start()
+            underTest.start()
             runCurrent()
+            val packageManager =
+                if (homeControlsDreamHsum()) {
+                    userPackageManager
+                } else {
+                    systemUserPackageManager
+                }
             verify(packageManager)
                 .setComponentEnabledSetting(
-                    eq(componentName),
-                    eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
-                    eq(PackageManager.DONT_KILL_APP),
+                    componentName,
+                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                    PackageManager.DONT_KILL_APP,
                 )
         }
 
@@ -143,8 +153,14 @@
     fun testStartDisablesDreamServiceWhenFlagIsDisabled() =
         testScope.runTest {
             selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
-            startable.start()
+            underTest.start()
             runCurrent()
+            val packageManager =
+                if (homeControlsDreamHsum()) {
+                    userPackageManager
+                } else {
+                    systemUserPackageManager
+                }
             verify(packageManager)
                 .setComponentEnabledSetting(
                     eq(componentName),
@@ -153,7 +169,7 @@
                 )
         }
 
-    private fun ControlsServiceInfo(
+    private fun buildControlsServiceInfo(
         componentName: ComponentName,
         label: CharSequence,
         hasPanel: Boolean,
@@ -186,6 +202,10 @@
     }
 
     companion object {
+        @get:Parameters(name = "{0}")
+        @JvmStatic
+        val params = FlagsParameterization.allCombinationsOf(FLAG_HOME_CONTROLS_DREAM_HSUM)
+
         private const val PRIMARY_USER_ID = 0
         private val PRIMARY_USER =
             UserInfo(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt
index f8a45e8..b343def 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt
@@ -31,7 +31,8 @@
 import com.android.systemui.controls.panels.selectedComponentRepository
 import com.android.systemui.controls.settings.FakeControlsSettingsRepository
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.dreams.homecontrols.shared.IOnControlsSettingsChangeListener
+import com.android.systemui.dreams.homecontrols.shared.controlsSettings
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
 import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsComponent
 import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsListingController
 import com.android.systemui.dreams.homecontrols.system.domain.interactor.homeControlsComponentInteractor
@@ -42,13 +43,10 @@
 import com.android.systemui.settings.fakeUserTracker
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.mockito.withArgCaptor
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -56,6 +54,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito
+import org.mockito.kotlin.whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -90,13 +89,13 @@
     fun testRegisterSingleListener() =
         testScope.runTest {
             setup()
-            val controlsSettings by collectLastValue(addCallback())
+            val controlsSettings by collectLastValue(underTest.controlsSettings)
             runServicesUpdate()
 
             assertThat(controlsSettings)
                 .isEqualTo(
-                    CallbackArgs(
-                        panelComponent = TEST_COMPONENT,
+                    HomeControlsComponentInfo(
+                        componentName = TEST_COMPONENT,
                         allowTrivialControlsOnLockscreen = false,
                     )
                 )
@@ -106,21 +105,21 @@
     fun testRegisterMultipleListeners() =
         testScope.runTest {
             setup()
-            val controlsSettings1 by collectLastValue(addCallback())
-            val controlsSettings2 by collectLastValue(addCallback())
+            val controlsSettings1 by collectLastValue(underTest.controlsSettings)
+            val controlsSettings2 by collectLastValue(underTest.controlsSettings)
             runServicesUpdate()
 
             assertThat(controlsSettings1)
                 .isEqualTo(
-                    CallbackArgs(
-                        panelComponent = TEST_COMPONENT,
+                    HomeControlsComponentInfo(
+                        componentName = TEST_COMPONENT,
                         allowTrivialControlsOnLockscreen = false,
                     )
                 )
             assertThat(controlsSettings2)
                 .isEqualTo(
-                    CallbackArgs(
-                        panelComponent = TEST_COMPONENT,
+                    HomeControlsComponentInfo(
+                        componentName = TEST_COMPONENT,
                         allowTrivialControlsOnLockscreen = false,
                     )
                 )
@@ -130,13 +129,13 @@
     fun testListenerCalledWhenStateChanges() =
         testScope.runTest {
             setup()
-            val controlsSettings by collectLastValue(addCallback())
+            val controlsSettings by collectLastValue(underTest.controlsSettings)
             runServicesUpdate()
 
             assertThat(controlsSettings)
                 .isEqualTo(
-                    CallbackArgs(
-                        panelComponent = TEST_COMPONENT,
+                    HomeControlsComponentInfo(
+                        componentName = TEST_COMPONENT,
                         allowTrivialControlsOnLockscreen = false,
                     )
                 )
@@ -146,13 +145,47 @@
             // Updated with null component now that we are no longer authorized.
             assertThat(controlsSettings)
                 .isEqualTo(
-                    CallbackArgs(panelComponent = null, allowTrivialControlsOnLockscreen = false)
+                    HomeControlsComponentInfo(
+                        componentName = null,
+                        allowTrivialControlsOnLockscreen = false,
+                    )
                 )
         }
 
+    @Test
+    fun testDestroy() =
+        testScope.runTest {
+            setup()
+            val controlsSettings1 by collectLastValue(underTest.controlsSettings)
+
+            assertThat(controlsSettings1)
+                .isEqualTo(
+                    HomeControlsComponentInfo(
+                        componentName = null,
+                        allowTrivialControlsOnLockscreen = false,
+                    )
+                )
+
+            underTest.onDestroy()
+            runServicesUpdate()
+            fakeControlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
+
+            // Existing callback is not triggered if destroyed.
+            assertThat(controlsSettings1)
+                .isEqualTo(
+                    HomeControlsComponentInfo(
+                        componentName = null,
+                        allowTrivialControlsOnLockscreen = false,
+                    )
+                )
+            // New callbacks cannot be added.
+            val controlsSettings2 by collectLastValue(underTest.controlsSettings)
+            assertThat(controlsSettings2).isNull()
+        }
+
     private fun TestScope.runServicesUpdate() {
         runCurrent()
-        val listings = listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = true))
+        val listings = listOf(buildControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = true))
         val callback = withArgCaptor {
             Mockito.verify(kosmos.controlsListingController).addCallback(capture())
         }
@@ -160,20 +193,6 @@
         runCurrent()
     }
 
-    private fun addCallback() = conflatedCallbackFlow {
-        val callback =
-            object : IOnControlsSettingsChangeListener.Stub() {
-                override fun onControlsSettingsChanged(
-                    panelComponent: ComponentName?,
-                    allowTrivialControlsOnLockscreen: Boolean,
-                ) {
-                    trySend(CallbackArgs(panelComponent, allowTrivialControlsOnLockscreen))
-                }
-            }
-        underTest.registerListenerForCurrentUser(callback)
-        awaitClose { underTest.unregisterListenerForCurrentUser(callback) }
-    }
-
     private suspend fun TestScope.setup() {
         kosmos.fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
         kosmos.fakeUserTracker.set(listOf(PRIMARY_USER), 0)
@@ -182,12 +201,7 @@
         runCurrent()
     }
 
-    private data class CallbackArgs(
-        val panelComponent: ComponentName?,
-        val allowTrivialControlsOnLockscreen: Boolean,
-    )
-
-    private fun ControlsServiceInfo(
+    private fun buildControlsServiceInfo(
         componentName: ComponentName,
         label: CharSequence,
         hasPanel: Boolean,
@@ -225,7 +239,7 @@
             UserInfo(
                 /* id= */ PRIMARY_USER_ID,
                 /* name= */ "primary user",
-                /* flags= */ UserInfo.FLAG_PRIMARY,
+                /* flags= */ UserInfo.FLAG_MAIN,
             )
 
         private const val TEST_PACKAGE = "pkg"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt
index f331060..5827c7b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.table.TableLogBuffer
 import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
 import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Test
@@ -134,6 +135,21 @@
     }
 
     @Test
+    fun registerDumpable_supportsAnonymousDumpables() {
+        val anonDumpable =
+            object : Dumpable {
+                override fun dump(pw: PrintWriter, args: Array<out String>) {
+                    pw.println("AnonDumpable")
+                }
+            }
+
+        // THEN registration with implicit names should succeed
+        dumpManager.registerCriticalDumpable(anonDumpable)
+
+        // No exception thrown
+    }
+
+    @Test
     fun getDumpables_returnsSafeCollection() {
         // GIVEN a variety of registered dumpables
         dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
index 3388a78..20cd860 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
@@ -28,11 +28,13 @@
 import com.android.systemui.Flags.FLAG_SYSUI_TEAMFOOD
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.withArgCaptor
 import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.Serializable
@@ -68,6 +70,7 @@
     @Mock private lateinit var systemProperties: SystemPropertiesHelper
     @Mock private lateinit var resources: Resources
     @Mock private lateinit var restarter: Restarter
+    private lateinit var fakeExecutor: FakeExecutor
     private lateinit var userTracker: FakeUserTracker
     private val flagMap = mutableMapOf<String, Flag<*>>()
     private lateinit var broadcastReceiver: BroadcastReceiver
@@ -83,6 +86,7 @@
         flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA)
         flagMap.put(releasedFlagB.name, releasedFlagB)
 
+        fakeExecutor = FakeExecutor(FakeSystemClock())
         userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockContext })
 
         mFeatureFlagsClassicDebug =
@@ -95,7 +99,8 @@
                 serverFlagReader,
                 flagMap,
                 restarter,
-                userTracker
+                userTracker,
+                fakeExecutor,
             )
         mFeatureFlagsClassicDebug.init()
         verify(flagManager).onSettingsChangedAction = any()
@@ -325,14 +330,14 @@
         // trying to erase an id not in the map does nothing
         broadcastReceiver.onReceive(
             mockContext,
-            Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "")
+            Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, ""),
         )
         verifyNoMoreInteractions(flagManager, globalSettings)
 
         // valid id with no value puts empty string in the setting
         broadcastReceiver.onReceive(
             mockContext,
-            Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "1")
+            Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "1"),
         )
         verifyPutData("1", "", numReads = 0)
     }
@@ -415,7 +420,7 @@
         serverFlagReader.setFlagValue(
             teamfoodableFlagA.namespace,
             teamfoodableFlagA.name,
-            !teamfoodableFlagA.default
+            !teamfoodableFlagA.default,
         )
         verify(restarter, never()).restartSystemUI(anyString())
     }
@@ -428,7 +433,7 @@
         serverFlagReader.setFlagValue(
             teamfoodableFlagA.namespace,
             teamfoodableFlagA.name,
-            !teamfoodableFlagA.default
+            !teamfoodableFlagA.default,
         )
         verify(restarter).restartSystemUI(anyString())
     }
@@ -441,7 +446,7 @@
         serverFlagReader.setFlagValue(
             teamfoodableFlagA.namespace,
             teamfoodableFlagA.name,
-            teamfoodableFlagA.default
+            teamfoodableFlagA.default,
         )
         verify(restarter, never()).restartSystemUI(anyString())
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt
index 2735d2f..a0bef72 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt
@@ -24,7 +24,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
 import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
 import com.android.systemui.keyboard.docking.domain.interactor.KeyboardDockingIndicationInteractor
 import com.google.common.truth.Truth.assertThat
@@ -59,7 +59,7 @@
 
         val keyboardDockingIndicationInteractor =
             KeyboardDockingIndicationInteractor(keyboardRepository)
-        val configurationInteractor = ConfigurationInteractor(configurationRepository)
+        val configurationInteractor = ConfigurationInteractorImpl(configurationRepository)
 
         underTest =
             KeyboardDockingIndicationViewModel(
@@ -67,7 +67,7 @@
                 context,
                 keyboardDockingIndicationInteractor,
                 configurationInteractor,
-                testScope.backgroundScope
+                testScope.backgroundScope,
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
index 9e20e7d..620b8b6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
@@ -16,7 +16,11 @@
 
 package com.android.systemui.keyboard.shortcut.data.repository
 
+import android.graphics.drawable.Drawable
+import android.hardware.input.KeyGlyphMap
 import android.hardware.input.fakeInputManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.view.KeyEvent.KEYCODE_1
 import android.view.KeyEvent.KEYCODE_A
 import android.view.KeyEvent.KEYCODE_B
@@ -26,10 +30,12 @@
 import android.view.KeyEvent.KEYCODE_F
 import android.view.KeyEvent.KEYCODE_G
 import android.view.KeyEvent.META_FUNCTION_ON
+import android.view.KeyEvent.META_META_ON
 import android.view.KeyboardShortcutGroup
 import android.view.KeyboardShortcutInfo
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_SHORTCUT_HELPER_KEY_GLYPH
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
@@ -49,6 +55,7 @@
 import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
 import com.android.systemui.kosmos.testDispatcher
 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
@@ -57,6 +64,9 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -187,6 +197,79 @@
                 )
         }
 
+    @EnableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
+    @Test
+    fun modifierMappedToCustomDrawableWhenKeyGlyphMapExists() =
+        testScope.runTest {
+            val metaDrawable = mock(Drawable::class.java)
+            val keyGlyph = mock(KeyGlyphMap::class.java)
+            whenever(keyGlyph.getDrawableForModifierState(context, META_META_ON))
+                .thenReturn(metaDrawable)
+            whenever(kosmos.fakeInputManager.inputManager.getKeyGlyphMap(anyInt()))
+                .thenReturn(keyGlyph)
+            fakeSystemSource.setGroups(simpleGroup(simpleShortcutInfo(KEYCODE_1, META_META_ON)))
+            helper.toggle(deviceId = 123)
+
+            val categories by collectLastValue(repo.categories)
+            val systemCategory = categories?.firstOrNull { it.type == ShortcutCategoryType.System }
+
+            val expectedCategory =
+                ShortcutCategory(
+                    type = ShortcutCategoryType.System,
+                    simpleSubCategory(
+                        simpleDrawableModifierShortcut("1", modifierDrawable = metaDrawable)
+                    ),
+                )
+
+            assertThat(systemCategory).isEqualTo(expectedCategory)
+        }
+
+    @EnableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
+    @Test
+    fun modifierMappedToDefaultDrawableWhenNoKeyGlyphMapExists() =
+        testScope.runTest {
+            fakeSystemSource.setGroups(simpleGroup(simpleShortcutInfo(KEYCODE_1, META_META_ON)))
+            helper.toggle(deviceId = 123)
+
+            val categories by collectLastValue(repo.categories)
+            val systemCategory = categories?.firstOrNull { it.type == ShortcutCategoryType.System }
+
+            val expectedCategory =
+                ShortcutCategory(
+                    type = ShortcutCategoryType.System,
+                    simpleSubCategory(
+                        simpleResIdModifierShortcut("1", modifierResId = R.drawable.ic_ksh_key_meta)
+                    ),
+                )
+            assertThat(systemCategory).isEqualTo(expectedCategory)
+        }
+
+    @DisableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
+    @Test
+    fun modifierMappedToDefaultDrawableWhenKeyGlyphDisabled() =
+        testScope.runTest {
+            val metaDrawable = mock(Drawable::class.java)
+            val keyGlyph = mock(KeyGlyphMap::class.java)
+            whenever(keyGlyph.getDrawableForModifierState(context, META_META_ON))
+                .thenReturn(metaDrawable)
+            whenever(kosmos.fakeInputManager.inputManager.getKeyGlyphMap(anyInt()))
+                .thenReturn(keyGlyph)
+            fakeSystemSource.setGroups(simpleGroup(simpleShortcutInfo(KEYCODE_1, META_META_ON)))
+            helper.toggle(deviceId = 123)
+
+            val categories by collectLastValue(repo.categories)
+            val systemCategory = categories?.firstOrNull { it.type == ShortcutCategoryType.System }
+
+            val expectedCategory =
+                ShortcutCategory(
+                    type = ShortcutCategoryType.System,
+                    simpleSubCategory(
+                        simpleResIdModifierShortcut("1", modifierResId = R.drawable.ic_ksh_key_meta)
+                    ),
+                )
+            assertThat(systemCategory).isEqualTo(expectedCategory)
+        }
+
     private fun simpleSubCategory(vararg shortcuts: Shortcut) =
         ShortcutSubCategory(simpleGroupLabel, shortcuts.asList())
 
@@ -196,6 +279,37 @@
             commands = listOf(ShortcutCommand(keys.map { ShortcutKey.Text(it) })),
         )
 
+    private fun simpleDrawableModifierShortcut(
+        vararg keys: String,
+        modifierDrawable: Drawable,
+    ): Shortcut {
+        val keyShortcuts = keys.map { ShortcutKey.Text(it) }
+        return Shortcut(
+            label = simpleShortcutLabel,
+            commands =
+                listOf(
+                    ShortcutCommand(
+                        listOf(ShortcutKey.Icon.DrawableIcon(drawable = modifierDrawable)) +
+                            keyShortcuts
+                    )
+                ),
+        )
+    }
+
+    private fun simpleResIdModifierShortcut(vararg keys: String, modifierResId: Int): Shortcut {
+        val keyShortcuts = keys.map { ShortcutKey.Text(it) }
+        return Shortcut(
+            label = simpleShortcutLabel,
+            commands =
+                listOf(
+                    ShortcutCommand(
+                        listOf(ShortcutKey.Icon.ResIdIcon(drawableResId = modifierResId)) +
+                            keyShortcuts
+                    )
+                ),
+        )
+    }
+
     private fun simpleGroup(vararg shortcuts: KeyboardShortcutInfo) =
         KeyboardShortcutGroup(simpleGroupLabel, shortcuts.asList())
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
index 26ce67d..7ec53df 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
@@ -40,8 +40,8 @@
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
 import com.android.systemui.util.settings.fakeSettings
-import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepositoryImpl
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
@@ -72,14 +72,13 @@
 
     @Before
     fun setup() {
-        val settingsRepository =
-            UserAwareSecureSettingsRepositoryImpl(secureSettings, userRepository, dispatcher)
+        val settingsRepository = kosmos.userAwareSecureSettingsRepository
         val stickyKeysRepository =
             StickyKeysRepositoryImpl(
                 inputManager,
                 dispatcher,
                 settingsRepository,
-                mock<StickyKeysLogger>()
+                mock<StickyKeysLogger>(),
             )
         setStickyKeySetting(enabled = false)
         viewModel =
@@ -114,7 +113,7 @@
             verify(inputManager)
                 .registerStickyModifierStateListener(
                     any(),
-                    any(InputManager.StickyModifierStateListener::class.java)
+                    any(InputManager.StickyModifierStateListener::class.java),
                 )
         }
     }
@@ -187,11 +186,7 @@
 
             assertThat(stickyKeys)
                 .isEqualTo(
-                    mapOf(
-                        ALT to Locked(false),
-                        META to Locked(false),
-                        SHIFT to Locked(false),
-                    )
+                    mapOf(ALT to Locked(false), META to Locked(false), SHIFT to Locked(false))
                 )
         }
     }
@@ -218,7 +213,7 @@
                 mapOf(
                     META to false,
                     SHIFT to false, // shift is sticky but not locked
-                    CTRL to false
+                    CTRL to false,
                 )
             )
             val previousShiftIndex = stickyKeys?.toList()?.indexOf(SHIFT to Locked(false))
@@ -228,7 +223,7 @@
                     SHIFT to false,
                     SHIFT to true, // shift is now locked
                     META to false,
-                    CTRL to false
+                    CTRL to false,
                 )
             )
             assertThat(stickyKeys?.toList()?.indexOf(SHIFT to Locked(true)))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
index 6e16705..2c12f87 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
@@ -34,6 +34,8 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.Transition
 import com.android.systemui.scene.data.repository.sceneContainerRepository
 import com.android.systemui.scene.data.repository.setSceneTransition
 import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -115,9 +117,9 @@
 
             assertEquals(
                 listOf(
-                    false, // We should start with the surface invisible on LOCKSCREEN.
+                    false // We should start with the surface invisible on LOCKSCREEN.
                 ),
-                values
+                values,
             )
 
             val lockscreenSpecificSurfaceVisibility = true
@@ -134,13 +136,7 @@
 
             // We started a transition from LOCKSCREEN, we should be using the value emitted by the
             // lockscreenSurfaceVisibilityFlow.
-            assertEquals(
-                listOf(
-                    false,
-                    lockscreenSpecificSurfaceVisibility,
-                ),
-                values
-            )
+            assertEquals(listOf(false, lockscreenSpecificSurfaceVisibility), values)
 
             // Go back to LOCKSCREEN, since we won't emit 'true' twice in a row.
             transitionRepository.sendTransitionStep(
@@ -166,7 +162,7 @@
                     lockscreenSpecificSurfaceVisibility,
                     false, // FINISHED (LOCKSCREEN)
                 ),
-                values
+                values,
             )
 
             val bouncerSpecificVisibility = true
@@ -191,13 +187,13 @@
                     false,
                     bouncerSpecificVisibility,
                 ),
-                values
+                values,
             )
         }
 
     @Test
     @EnableSceneContainer
-    fun surfaceBehindVisibility_fromLockscreenToGone_noUserInput_trueThroughout() =
+    fun surfaceBehindVisibility_fromLockscreenToGone_dependsOnDeviceEntry() =
         testScope.runTest {
             val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility)
             val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
@@ -212,7 +208,7 @@
                 SuccessFingerprintAuthenticationStatus(0, true)
             )
 
-            // Start the transition to Gone, the surface should become immediately visible.
+            // Start the transition to Gone, the surface should remain invisible.
             kosmos.setSceneTransition(
                 ObservableTransitionState.Transition(
                     fromScene = Scenes.Lockscreen,
@@ -224,9 +220,9 @@
                 )
             )
             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
-            assertThat(isSurfaceBehindVisible).isTrue()
+            assertThat(isSurfaceBehindVisible).isFalse()
 
-            // Towards the end of the transition, the surface should continue to be visible.
+            // Towards the end of the transition, the surface should continue to remain invisible.
             kosmos.setSceneTransition(
                 ObservableTransitionState.Transition(
                     fromScene = Scenes.Lockscreen,
@@ -238,7 +234,7 @@
                 )
             )
             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
-            assertThat(isSurfaceBehindVisible).isTrue()
+            assertThat(isSurfaceBehindVisible).isFalse()
 
             // After the transition, settles on Gone. Surface behind should stay visible now.
             kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Gone))
@@ -249,43 +245,6 @@
 
     @Test
     @EnableSceneContainer
-    fun surfaceBehindVisibility_fromLockscreenToGone_withUserInput_falseUntilInputStops() =
-        testScope.runTest {
-            val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility)
-            val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
-
-            // Before the transition, we start on Lockscreen so the surface should start invisible.
-            kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Lockscreen))
-            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
-            assertThat(isSurfaceBehindVisible).isFalse()
-
-            // Unlocked with fingerprint.
-            kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus(
-                SuccessFingerprintAuthenticationStatus(0, true)
-            )
-
-            // Start the transition to Gone, the surface should not be visible while
-            // isUserInputOngoing is true
-            val isUserInputOngoing = MutableStateFlow(true)
-            kosmos.setSceneTransition(
-                ObservableTransitionState.Transition(
-                    fromScene = Scenes.Lockscreen,
-                    toScene = Scenes.Gone,
-                    isInitiatedByUserInput = true,
-                    isUserInputOngoing = isUserInputOngoing,
-                    progress = flowOf(0.51f),
-                    currentScene = flowOf(Scenes.Gone),
-                )
-            )
-            assertThat(isSurfaceBehindVisible).isFalse()
-
-            // When isUserInputOngoing becomes false, then the surface should become visible.
-            isUserInputOngoing.value = false
-            assertThat(isSurfaceBehindVisible).isTrue()
-        }
-
-    @Test
-    @EnableSceneContainer
     fun surfaceBehindVisibility_fromBouncerToGone_becomesTrue() =
         testScope.runTest {
             val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility)
@@ -362,20 +321,14 @@
             kosmos.sceneInteractor.changeScene(Scenes.Gone, "")
             assertThat(currentScene).isEqualTo(Scenes.Gone)
 
-            listOf(
-                    Scenes.Shade,
-                    Scenes.QuickSettings,
-                    Scenes.Shade,
-                    Scenes.Gone,
-                )
-                .forEach { scene ->
-                    kosmos.setSceneTransition(ObservableTransitionState.Idle(scene))
-                    kosmos.sceneInteractor.changeScene(scene, "")
-                    assertThat(currentScene).isEqualTo(scene)
-                    assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"")
-                        .that(isSurfaceBehindVisible)
-                        .isTrue()
-                }
+            listOf(Scenes.Shade, Scenes.QuickSettings, Scenes.Shade, Scenes.Gone).forEach { scene ->
+                kosmos.setSceneTransition(ObservableTransitionState.Idle(scene))
+                kosmos.sceneInteractor.changeScene(scene, "")
+                assertThat(currentScene).isEqualTo(scene)
+                assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"")
+                    .that(isSurfaceBehindVisible)
+                    .isTrue()
+            }
         }
 
     @Test
@@ -386,19 +339,14 @@
             val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
 
-            listOf(
-                    Scenes.Shade,
-                    Scenes.QuickSettings,
-                    Scenes.Shade,
-                    Scenes.Lockscreen,
-                )
-                .forEach { scene ->
-                    kosmos.setSceneTransition(ObservableTransitionState.Idle(scene))
-                    kosmos.sceneInteractor.changeScene(scene, "")
-                    assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"")
-                        .that(isSurfaceBehindVisible)
-                        .isFalse()
-                }
+            listOf(Scenes.Shade, Scenes.QuickSettings, Scenes.Shade, Scenes.Lockscreen).forEach {
+                scene ->
+                kosmos.setSceneTransition(ObservableTransitionState.Idle(scene))
+                kosmos.sceneInteractor.changeScene(scene, "")
+                assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"")
+                    .that(isSurfaceBehindVisible)
+                    .isFalse()
+            }
         }
 
     @Test
@@ -427,9 +375,9 @@
 
             assertEquals(
                 listOf(
-                    false, // Not using the animation when we're just sitting on LOCKSCREEN.
+                    false // Not using the animation when we're just sitting on LOCKSCREEN.
                 ),
-                values
+                values,
             )
 
             surfaceBehindIsAnimatingFlow.emit(true)
@@ -437,7 +385,7 @@
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.GONE,
-                testScope
+                testScope,
             )
             runCurrent()
 
@@ -446,7 +394,7 @@
                     false,
                     true, // Still true when we're FINISHED -> GONE, since we're still animating.
                 ),
-                values
+                values,
             )
 
             surfaceBehindIsAnimatingFlow.emit(false)
@@ -458,7 +406,7 @@
                     true,
                     false, // False once the animation ends.
                 ),
-                values
+                values,
             )
         }
 
@@ -488,9 +436,9 @@
 
             assertEquals(
                 listOf(
-                    false, // Not using the animation when we're just sitting on LOCKSCREEN.
+                    false // Not using the animation when we're just sitting on LOCKSCREEN.
                 ),
-                values
+                values,
             )
 
             surfaceBehindIsAnimatingFlow.emit(true)
@@ -509,7 +457,7 @@
                     false,
                     true, // We're happily animating while transitioning to gone.
                 ),
-                values
+                values,
             )
 
             // Oh no, we're still surfaceBehindAnimating=true, but no longer transitioning to GONE.
@@ -536,7 +484,7 @@
                     true,
                     false, // Despite the animator still running, this should be false.
                 ),
-                values
+                values,
             )
 
             surfaceBehindIsAnimatingFlow.emit(false)
@@ -548,7 +496,7 @@
                     true,
                     false, // The animator ending should have no effect.
                 ),
-                values
+                values,
             )
         }
 
@@ -579,10 +527,10 @@
 
             assertEquals(
                 listOf(
-                    true, // Unsurprisingly, we should start with the lockscreen visible on
+                    true // Unsurprisingly, we should start with the lockscreen visible on
                     // LOCKSCREEN.
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionStep(
@@ -596,9 +544,9 @@
 
             assertEquals(
                 listOf(
-                    true, // Lockscreen remains visible while we're transitioning to GONE.
+                    true // Lockscreen remains visible while we're transitioning to GONE.
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionStep(
@@ -615,7 +563,7 @@
                     true,
                     false, // Once we're fully GONE, the lockscreen should not be visible.
                 ),
-                values
+                values,
             )
         }
 
@@ -628,7 +576,7 @@
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.GONE,
-                testScope
+                testScope,
             )
 
             runCurrent()
@@ -640,7 +588,7 @@
                     // Then, false, since we finish in GONE.
                     false,
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionStep(
@@ -665,7 +613,7 @@
                     // Should remain false as we transition from GONE.
                     false,
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionStep(
@@ -693,7 +641,7 @@
                     // visibility of the from state (LS).
                     true,
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionStep(
@@ -706,14 +654,7 @@
 
             runCurrent()
 
-            assertEquals(
-                listOf(
-                    true,
-                    false,
-                    true,
-                ),
-                values
-            )
+            assertEquals(listOf(true, false, true), values)
         }
 
     /**
@@ -730,7 +671,7 @@
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.GONE,
-                testScope
+                testScope,
             )
 
             runCurrent()
@@ -740,7 +681,7 @@
                     // Not visible since we're GONE.
                     false,
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionStep(
@@ -803,7 +744,7 @@
                     // STARTED to GONE after a CANCELED from GONE.
                     false,
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionSteps(
@@ -820,7 +761,7 @@
                     // visible again once we're finished in LOCKSCREEN.
                     true,
                 ),
-                values
+                values,
             )
         }
 
@@ -833,7 +774,7 @@
             transitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
                 to = KeyguardState.GONE,
-                testScope
+                testScope,
             )
 
             runCurrent()
@@ -843,7 +784,7 @@
                     // Not visible when finished in GONE.
                     false,
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionStep(
@@ -869,7 +810,7 @@
                     // Still not visible during GONE -> AOD.
                     false,
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionStep(
@@ -886,9 +827,9 @@
                     true,
                     false,
                     // Visible now that we're FINISHED in AOD.
-                    true
+                    true,
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionStep(
@@ -914,9 +855,9 @@
                     true,
                     false,
                     // Remains visible from AOD during transition.
-                    true
+                    true,
                 ),
-                values
+                values,
             )
 
             transitionRepository.sendTransitionStep(
@@ -934,15 +875,15 @@
                     false,
                     true,
                     // Until we're finished in GONE again.
-                    false
+                    false,
                 ),
-                values
+                values,
             )
         }
 
     @Test
     @EnableSceneContainer
-    fun lockscreenVisibility() =
+    fun lockscreenVisibilityWithScenes() =
         testScope.runTest {
             val isDeviceUnlocked by
                 collectLastValue(
@@ -956,32 +897,69 @@
             val lockscreenVisibility by collectLastValue(underTest.value.lockscreenVisibility)
             assertThat(lockscreenVisibility).isTrue()
 
+            kosmos.setSceneTransition(Idle(Scenes.Shade))
+            kosmos.sceneInteractor.changeScene(Scenes.Shade, "")
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
+            assertThat(lockscreenVisibility).isTrue()
+
+            kosmos.setSceneTransition(Transition(from = Scenes.Shade, to = Scenes.QuickSettings))
+            assertThat(lockscreenVisibility).isTrue()
+
+            kosmos.setSceneTransition(Idle(Scenes.QuickSettings))
+            kosmos.sceneInteractor.changeScene(Scenes.QuickSettings, "")
+            assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+            assertThat(lockscreenVisibility).isTrue()
+
+            kosmos.setSceneTransition(Transition(from = Scenes.QuickSettings, to = Scenes.Shade))
+            assertThat(lockscreenVisibility).isTrue()
+
+            kosmos.setSceneTransition(Idle(Scenes.Shade))
+            kosmos.sceneInteractor.changeScene(Scenes.Shade, "")
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
+            assertThat(lockscreenVisibility).isTrue()
+
+            kosmos.setSceneTransition(Idle(Scenes.Bouncer))
             kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "")
             assertThat(currentScene).isEqualTo(Scenes.Bouncer)
             assertThat(lockscreenVisibility).isTrue()
 
+            kosmos.setSceneTransition(Transition(from = Scenes.Bouncer, to = Scenes.Gone))
+            assertThat(lockscreenVisibility).isTrue()
+
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
             kosmos.authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
             assertThat(isDeviceUnlocked).isTrue()
             kosmos.sceneInteractor.changeScene(Scenes.Gone, "")
             assertThat(currentScene).isEqualTo(Scenes.Gone)
             assertThat(lockscreenVisibility).isFalse()
 
+            kosmos.setSceneTransition(Idle(Scenes.Shade))
             kosmos.sceneInteractor.changeScene(Scenes.Shade, "")
             assertThat(currentScene).isEqualTo(Scenes.Shade)
             assertThat(lockscreenVisibility).isFalse()
 
+            kosmos.setSceneTransition(Transition(from = Scenes.Shade, to = Scenes.QuickSettings))
+            assertThat(lockscreenVisibility).isFalse()
+
+            kosmos.setSceneTransition(Idle(Scenes.QuickSettings))
             kosmos.sceneInteractor.changeScene(Scenes.QuickSettings, "")
             assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
             assertThat(lockscreenVisibility).isFalse()
 
+            kosmos.setSceneTransition(Idle(Scenes.Shade))
             kosmos.sceneInteractor.changeScene(Scenes.Shade, "")
             assertThat(currentScene).isEqualTo(Scenes.Shade)
             assertThat(lockscreenVisibility).isFalse()
 
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
             kosmos.sceneInteractor.changeScene(Scenes.Gone, "")
             assertThat(currentScene).isEqualTo(Scenes.Gone)
             assertThat(lockscreenVisibility).isFalse()
 
+            kosmos.setSceneTransition(Transition(from = Scenes.Gone, to = Scenes.Lockscreen))
+            assertThat(lockscreenVisibility).isFalse()
+
+            kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
             kosmos.sceneInteractor.changeScene(Scenes.Lockscreen, "")
             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
             assertThat(lockscreenVisibility).isTrue()
@@ -1037,7 +1015,7 @@
                 flowOf(Scenes.Lockscreen),
                 progress,
                 false,
-                flowOf(false)
+                flowOf(false),
             )
 
         private val goneToLs =
@@ -1047,7 +1025,7 @@
                 flowOf(Scenes.Lockscreen),
                 progress,
                 false,
-                flowOf(false)
+                flowOf(false),
             )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
index 9c58e2b..92764ae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
@@ -29,11 +29,13 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -112,4 +114,20 @@
         verify(activityTaskManagerService).setLockScreenShown(true, false)
         verifyNoMoreInteractions(activityTaskManagerService)
     }
+
+    @Test
+    fun setSurfaceBehindVisibility_goesAwayFirst_andIgnoresSecondCall() {
+        underTest.setLockscreenShown(true)
+        underTest.setSurfaceBehindVisibility(true)
+        verify(activityTaskManagerService).keyguardGoingAway(0)
+
+        underTest.setSurfaceBehindVisibility(true)
+        verifyNoMoreInteractions(keyguardTransitions)
+    }
+
+    @Test
+    fun setSurfaceBehindVisibility_falseSetsLockscreenVisibility() {
+        underTest.setSurfaceBehindVisibility(false)
+        verify(activityTaskManagerService).setLockScreenShown(eq(true), any())
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
index 7f09370..0e3b03f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -44,6 +44,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Ignore
@@ -107,6 +108,7 @@
     fun translationAndScale_whenNotDozing() =
         testScope.runTest {
             val movement by collectLastValue(underTest.movement)
+            assertThat(movement?.translationX).isEqualTo(0)
 
             // Set to not dozing (on lockscreen)
             keyguardTransitionRepository.sendTransitionStep(
@@ -180,6 +182,7 @@
         testScope.runTest {
             underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100))
             val movement by collectLastValue(underTest.movement)
+            assertThat(movement?.translationX).isEqualTo(0)
 
             // Set to dozing (on AOD)
             keyguardTransitionRepository.sendTransitionStep(
@@ -221,6 +224,7 @@
         testScope.runTest {
             underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100, topInset = 80))
             val movement by collectLastValue(underTest.movement)
+            assertThat(movement?.translationX).isEqualTo(0)
 
             // Set to dozing (on AOD)
             keyguardTransitionRepository.sendTransitionStep(
@@ -263,6 +267,7 @@
         testScope.runTest {
             underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100, topInset = 80))
             val movement by collectLastValue(underTest.movement)
+            assertThat(movement?.translationX).isEqualTo(0)
 
             // Set to dozing (on AOD)
             keyguardTransitionRepository.sendTransitionStep(
@@ -305,6 +310,7 @@
             whenever(clockController.config.useAlternateSmartspaceAODTransition).thenReturn(true)
 
             val movement by collectLastValue(underTest.movement)
+            assertThat(movement?.translationX).isEqualTo(0)
 
             // Set to dozing (on AOD)
             keyguardTransitionRepository.sendTransitionStep(
@@ -423,6 +429,7 @@
                 .thenReturn(if (isWeatherClock) true else false)
 
             val movement by collectLastValue(underTest.movement)
+            assertThat(movement?.translationX).isEqualTo(0)
 
             // Set to dozing (on AOD)
             keyguardTransitionRepository.sendTransitionStep(
@@ -434,6 +441,7 @@
                 ),
                 validateStep = false,
             )
+            runCurrent()
 
             // Trigger a change to the burn-in model
             burnInFlow.value = BurnInModel(translationX = 20, translationY = 30, scale = 0.5f)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/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
new file mode 100644
index 0000000..8e67e60
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline
+
+import android.media.session.MediaSession
+import android.os.Bundle
+import android.os.Handler
+import android.os.looper
+import android.testing.TestableLooper.RunWithLooper
+import androidx.media.utils.MediaConstants
+import androidx.media3.common.Player
+import androidx.media3.session.CommandButton
+import androidx.media3.session.MediaController as Media3Controller
+import androidx.media3.session.SessionCommand
+import androidx.media3.session.SessionToken
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.graphics.imageLoader
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.shared.mediaLogger
+import com.android.systemui.media.controls.shared.model.MediaButton
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
+import com.android.systemui.media.controls.util.fakeSessionTokenFactory
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.collect.ImmutableList
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+private const val PACKAGE_NAME = "package_name"
+private const val CUSTOM_ACTION_NAME = "Custom Action"
+private const val CUSTOM_ACTION_COMMAND = "custom-action"
+
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidJUnit4::class)
+class Media3ActionFactoryTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val controllerFactory = kosmos.fakeMediaControllerFactory
+    private val tokenFactory = kosmos.fakeSessionTokenFactory
+
+    private val commandCaptor = argumentCaptor<SessionCommand>()
+    private val runnableCaptor = argumentCaptor<Runnable>()
+
+    private val legacyToken = MediaSession.Token(1, null)
+    private val token = mock<SessionToken>()
+    private val handler =
+        mock<Handler> {
+            on { post(runnableCaptor.capture()) } doAnswer
+                {
+                    runnableCaptor.lastValue.run()
+                    true
+                }
+        }
+    private val customLayout = ImmutableList.of<CommandButton>()
+    private val media3Controller =
+        mock<Media3Controller> {
+            on { customLayout } doReturn customLayout
+            on { sessionExtras } doReturn Bundle()
+            on { isCommandAvailable(any()) } doReturn true
+            on { isSessionCommandAvailable(any<SessionCommand>()) } doReturn true
+        }
+
+    private lateinit var underTest: Media3ActionFactory
+
+    @Before
+    fun setup() {
+        underTest =
+            Media3ActionFactory(
+                context,
+                kosmos.imageLoader,
+                controllerFactory,
+                tokenFactory,
+                kosmos.mediaLogger,
+                kosmos.looper,
+                handler,
+                kosmos.testScope,
+            )
+
+        controllerFactory.setMedia3Controller(media3Controller)
+        tokenFactory.setMedia3SessionToken(token)
+    }
+
+    @Test
+    fun media3Actions_playingState_withCustomActions() =
+        testScope.runTest {
+            // Media is playing, all commands available, with custom actions
+            val customLayout = ImmutableList.copyOf((0..1).map { createCustomCommandButton(it) })
+            whenever(media3Controller.customLayout).thenReturn(customLayout)
+            whenever(media3Controller.isPlaying).thenReturn(true)
+            val result = getActions()
+
+            assertThat(result).isNotNull()
+
+            val actions = result!!
+            assertThat(actions.playOrPause!!.contentDescription)
+                .isEqualTo(context.getString(R.string.controls_media_button_pause))
+            actions.playOrPause!!.action!!.run()
+            runCurrent()
+            verify(media3Controller).pause()
+            verify(media3Controller).release()
+            clearInvocations(media3Controller)
+
+            assertThat(actions.prevOrCustom!!.contentDescription)
+                .isEqualTo(context.getString(R.string.controls_media_button_prev))
+            actions.prevOrCustom!!.action!!.run()
+            runCurrent()
+            verify(media3Controller).seekToPrevious()
+            verify(media3Controller).release()
+            clearInvocations(media3Controller)
+
+            assertThat(actions.nextOrCustom!!.contentDescription)
+                .isEqualTo(context.getString(R.string.controls_media_button_next))
+            actions.nextOrCustom!!.action!!.run()
+            runCurrent()
+            verify(media3Controller).seekToNext()
+            verify(media3Controller).release()
+            clearInvocations(media3Controller)
+
+            assertThat(actions.custom0!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 0")
+            actions.custom0!!.action!!.run()
+            runCurrent()
+            verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+            assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 0")
+            verify(media3Controller).release()
+            clearInvocations(media3Controller)
+
+            assertThat(actions.custom1!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 1")
+            actions.custom1!!.action!!.run()
+            runCurrent()
+            verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+            assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 1")
+            verify(media3Controller).release()
+        }
+
+    @Test
+    fun media3Actions_pausedState_hasPauseAction() =
+        testScope.runTest {
+            whenever(media3Controller.isPlaying).thenReturn(false)
+            val result = getActions()
+
+            assertThat(result).isNotNull()
+            val actions = result!!
+            assertThat(actions.playOrPause!!.contentDescription)
+                .isEqualTo(context.getString(R.string.controls_media_button_play))
+            clearInvocations(media3Controller)
+
+            actions.playOrPause!!.action!!.run()
+            runCurrent()
+            verify(media3Controller).play()
+            verify(media3Controller).release()
+            clearInvocations(media3Controller)
+        }
+
+    @Test
+    fun media3Actions_bufferingState_hasLoadingSpinner() =
+        testScope.runTest {
+            whenever(media3Controller.isPlaying).thenReturn(false)
+            whenever(media3Controller.playbackState).thenReturn(Player.STATE_BUFFERING)
+            val result = getActions()
+
+            assertThat(result).isNotNull()
+            val actions = result!!
+            assertThat(actions.playOrPause!!.contentDescription)
+                .isEqualTo(context.getString(R.string.controls_media_button_connecting))
+            assertThat(actions.playOrPause!!.action).isNull()
+            assertThat(actions.playOrPause!!.rebindId)
+                .isEqualTo(com.android.internal.R.drawable.progress_small_material)
+        }
+
+    @Test
+    fun media3Actions_noPrevNext_usesCustom() =
+        testScope.runTest {
+            val customLayout = ImmutableList.copyOf((0..4).map { createCustomCommandButton(it) })
+            whenever(media3Controller.customLayout).thenReturn(customLayout)
+            whenever(media3Controller.isPlaying).thenReturn(true)
+            whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_PREVIOUS)))
+                .thenReturn(false)
+            whenever(
+                    media3Controller.isCommandAvailable(
+                        eq(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
+                    )
+                )
+                .thenReturn(false)
+            whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT)))
+                .thenReturn(false)
+            whenever(
+                    media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM))
+                )
+                .thenReturn(false)
+            val result = getActions()
+
+            assertThat(result).isNotNull()
+            val actions = result!!
+
+            assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 0")
+            actions.prevOrCustom!!.action!!.run()
+            runCurrent()
+            verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+            assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 0")
+            verify(media3Controller).release()
+            clearInvocations(media3Controller)
+
+            assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 1")
+            actions.nextOrCustom!!.action!!.run()
+            runCurrent()
+            verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+            assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 1")
+            verify(media3Controller).release()
+            clearInvocations(media3Controller)
+
+            assertThat(actions.custom0!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 2")
+            actions.custom0!!.action!!.run()
+            runCurrent()
+            verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+            assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 2")
+            verify(media3Controller).release()
+            clearInvocations(media3Controller)
+
+            assertThat(actions.custom1!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 3")
+            actions.custom1!!.action!!.run()
+            runCurrent()
+            verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+            assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 3")
+            verify(media3Controller).release()
+        }
+
+    @Test
+    fun media3Actions_noPrevNext_reservedSpace() =
+        testScope.runTest {
+            val customLayout = ImmutableList.copyOf((0..4).map { createCustomCommandButton(it) })
+            whenever(media3Controller.customLayout).thenReturn(customLayout)
+            whenever(media3Controller.isPlaying).thenReturn(true)
+            whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_PREVIOUS)))
+                .thenReturn(false)
+            whenever(
+                    media3Controller.isCommandAvailable(
+                        eq(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
+                    )
+                )
+                .thenReturn(false)
+            whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT)))
+                .thenReturn(false)
+            whenever(
+                    media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM))
+                )
+                .thenReturn(false)
+            val extras =
+                Bundle().apply {
+                    putBoolean(
+                        MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV,
+                        true,
+                    )
+                    putBoolean(
+                        MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT,
+                        true,
+                    )
+                }
+            whenever(media3Controller.sessionExtras).thenReturn(extras)
+            val result = getActions()
+
+            assertThat(result).isNotNull()
+            val actions = result!!
+
+            assertThat(actions.prevOrCustom).isNull()
+            assertThat(actions.nextOrCustom).isNull()
+
+            assertThat(actions.custom0!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 0")
+            actions.custom0!!.action!!.run()
+            runCurrent()
+            verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+            assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 0")
+            verify(media3Controller).release()
+            clearInvocations(media3Controller)
+
+            assertThat(actions.custom1!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 1")
+            actions.custom1!!.action!!.run()
+            runCurrent()
+            verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+            assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 1")
+            verify(media3Controller).release()
+        }
+
+    private suspend fun getActions(): MediaButton? {
+        val result = underTest.createActionsFromSession(PACKAGE_NAME, legacyToken)
+        testScope.runCurrent()
+        verify(media3Controller).release()
+
+        // Clear so tests can verify the correct number of release() calls in later operations
+        clearInvocations(media3Controller)
+        return result
+    }
+
+    private fun createCustomCommandButton(id: Int): CommandButton {
+        return CommandButton.Builder()
+            .setDisplayName("$CUSTOM_ACTION_NAME $id")
+            .setSessionCommand(SessionCommand("$CUSTOM_ACTION_COMMAND $id", Bundle()))
+            .build()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
index fc9e595..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
@@ -29,6 +29,7 @@
 import android.media.session.PlaybackState
 import android.os.Bundle
 import android.service.notification.StatusBarNotification
+import android.testing.TestableLooper.RunWithLooper
 import androidx.media.utils.MediaConstants
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -69,6 +70,7 @@
 private const val SESSION_EMPTY_TITLE = ""
 
 @SmallTest
+@RunWithLooper
 @RunWith(AndroidJUnit4::class)
 class MediaDataLoaderTest : SysuiTestCase() {
 
@@ -80,6 +82,7 @@
     private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic
     private val mediaFlags = kosmos.mediaFlags
     private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
+    private lateinit var media3ActionFactory: Media3ActionFactory
     private val session = MediaSession(context, "MediaDataLoaderTestSession")
     private val metadataBuilder =
         MediaMetadata.Builder().apply {
@@ -87,21 +90,26 @@
             putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
         }
 
-    private val underTest: MediaDataLoader =
-        MediaDataLoader(
-            context,
-            testDispatcher,
-            testScope,
-            mediaControllerFactory,
-            mediaFlags,
-            kosmos.imageLoader,
-            statusBarManager,
-        )
+    private lateinit var underTest: MediaDataLoader
 
     @Before
     fun setUp() {
+        media3ActionFactory = kosmos.media3ActionFactory
         mediaControllerFactory.setControllerForToken(session.sessionToken, mediaController)
+        whenever(mediaController.sessionToken).thenReturn(session.sessionToken)
         whenever(mediaController.metadata).then { metadataBuilder.build() }
+
+        underTest =
+            MediaDataLoader(
+                context,
+                testDispatcher,
+                testScope,
+                mediaControllerFactory,
+                mediaFlags,
+                kosmos.imageLoader,
+                statusBarManager,
+                kosmos.media3ActionFactory,
+            )
     }
 
     @Test
@@ -304,7 +312,6 @@
                     }
                     build()
                 }
-
             val result = underTest.loadMediaData(KEY, mediaNotification)
 
             assertThat(result).isNotNull()
@@ -394,6 +401,7 @@
                     mediaFlags,
                     mockImageLoader,
                     statusBarManager,
+                    media3ActionFactory,
                 )
             metadataBuilder.putString(
                 MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
@@ -422,6 +430,7 @@
                     mediaFlags,
                     mockImageLoader,
                     statusBarManager,
+                    media3ActionFactory,
                 )
             metadataBuilder.putString(
                 MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index e12c67b..104aa51 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -16,7 +16,7 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.wm.shell.recents.RecentTasks
-import com.android.wm.shell.shared.GroupedRecentTaskInfo
+import com.android.wm.shell.shared.GroupedTaskInfo
 import com.android.wm.shell.shared.split.SplitBounds
 import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50
 import com.google.common.truth.Truth.assertThat
@@ -219,9 +219,9 @@
     }
 
     @Suppress("UNCHECKED_CAST")
-    private fun givenRecentTasks(vararg tasks: GroupedRecentTaskInfo) {
+    private fun givenRecentTasks(vararg tasks: GroupedTaskInfo) {
         whenever(recentTasks.getRecentTasks(any(), any(), any(), any(), any())).thenAnswer {
-            val consumer = it.arguments.last() as Consumer<List<GroupedRecentTaskInfo>>
+            val consumer = it.arguments.last() as Consumer<List<GroupedTaskInfo>>
             consumer.accept(tasks.toList())
         }
     }
@@ -247,7 +247,7 @@
         userId: Int = 0,
         isVisible: Boolean = false,
         userType: RecentTask.UserType = STANDARD,
-    ): GroupedRecentTaskInfo {
+    ): GroupedTaskInfo {
         val userInfo =
             mock<UserInfo> {
                 whenever(isCloneProfile).thenReturn(userType == CLONED)
@@ -255,7 +255,7 @@
                 whenever(isPrivateProfile).thenReturn(userType == PRIVATE)
             }
         whenever(userManager.getUserInfo(userId)).thenReturn(userInfo)
-        return GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId, userId, isVisible))
+        return GroupedTaskInfo.forFullscreenTasks(createTaskInfo(taskId, userId, isVisible))
     }
 
     private fun createTaskPair(
@@ -263,9 +263,9 @@
         userId1: Int = 0,
         taskId2: Int,
         userId2: Int = 0,
-        isVisible: Boolean = false
-    ): GroupedRecentTaskInfo =
-        GroupedRecentTaskInfo.forSplitTasks(
+        isVisible: Boolean = false,
+    ): GroupedTaskInfo =
+        GroupedTaskInfo.forSplitTasks(
             createTaskInfo(taskId1, userId1, isVisible),
             createTaskInfo(taskId2, userId2, isVisible),
             SplitBounds(Rect(), Rect(), taskId1, taskId2, SNAP_TO_2_50_50)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
index 2905a73..646722b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
@@ -116,6 +116,7 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.data.repository.LightBarControllerStore;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.LightBarController;
@@ -208,7 +209,7 @@
     @Mock
     private LightBarController mLightBarController;
     @Mock
-    private LightBarController.Factory mLightBarcontrollerFactory;
+    private LightBarControllerStore mLightBarControllerStore;
     @Mock
     private AutoHideController mAutoHideController;
     @Mock
@@ -257,7 +258,7 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        when(mLightBarcontrollerFactory.create(any(Context.class))).thenReturn(mLightBarController);
+        when(mLightBarControllerStore.forDisplay(anyInt())).thenReturn(mLightBarController);
         when(mAutoHideControllerFactory.create(any(Context.class))).thenReturn(mAutoHideController);
         when(mNavigationBarView.getHomeButton()).thenReturn(mHomeButton);
         when(mNavigationBarView.getRecentsButton()).thenReturn(mRecentsButton);
@@ -649,8 +650,7 @@
                 mFakeExecutor,
                 mUiEventLogger,
                 mNavBarHelper,
-                mLightBarController,
-                mLightBarcontrollerFactory,
+                mLightBarControllerStore,
                 mAutoHideController,
                 mAutoHideControllerFactory,
                 Optional.of(mTelecomManager),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/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/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
index ff40e43..a063531 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
@@ -115,7 +115,7 @@
         underTest.logUserActionPipeline(
             TileSpec.create("test_spec"),
             QSTileUserAction.Click(null),
-            QSTileState.build({ Icon.Resource(0, ContentDescription.Resource(0)) }, "") {},
+            QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {},
             "test_data",
         )
 
@@ -141,7 +141,7 @@
     fun testLogStateUpdate() {
         underTest.logStateUpdate(
             TileSpec.create("test_spec"),
-            QSTileState.build({ Icon.Resource(0, ContentDescription.Resource(0)) }, "") {},
+            QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {},
             "test_data",
         )
 
@@ -162,18 +162,14 @@
 
     @Test
     fun testLogForceUpdate() {
-        underTest.logForceUpdate(
-            TileSpec.create("test_spec"),
-        )
+        underTest.logForceUpdate(TileSpec.create("test_spec"))
 
         assertThat(logBuffer.getStringBuffer()).contains("tile data force update")
     }
 
     @Test
     fun testLogInitialUpdate() {
-        underTest.logInitialRequest(
-            TileSpec.create("test_spec"),
-        )
+        underTest.logInitialRequest(TileSpec.create("test_spec"))
 
         assertThat(logBuffer.getStringBuffer()).contains("tile data initial update")
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
index c918ed8..056efb3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
@@ -85,8 +85,8 @@
                     object : QSTileDataToStateMapper<Any> {
                         override fun map(config: QSTileConfig, data: Any): QSTileState =
                             QSTileState.build(
-                                { Icon.Resource(0, ContentDescription.Resource(0)) },
-                                data.toString()
+                                Icon.Resource(0, ContentDescription.Resource(0)),
+                                data.toString(),
                             ) {}
                     }
                 },
@@ -116,7 +116,7 @@
                 .isEqualTo(
                     "test_spec:\n" +
                         "    QSTileState(" +
-                        "icon=() -> com.android.systemui.common.shared.model.Icon?, " +
+                        "icon=Resource(res=0, contentDescription=Resource(res=0)), " +
                         "iconRes=null, " +
                         "label=test_data, " +
                         "activationState=INACTIVE, " +
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt
index 5a73fe2..00460bf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt
@@ -66,7 +66,7 @@
             createAirplaneModeState(
                 QSTileState.ActivationState.ACTIVE,
                 context.resources.getStringArray(R.array.tile_states_airplane)[Tile.STATE_ACTIVE],
-                R.drawable.qs_airplane_icon_on
+                R.drawable.qs_airplane_icon_on,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -81,7 +81,7 @@
             createAirplaneModeState(
                 QSTileState.ActivationState.INACTIVE,
                 context.resources.getStringArray(R.array.tile_states_airplane)[Tile.STATE_INACTIVE],
-                R.drawable.qs_airplane_icon_off
+                R.drawable.qs_airplane_icon_off,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -89,11 +89,11 @@
     private fun createAirplaneModeState(
         activationState: QSTileState.ActivationState,
         secondaryLabel: String,
-        iconRes: Int
+        iconRes: Int,
     ): QSTileState {
         val label = context.getString(R.string.airplane_mode)
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
@@ -103,7 +103,7 @@
             null,
             QSTileState.SideViewIcon.None,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
index 79e4fef..632aae0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
@@ -51,7 +51,7 @@
                 .apply { addOverride(R.drawable.ic_alarm, TestStubDrawable()) }
                 .resources,
             context.theme,
-            fakeClock
+            fakeClock,
         )
     }
 
@@ -69,7 +69,7 @@
         val expectedState =
             createAlarmTileState(
                 QSTileState.ActivationState.INACTIVE,
-                context.getString(R.string.qs_alarm_tile_no_alarm)
+                context.getString(R.string.qs_alarm_tile_no_alarm),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -85,7 +85,7 @@
         val localDateTime =
             LocalDateTime.ofInstant(
                 Instant.ofEpochMilli(triggerTime),
-                TimeZone.getDefault().toZoneId()
+                TimeZone.getDefault().toZoneId(),
             )
         val expectedSecondaryLabel = AlarmTileMapper.formatter24Hour.format(localDateTime)
         val expectedState =
@@ -104,7 +104,7 @@
         val localDateTime =
             LocalDateTime.ofInstant(
                 Instant.ofEpochMilli(triggerTime),
-                TimeZone.getDefault().toZoneId()
+                TimeZone.getDefault().toZoneId(),
             )
         val expectedSecondaryLabel = AlarmTileMapper.formatter12Hour.format(localDateTime)
         val expectedState =
@@ -124,7 +124,7 @@
         val localDateTime =
             LocalDateTime.ofInstant(
                 Instant.ofEpochMilli(triggerTime),
-                TimeZone.getDefault().toZoneId()
+                TimeZone.getDefault().toZoneId(),
             )
         val expectedSecondaryLabel = AlarmTileMapper.formatterDateOnly.format(localDateTime)
         val expectedState =
@@ -144,7 +144,7 @@
         val localDateTime =
             LocalDateTime.ofInstant(
                 Instant.ofEpochMilli(triggerTime),
-                TimeZone.getDefault().toZoneId()
+                TimeZone.getDefault().toZoneId(),
             )
         val expectedSecondaryLabel = AlarmTileMapper.formatter12Hour.format(localDateTime)
         val expectedState =
@@ -164,7 +164,7 @@
         val localDateTime =
             LocalDateTime.ofInstant(
                 Instant.ofEpochMilli(triggerTime),
-                TimeZone.getDefault().toZoneId()
+                TimeZone.getDefault().toZoneId(),
             )
         val expectedSecondaryLabel = AlarmTileMapper.formatterDateOnly.format(localDateTime)
         val expectedState =
@@ -174,11 +174,11 @@
 
     private fun createAlarmTileState(
         activationState: QSTileState.ActivationState,
-        secondaryLabel: String
+        secondaryLabel: String,
     ): QSTileState {
         val label = context.getString(R.string.status_bar_alarm)
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(R.drawable.ic_alarm)!!, null) },
+            Icon.Loaded(context.getDrawable(R.drawable.ic_alarm)!!, null),
             R.drawable.ic_alarm,
             label,
             activationState,
@@ -188,7 +188,7 @@
             null,
             QSTileState.SideViewIcon.Chevron,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
index a0d26c2..5385f94 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
@@ -253,7 +253,7 @@
     ): QSTileState {
         val label = context.getString(R.string.battery_detail_switch_title)
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
@@ -265,7 +265,7 @@
             stateDescription,
             QSTileState.SideViewIcon.None,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
index ea7b7c5..356b98e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
@@ -45,7 +45,7 @@
             context.orCreateTestableResources
                 .apply { addOverride(R.drawable.ic_qs_color_correction, TestStubDrawable()) }
                 .resources,
-            context.theme
+            context.theme,
         )
     }
 
@@ -73,11 +73,11 @@
 
     private fun createColorCorrectionTileState(
         activationState: QSTileState.ActivationState,
-        secondaryLabel: String
+        secondaryLabel: String,
     ): QSTileState {
         val label = context.getString(R.string.quick_settings_color_correction_label)
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(R.drawable.ic_qs_color_correction)!!, null) },
+            Icon.Loaded(context.getDrawable(R.drawable.ic_qs_color_correction)!!, null),
             R.drawable.ic_qs_color_correction,
             label,
             activationState,
@@ -87,7 +87,7 @@
             null,
             QSTileState.SideViewIcon.None,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
index f1d08c0..8236c4c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
@@ -57,10 +57,7 @@
     private val kosmos =
         testKosmos().apply { customTileSpec = TileSpec.Companion.create(TEST_COMPONENT) }
     private val underTest by lazy {
-        CustomTileMapper(
-            context = mockContext,
-            uriGrantsManager = uriGrantsManager,
-        )
+        CustomTileMapper(context = mockContext, uriGrantsManager = uriGrantsManager)
     }
 
     @Test
@@ -68,10 +65,7 @@
         with(kosmos) {
             testScope.runTest {
                 val actual =
-                    underTest.map(
-                        customTileQsTileConfig,
-                        createModel(hasPendingBind = true),
-                    )
+                    underTest.map(customTileQsTileConfig, createModel(hasPendingBind = true))
                 val expected =
                     createTileState(
                         activationState = QSTileState.ActivationState.UNAVAILABLE,
@@ -91,10 +85,7 @@
                         customTileQsTileConfig,
                         createModel(tileState = Tile.STATE_ACTIVE),
                     )
-                val expected =
-                    createTileState(
-                        activationState = QSTileState.ActivationState.ACTIVE,
-                    )
+                val expected = createTileState(activationState = QSTileState.ActivationState.ACTIVE)
 
                 assertThat(actual).isEqualTo(expected)
             }
@@ -110,9 +101,7 @@
                         createModel(tileState = Tile.STATE_INACTIVE),
                     )
                 val expected =
-                    createTileState(
-                        activationState = QSTileState.ActivationState.INACTIVE,
-                    )
+                    createTileState(activationState = QSTileState.ActivationState.INACTIVE)
 
                 assertThat(actual).isEqualTo(expected)
             }
@@ -142,10 +131,7 @@
         with(kosmos) {
             testScope.runTest {
                 val actual =
-                    underTest.map(
-                        customTileQsTileConfig,
-                        createModel(isToggleable = false),
-                    )
+                    underTest.map(customTileQsTileConfig, createModel(isToggleable = false))
                 val expected =
                     createTileState(
                         sideIcon = QSTileState.SideViewIcon.Chevron,
@@ -184,7 +170,7 @@
                         customTileQsTileConfig,
                         createModel(
                             tileIcon = createIcon(RuntimeException(), false),
-                            defaultTileIcon = createIcon(null, true)
+                            defaultTileIcon = createIcon(null, true),
                         ),
                     )
                 val expected =
@@ -266,7 +252,7 @@
         a11yClass: String? = Switch::class.qualifiedName,
     ): QSTileState {
         return QSTileState(
-            { icon?.let { com.android.systemui.common.shared.model.Icon.Loaded(icon, null) } },
+            icon?.let { com.android.systemui.common.shared.model.Icon.Loaded(icon, null) },
             null,
             "test label",
             activationState,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
index 63fb67d..587585c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
@@ -44,7 +44,7 @@
                     addOverride(R.drawable.qs_flashlight_icon_on, TestStubDrawable())
                 }
                 .resources,
-            context.theme
+            context.theme,
         )
     }
 
@@ -74,7 +74,7 @@
 
         val expectedIcon =
             Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_on)!!, null)
-        val actualIcon = tileState.icon()
+        val actualIcon = tileState.icon
         assertThat(actualIcon).isEqualTo(expectedIcon)
     }
 
@@ -85,7 +85,7 @@
 
         val expectedIcon =
             Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
-        val actualIcon = tileState.icon()
+        val actualIcon = tileState.icon
         assertThat(actualIcon).isEqualTo(expectedIcon)
     }
 
@@ -96,7 +96,7 @@
 
         val expectedIcon =
             Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
-        val actualIcon = tileState.icon()
+        val actualIcon = tileState.icon
         assertThat(actualIcon).isEqualTo(expectedIcon)
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
index f8e01be..e81771e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
@@ -42,7 +42,7 @@
             context.orCreateTestableResources
                 .apply { addOverride(R.drawable.ic_qs_font_scaling, TestStubDrawable()) }
                 .resources,
-            context.theme
+            context.theme,
         )
     }
 
@@ -58,14 +58,7 @@
 
     private fun createFontScalingTileState(): QSTileState =
         QSTileState(
-            {
-                Icon.Loaded(
-                    context.getDrawable(
-                        R.drawable.ic_qs_font_scaling,
-                    )!!,
-                    null
-                )
-            },
+            Icon.Loaded(context.getDrawable(R.drawable.ic_qs_font_scaling)!!, null),
             R.drawable.ic_qs_font_scaling,
             context.getString(R.string.quick_settings_font_scaling_label),
             QSTileState.ActivationState.ACTIVE,
@@ -75,6 +68,6 @@
             null,
             QSTileState.SideViewIcon.Chevron,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt
index cdf6bda..12d604f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt
@@ -102,7 +102,7 @@
         val label = context.getString(R.string.quick_settings_hearing_devices_label)
         val iconRes = R.drawable.qs_hearing_devices_icon
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
index d32ba47..9dcf49e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
@@ -187,7 +187,7 @@
     ): QSTileState {
         val label = context.getString(R.string.quick_settings_internet_label)
         return QSTileState(
-            { icon },
+            icon,
             iconRes,
             label,
             activationState,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
index a7bd697..30fce73 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
@@ -49,7 +49,7 @@
                     addOverride(R.drawable.qs_invert_colors_icon_on, TestStubDrawable())
                 }
                 .resources,
-            context.theme
+            context.theme,
         )
     }
 
@@ -63,7 +63,7 @@
             createColorInversionTileState(
                 QSTileState.ActivationState.INACTIVE,
                 subtitleArray[1],
-                R.drawable.qs_invert_colors_icon_off
+                R.drawable.qs_invert_colors_icon_off,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -78,7 +78,7 @@
             createColorInversionTileState(
                 QSTileState.ActivationState.ACTIVE,
                 subtitleArray[2],
-                R.drawable.qs_invert_colors_icon_on
+                R.drawable.qs_invert_colors_icon_on,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -90,7 +90,7 @@
     ): QSTileState {
         val label = context.getString(R.string.quick_settings_inversion_label)
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
@@ -100,7 +100,7 @@
             null,
             QSTileState.SideViewIcon.None,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
index ea74a4c..37e8a60 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
@@ -45,7 +45,7 @@
                     addOverride(R.drawable.qs_location_icon_on, TestStubDrawable())
                 }
                 .resources,
-            context.theme
+            context.theme,
         )
     }
 
@@ -70,7 +70,7 @@
         val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(true))
 
         val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_on)!!, null)
-        val actualIcon = tileState.icon()
+        val actualIcon = tileState.icon
         Truth.assertThat(actualIcon).isEqualTo(expectedIcon)
     }
 
@@ -79,7 +79,7 @@
         val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(false))
 
         val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_off)!!, null)
-        val actualIcon = tileState.icon()
+        val actualIcon = tileState.icon
         Truth.assertThat(actualIcon).isEqualTo(expectedIcon)
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
index c3d45db..4e91d16 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
@@ -59,34 +59,24 @@
     @Test
     fun inactiveState() {
         val icon = TestStubDrawable("res123").asIcon()
-        val model =
-            ModesTileModel(
-                isActivated = false,
-                activeModes = emptyList(),
-                icon = icon,
-            )
+        val model = ModesTileModel(isActivated = false, activeModes = emptyList(), icon = icon)
 
         val state = underTest.map(config, model)
 
         assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.INACTIVE)
-        assertThat(state.icon()).isEqualTo(icon)
+        assertThat(state.icon).isEqualTo(icon)
         assertThat(state.secondaryLabel).isEqualTo("No active modes")
     }
 
     @Test
     fun activeState_oneMode() {
         val icon = TestStubDrawable("res123").asIcon()
-        val model =
-            ModesTileModel(
-                isActivated = true,
-                activeModes = listOf("DND"),
-                icon = icon,
-            )
+        val model = ModesTileModel(isActivated = true, activeModes = listOf("DND"), icon = icon)
 
         val state = underTest.map(config, model)
 
         assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE)
-        assertThat(state.icon()).isEqualTo(icon)
+        assertThat(state.icon).isEqualTo(icon)
         assertThat(state.secondaryLabel).isEqualTo("DND is active")
     }
 
@@ -103,7 +93,7 @@
         val state = underTest.map(config, model)
 
         assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE)
-        assertThat(state.icon()).isEqualTo(icon)
+        assertThat(state.icon).isEqualTo(icon)
         assertThat(state.secondaryLabel).isEqualTo("3 modes are active")
     }
 
@@ -115,12 +105,12 @@
                 isActivated = false,
                 activeModes = emptyList(),
                 icon = icon,
-                iconResId = 123
+                iconResId = 123,
             )
 
         val state = underTest.map(config, model)
 
-        assertThat(state.icon()).isEqualTo(icon)
+        assertThat(state.icon).isEqualTo(icon)
         assertThat(state.iconRes).isEqualTo(123)
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
index 75273f2..1457f53 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
@@ -73,7 +73,7 @@
         val expectedState =
             createNightDisplayTileState(
                 QSTileState.ActivationState.INACTIVE,
-                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE]
+                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE],
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -88,7 +88,7 @@
         val expectedState =
             createNightDisplayTileState(
                 QSTileState.ActivationState.INACTIVE,
-                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE]
+                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE],
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -102,7 +102,7 @@
         val expectedState =
             createNightDisplayTileState(
                 QSTileState.ActivationState.ACTIVE,
-                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE]
+                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE],
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -116,7 +116,7 @@
         val expectedState =
             createNightDisplayTileState(
                 QSTileState.ActivationState.ACTIVE,
-                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE]
+                context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE],
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -140,7 +140,7 @@
         val expectedState =
             createNightDisplayTileState(
                 QSTileState.ActivationState.ACTIVE,
-                context.getString(R.string.quick_settings_night_secondary_label_until_sunrise)
+                context.getString(R.string.quick_settings_night_secondary_label_until_sunrise),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -154,7 +154,7 @@
         val expectedState =
             createNightDisplayTileState(
                 QSTileState.ActivationState.INACTIVE,
-                context.getString(R.string.quick_settings_night_secondary_label_on_at_sunset)
+                context.getString(R.string.quick_settings_night_secondary_label_on_at_sunset),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -181,8 +181,8 @@
                 QSTileState.ActivationState.INACTIVE,
                 context.getString(
                     R.string.quick_settings_night_secondary_label_on_at,
-                    formatter24Hour.format(testStartTime)
-                )
+                    formatter24Hour.format(testStartTime),
+                ),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -199,8 +199,8 @@
                 QSTileState.ActivationState.INACTIVE,
                 context.getString(
                     R.string.quick_settings_night_secondary_label_on_at,
-                    formatter12Hour.format(testStartTime)
-                )
+                    formatter12Hour.format(testStartTime),
+                ),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -218,8 +218,8 @@
                 QSTileState.ActivationState.INACTIVE,
                 context.getString(
                     R.string.quick_settings_night_secondary_label_on_at,
-                    formatter12Hour.format(testStartTime)
-                )
+                    formatter12Hour.format(testStartTime),
+                ),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -235,8 +235,8 @@
                 QSTileState.ActivationState.ACTIVE,
                 context.getString(
                     R.string.quick_settings_secondary_label_until,
-                    formatter24Hour.format(testEndTime)
-                )
+                    formatter24Hour.format(testEndTime),
+                ),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -252,8 +252,8 @@
                 QSTileState.ActivationState.ACTIVE,
                 context.getString(
                     R.string.quick_settings_secondary_label_until,
-                    formatter12Hour.format(testEndTime)
-                )
+                    formatter12Hour.format(testEndTime),
+                ),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -270,15 +270,15 @@
                 QSTileState.ActivationState.ACTIVE,
                 context.getString(
                     R.string.quick_settings_secondary_label_until,
-                    formatter24Hour.format(testEndTime)
-                )
+                    formatter24Hour.format(testEndTime),
+                ),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
 
     private fun createNightDisplayTileState(
         activationState: QSTileState.ActivationState,
-        secondaryLabel: String?
+        secondaryLabel: String?,
     ): QSTileState {
         val label = context.getString(R.string.quick_settings_night_display_label)
         val iconRes =
@@ -289,7 +289,7 @@
             if (TextUtils.isEmpty(secondaryLabel)) label
             else TextUtils.concat(label, ", ", secondaryLabel)
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
@@ -299,7 +299,7 @@
             null,
             QSTileState.SideViewIcon.None,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/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/onehanded/ui/OneHandedModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
index 3189a9e..7782d2b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
@@ -51,11 +51,11 @@
                     .apply {
                         addOverride(
                             com.android.internal.R.drawable.ic_qs_one_handed_mode,
-                            TestStubDrawable()
+                            TestStubDrawable(),
                         )
                     }
                     .resources,
-                context.theme
+                context.theme,
             )
     }
 
@@ -69,7 +69,7 @@
             createOneHandedModeTileState(
                 QSTileState.ActivationState.INACTIVE,
                 subtitleArray[1],
-                com.android.internal.R.drawable.ic_qs_one_handed_mode
+                com.android.internal.R.drawable.ic_qs_one_handed_mode,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -84,7 +84,7 @@
             createOneHandedModeTileState(
                 QSTileState.ActivationState.ACTIVE,
                 subtitleArray[2],
-                com.android.internal.R.drawable.ic_qs_one_handed_mode
+                com.android.internal.R.drawable.ic_qs_one_handed_mode,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -96,7 +96,7 @@
     ): QSTileState {
         val label = context.getString(R.string.quick_settings_onehanded_label)
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
@@ -106,7 +106,7 @@
             null,
             QSTileState.SideViewIcon.None,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
index 08e5cbe..ed33250 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
@@ -49,11 +49,11 @@
                     .apply {
                         addOverride(
                             com.android.systemui.res.R.drawable.ic_qr_code_scanner,
-                            TestStubDrawable()
+                            TestStubDrawable(),
                         )
                     }
                     .resources,
-                context.theme
+                context.theme,
             )
     }
 
@@ -64,11 +64,7 @@
 
         val outputState = mapper.map(config, inputModel)
 
-        val expectedState =
-            createQRCodeScannerTileState(
-                QSTileState.ActivationState.INACTIVE,
-                null,
-            )
+        val expectedState = createQRCodeScannerTileState(QSTileState.ActivationState.INACTIVE, null)
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
 
@@ -83,7 +79,7 @@
                 QSTileState.ActivationState.UNAVAILABLE,
                 context.getString(
                     com.android.systemui.res.R.string.qr_code_scanner_updating_secondary_label
-                )
+                ),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -94,12 +90,10 @@
     ): QSTileState {
         val label = context.getString(com.android.systemui.res.R.string.qr_code_scanner_title)
         return QSTileState(
-            {
-                Icon.Loaded(
-                    context.getDrawable(com.android.systemui.res.R.drawable.ic_qr_code_scanner)!!,
-                    null
-                )
-            },
+            Icon.Loaded(
+                context.getDrawable(com.android.systemui.res.R.drawable.ic_qr_code_scanner)!!,
+                null,
+            ),
             com.android.systemui.res.R.drawable.ic_qr_code_scanner,
             label,
             activationState,
@@ -109,7 +103,7 @@
             null,
             QSTileState.SideViewIcon.Chevron,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
index ca30e9c..85111fd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
@@ -51,7 +51,7 @@
                         addOverride(R.drawable.qs_extra_dim_icon_off, TestStubDrawable())
                     }
                     .resources,
-                context.theme
+                context.theme,
             )
     }
 
@@ -61,10 +61,7 @@
 
         val outputState = mapper.map(config, inputModel)
 
-        val expectedState =
-            createReduceBrightColorsTileState(
-                QSTileState.ActivationState.INACTIVE,
-            )
+        val expectedState = createReduceBrightColorsTileState(QSTileState.ActivationState.INACTIVE)
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
 
@@ -79,7 +76,7 @@
     }
 
     private fun createReduceBrightColorsTileState(
-        activationState: QSTileState.ActivationState,
+        activationState: QSTileState.ActivationState
     ): QSTileState {
         val label =
             context.getString(com.android.internal.R.string.reduce_bright_colors_feature_name)
@@ -88,7 +85,7 @@
                 R.drawable.qs_extra_dim_icon_on
             else R.drawable.qs_extra_dim_icon_off
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
@@ -101,7 +98,7 @@
             null,
             QSTileState.SideViewIcon.None,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
index 3e40c5c..53671ba 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
@@ -66,13 +66,13 @@
                         addOverride(com.android.internal.R.bool.config_allowRotationResolver, true)
                         addOverride(
                             com.android.internal.R.array.config_foldedDeviceStates,
-                            intArrayOf() // empty array <=> device is not foldable
+                            intArrayOf(), // empty array <=> device is not foldable
                         )
                     }
                     .resources,
                 context.theme,
                 devicePostureController,
-                deviceStateManager
+                deviceStateManager,
             )
     }
 
@@ -86,7 +86,7 @@
             createRotationLockTileState(
                 QSTileState.ActivationState.ACTIVE,
                 EMPTY_SECONDARY_STRING,
-                R.drawable.qs_auto_rotate_icon_on
+                R.drawable.qs_auto_rotate_icon_on,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -101,7 +101,7 @@
             createRotationLockTileState(
                 QSTileState.ActivationState.ACTIVE,
                 context.getString(R.string.rotation_lock_camera_rotation_on),
-                R.drawable.qs_auto_rotate_icon_on
+                R.drawable.qs_auto_rotate_icon_on,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -116,7 +116,7 @@
             createRotationLockTileState(
                 QSTileState.ActivationState.INACTIVE,
                 EMPTY_SECONDARY_STRING,
-                R.drawable.qs_auto_rotate_icon_off
+                R.drawable.qs_auto_rotate_icon_off,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -167,7 +167,7 @@
         mapper.apply {
             overrideResource(
                 com.android.internal.R.array.config_foldedDeviceStates,
-                intArrayOf(1, 2, 3)
+                intArrayOf(1, 2, 3),
             )
         }
         whenever(deviceStateManager.supportedDeviceStates).thenReturn(kosmos.foldedDeviceStateList)
@@ -176,11 +176,11 @@
     private fun createRotationLockTileState(
         activationState: QSTileState.ActivationState,
         secondaryLabel: String,
-        iconRes: Int
+        iconRes: Int,
     ): QSTileState {
         val label = context.getString(R.string.quick_settings_rotation_unlocked_label)
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
@@ -190,7 +190,7 @@
             secondaryLabel,
             QSTileState.SideViewIcon.None,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
index 9bb6141..9a45065 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
@@ -46,7 +46,7 @@
                     addOverride(R.drawable.qs_data_saver_icon_on, TestStubDrawable())
                 }
                 .resources,
-            context.theme
+            context.theme,
         )
     }
 
@@ -59,7 +59,7 @@
         val expectedState =
             createDataSaverTileState(
                 QSTileState.ActivationState.ACTIVE,
-                R.drawable.qs_data_saver_icon_on
+                R.drawable.qs_data_saver_icon_on,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -73,14 +73,14 @@
         val expectedState =
             createDataSaverTileState(
                 QSTileState.ActivationState.INACTIVE,
-                R.drawable.qs_data_saver_icon_off
+                R.drawable.qs_data_saver_icon_off,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
 
     private fun createDataSaverTileState(
         activationState: QSTileState.ActivationState,
-        iconRes: Int
+        iconRes: Int,
     ): QSTileState {
         val label = context.getString(R.string.data_saver)
         val secondaryLabel =
@@ -91,7 +91,7 @@
             else context.resources.getStringArray(R.array.tile_states_saver)[0]
 
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
@@ -101,7 +101,7 @@
             null,
             QSTileState.SideViewIcon.None,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/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/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt
index 336b566..cd683c4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt
@@ -52,7 +52,7 @@
                         addOverride(R.drawable.qs_screen_record_icon_off, TestStubDrawable())
                     }
                     .resources,
-                context.theme
+                context.theme,
             )
     }
 
@@ -82,7 +82,7 @@
             createScreenRecordTileState(
                 QSTileState.ActivationState.ACTIVE,
                 R.drawable.qs_screen_record_icon_on,
-                String.format("%d...", timeLeft)
+                String.format("%d...", timeLeft),
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -110,7 +110,7 @@
         val label = context.getString(R.string.quick_settings_screen_record_label)
 
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
@@ -123,7 +123,7 @@
                 QSTileState.SideViewIcon.Chevron
             else QSTileState.SideViewIcon.None,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
index b08f39b..c569403 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
@@ -56,7 +56,7 @@
                 context.getString(R.string.quick_settings_camera_mic_available),
                 R.drawable.qs_camera_access_icon_on,
                 null,
-                CAMERA
+                CAMERA,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -74,7 +74,7 @@
                 context.getString(R.string.quick_settings_camera_mic_blocked),
                 R.drawable.qs_camera_access_icon_off,
                 null,
-                CAMERA
+                CAMERA,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -92,7 +92,7 @@
                 context.getString(R.string.quick_settings_camera_mic_available),
                 R.drawable.qs_mic_access_on,
                 null,
-                MICROPHONE
+                MICROPHONE,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -110,7 +110,7 @@
                 context.getString(R.string.quick_settings_camera_mic_blocked),
                 R.drawable.qs_mic_access_off,
                 null,
-                MICROPHONE
+                MICROPHONE,
             )
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
@@ -146,7 +146,7 @@
             else context.getString(R.string.quick_settings_mic_label)
 
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
@@ -156,7 +156,7 @@
             stateDescription,
             QSTileState.SideViewIcon.None,
             QSTileState.EnabledState.ENABLED,
-            Switch::class.qualifiedName
+            Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
index c021caa..0d2ebe4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
@@ -49,7 +49,7 @@
                     addOverride(R.drawable.qs_light_dark_theme_icon_on, TestStubDrawable())
                 }
                 .resources,
-            context.theme
+            context.theme,
         )
     }
 
@@ -69,7 +69,7 @@
         expandedAccessibilityClass: KClass<out View>? = Switch::class,
     ): QSTileState {
         return QSTileState(
-            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes,
             label,
             activationState,
@@ -79,7 +79,7 @@
             stateDescription,
             sideViewIcon,
             enabledState,
-            expandedAccessibilityClass?.qualifiedName
+            expandedAccessibilityClass?.qualifiedName,
         )
     }
 
@@ -98,7 +98,7 @@
             createUiNightModeTileState(
                 activationState = QSTileState.ActivationState.UNAVAILABLE,
                 secondaryLabel = expectedSecondaryLabel,
-                contentDescription = expectedContentDescription
+                contentDescription = expectedContentDescription,
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -118,7 +118,7 @@
             createUiNightModeTileState(
                 activationState = QSTileState.ActivationState.UNAVAILABLE,
                 secondaryLabel = expectedSecondaryLabel,
-                contentDescription = expectedContentDescription
+                contentDescription = expectedContentDescription,
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -136,7 +136,7 @@
                 activationState = QSTileState.ActivationState.INACTIVE,
                 label = expectedLabel,
                 secondaryLabel = expectedSecondaryLabel,
-                contentDescription = expectedLabel
+                contentDescription = expectedLabel,
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -155,7 +155,7 @@
                 label = expectedLabel,
                 secondaryLabel = expectedSecondaryLabel,
                 activationState = QSTileState.ActivationState.ACTIVE,
-                contentDescription = expectedLabel
+                contentDescription = expectedLabel,
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -174,7 +174,7 @@
                 label = expectedLabel,
                 secondaryLabel = expectedSecondaryLabel,
                 activationState = QSTileState.ActivationState.ACTIVE,
-                contentDescription = expectedLabel
+                contentDescription = expectedLabel,
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -193,7 +193,7 @@
                 label = expectedLabel,
                 secondaryLabel = expectedSecondaryLabel,
                 activationState = QSTileState.ActivationState.INACTIVE,
-                contentDescription = expectedLabel
+                contentDescription = expectedLabel,
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -214,7 +214,7 @@
                 activationState = QSTileState.ActivationState.ACTIVE,
                 contentDescription = expectedLabel,
                 supportedActions =
-                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -237,7 +237,7 @@
                 secondaryLabel = expectedSecondaryLabel,
                 activationState = QSTileState.ActivationState.UNAVAILABLE,
                 contentDescription = expectedContentDescription,
-                supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+                supportedActions = setOf(QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -258,7 +258,7 @@
                 activationState = QSTileState.ActivationState.INACTIVE,
                 contentDescription = expectedLabel,
                 supportedActions =
-                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -279,7 +279,7 @@
                 secondaryLabel = expectedSecondaryLabel,
                 activationState = QSTileState.ActivationState.UNAVAILABLE,
                 contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel),
-                supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+                supportedActions = setOf(QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -300,7 +300,7 @@
                 secondaryLabel = expectedSecondaryLabel,
                 activationState = QSTileState.ActivationState.UNAVAILABLE,
                 contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel),
-                supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+                supportedActions = setOf(QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -312,7 +312,7 @@
                 nightMode = true,
                 powerSave = false,
                 isLocationEnabled = true,
-                uiMode = UiModeManager.MODE_NIGHT_AUTO
+                uiMode = UiModeManager.MODE_NIGHT_AUTO,
             )
 
         val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
@@ -328,7 +328,7 @@
                 activationState = QSTileState.ActivationState.ACTIVE,
                 contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel),
                 supportedActions =
-                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -340,7 +340,7 @@
                 nightMode = false,
                 powerSave = false,
                 isLocationEnabled = true,
-                uiMode = UiModeManager.MODE_NIGHT_AUTO
+                uiMode = UiModeManager.MODE_NIGHT_AUTO,
             )
 
         val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
@@ -356,7 +356,7 @@
                 activationState = QSTileState.ActivationState.INACTIVE,
                 contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel),
                 supportedActions =
-                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -379,7 +379,7 @@
                 activationState = QSTileState.ActivationState.ACTIVE,
                 contentDescription = expectedLabel,
                 supportedActions =
-                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -401,7 +401,7 @@
                 activationState = QSTileState.ActivationState.INACTIVE,
                 contentDescription = expectedLabel,
                 supportedActions =
-                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -413,7 +413,7 @@
                 nightMode = false,
                 powerSave = false,
                 uiMode = UiModeManager.MODE_NIGHT_CUSTOM,
-                nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN
+                nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN,
             )
 
         val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
@@ -428,7 +428,7 @@
                 activationState = QSTileState.ActivationState.INACTIVE,
                 contentDescription = expectedLabel,
                 supportedActions =
-                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -440,7 +440,7 @@
                 nightMode = true,
                 powerSave = false,
                 uiMode = UiModeManager.MODE_NIGHT_CUSTOM,
-                nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN
+                nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN,
             )
 
         val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
@@ -455,7 +455,7 @@
                 activationState = QSTileState.ActivationState.ACTIVE,
                 contentDescription = expectedLabel,
                 supportedActions =
-                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
@@ -467,7 +467,7 @@
                 nightMode = false,
                 powerSave = true,
                 uiMode = UiModeManager.MODE_NIGHT_CUSTOM,
-                nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN
+                nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN,
             )
 
         val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
@@ -484,7 +484,7 @@
                 secondaryLabel = expectedSecondaryLabel,
                 activationState = QSTileState.ActivationState.UNAVAILABLE,
                 contentDescription = expectedContentDescription,
-                supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+                supportedActions = setOf(QSTileState.UserAction.LONG_CLICK),
             )
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
index e7bde681..86321ea 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
@@ -56,7 +56,7 @@
         whenever(
                 devicePolicyResourceManager.getString(
                     eq(DevicePolicyResources.Strings.SystemUi.QS_WORK_PROFILE_LABEL),
-                    any()
+                    any(),
                 )
             )
             .thenReturn(testLabel)
@@ -66,12 +66,12 @@
                     .apply {
                         addOverride(
                             com.android.internal.R.drawable.stat_sys_managed_profile_status,
-                            TestStubDrawable()
+                            TestStubDrawable(),
                         )
                     }
                     .resources,
                 context.theme,
-                devicePolicyManager
+                devicePolicyManager,
             )
     }
 
@@ -105,13 +105,11 @@
         QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
     }
 
-    private fun createWorkModeTileState(
-        activationState: QSTileState.ActivationState,
-    ): QSTileState {
+    private fun createWorkModeTileState(activationState: QSTileState.ActivationState): QSTileState {
         val label = testLabel
         val iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status
         return QSTileState(
-            icon = { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            icon = Icon.Loaded(context.getDrawable(iconRes)!!, null),
             iconRes = iconRes,
             label = label,
             activationState = activationState,
@@ -134,7 +132,7 @@
             stateDescription = null,
             sideViewIcon = QSTileState.SideViewIcon.None,
             enabledState = QSTileState.EnabledState.ENABLED,
-            expandedAccessibilityClassName = Switch::class.qualifiedName
+            expandedAccessibilityClassName = Switch::class.qualifiedName,
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
index c33e2a4..954215ee 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
@@ -184,10 +184,7 @@
             {
                 object : QSTileDataToStateMapper<String> {
                     override fun map(config: QSTileConfig, data: String): QSTileState =
-                        QSTileState.build(
-                            { Icon.Resource(0, ContentDescription.Resource(0)) },
-                            data
-                        ) {}
+                        QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), data) {}
                 }
             },
             disabledByPolicyInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
index 7955f2f..0219a4c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
@@ -104,7 +104,7 @@
                     eq(tileConfig.tileSpec),
                     eq(userAction),
                     any(),
-                    eq("initial_data")
+                    eq("initial_data"),
                 )
             verify(qsTileAnalytics).trackUserAction(eq(tileConfig), eq(userAction))
         }
@@ -130,7 +130,7 @@
                 .logUserActionRejectedByPolicy(
                     eq(userAction),
                     eq(tileConfig.tileSpec),
-                    eq(DISABLED_RESTRICTION)
+                    eq(DISABLED_RESTRICTION),
                 )
             verify(qsTileAnalytics, never()).trackUserAction(any(), any())
         }
@@ -159,7 +159,7 @@
                 .logUserActionRejectedByPolicy(
                     eq(userAction),
                     eq(tileConfig.tileSpec),
-                    eq(DISABLED_RESTRICTION)
+                    eq(DISABLED_RESTRICTION),
                 )
             verify(qsTileAnalytics, never()).trackUserAction(any(), any())
         }
@@ -174,7 +174,7 @@
                         QSTilePolicy.Restricted(
                             listOf(
                                 DISABLED_RESTRICTION,
-                                FakeDisabledByPolicyInteractor.DISABLED_RESTRICTION_2
+                                FakeDisabledByPolicyInteractor.DISABLED_RESTRICTION_2,
                             )
                         )
                 }
@@ -194,13 +194,13 @@
                 .logUserActionRejectedByPolicy(
                     eq(userAction),
                     eq(tileConfig.tileSpec),
-                    eq(DISABLED_RESTRICTION)
+                    eq(DISABLED_RESTRICTION),
                 )
             verify(qsTileLogger, never())
                 .logUserActionRejectedByPolicy(
                     eq(userAction),
                     eq(tileConfig.tileSpec),
-                    eq(FakeDisabledByPolicyInteractor.DISABLED_RESTRICTION_2)
+                    eq(FakeDisabledByPolicyInteractor.DISABLED_RESTRICTION_2),
                 )
             verify(qsTileAnalytics, never()).trackUserAction(any(), any())
         }
@@ -243,10 +243,7 @@
             {
                 object : QSTileDataToStateMapper<String> {
                     override fun map(config: QSTileConfig, data: String): QSTileState =
-                        QSTileState.build(
-                            { Icon.Resource(0, ContentDescription.Resource(0)) },
-                            data
-                        ) {}
+                        QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), data) {}
                 }
             },
             disabledByPolicyInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
index 22913f1..8769022 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.display.data.repository.displayStateRepository
 import com.android.systemui.dump.DumpManager
@@ -101,7 +101,7 @@
 
     private val fakeConfigurationRepository =
         FakeConfigurationRepository().apply { onConfigurationChange(configuration) }
-    private val configurationInteractor = ConfigurationInteractor(fakeConfigurationRepository)
+    private val configurationInteractor = ConfigurationInteractorImpl(fakeConfigurationRepository)
 
     private val mockAsyncLayoutInflater =
         mock<AsyncLayoutInflater>() {
@@ -151,10 +151,7 @@
             inOrder.verify(qsImpl!!).onCreate(nullable())
             inOrder
                 .verify(qsImpl!!)
-                .onComponentCreated(
-                    eq(qsSceneComponentFactory.components[0]),
-                    any(),
-                )
+                .onComponentCreated(eq(qsSceneComponentFactory.components[0]), any())
         }
 
     @Test
@@ -422,10 +419,7 @@
             inOrder.verify(newQSImpl).onCreate(nullable())
             inOrder
                 .verify(newQSImpl)
-                .onComponentCreated(
-                    qsSceneComponentFactory.components[1],
-                    bundleArgCaptor.value,
-                )
+                .onComponentCreated(qsSceneComponentFactory.components[1], bundleArgCaptor.value)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/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/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 319f1e5..3be8a38 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -44,6 +44,7 @@
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.ui.viewmodel.lockscreenUserActionsViewModel
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
 import com.android.systemui.kosmos.runCurrent
 import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testScope
@@ -153,7 +154,7 @@
     @Test
     fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
         kosmos.runTest {
-            val actions by testScope.collectLastValue(kosmos.lockscreenUserActionsViewModel.actions)
+            val actions by collectLastValue(kosmos.lockscreenUserActionsViewModel.actions)
             val upDestinationSceneKey =
                 (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
@@ -170,7 +171,7 @@
         kosmos.runTest {
             setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
 
-            val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
+            val actions by collectLastValue(lockscreenUserActionsViewModel.actions)
             val upDestinationSceneKey =
                 (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
@@ -180,7 +181,7 @@
     @Test
     fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
         kosmos.runTest {
-            val actions by testScope.collectLastValue(shadeUserActionsViewModel.actions)
+            val actions by collectLastValue(shadeUserActionsViewModel.actions)
             setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
             assertCurrentScene(Scenes.Lockscreen)
 
@@ -197,8 +198,8 @@
     @Test
     fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenDismissed_goesToGone() =
         kosmos.runTest {
-            val actions by testScope.collectLastValue(shadeUserActionsViewModel.actions)
-            val canSwipeToEnter by testScope.collectLastValue(deviceEntryInteractor.canSwipeToEnter)
+            val actions by collectLastValue(shadeUserActionsViewModel.actions)
+            val canSwipeToEnter by collectLastValue(deviceEntryInteractor.canSwipeToEnter)
 
             setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
 
@@ -279,7 +280,7 @@
     fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
         kosmos.runTest {
             unlockDevice()
-            val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
+            val actions by collectLastValue(lockscreenUserActionsViewModel.actions)
             val upDestinationSceneKey =
                 (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
@@ -302,7 +303,7 @@
     fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() =
         kosmos.runTest {
             setAuthMethod(AuthenticationMethodModel.Password)
-            val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
+            val actions by collectLastValue(lockscreenUserActionsViewModel.actions)
             val upDestinationSceneKey =
                 (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
@@ -319,14 +320,13 @@
     fun bouncerActionButtonClick_opensEmergencyServicesDialer() =
         kosmos.runTest {
             setAuthMethod(AuthenticationMethodModel.Password)
-            val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
+            val actions by collectLastValue(lockscreenUserActionsViewModel.actions)
             val upDestinationSceneKey =
                 (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(to = upDestinationSceneKey)
 
-            val bouncerActionButton by
-                testScope.collectLastValue(bouncerSceneContentViewModel.actionButton)
+            val bouncerActionButton by collectLastValue(bouncerSceneContentViewModel.actionButton)
             assertWithMessage("Bouncer action button not visible")
                 .that(bouncerActionButton)
                 .isNotNull()
@@ -341,14 +341,13 @@
         kosmos.runTest {
             setAuthMethod(AuthenticationMethodModel.Password)
             startPhoneCall()
-            val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
+            val actions by collectLastValue(lockscreenUserActionsViewModel.actions)
             val upDestinationSceneKey =
                 (actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(to = upDestinationSceneKey)
 
-            val bouncerActionButton by
-                testScope.collectLastValue(bouncerSceneContentViewModel.actionButton)
+            val bouncerActionButton by collectLastValue(bouncerSceneContentViewModel.actionButton)
             assertWithMessage("Bouncer action button not visible during call")
                 .that(bouncerActionButton)
                 .isNotNull()
@@ -574,7 +573,7 @@
             .that(getCurrentSceneInUi())
             .isEqualTo(Scenes.Bouncer)
         val authMethodViewModel by
-            testScope.collectLastValue(bouncerSceneContentViewModel.authMethodViewModel)
+            collectLastValue(bouncerSceneContentViewModel.authMethodViewModel)
         assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
             .that(authMethodViewModel)
             .isInstanceOf(PinBouncerViewModel::class.java)
@@ -603,7 +602,7 @@
             .that(getCurrentSceneInUi())
             .isEqualTo(Scenes.Bouncer)
         val authMethodViewModel by
-            testScope.collectLastValue(bouncerSceneContentViewModel.authMethodViewModel)
+            collectLastValue(bouncerSceneContentViewModel.authMethodViewModel)
         assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
             .that(authMethodViewModel)
             .isInstanceOf(PinBouncerViewModel::class.java)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
index bfe5ef7..db2297c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
@@ -32,7 +32,7 @@
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
 import com.android.systemui.statusbar.NotificationPresenter
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.init.NotificationsController
@@ -69,7 +69,7 @@
     private val notificationPresenter = mock<NotificationPresenter>()
     private val notificationsController = mock<NotificationsController>()
     private val powerInteractor = PowerInteractorFactory.create().powerInteractor
-    private val activeNotificationsRepository = ActiveNotificationListRepository()
+    private val activeNotificationsRepository = kosmos.activeNotificationListRepository
     private val activeNotificationsInteractor =
         ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher)
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 2c8f7cf..55f88cc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -2119,40 +2119,6 @@
         }
 
     @Test
-    fun switchToGone_whenSurfaceBehindLockscreenVisibleMidTransition() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
-            val transitionStateFlow =
-                prepareState(authenticationMethod = AuthenticationMethodModel.None)
-            underTest.start()
-            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
-            // Swipe to Gone, more than halfway
-            transitionStateFlow.value =
-                ObservableTransitionState.Transition(
-                    fromScene = Scenes.Lockscreen,
-                    toScene = Scenes.Gone,
-                    currentScene = flowOf(Scenes.Gone),
-                    progress = flowOf(0.51f),
-                    isInitiatedByUserInput = true,
-                    isUserInputOngoing = flowOf(true),
-                )
-            runCurrent()
-            // Lift finger
-            transitionStateFlow.value =
-                ObservableTransitionState.Transition(
-                    fromScene = Scenes.Lockscreen,
-                    toScene = Scenes.Gone,
-                    currentScene = flowOf(Scenes.Gone),
-                    progress = flowOf(0.51f),
-                    isInitiatedByUserInput = true,
-                    isUserInputOngoing = flowOf(false),
-                )
-            runCurrent()
-
-            assertThat(currentScene).isEqualTo(Scenes.Gone)
-        }
-
-    @Test
     fun switchToGone_extendUnlock() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/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/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index b632a8a..af895c8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -21,6 +21,8 @@
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_OUTSIDE
 import android.view.View
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -43,6 +45,7 @@
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shade.shared.flag.DualShade
 import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.statusbar.data.repository.fakeRemoteInputRepository
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -68,6 +71,7 @@
     private val fakeSceneDataSource by lazy { kosmos.fakeSceneDataSource }
     private val fakeShadeRepository by lazy { kosmos.fakeShadeRepository }
     private val sceneContainerConfig by lazy { kosmos.sceneContainerConfig }
+    private val fakeRemoteInputRepository by lazy { kosmos.fakeRemoteInputRepository }
     private val falsingManager by lazy { kosmos.fakeFalsingManager }
     private val view = mock<View>()
 
@@ -234,6 +238,35 @@
         }
 
     @Test
+    fun userInputOnEmptySpace_insideEvent() =
+        testScope.runTest {
+            assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse()
+            val insideMotionEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0f, 0f, 0)
+            underTest.onEmptySpaceMotionEvent(insideMotionEvent)
+            assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse()
+        }
+
+    @Test
+    fun userInputOnEmptySpace_outsideEvent_remoteInputActive() =
+        testScope.runTest {
+            fakeRemoteInputRepository.isRemoteInputActive.value = true
+            assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse()
+            val outsideMotionEvent = MotionEvent.obtain(0, 0, ACTION_OUTSIDE, 0f, 0f, 0)
+            underTest.onEmptySpaceMotionEvent(outsideMotionEvent)
+            assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isTrue()
+        }
+
+    @Test
+    fun userInputOnEmptySpace_outsideEvent_remoteInputInactive() =
+        testScope.runTest {
+            fakeRemoteInputRepository.isRemoteInputActive.value = false
+            assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse()
+            val outsideMotionEvent = MotionEvent.obtain(0, 0, ACTION_OUTSIDE, 0f, 0f, 0)
+            underTest.onEmptySpaceMotionEvent(outsideMotionEvent)
+            assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse()
+        }
+
+    @Test
     fun remoteUserInteraction_keepsContainerVisible() =
         testScope.runTest {
             sceneInteractor.setVisible(false, "reason")
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/screenshot/data/model/DisplayContentScenarios.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
index 4d71dc4..4871564 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
@@ -18,6 +18,8 @@
 
 import android.content.ComponentName
 import android.graphics.Rect
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREEFORM_FULL_SCREEN
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREEFORM_MAXIMIZED
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREE_FORM
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FULL_SCREEN
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.PIP
@@ -153,11 +155,23 @@
     fun freeFormApps(
         vararg tasks: TaskSpec,
         focusedTaskId: Int,
+        maximizedTaskId: Int = -1,
         shadeExpanded: Boolean = false,
     ): DisplayContentModel {
         val freeFormTasks =
             tasks
-                .map { freeForm(it) }
+                .map {
+                    freeForm(
+                        task = it,
+                        bounds =
+                            if (it.taskId == maximizedTaskId) {
+                                FREEFORM_MAXIMIZED
+                            } else {
+                                FREE_FORM
+                            },
+                        maxBounds = FREEFORM_FULL_SCREEN,
+                    )
+                }
                 // Root tasks are ordered top-down in List<RootTaskInfo>.
                 // Sort 'focusedTaskId' last (Boolean natural ordering: [false, true])
                 .sortedBy { it.childTaskIds[0] != focusedTaskId }
@@ -180,9 +194,9 @@
         val PIP = Rect(440, 1458, 1038, 1794)
         val SPLIT_TOP = Rect(0, 0, 1080, 1187)
         val SPLIT_BOTTOM = Rect(0, 1213, 1080, 2400)
-        val FREE_FORM = Rect(119, 332, 1000, 1367)
 
         // "Tablet" size
+        val FREE_FORM = Rect(119, 332, 1000, 1367)
         val FREEFORM_FULL_SCREEN = Rect(0, 0, 2560, 1600)
         val FREEFORM_MAXIMIZED = Rect(0, 48, 2560, 1480)
         val FREEFORM_SPLIT_LEFT = Rect(0, 0, 1270, 1600)
@@ -301,11 +315,12 @@
             }
 
         /** An activity in FreeForm mode */
-        fun freeForm(task: TaskSpec, bounds: Rect = FREE_FORM) =
+        fun freeForm(task: TaskSpec, bounds: Rect = FREE_FORM, maxBounds: Rect = bounds) =
             newRootTaskInfo(
                 taskId = task.taskId,
                 userId = task.userId,
                 bounds = bounds,
+                maxBounds = maxBounds,
                 windowingMode = WindowingMode.Freeform,
                 topActivity = ComponentName.unflattenFromString(task.name),
             ) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt
index cedf0c8..4f6871e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt
@@ -84,6 +84,7 @@
     activityType: ActivityType = Standard,
     windowingMode: WindowingMode = FullScreen,
     bounds: Rect = Rect(),
+    maxBounds: Rect = bounds,
     topActivity: ComponentName? = null,
     topActivityType: ActivityType = Standard,
     numActivities: Int? = null,
@@ -94,6 +95,7 @@
             setWindowingMode(windowingMode.toInt())
             setActivityType(activityType.toInt())
             setBounds(bounds)
+            setMaxBounds(maxBounds)
         }
         this.bounds = bounds
         this.displayId = displayId
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt
index b7f565d..c884b9a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt
@@ -90,9 +90,11 @@
                 Matched(
                     PrivateProfilePolicy.NAME,
                     PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
-                    CaptureParameters(
+                    LegacyCaptureParameters(
                         type = FullScreen(displayId = 0),
-                        component = ComponentName.unflattenFromString(YOUTUBE),
+                        component =
+                            ComponentName.unflattenFromString(YOUTUBE)
+                                ?: error("Invalid component name"),
                         owner = UserHandle.of(PRIVATE),
                     ),
                 )
@@ -142,7 +144,7 @@
                 Matched(
                     PrivateProfilePolicy.NAME,
                     PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
-                    CaptureParameters(
+                    LegacyCaptureParameters(
                         type = FullScreen(displayId = 0),
                         component = ComponentName.unflattenFromString(YOUTUBE),
                         owner = UserHandle.of(PRIVATE),
@@ -167,7 +169,7 @@
                 Matched(
                     PrivateProfilePolicy.NAME,
                     PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
-                    CaptureParameters(
+                    LegacyCaptureParameters(
                         type = FullScreen(displayId = 0),
                         component = ComponentName.unflattenFromString(FILES),
                         owner = UserHandle.of(PRIVATE),
@@ -188,7 +190,7 @@
                 Matched(
                     PrivateProfilePolicy.NAME,
                     PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
-                    CaptureParameters(
+                    LegacyCaptureParameters(
                         type = FullScreen(displayId = 0),
                         component = ComponentName.unflattenFromString(YOUTUBE_PIP),
                         owner = UserHandle.of(PRIVATE),
@@ -212,7 +214,7 @@
                 Matched(
                     PrivateProfilePolicy.NAME,
                     PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
-                    CaptureParameters(
+                    LegacyCaptureParameters(
                         type = FullScreen(displayId = 0),
                         component = ComponentName.unflattenFromString(YOUTUBE_PIP),
                         owner = UserHandle.of(PRIVATE),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/ScreenshotPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/ScreenshotPolicyTest.kt
index 28eb9fc..948c24e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/ScreenshotPolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/ScreenshotPolicyTest.kt
@@ -17,11 +17,11 @@
 package com.android.systemui.screenshot.policy
 
 import android.content.ComponentName
+import android.graphics.Rect
 import android.os.UserHandle
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES
-import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.LAUNCHER
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.MESSAGES
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.YOUTUBE
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREEFORM_FULL_SCREEN
@@ -32,10 +32,10 @@
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.pictureInPictureApp
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.singleFullScreen
 import com.android.systemui.screenshot.data.model.DisplayContentScenarios.splitScreenApps
+import com.android.systemui.screenshot.data.model.allTasks
 import com.android.systemui.screenshot.data.repository.profileTypeRepository
 import com.android.systemui.screenshot.policy.CaptureType.FullScreen
 import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
-import com.android.systemui.screenshot.policy.CaptureType.RootTask
 import com.android.systemui.screenshot.policy.TestUserIds.PERSONAL
 import com.android.systemui.screenshot.policy.TestUserIds.PRIVATE
 import com.android.systemui.screenshot.policy.TestUserIds.WORK
@@ -50,69 +50,81 @@
 
     private val defaultComponent = ComponentName("default", "default")
     private val defaultOwner = UserHandle.SYSTEM
+    private val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
 
     @Test
     fun fullScreen_work() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
+        val displayContent = singleFullScreen(TaskSpec(taskId = 1002, name = FILES, userId = WORK))
+        val expectedFocusedTask =
+            displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
 
-        val result =
-            policy.apply(
-                singleFullScreen(TaskSpec(taskId = 1002, name = FILES, userId = WORK)),
-                defaultComponent,
-                defaultOwner,
-            )
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
                     type = IsolatedTask(taskId = 1002, taskBounds = FULL_SCREEN),
-                    component = ComponentName.unflattenFromString(FILES),
-                    owner = UserHandle.of(WORK),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
+                        ),
+                    owner = UserHandle.of(expectedFocusedTask.userId),
                 )
             )
     }
 
     @Test
     fun fullScreen_private() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
+        val displayContent =
+            singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE))
+        val expectedFocusedTask =
+            displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
 
-        val result =
-            policy.apply(
-                singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE)),
-                defaultComponent,
-                defaultOwner,
-            )
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
                     type = FullScreen(displayId = 0),
-                    component = ComponentName.unflattenFromString(YOUTUBE),
-                    owner = UserHandle.of(PRIVATE),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
+                        ),
+                    owner = UserHandle.of(expectedFocusedTask.userId),
                 )
             )
     }
 
     @Test
     fun splitScreen_workAndPersonal() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
-        val result =
-            policy.apply(
-                splitScreenApps(
-                    first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
-                    second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
-                    focusedTaskId = 1002,
-                ),
-                defaultComponent,
-                defaultOwner,
+        val displayContent =
+            splitScreenApps(
+                first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+                second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
+                focusedTaskId = 1002,
             )
+        val expectedFocusedTask =
+            displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
                     type = FullScreen(displayId = 0),
-                    component = ComponentName.unflattenFromString(YOUTUBE),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
+                        ),
                     owner = UserHandle.of(PERSONAL),
                 )
             )
@@ -120,24 +132,28 @@
 
     @Test
     fun splitScreen_personalAndPrivate() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
-        val result =
-            policy.apply(
-                splitScreenApps(
-                    first = TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL),
-                    second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
-                    focusedTaskId = 1002,
-                ),
-                defaultComponent,
-                defaultOwner,
+        val displayContent =
+            splitScreenApps(
+                first = TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL),
+                second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+                focusedTaskId = 1002,
             )
+        val expectedFocusedTask =
+            displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
                     type = FullScreen(displayId = 0),
-                    component = ComponentName.unflattenFromString(YOUTUBE),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
+                        ),
                     owner = UserHandle.of(PRIVATE),
                 )
             )
@@ -145,24 +161,28 @@
 
     @Test
     fun splitScreen_workAndPrivate() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
-        val result =
-            policy.apply(
-                splitScreenApps(
-                    first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
-                    second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
-                    focusedTaskId = 1002,
-                ),
-                defaultComponent,
-                defaultOwner,
+        val displayContent =
+            splitScreenApps(
+                first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+                second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+                focusedTaskId = 1002,
             )
+        val expectedFocusedTask =
+            displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
                     type = FullScreen(displayId = 0),
-                    component = ComponentName.unflattenFromString(YOUTUBE),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
+                        ),
                     owner = UserHandle.of(PRIVATE),
                 )
             )
@@ -170,32 +190,31 @@
 
     @Test
     fun splitScreen_twoWorkTasks() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
-        val result =
-            policy.apply(
-                splitScreenApps(
-                    parentTaskId = 1,
-                    parentBounds = FREEFORM_FULL_SCREEN,
-                    orientation = VERTICAL,
-                    first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
-                    second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = WORK),
-                    focusedTaskId = 1002,
-                ),
-                defaultComponent,
-                defaultOwner,
+        val displayContent =
+            splitScreenApps(
+                parentTaskId = 1,
+                parentBounds = FREEFORM_FULL_SCREEN,
+                orientation = VERTICAL,
+                first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+                second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = WORK),
+                focusedTaskId = 1002,
             )
+        val expectedFocusedTask =
+            displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
-                    type =
-                        RootTask(
-                            parentTaskId = 1,
-                            taskBounds = FREEFORM_FULL_SCREEN,
-                            childTaskIds = listOf(1002, 1003),
+                    type = IsolatedTask(taskBounds = FREEFORM_FULL_SCREEN, taskId = 1),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
                         ),
-                    component = ComponentName.unflattenFromString(FILES),
                     owner = UserHandle.of(WORK),
                 )
             )
@@ -203,99 +222,112 @@
 
     @Test
     fun freeform_floatingWindows() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
-        val result =
-            policy.apply(
-                freeFormApps(
-                    TaskSpec(taskId = 1002, name = FILES, userId = WORK),
-                    TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
-                    focusedTaskId = 1003,
-                ),
-                defaultComponent,
-                defaultOwner,
+        val displayContent =
+            freeFormApps(
+                TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+                TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
+                focusedTaskId = 1003,
             )
+        val expectedFocusedTask =
+            displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1003 }
+
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
                     type = FullScreen(displayId = 0),
-                    component = ComponentName.unflattenFromString(YOUTUBE),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
+                        ),
                     owner = UserHandle.of(PERSONAL),
                 )
             )
     }
 
     @Test
-    fun freeform_floatingWindows_maximized() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
-        val result =
-            policy.apply(
-                freeFormApps(
-                    TaskSpec(taskId = 1002, name = FILES, userId = WORK),
-                    TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
-                    focusedTaskId = 1003,
-                ),
-                defaultComponent,
-                defaultOwner,
+    fun freeform_floatingWindows_work_maximized() = runTest {
+        val displayContent =
+            freeFormApps(
+                TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+                TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
+                focusedTaskId = 1002,
+                maximizedTaskId = 1002,
             )
+        val expectedFocusedTask =
+            displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
-                    type = FullScreen(displayId = 0),
-                    component = ComponentName.unflattenFromString(YOUTUBE),
-                    owner = UserHandle.of(PERSONAL),
+                    type = IsolatedTask(taskId = 1002, taskBounds = expectedFocusedTask.bounds),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
+                        ),
+                    owner = UserHandle.of(WORK),
                 )
             )
     }
 
     @Test
     fun freeform_floatingWindows_withPrivate() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
-        val result =
-            policy.apply(
-                freeFormApps(
-                    TaskSpec(taskId = 1002, name = FILES, userId = WORK),
-                    TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
-                    TaskSpec(taskId = 1004, name = MESSAGES, userId = PERSONAL),
-                    focusedTaskId = 1004,
-                ),
-                defaultComponent,
-                defaultOwner,
+        val displayContent =
+            freeFormApps(
+                TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+                TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+                TaskSpec(taskId = 1004, name = MESSAGES, userId = PERSONAL),
+                focusedTaskId = 1004,
             )
+        val expectedFocusedTask = displayContent.allTasks().single { it.id == 1004 }
+
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
                     type = FullScreen(displayId = 0),
-                    component = ComponentName.unflattenFromString(YOUTUBE),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
+                        ),
                     owner = UserHandle.of(PRIVATE),
                 )
             )
     }
 
     @Test
-    fun freeform_floating_workOnly() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
+    fun freeform_floating_work() = runTest {
+        val displayContent =
+            freeFormApps(TaskSpec(taskId = 1002, name = FILES, userId = WORK), focusedTaskId = 1002)
+        val expectedFocusedTask =
+            displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
 
-        val result =
-            policy.apply(
-                freeFormApps(
-                    TaskSpec(taskId = 1002, name = FILES, userId = WORK),
-                    focusedTaskId = 1002,
-                ),
-                defaultComponent,
-                defaultOwner,
-            )
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
                     type = FullScreen(displayId = 0),
-                    component = ComponentName.unflattenFromString(LAUNCHER),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
+                        ),
                     owner = defaultOwner,
                 )
             )
@@ -303,23 +335,27 @@
 
     @Test
     fun fullScreen_shadeExpanded() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
-        val result =
-            policy.apply(
-                singleFullScreen(
-                    TaskSpec(taskId = 1002, name = FILES, userId = WORK),
-                    shadeExpanded = true,
-                ),
-                defaultComponent,
-                defaultOwner,
+        val displayContent =
+            singleFullScreen(
+                TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+                shadeExpanded = true,
             )
+        val expectedFocusedTask =
+            displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
                     type = FullScreen(displayId = 0),
-                    component = defaultComponent,
+                    contentTask =
+                        TaskReference(
+                            taskId = -1,
+                            component = defaultComponent,
+                            owner = defaultOwner,
+                            bounds = Rect(),
+                        ),
                     owner = defaultOwner,
                 )
             )
@@ -327,25 +363,55 @@
 
     @Test
     fun fullScreen_with_PictureInPicture() = runTest {
-        val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
-        val result =
-            policy.apply(
-                pictureInPictureApp(
-                    pip = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL),
-                    fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = WORK),
-                ),
-                defaultComponent,
-                defaultOwner,
+        val displayContent =
+            pictureInPictureApp(
+                pip = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL),
+                fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = WORK),
             )
+        val expectedFocusedTask = displayContent.allTasks().single { it.id == 1003 }
+
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
 
         assertThat(result)
             .isEqualTo(
                 CaptureParameters(
                     type = IsolatedTask(taskId = 1003, taskBounds = FULL_SCREEN),
-                    component = ComponentName.unflattenFromString(FILES),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
+                        ),
                     owner = UserHandle.of(WORK),
                 )
             )
     }
+
+    // TODO: PiP tasks should affect ownership (e.g. Private)
+    @Test
+    fun fullScreen_with_PictureInPicture_private() = runTest {
+        val displayContent =
+            pictureInPictureApp(
+                pip = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE),
+                fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = PERSONAL),
+            )
+        val expectedFocusedTask = displayContent.allTasks().single { it.id == 1003 }
+
+        val result = policy.apply(displayContent, defaultComponent, defaultOwner)
+        assertThat(result)
+            .isEqualTo(
+                CaptureParameters(
+                    type = FullScreen(displayId = 0),
+                    contentTask =
+                        TaskReference(
+                            taskId = expectedFocusedTask.id,
+                            component = expectedFocusedTask.componentName,
+                            owner = UserHandle.of(expectedFocusedTask.userId),
+                            bounds = expectedFocusedTask.bounds,
+                        ),
+                    owner = UserHandle.of(PRIVATE),
+                )
+            )
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
index 30a786c..c1477fe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
@@ -135,7 +135,7 @@
                 PolicyResult.Matched(
                     policy = WorkProfilePolicy.NAME,
                     reason = WORK_TASK_IS_TOP,
-                    CaptureParameters(
+                    LegacyCaptureParameters(
                         type = IsolatedTask(taskId = 1002, taskBounds = FULL_SCREEN),
                         component = ComponentName.unflattenFromString(FILES),
                         owner = UserHandle.of(WORK),
@@ -162,7 +162,7 @@
                 PolicyResult.Matched(
                     policy = WorkProfilePolicy.NAME,
                     reason = WORK_TASK_IS_TOP,
-                    CaptureParameters(
+                    LegacyCaptureParameters(
                         type = IsolatedTask(taskId = 1002, taskBounds = FULL_SCREEN.splitTop(20)),
                         component = ComponentName.unflattenFromString(FILES),
                         owner = UserHandle.of(WORK),
@@ -200,7 +200,7 @@
                 PolicyResult.Matched(
                     policy = WorkProfilePolicy.NAME,
                     reason = WORK_TASK_IS_TOP,
-                    CaptureParameters(
+                    LegacyCaptureParameters(
                         type = IsolatedTask(taskId = 1003, taskBounds = FULL_SCREEN),
                         component = ComponentName.unflattenFromString(FILES),
                         owner = UserHandle.of(WORK),
@@ -226,7 +226,7 @@
                 PolicyResult.Matched(
                     policy = WorkProfilePolicy.NAME,
                     reason = WORK_TASK_IS_TOP,
-                    CaptureParameters(
+                    LegacyCaptureParameters(
                         type = IsolatedTask(taskId = 1003, taskBounds = FREE_FORM),
                         component = ComponentName.unflattenFromString(FILES),
                         owner = UserHandle.of(WORK),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 59d0d70..041d1a6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -238,7 +238,7 @@
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
                 mock(BouncerViewBinder::class.java),
-                mock(ConfigurationForwarder::class.java),
+                { mock(ConfigurationForwarder::class.java) },
                 brightnessMirrorShowingInteractor,
             )
         underTest.setupExpandedStatusBar()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 9b91fc7..5d1ce7c5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -203,7 +203,7 @@
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
                 mock(),
-                configurationForwarder,
+                { configurationForwarder },
                 brightnessMirrorShowingInteractor,
             )
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 2e759a3..443595d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -17,7 +17,6 @@
 package com.android.systemui.shade;
 
 import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
-import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doAnswer;
@@ -63,8 +62,6 @@
 import com.android.systemui.statusbar.QsFrameTranslateController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.DozeParameters;
@@ -159,8 +156,6 @@
     protected SysuiStatusBarStateController mStatusBarStateController;
     protected ShadeInteractor mShadeInteractor;
 
-    protected ActiveNotificationsInteractor mActiveNotificationsInteractor;
-
     protected Handler mMainHandler;
     protected LockscreenShadeTransitionController.Callback mLockscreenShadeTransitionCallback;
 
@@ -204,11 +199,6 @@
                 ),
                 mKosmos.getShadeModeInteractor());
 
-        mActiveNotificationsInteractor = new ActiveNotificationsInteractor(
-                        new ActiveNotificationListRepository(),
-                        StandardTestDispatcher(/* scheduler = */ null, /* name = */ null)
-                );
-
         KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
         keyguardStatusView.setId(R.id.keyguard_status_view);
 
@@ -277,7 +267,7 @@
                 mock(DeviceEntryFaceAuthInteractor.class),
                 mShadeRepository,
                 mShadeInteractor,
-                mActiveNotificationsInteractor,
+                mKosmos.getActiveNotificationsInteractor(),
                 new JavaAdapter(mTestScope.getBackgroundScope()),
                 mCastController,
                 splitShadeStateController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 943fb62..0f476d0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -35,8 +35,7 @@
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.NotificationShadeWindowController
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -50,7 +49,6 @@
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import dagger.Lazy
-import kotlinx.coroutines.test.StandardTestDispatcher
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -65,8 +63,6 @@
 @SmallTest
 class ShadeControllerImplTest : SysuiTestCase() {
     private val executor = FakeExecutor(FakeSystemClock())
-    private val testDispatcher = StandardTestDispatcher()
-    private val activeNotificationsRepository = ActiveNotificationListRepository()
     private val kosmos = Kosmos()
     private val testScope = kosmos.testScope
 
@@ -95,7 +91,7 @@
             FakeKeyguardRepository(),
             headsUpManager,
             PowerInteractorFactory.create().powerInteractor,
-            ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher),
+            kosmos.activeNotificationsInteractor,
             kosmos::sceneInteractor,
         )
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/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/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 022825a..f4a43a4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -22,19 +22,20 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.PluginLifecycleManager
+import com.android.systemui.plugins.PluginListener
+import com.android.systemui.plugins.PluginManager
 import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockFontAxisSetting
 import com.android.systemui.plugins.clocks.ClockId
 import com.android.systemui.plugins.clocks.ClockMessageBuffers
 import com.android.systemui.plugins.clocks.ClockMetadata
 import com.android.systemui.plugins.clocks.ClockPickerConfig
 import com.android.systemui.plugins.clocks.ClockProviderPlugin
 import com.android.systemui.plugins.clocks.ClockSettings
-import com.android.systemui.plugins.PluginLifecycleManager
-import com.android.systemui.plugins.PluginListener
-import com.android.systemui.plugins.PluginManager
+import com.android.systemui.util.ThreadAssert
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.ThreadAssert
 import java.util.function.BiConsumer
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.fail
@@ -42,6 +43,8 @@
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestCoroutineScheduler
 import kotlinx.coroutines.test.TestScope
+import org.json.JSONArray
+import org.json.JSONObject
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -81,28 +84,32 @@
             return null!!
         }
 
-        private fun failPickerConfig(clockId: ClockId): ClockPickerConfig {
-            fail("Unexpected call to getClockPickerConfig: $clockId")
+        private fun failPickerConfig(settings: ClockSettings): ClockPickerConfig {
+            fail("Unexpected call to getClockPickerConfig: ${settings.clockId}")
             return null!!
         }
     }
 
-    private class FakeLifecycle(
-        private val tag: String,
-        private val plugin: ClockProviderPlugin?,
-    ) : PluginLifecycleManager<ClockProviderPlugin> {
+    private class FakeLifecycle(private val tag: String, private val plugin: ClockProviderPlugin?) :
+        PluginLifecycleManager<ClockProviderPlugin> {
         var onLoad: (() -> Unit)? = null
         var onUnload: (() -> Unit)? = null
 
         private var mIsLoaded: Boolean = true
+
         override fun isLoaded() = mIsLoaded
+
         override fun getPlugin(): ClockProviderPlugin? = if (isLoaded) plugin else null
 
         var mComponentName = ComponentName("Package[$tag]", "Class[$tag]")
+
         override fun toString() = "Manager[$tag]"
+
         override fun getPackage(): String = mComponentName.getPackageName()
+
         override fun getComponentName(): ComponentName = mComponentName
-        override fun setLogFunc(func: BiConsumer<String, String>) { }
+
+        override fun setLogFunc(func: BiConsumer<String, String>) {}
 
         override fun loadPlugin() {
             if (!mIsLoaded) {
@@ -122,7 +129,7 @@
     private class FakeClockPlugin : ClockProviderPlugin {
         private val metadata = mutableListOf<ClockMetadata>()
         private val createCallbacks = mutableMapOf<ClockId, (ClockId) -> ClockController>()
-        private val pickerConfigs = mutableMapOf<ClockId, (ClockId) -> ClockPickerConfig>()
+        private val pickerConfigs = mutableMapOf<ClockId, (ClockSettings) -> ClockPickerConfig>()
 
         override fun getClocks() = metadata
 
@@ -132,17 +139,17 @@
                 ?: throw NotImplementedError("No callback for '$clockId'")
         }
 
-        override fun getClockPickerConfig(clockId: ClockId): ClockPickerConfig {
-            return pickerConfigs[clockId]?.invoke(clockId)
-                ?: throw NotImplementedError("No picker config for '$clockId'")
+        override fun getClockPickerConfig(settings: ClockSettings): ClockPickerConfig {
+            return pickerConfigs[settings.clockId]?.invoke(settings)
+                ?: throw NotImplementedError("No picker config for '${settings.clockId}'")
         }
 
-        override fun initialize(buffers: ClockMessageBuffers?) { }
+        override fun initialize(buffers: ClockMessageBuffers?) {}
 
         fun addClock(
             id: ClockId,
             create: (ClockId) -> ClockController = ::failFactory,
-            getPickerConfig: (ClockId) -> ClockPickerConfig = ::failPickerConfig
+            getPickerConfig: (ClockSettings) -> ClockPickerConfig = ::failPickerConfig,
         ): FakeClockPlugin {
             metadata.add(ClockMetadata(id))
             createCallbacks[id] = create
@@ -158,29 +165,32 @@
         scope = TestScope(dispatcher)
         pickerConfig = ClockPickerConfig("CLOCK_ID", "NAME", "DESC", mockThumbnail)
 
-        fakeDefaultProvider = FakeClockPlugin()
-            .addClock(DEFAULT_CLOCK_ID, { mockDefaultClock }, { pickerConfig })
+        fakeDefaultProvider =
+            FakeClockPlugin().addClock(DEFAULT_CLOCK_ID, { mockDefaultClock }, { pickerConfig })
         whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
 
         val captor = argumentCaptor<PluginListener<ClockProviderPlugin>>()
-        registry = object : ClockRegistry(
-            mockContext,
-            mockPluginManager,
-            scope = scope.backgroundScope,
-            mainDispatcher = dispatcher,
-            bgDispatcher = dispatcher,
-            isEnabled = true,
-            handleAllUsers = true,
-            defaultClockProvider = fakeDefaultProvider,
-            keepAllLoaded = false,
-            subTag = "Test",
-            assert = mockThreadAssert,
-        ) {
-            override fun querySettings() { }
-            override fun applySettings(value: ClockSettings?) {
-                settings = value
+        registry =
+            object :
+                ClockRegistry(
+                    mockContext,
+                    mockPluginManager,
+                    scope = scope.backgroundScope,
+                    mainDispatcher = dispatcher,
+                    bgDispatcher = dispatcher,
+                    isEnabled = true,
+                    handleAllUsers = true,
+                    defaultClockProvider = fakeDefaultProvider,
+                    keepAllLoaded = false,
+                    subTag = "Test",
+                    assert = mockThreadAssert,
+                ) {
+                override fun querySettings() {}
+
+                override fun applySettings(value: ClockSettings?) {
+                    settings = value
+                }
             }
-        }
         registry.registerListeners()
 
         verify(mockPluginManager)
@@ -190,14 +200,10 @@
 
     @Test
     fun pluginRegistration_CorrectState() {
-        val plugin1 = FakeClockPlugin()
-            .addClock("clock_1")
-            .addClock("clock_2")
+        val plugin1 = FakeClockPlugin().addClock("clock_1").addClock("clock_2")
         val lifecycle1 = FakeLifecycle("1", plugin1)
 
-        val plugin2 = FakeClockPlugin()
-            .addClock("clock_3")
-            .addClock("clock_4")
+        val plugin2 = FakeClockPlugin().addClock("clock_3").addClock("clock_4")
         val lifecycle2 = FakeLifecycle("2", plugin2)
 
         pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1)
@@ -210,8 +216,8 @@
                 ClockMetadata("clock_1"),
                 ClockMetadata("clock_2"),
                 ClockMetadata("clock_3"),
-                ClockMetadata("clock_4")
-            )
+                ClockMetadata("clock_4"),
+            ),
         )
     }
 
@@ -223,14 +229,13 @@
 
     @Test
     fun clockIdConflict_ErrorWithoutCrash_unloadDuplicate() {
-        val plugin1 = FakeClockPlugin()
-            .addClock("clock_1", { mockClock }, { pickerConfig })
-            .addClock("clock_2", { mockClock }, { pickerConfig })
+        val plugin1 =
+            FakeClockPlugin()
+                .addClock("clock_1", { mockClock }, { pickerConfig })
+                .addClock("clock_2", { mockClock }, { pickerConfig })
         val lifecycle1 = spy(FakeLifecycle("1", plugin1))
 
-        val plugin2 = FakeClockPlugin()
-            .addClock("clock_1")
-            .addClock("clock_2")
+        val plugin2 = FakeClockPlugin().addClock("clock_1").addClock("clock_2")
         val lifecycle2 = spy(FakeLifecycle("2", plugin2))
 
         pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1)
@@ -241,8 +246,8 @@
             setOf(
                 ClockMetadata(DEFAULT_CLOCK_ID),
                 ClockMetadata("clock_1"),
-                ClockMetadata("clock_2")
-            )
+                ClockMetadata("clock_2"),
+            ),
         )
 
         assertEquals(registry.createExampleClock("clock_1"), mockClock)
@@ -255,14 +260,10 @@
 
     @Test
     fun createCurrentClock_pluginConnected() {
-        val plugin1 = FakeClockPlugin()
-            .addClock("clock_1")
-            .addClock("clock_2")
+        val plugin1 = FakeClockPlugin().addClock("clock_1").addClock("clock_2")
         val lifecycle1 = spy(FakeLifecycle("1", plugin1))
 
-        val plugin2 = FakeClockPlugin()
-            .addClock("clock_3", { mockClock })
-            .addClock("clock_4")
+        val plugin2 = FakeClockPlugin().addClock("clock_3", { mockClock }).addClock("clock_4")
         val lifecycle2 = spy(FakeLifecycle("2", plugin2))
 
         registry.applySettings(ClockSettings("clock_3", null))
@@ -275,14 +276,10 @@
 
     @Test
     fun activeClockId_changeAfterPluginConnected() {
-        val plugin1 = FakeClockPlugin()
-            .addClock("clock_1")
-            .addClock("clock_2")
+        val plugin1 = FakeClockPlugin().addClock("clock_1").addClock("clock_2")
         val lifecycle1 = spy(FakeLifecycle("1", plugin1))
 
-        val plugin2 = FakeClockPlugin()
-            .addClock("clock_3", { mockClock })
-            .addClock("clock_4")
+        val plugin2 = FakeClockPlugin().addClock("clock_3", { mockClock }).addClock("clock_4")
         val lifecycle2 = spy(FakeLifecycle("2", plugin2))
 
         registry.applySettings(ClockSettings("clock_3", null))
@@ -296,14 +293,10 @@
 
     @Test
     fun createDefaultClock_pluginDisconnected() {
-        val plugin1 = FakeClockPlugin()
-            .addClock("clock_1")
-            .addClock("clock_2")
+        val plugin1 = FakeClockPlugin().addClock("clock_1").addClock("clock_2")
         val lifecycle1 = spy(FakeLifecycle("1", plugin1))
 
-        val plugin2 = FakeClockPlugin()
-            .addClock("clock_3")
-            .addClock("clock_4")
+        val plugin2 = FakeClockPlugin().addClock("clock_3").addClock("clock_4")
         val lifecycle2 = spy(FakeLifecycle("2", plugin2))
 
         registry.applySettings(ClockSettings("clock_3", null))
@@ -317,22 +310,25 @@
 
     @Test
     fun pluginRemoved_clockAndListChanged() {
-        val plugin1 = FakeClockPlugin()
-            .addClock("clock_1")
-            .addClock("clock_2")
+        val plugin1 = FakeClockPlugin().addClock("clock_1").addClock("clock_2")
         val lifecycle1 = spy(FakeLifecycle("1", plugin1))
 
-        val plugin2 = FakeClockPlugin()
-            .addClock("clock_3", { mockClock })
-            .addClock("clock_4")
+        val plugin2 = FakeClockPlugin().addClock("clock_3", { mockClock }).addClock("clock_4")
         val lifecycle2 = spy(FakeLifecycle("2", plugin2))
 
         var changeCallCount = 0
         var listChangeCallCount = 0
-        registry.registerClockChangeListener(object : ClockRegistry.ClockChangeListener {
-            override fun onCurrentClockChanged() { changeCallCount++ }
-            override fun onAvailableClocksChanged() { listChangeCallCount++ }
-        })
+        registry.registerClockChangeListener(
+            object : ClockRegistry.ClockChangeListener {
+                override fun onCurrentClockChanged() {
+                    changeCallCount++
+                }
+
+                override fun onAvailableClocksChanged() {
+                    listChangeCallCount++
+                }
+            }
+        )
 
         registry.applySettings(ClockSettings("clock_3", null))
         scheduler.runCurrent()
@@ -372,16 +368,24 @@
 
     @Test
     fun unknownPluginAttached_clockAndListUnchanged_loadRequested() {
-        val lifecycle = FakeLifecycle("", null).apply {
-            mComponentName = ComponentName("some.other.package", "SomeClass")
-        }
+        val lifecycle =
+            FakeLifecycle("", null).apply {
+                mComponentName = ComponentName("some.other.package", "SomeClass")
+            }
 
         var changeCallCount = 0
         var listChangeCallCount = 0
-        registry.registerClockChangeListener(object : ClockRegistry.ClockChangeListener {
-            override fun onCurrentClockChanged() { changeCallCount++ }
-            override fun onAvailableClocksChanged() { listChangeCallCount++ }
-        })
+        registry.registerClockChangeListener(
+            object : ClockRegistry.ClockChangeListener {
+                override fun onCurrentClockChanged() {
+                    changeCallCount++
+                }
+
+                override fun onAvailableClocksChanged() {
+                    listChangeCallCount++
+                }
+            }
+        )
 
         assertEquals(true, pluginListener.onPluginAttached(lifecycle))
         scheduler.runCurrent()
@@ -391,22 +395,33 @@
 
     @Test
     fun knownPluginAttached_clockAndListChanged_loadedCurrent() {
-        val metroLifecycle = FakeLifecycle("Metro", null).apply {
-            mComponentName = ComponentName("com.android.systemui.clocks.metro", "Metro")
-        }
-        val bignumLifecycle = FakeLifecycle("BigNum", null).apply {
-            mComponentName = ComponentName("com.android.systemui.clocks.bignum", "BigNum")
-        }
-        val calligraphyLifecycle = FakeLifecycle("Calligraphy", null).apply {
-            mComponentName = ComponentName("com.android.systemui.clocks.calligraphy", "Calligraphy")
-        }
+        val metroLifecycle =
+            FakeLifecycle("Metro", null).apply {
+                mComponentName = ComponentName("com.android.systemui.clocks.metro", "Metro")
+            }
+        val bignumLifecycle =
+            FakeLifecycle("BigNum", null).apply {
+                mComponentName = ComponentName("com.android.systemui.clocks.bignum", "BigNum")
+            }
+        val calligraphyLifecycle =
+            FakeLifecycle("Calligraphy", null).apply {
+                mComponentName =
+                    ComponentName("com.android.systemui.clocks.calligraphy", "Calligraphy")
+            }
 
         var changeCallCount = 0
         var listChangeCallCount = 0
-        registry.registerClockChangeListener(object : ClockRegistry.ClockChangeListener {
-            override fun onCurrentClockChanged() { changeCallCount++ }
-            override fun onAvailableClocksChanged() { listChangeCallCount++ }
-        })
+        registry.registerClockChangeListener(
+            object : ClockRegistry.ClockChangeListener {
+                override fun onCurrentClockChanged() {
+                    changeCallCount++
+                }
+
+                override fun onAvailableClocksChanged() {
+                    listChangeCallCount++
+                }
+            }
+        )
 
         registry.applySettings(ClockSettings("DIGITAL_CLOCK_CALLIGRAPHY", null))
         scheduler.runCurrent()
@@ -466,67 +481,84 @@
         scheduler.runCurrent()
 
         // Verify all plugins were correctly loaded into the registry
-        assertEquals(registry.getClocks().toSet(), setOf(
-            ClockMetadata("DEFAULT"),
-            ClockMetadata("clock_2"),
-            ClockMetadata("clock_3"),
-            ClockMetadata("clock_4")
-        ))
+        assertEquals(
+            registry.getClocks().toSet(),
+            setOf(
+                ClockMetadata("DEFAULT"),
+                ClockMetadata("clock_2"),
+                ClockMetadata("clock_3"),
+                ClockMetadata("clock_4"),
+            ),
+        )
     }
 
     @Test
-    fun jsonDeserialization_gotExpectedObject() {
-        val expected = ClockSettings("ID", null).apply {
-            metadata.put("appliedTimestamp", 500)
-        }
-        val actual = ClockSettings.deserialize("""{
-            "clockId":"ID",
-            "metadata": {
-                "appliedTimestamp":500
-            }
-        }""")
+    fun jsonDeserialization() {
+        val expected = ClockSettings("ID").apply { metadata.put("appliedTimestamp", 50) }
+        val json = JSONObject("""{"clockId":"ID", "metadata": { "appliedTimestamp":50 } }""")
+        val actual = ClockSettings.fromJson(json)
         assertEquals(expected, actual)
     }
 
     @Test
-    fun jsonDeserialization_noTimestamp_gotExpectedObject() {
-        val expected = ClockSettings("ID", null)
-        val actual = ClockSettings.deserialize("{\"clockId\":\"ID\"}")
+    fun jsonDeserialization_noTimestamp() {
+        val expected = ClockSettings("ID")
+        val actual = ClockSettings.fromJson(JSONObject("""{"clockId":"ID"}"""))
         assertEquals(expected, actual)
     }
 
     @Test
-    fun jsonDeserialization_nullTimestamp_gotExpectedObject() {
-        val expected = ClockSettings("ID", null)
-        val actual = ClockSettings.deserialize("""{
-            "clockId":"ID",
-            "metadata":null
-        }""")
+    fun jsonDeserialization_nullTimestamp() {
+        val expected = ClockSettings("ID")
+        val actual = ClockSettings.fromJson(JSONObject("""{"clockId":"ID", "metadata":null}"""))
         assertEquals(expected, actual)
     }
 
     @Test
     fun jsonDeserialization_noId_deserializedEmpty() {
-        val expected = ClockSettings(null, null).apply {
-            metadata.put("appliedTimestamp", 500)
-        }
-        val actual = ClockSettings.deserialize("{\"metadata\":{\"appliedTimestamp\":500}}")
+        val expected = ClockSettings().apply { metadata.put("appliedTimestamp", 50) }
+        val actual = ClockSettings.fromJson(JSONObject("""{"metadata":{"appliedTimestamp":50}}"""))
         assertEquals(expected, actual)
     }
 
     @Test
-    fun jsonSerialization_gotExpectedString() {
-        val expected = "{\"clockId\":\"ID\",\"metadata\":{\"appliedTimestamp\":500}}"
-        val actual = ClockSettings.serialize(ClockSettings("ID", null).apply {
-            metadata.put("appliedTimestamp", 500)
-        })
+    fun jsonDeserialization_fontAxes() {
+        val expected = ClockSettings(axes = listOf(ClockFontAxisSetting("KEY", 10f)))
+        val json = JSONObject("""{"axes":[{"key":"KEY","value":10}]}""")
+        val actual = ClockSettings.fromJson(json)
         assertEquals(expected, actual)
     }
 
     @Test
-    fun jsonSerialization_noTimestamp_gotExpectedString() {
-        val expected = "{\"clockId\":\"ID\",\"metadata\":{}}"
-        val actual = ClockSettings.serialize(ClockSettings("ID", null))
-        assertEquals(expected, actual)
+    fun jsonSerialization() {
+        val expected =
+            JSONObject().apply {
+                put("clockId", "ID")
+                put("metadata", JSONObject().apply { put("appliedTimestamp", 50) })
+                put("axes", JSONArray())
+            }
+        val settings = ClockSettings("ID", null).apply { metadata.put("appliedTimestamp", 50) }
+        val actual = ClockSettings.toJson(settings)
+        assertEquals(expected.toString(), actual.toString())
+    }
+
+    @Test
+    fun jsonSerialization_noTimestamp() {
+        val expected =
+            JSONObject().apply {
+                put("clockId", "ID")
+                put("metadata", JSONObject())
+                put("axes", JSONArray())
+            }
+        val actual = ClockSettings.toJson(ClockSettings("ID", null))
+        assertEquals(expected.toString(), actual.toString())
+    }
+
+    @Test
+    fun jsonSerialization_axisSettings() {
+        val settings = ClockSettings(axes = listOf(ClockFontAxisSetting("KEY", 10f)))
+        val actual = ClockSettings.toJson(settings)
+        val expected = JSONObject("""{"metadata":{},"axes":[{"key":"KEY","value":10}]}""")
+        assertEquals(expected.toString(), actual.toString())
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index 7e86ff3..aa8b4f1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -89,7 +89,7 @@
         // All providers need to provide clocks & thumbnails for exposed clocks
         for (metadata in provider.getClocks()) {
             assertNotNull(provider.createClock(metadata.clockId))
-            assertNotNull(provider.getClockPickerConfig(metadata.clockId))
+            assertNotNull(provider.getClockPickerConfig(ClockSettings(metadata.clockId)))
         }
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
index ce9b3be..7842d75 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.concurrency.Execution
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.withArgCaptor
@@ -52,6 +53,10 @@
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 class CommunalSmartspaceControllerTest : SysuiTestCase() {
+    @Mock private lateinit var userTracker: UserTracker
+
+    @Mock private lateinit var userContextPrimary: Context
+
     @Mock private lateinit var smartspaceManager: SmartspaceManager
 
     @Mock private lateinit var execution: Execution
@@ -113,15 +118,18 @@
         MockitoAnnotations.initMocks(this)
         `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session)
 
+        `when`(userTracker.userContext).thenReturn(userContextPrimary)
+        `when`(userContextPrimary.getSystemService(SmartspaceManager::class.java))
+            .thenReturn(smartspaceManager)
+
         controller =
             CommunalSmartspaceController(
-                context,
-                smartspaceManager,
+                userTracker,
                 execution,
                 uiExecutor,
                 precondition,
                 Optional.of(targetFilter),
-                Optional.of(plugin)
+                Optional.of(plugin),
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
index e774aed..c83c82d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.smartspace.dagger.SmartspaceViewComponent
 import com.android.systemui.util.concurrency.Execution
 import com.android.systemui.util.mockito.any
@@ -46,66 +47,51 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito
+import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
-import org.mockito.Mockito.anyInt
 import org.mockito.MockitoAnnotations
-import org.mockito.Spy
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 class DreamSmartspaceControllerTest : SysuiTestCase() {
-    @Mock
-    private lateinit var smartspaceManager: SmartspaceManager
+    @Mock private lateinit var userTracker: UserTracker
 
-    @Mock
-    private lateinit var execution: Execution
+    @Mock private lateinit var userContextPrimary: Context
 
-    @Mock
-    private lateinit var uiExecutor: Executor
+    @Mock private lateinit var smartspaceManager: SmartspaceManager
 
-    @Mock
-    private lateinit var viewComponentFactory: SmartspaceViewComponent.Factory
+    @Mock private lateinit var execution: Execution
 
-    @Mock
-    private lateinit var viewComponent: SmartspaceViewComponent
+    @Mock private lateinit var uiExecutor: Executor
 
-    @Mock
-    private lateinit var weatherViewComponent: SmartspaceViewComponent
+    @Mock private lateinit var viewComponentFactory: SmartspaceViewComponent.Factory
 
-    private val weatherSmartspaceView: SmartspaceView by lazy {
-        Mockito.spy(TestView(context))
-    }
+    @Mock private lateinit var viewComponent: SmartspaceViewComponent
 
-    @Mock
-    private lateinit var targetFilter: SmartspaceTargetFilter
+    @Mock private lateinit var weatherViewComponent: SmartspaceViewComponent
 
-    @Mock
-    private lateinit var plugin: BcSmartspaceDataPlugin
+    private val weatherSmartspaceView: SmartspaceView by lazy { Mockito.spy(TestView(context)) }
 
-    @Mock
-    private lateinit var weatherPlugin: BcSmartspaceDataPlugin
+    @Mock private lateinit var targetFilter: SmartspaceTargetFilter
 
-    @Mock
-    private lateinit var precondition: SmartspacePrecondition
+    @Mock private lateinit var plugin: BcSmartspaceDataPlugin
 
-    private val smartspaceView: SmartspaceView by lazy {
-        Mockito.spy(TestView(context))
-    }
+    @Mock private lateinit var weatherPlugin: BcSmartspaceDataPlugin
 
-    @Mock
-    private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener
+    @Mock private lateinit var precondition: SmartspacePrecondition
 
-    @Mock
-    private lateinit var session: SmartspaceSession
+    private val smartspaceView: SmartspaceView by lazy { Mockito.spy(TestView(context)) }
+
+    @Mock private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener
+
+    @Mock private lateinit var session: SmartspaceSession
 
     private lateinit var controller: DreamSmartspaceController
 
     // TODO(b/272811280): Remove usage of real view
-    private val fakeParent by lazy {
-        FrameLayout(context)
-    }
+    private val fakeParent by lazy { FrameLayout(context) }
 
     /**
      * A class which implements SmartspaceView and extends View. This is mocked to provide the right
@@ -134,30 +120,44 @@
 
         override fun setMediaTarget(target: SmartspaceTarget?) {}
 
-        override fun getSelectedPage(): Int { return 0; }
+        override fun getSelectedPage(): Int {
+            return 0
+        }
 
-        override fun getCurrentCardTopPadding(): Int { return 0; }
+        override fun getCurrentCardTopPadding(): Int {
+            return 0
+        }
     }
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
         `when`(viewComponentFactory.create(any(), eq(plugin), any(), eq(null)))
-                .thenReturn(viewComponent)
+            .thenReturn(viewComponent)
         `when`(viewComponent.getView()).thenReturn(smartspaceView)
         `when`(viewComponentFactory.create(any(), eq(weatherPlugin), any(), any()))
             .thenReturn(weatherViewComponent)
         `when`(weatherViewComponent.getView()).thenReturn(weatherSmartspaceView)
         `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session)
 
-        controller = DreamSmartspaceController(context, smartspaceManager, execution, uiExecutor,
-                viewComponentFactory, precondition, Optional.of(targetFilter), Optional.of(plugin),
-        Optional.of(weatherPlugin))
+        `when`(userTracker.userContext).thenReturn(userContextPrimary)
+        `when`(userContextPrimary.getSystemService(SmartspaceManager::class.java))
+            .thenReturn(smartspaceManager)
+
+        controller =
+            DreamSmartspaceController(
+                userTracker,
+                execution,
+                uiExecutor,
+                viewComponentFactory,
+                precondition,
+                Optional.of(targetFilter),
+                Optional.of(plugin),
+                Optional.of(weatherPlugin),
+            )
     }
 
-    /**
-     * Ensures smartspace session begins on a listener only flow.
-     */
+    /** Ensures smartspace session begins on a listener only flow. */
     @Test
     fun testConnectOnListen() {
         `when`(precondition.conditionsMet()).thenReturn(true)
@@ -165,18 +165,18 @@
 
         verify(smartspaceManager).createSmartspaceSession(any())
 
-        var targetListener = withArgCaptor<SmartspaceSession.OnTargetsAvailableListener> {
-            verify(session).addOnTargetsAvailableListener(any(), capture())
-        }
+        var targetListener =
+            withArgCaptor<SmartspaceSession.OnTargetsAvailableListener> {
+                verify(session).addOnTargetsAvailableListener(any(), capture())
+            }
 
         `when`(targetFilter.filterSmartspaceTarget(any())).thenReturn(true)
 
         var target = Mockito.mock(SmartspaceTarget::class.java)
         targetListener.onTargetsAvailable(listOf(target))
 
-        var targets = withArgCaptor<List<SmartspaceTarget>> {
-            verify(plugin).onTargetsAvailable(capture())
-        }
+        var targets =
+            withArgCaptor<List<SmartspaceTarget>> { verify(plugin).onTargetsAvailable(capture()) }
 
         assertThat(targets.contains(target)).isTrue()
 
@@ -185,17 +185,16 @@
         verify(session).close()
     }
 
-    /**
-     * Ensures session begins when a view is attached.
-     */
+    /** Ensures session begins when a view is attached. */
     @Test
     fun testConnectOnViewCreate() {
         `when`(precondition.conditionsMet()).thenReturn(true)
         controller.buildAndConnectView(Mockito.mock(ViewGroup::class.java))
 
-        val stateChangeListener = withArgCaptor<View.OnAttachStateChangeListener> {
-            verify(viewComponentFactory).create(any(), eq(plugin), capture(), eq(null))
-        }
+        val stateChangeListener =
+            withArgCaptor<View.OnAttachStateChangeListener> {
+                verify(viewComponentFactory).create(any(), eq(plugin), capture(), eq(null))
+            }
 
         val mockView = Mockito.mock(TestView::class.java)
         `when`(precondition.conditionsMet()).thenReturn(true)
@@ -209,9 +208,7 @@
         verify(session).close()
     }
 
-    /**
-     * Ensures session is created when weather smartspace view is created and attached.
-     */
+    /** Ensures session is created when weather smartspace view is created and attached. */
     @Test
     fun testConnectOnWeatherViewCreate() {
         `when`(precondition.conditionsMet()).thenReturn(true)
@@ -223,8 +220,8 @@
 
         // Then weather view is created with custom view and the default weatherPlugin.getView
         // should not be called
-        verify(viewComponentFactory).create(eq(fakeParent), eq(weatherPlugin), any(),
-            eq(customView))
+        verify(viewComponentFactory)
+            .create(eq(fakeParent), eq(weatherPlugin), any(), eq(customView))
         verify(weatherPlugin, Mockito.never()).getView(fakeParent)
 
         // And then session is created
@@ -234,9 +231,7 @@
         verify(weatherSmartspaceView).setDozeAmount(0f)
     }
 
-    /**
-     * Ensures weather plugin registers target listener when it is added from the controller.
-     */
+    /** Ensures weather plugin registers target listener when it is added from the controller. */
     @Test
     fun testAddListenerInController_registersListenerForWeatherPlugin() {
         val customView = Mockito.mock(TestView::class.java)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/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 6e19096..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
@@ -66,27 +66,43 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.chips)
 
-            setNotifs(listOf(activeNotificationModel(key = "notif", statusBarChipIcon = null)))
+            setNotifs(
+                listOf(
+                    activeNotificationModel(
+                        key = "notif",
+                        statusBarChipIcon = null,
+                        isPromoted = true,
+                    )
+                )
+            )
 
             assertThat(latest).isEmpty()
         }
 
     @Test
-    fun chips_oneNotif_statusBarIconViewMatches() =
+    fun chips_onePromotedNotif_statusBarIconViewMatches() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chips)
 
             val icon = mock<StatusBarIconView>()
-            setNotifs(listOf(activeNotificationModel(key = "notif", statusBarChipIcon = icon)))
+            setNotifs(
+                listOf(
+                    activeNotificationModel(
+                        key = "notif",
+                        statusBarChipIcon = icon,
+                        isPromoted = true,
+                    )
+                )
+            )
 
             assertThat(latest).hasSize(1)
             val chip = latest!![0]
-            assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java)
+            assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
             assertThat(chip.icon).isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(icon))
         }
 
     @Test
-    fun chips_twoNotifs_twoChips() =
+    fun chips_onlyForPromotedNotifs() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chips)
 
@@ -94,8 +110,21 @@
             val secondIcon = mock<StatusBarIconView>()
             setNotifs(
                 listOf(
-                    activeNotificationModel(key = "notif1", statusBarChipIcon = firstIcon),
-                    activeNotificationModel(key = "notif2", statusBarChipIcon = secondIcon),
+                    activeNotificationModel(
+                        key = "notif1",
+                        statusBarChipIcon = firstIcon,
+                        isPromoted = true,
+                    ),
+                    activeNotificationModel(
+                        key = "notif2",
+                        statusBarChipIcon = secondIcon,
+                        isPromoted = true,
+                    ),
+                    activeNotificationModel(
+                        key = "notif3",
+                        statusBarChipIcon = mock<StatusBarIconView>(),
+                        isPromoted = false,
+                    ),
                 )
             )
 
@@ -118,6 +147,7 @@
                     activeNotificationModel(
                         key = "clickTest",
                         statusBarChipIcon = mock<StatusBarIconView>(),
+                        isPromoted = true,
                     )
                 )
             )
@@ -138,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/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
index b12d7c5..25d5ce5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
@@ -293,19 +293,27 @@
         }
 
     @Test
-    fun chips_singleNotifChip_primaryIsNotifSecondaryIsHidden() =
+    fun chips_singlePromotedNotif_primaryIsNotifSecondaryIsHidden() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chips)
 
             val icon = mock<StatusBarIconView>()
-            setNotifs(listOf(activeNotificationModel(key = "notif", statusBarChipIcon = icon)))
+            setNotifs(
+                listOf(
+                    activeNotificationModel(
+                        key = "notif",
+                        statusBarChipIcon = icon,
+                        isPromoted = true,
+                    )
+                )
+            )
 
             assertIsNotifChip(latest!!.primary, icon)
             assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
         }
 
     @Test
-    fun chips_twoNotifChips_primaryAndSecondaryAreNotifsInOrder() =
+    fun chips_twoPromotedNotifs_primaryAndSecondaryAreNotifsInOrder() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chips)
 
@@ -313,8 +321,16 @@
             val secondIcon = mock<StatusBarIconView>()
             setNotifs(
                 listOf(
-                    activeNotificationModel(key = "firstNotif", statusBarChipIcon = firstIcon),
-                    activeNotificationModel(key = "secondNotif", statusBarChipIcon = secondIcon),
+                    activeNotificationModel(
+                        key = "firstNotif",
+                        statusBarChipIcon = firstIcon,
+                        isPromoted = true,
+                    ),
+                    activeNotificationModel(
+                        key = "secondNotif",
+                        statusBarChipIcon = secondIcon,
+                        isPromoted = true,
+                    ),
                 )
             )
 
@@ -323,7 +339,7 @@
         }
 
     @Test
-    fun chips_threeNotifChips_topTwoShown() =
+    fun chips_threePromotedNotifs_topTwoShown() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chips)
 
@@ -332,9 +348,21 @@
             val thirdIcon = mock<StatusBarIconView>()
             setNotifs(
                 listOf(
-                    activeNotificationModel(key = "firstNotif", statusBarChipIcon = firstIcon),
-                    activeNotificationModel(key = "secondNotif", statusBarChipIcon = secondIcon),
-                    activeNotificationModel(key = "thirdNotif", statusBarChipIcon = thirdIcon),
+                    activeNotificationModel(
+                        key = "firstNotif",
+                        statusBarChipIcon = firstIcon,
+                        isPromoted = true,
+                    ),
+                    activeNotificationModel(
+                        key = "secondNotif",
+                        statusBarChipIcon = secondIcon,
+                        isPromoted = true,
+                    ),
+                    activeNotificationModel(
+                        key = "thirdNotif",
+                        statusBarChipIcon = thirdIcon,
+                        isPromoted = true,
+                    ),
                 )
             )
 
@@ -343,7 +371,7 @@
         }
 
     @Test
-    fun chips_callAndNotifs_primaryIsCallSecondaryIsNotif() =
+    fun chips_callAndPromotedNotifs_primaryIsCallSecondaryIsNotif() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chips)
 
@@ -351,10 +379,15 @@
             val firstIcon = mock<StatusBarIconView>()
             setNotifs(
                 listOf(
-                    activeNotificationModel(key = "firstNotif", statusBarChipIcon = firstIcon),
+                    activeNotificationModel(
+                        key = "firstNotif",
+                        statusBarChipIcon = firstIcon,
+                        isPromoted = true,
+                    ),
                     activeNotificationModel(
                         key = "secondNotif",
                         statusBarChipIcon = mock<StatusBarIconView>(),
+                        isPromoted = true,
                     ),
                 )
             )
@@ -364,7 +397,7 @@
         }
 
     @Test
-    fun chips_screenRecordAndCallAndNotifs_notifsNotShown() =
+    fun chips_screenRecordAndCallAndPromotedNotifs_notifsNotShown() =
         testScope.runTest {
             val latest by collectLastValue(underTest.chips)
 
@@ -375,6 +408,7 @@
                     activeNotificationModel(
                         key = "notif",
                         statusBarChipIcon = mock<StatusBarIconView>(),
+                        isPromoted = true,
                     )
                 )
             )
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
index 2a196c6..1b3f29a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
@@ -32,7 +32,7 @@
 import org.junit.runner.RunWith
 import org.mockito.kotlin.verify
 
-@EnableFlags(StatusBarSimpleFragment.FLAG_NAME)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class CommandQueueInitializerTest : SysuiTestCase() {
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 8dcc444..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
@@ -17,19 +17,24 @@
 package com.android.systemui.statusbar.core
 
 import android.platform.test.annotations.EnableFlags
+import android.view.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.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
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.never
 import org.mockito.kotlin.verify
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -39,16 +44,13 @@
 class MultiDisplayStatusBarStarterTest : SysuiTestCase() {
     @get:Rule val expect: Expect = Expect.create()
 
-    private val kosmos =
-        testKosmos().also {
-            it.statusBarOrchestratorFactory = it.fakeStatusBarOrchestratorFactory
-            it.statusBarInitializerStore = it.fakeStatusBarInitializerStore
-        }
+    private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val fakeDisplayRepository = kosmos.displayRepository
     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 }
 
@@ -83,6 +85,56 @@
         }
 
     @Test
+    fun start_startsPrivacyDotForCurrentDisplays() =
+        testScope.runTest {
+            fakeDisplayRepository.addDisplay(displayId = 1)
+            fakeDisplayRepository.addDisplay(displayId = 2)
+
+            underTest.start()
+            runCurrent()
+
+            verify(fakePrivacyDotStore.forDisplay(displayId = 1)).start()
+            verify(fakePrivacyDotStore.forDisplay(displayId = 2)).start()
+        }
+
+    @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)
+
+            underTest.start()
+            runCurrent()
+
+            verify(fakePrivacyDotStore.forDisplay(displayId = Display.DEFAULT_DISPLAY), never())
+                .start()
+        }
+
+    @Test
     fun displayAdded_orchestratorForNewDisplayIsStarted() =
         testScope.runTest {
             underTest.start()
@@ -109,6 +161,42 @@
         }
 
     @Test
+    fun displayAdded_privacyDotForNewDisplayIsStarted() =
+        testScope.runTest {
+            underTest.start()
+            runCurrent()
+
+            fakeDisplayRepository.addDisplay(displayId = 3)
+            runCurrent()
+
+            verify(fakePrivacyDotStore.forDisplay(displayId = 3)).start()
+        }
+
+    @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()
@@ -129,8 +217,39 @@
             fakeDisplayRepository.addDisplay(displayId = 3)
             runCurrent()
 
-            expect
-                .that(fakeInitializerStore.forDisplay(displayId = 3).startedByCoreStartable)
-                .isTrue()
+            verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 3)!!).start()
+        }
+
+    @Test
+    fun displayAddedDuringStart_privacyDotForNewDisplayIsStarted() =
+        testScope.runTest {
+            underTest.start()
+
+            fakeDisplayRepository.addDisplay(displayId = 3)
+            runCurrent()
+
+            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/core/StatusBarInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
index 20a19a9..938da88 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
@@ -26,11 +26,14 @@
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.fragments.FragmentHostManager
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
 import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
 import com.android.systemui.statusbar.pipeline.shared.ui.composable.StatusBarRootFactory
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
 import org.junit.Assert.assertThrows
@@ -45,12 +48,14 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class StatusBarInitializerTest : SysuiTestCase() {
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
     private val windowController = mock(StatusBarWindowController::class.java)
     private val windowControllerStore = mock(StatusBarWindowControllerStore::class.java)
     private val transaction = mock(FragmentTransaction::class.java)
     private val fragmentManager = mock(FragmentManager::class.java)
     private val fragmentHostManager = mock(FragmentHostManager::class.java)
     private val backgroundView = mock(ViewGroup::class.java)
+    private val statusBarModePerDisplayRepository = kosmos.fakeStatusBarModePerDisplayRepository
 
     @Before
     fun setup() {
@@ -72,6 +77,7 @@
             statusBarRootFactory = mock(StatusBarRootFactory::class.java),
             componentFactory = mock(HomeStatusBarComponent.Factory::class.java),
             creationListeners = setOf(),
+            statusBarModePerDisplayRepository = statusBarModePerDisplayRepository,
         )
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt
new file mode 100644
index 0000000..18eef33
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LightBarControllerStoreImplTest : SysuiTestCase() {
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+    private val testScope = kosmos.testScope
+    private val fakeDisplayRepository = kosmos.displayRepository
+
+    private val underTest = kosmos.lightBarControllerStoreImpl
+
+    @Before
+    fun start() {
+        underTest.start()
+    }
+
+    @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
+
+    @Test
+    fun forDisplay_startsInstance() =
+        testScope.runTest {
+            val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+            verify(instance).start()
+        }
+
+    @Test
+    fun beforeDisplayRemoved_doesNotStopInstances() =
+        testScope.runTest {
+            val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+            verify(instance, never()).stop()
+        }
+
+    @Test
+    fun displayRemoved_stopsInstance() =
+        testScope.runTest {
+            val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+            fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+
+            verify(instance).stop()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/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/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt
new file mode 100644
index 0000000..a9920ec5
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import android.platform.test.annotations.EnableFlags
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class MultiDisplayStatusBarModeRepositoryStoreTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+    private val testScope = kosmos.testScope
+    private val fakeDisplayRepository = kosmos.displayRepository
+    private val underTest by lazy { kosmos.multiDisplayStatusBarModeRepositoryStore }
+
+    @Before
+    fun start() {
+        underTest.start()
+    }
+
+    @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
+
+    @Test
+    fun forDisplay_startsInstance() =
+        testScope.runTest {
+            val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+            verify(instance).start()
+        }
+
+    @Test
+    fun displayRemoved_stopsInstance() =
+        testScope.runTest {
+            val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+            fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+
+            verify(instance).stop()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt
new file mode 100644
index 0000000..ae734b3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.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.statusbar.data.repository
+
+import android.platform.test.annotations.EnableFlags
+import android.view.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.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class PrivacyDotWindowControllerStoreImplTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val underTest by lazy { kosmos.privacyDotWindowControllerStoreImpl }
+
+    @Before
+    fun installDisplays() = runBlocking {
+        kosmos.displayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY)
+        kosmos.displayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY + 1)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun forDisplay_defaultDisplay_throws() {
+        underTest.forDisplay(displayId = Display.DEFAULT_DISPLAY)
+    }
+
+    @Test
+    fun forDisplay_nonDefaultDisplay_doesNotThrow() {
+        underTest.forDisplay(displayId = Display.DEFAULT_DISPLAY + 1)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt
index f7a8858..3b720ef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt
@@ -50,7 +50,7 @@
         MockitoAnnotations.initMocks(this)
 
         testScope = TestScope()
-        underTest = RemoteInputRepositoryImpl(remoteInputManager)
+        underTest = RemoteInputRepositoryImpl(testScope.backgroundScope, remoteInputManager)
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt
new file mode 100644
index 0000000..e65c04c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.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 SystemEventChipAnimationControllerStoreImplTest : 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.systemEventChipAnimationControllerStoreImpl }
+
+    @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/events/MultiDisplaySystemEventChipAnimationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationControllerTest.kt
new file mode 100644
index 0000000..d4007d7
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationControllerTest.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.events
+
+import android.platform.test.annotations.EnableFlags
+import androidx.core.animation.AnimatorSet
+import androidx.core.animation.ValueAnimator
+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.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.data.repository.systemEventChipAnimationControllerStore
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class MultiDisplaySystemEventChipAnimationControllerTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val displayRepository = kosmos.displayRepository
+    private val store = kosmos.systemEventChipAnimationControllerStore
+
+    // Lazy so that @EnableFlags has time to switch the flags before the instance is created.
+    private val underTest by lazy { kosmos.multiDisplaySystemEventChipAnimationController }
+
+    @Before
+    fun installDisplays() = runBlocking {
+        INSTALLED_DISPLAY_IDS.forEach { displayRepository.addDisplay(displayId = it) }
+    }
+
+    @Test
+    fun init_forwardsToAllControllers() {
+        underTest.init()
+
+        INSTALLED_DISPLAY_IDS.forEach { verify(store.forDisplay(it)).init() }
+    }
+
+    @Test
+    fun stop_forwardsToAllControllers() {
+        underTest.stop()
+
+        INSTALLED_DISPLAY_IDS.forEach { verify(store.forDisplay(it)).stop() }
+    }
+
+    @Test
+    fun announceForAccessibility_forwardsToAllControllers() {
+        val contentDescription = "test content description"
+        underTest.announceForAccessibility(contentDescription)
+
+        INSTALLED_DISPLAY_IDS.forEach {
+            verify(store.forDisplay(it)).announceForAccessibility(contentDescription)
+        }
+    }
+
+    @Test
+    fun onSystemEventAnimationBegin_returnsAnimatorSetWithOneAnimatorPerDisplay() {
+        INSTALLED_DISPLAY_IDS.forEach {
+            val controller = store.forDisplay(it)
+            whenever(controller.onSystemEventAnimationBegin()).thenReturn(ValueAnimator.ofInt(0, 1))
+        }
+        val animator = underTest.onSystemEventAnimationBegin() as AnimatorSet
+
+        assertThat(animator.childAnimations).hasSize(INSTALLED_DISPLAY_IDS.size)
+    }
+
+    @Test
+    fun onSystemEventAnimationFinish_returnsAnimatorSetWithOneAnimatorPerDisplay() {
+        INSTALLED_DISPLAY_IDS.forEach {
+            val controller = store.forDisplay(it)
+            whenever(controller.onSystemEventAnimationFinish(any()))
+                .thenReturn(ValueAnimator.ofInt(0, 1))
+        }
+        val animator =
+            underTest.onSystemEventAnimationFinish(hasPersistentDot = true) as AnimatorSet
+
+        assertThat(animator.childAnimations).hasSize(INSTALLED_DISPLAY_IDS.size)
+    }
+
+    companion object {
+        private const val DISPLAY_ID_1 = 123
+        private const val DISPLAY_ID_2 = 456
+        private val INSTALLED_DISPLAY_IDS = listOf(DISPLAY_ID_1, DISPLAY_ID_2)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerTest.kt
new file mode 100644
index 0000000..6bcd735
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerTest.kt
@@ -0,0 +1,185 @@
+/*
+ * 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.events
+
+import android.view.Gravity.BOTTOM
+import android.view.Gravity.LEFT
+import android.view.Gravity.RIGHT
+import android.view.Gravity.TOP
+import android.view.Surface
+import android.view.View
+import android.view.WindowManager
+import android.view.fakeWindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.truth.Expect
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PrivacyDotWindowControllerTest : SysuiTestCase() {
+
+    @get:Rule val expect: Expect = Expect.create()
+
+    private val kosmos = testKosmos()
+    private val underTest = kosmos.privacyDotWindowController
+    private val viewController = kosmos.privacyDotViewController
+    private val windowManager = kosmos.fakeWindowManager
+    private val executor = kosmos.fakeExecutor
+
+    @After
+    fun cleanUpCustomDisplay() {
+        context.display = null
+    }
+
+    @Test
+    fun start_beforeUiThreadExecutes_doesNotAddWindows() {
+        underTest.start()
+
+        assertThat(windowManager.addedViews).isEmpty()
+    }
+
+    @Test
+    fun start_beforeUiThreadExecutes_doesNotInitializeViewController() {
+        underTest.start()
+
+        assertThat(viewController.isInitialized).isFalse()
+    }
+
+    @Test
+    fun start_afterUiThreadExecutes_addsWindowsOnUiThread() {
+        underTest.start()
+
+        executor.runAllReady()
+
+        assertThat(windowManager.addedViews).hasSize(4)
+    }
+
+    @Test
+    fun start_afterUiThreadExecutes_initializesViewController() {
+        underTest.start()
+
+        executor.runAllReady()
+
+        assertThat(viewController.isInitialized).isTrue()
+    }
+
+    @Test
+    fun start_initializesTopLeft() {
+        underTest.start()
+        executor.runAllReady()
+
+        assertThat(viewController.topLeft?.id).isEqualTo(R.id.privacy_dot_top_left_container)
+    }
+
+    @Test
+    fun start_initializesTopRight() {
+        underTest.start()
+        executor.runAllReady()
+
+        assertThat(viewController.topRight?.id).isEqualTo(R.id.privacy_dot_top_right_container)
+    }
+
+    @Test
+    fun start_initializesTopBottomLeft() {
+        underTest.start()
+        executor.runAllReady()
+
+        assertThat(viewController.bottomLeft?.id).isEqualTo(R.id.privacy_dot_bottom_left_container)
+    }
+
+    @Test
+    fun start_initializesBottomRight() {
+        underTest.start()
+        executor.runAllReady()
+
+        assertThat(viewController.bottomRight?.id)
+            .isEqualTo(R.id.privacy_dot_bottom_right_container)
+    }
+
+    @Test
+    fun start_viewsAddedInRespectiveCorners() {
+        context.display = mock { on { rotation } doReturn Surface.ROTATION_0 }
+
+        underTest.start()
+        executor.runAllReady()
+
+        expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(TOP or LEFT)
+        expect.that(gravityForView(viewController.topRight!!)).isEqualTo(TOP or RIGHT)
+        expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(BOTTOM or LEFT)
+        expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(BOTTOM or RIGHT)
+    }
+
+    @Test
+    fun start_rotation90_viewsPositionIsShifted90degrees() {
+        context.display = mock { on { rotation } doReturn Surface.ROTATION_90 }
+
+        underTest.start()
+        executor.runAllReady()
+
+        expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(BOTTOM or LEFT)
+        expect.that(gravityForView(viewController.topRight!!)).isEqualTo(TOP or LEFT)
+        expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(BOTTOM or RIGHT)
+        expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(TOP or RIGHT)
+    }
+
+    @Test
+    fun start_rotation180_viewsPositionIsShifted180degrees() {
+        context.display = mock { on { rotation } doReturn Surface.ROTATION_180 }
+
+        underTest.start()
+        executor.runAllReady()
+
+        expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(BOTTOM or RIGHT)
+        expect.that(gravityForView(viewController.topRight!!)).isEqualTo(BOTTOM or LEFT)
+        expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(TOP or RIGHT)
+        expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(TOP or LEFT)
+    }
+
+    @Test
+    fun start_rotation270_viewsPositionIsShifted270degrees() {
+        context.display = mock { on { rotation } doReturn Surface.ROTATION_270 }
+
+        underTest.start()
+        executor.runAllReady()
+
+        expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(TOP or RIGHT)
+        expect.that(gravityForView(viewController.topRight!!)).isEqualTo(BOTTOM or RIGHT)
+        expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(TOP or LEFT)
+        expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(BOTTOM or LEFT)
+    }
+
+    private fun paramsForView(view: View): WindowManager.LayoutParams {
+        return windowManager.addedViews.entries
+            .first { it.key == view || it.key.findViewById<View>(view.id) != null }
+            .value
+    }
+
+    private fun gravityForView(view: View): Int {
+        return paramsForView(view).gravity
+    }
+}
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/NotificationScrimNestedScrollConnectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt
index 75479ad..60b95ad 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification
 
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.ScrollScope
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.UserInput
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -35,6 +37,13 @@
     private var scrimOffset = 0f
     private var contentHeight = 0f
     private var isCurrentGestureOverscroll = false
+    private val customFlingBehavior =
+        object : FlingBehavior {
+            override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
+                scrollBy(initialVelocity)
+                return initialVelocity / 2f
+            }
+        }
 
     private val scrollConnection =
         NotificationScrimNestedScrollConnection(
@@ -51,6 +60,7 @@
                 wasStarted = true
                 isStarted = false
             },
+            flingBehavior = customFlingBehavior,
         )
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
index 9f40f60..99bda85 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
@@ -18,11 +18,14 @@
 
 package com.android.systemui.statusbar.notification.domain.interactor
 
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.notification.collection.render.NotifStats
 import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
@@ -44,7 +47,7 @@
     private val testScope = kosmos.testScope
     private val activeNotificationListRepository = kosmos.activeNotificationListRepository
 
-    private val underTest = kosmos.activeNotificationsInteractor
+    private val underTest by lazy { kosmos.activeNotificationsInteractor }
 
     @Test
     fun testAllNotificationsCount() =
@@ -65,14 +68,8 @@
 
             val normalNotifs =
                 listOf(
-                    activeNotificationModel(
-                        key = "notif1",
-                        callType = CallType.None,
-                    ),
-                    activeNotificationModel(
-                        key = "notif2",
-                        callType = CallType.None,
-                    )
+                    activeNotificationModel(key = "notif1", callType = CallType.None),
+                    activeNotificationModel(key = "notif2", callType = CallType.None),
                 )
 
             activeNotificationListRepository.activeNotifications.value =
@@ -129,10 +126,7 @@
             val latest by collectLastValue(underTest.ongoingCallNotification)
 
             val ongoingNotif =
-                activeNotificationModel(
-                    key = "ongoingNotif",
-                    callType = CallType.Ongoing,
-                )
+                activeNotificationModel(key = "ongoingNotif", callType = CallType.Ongoing)
 
             activeNotificationListRepository.activeNotifications.value =
                 ActiveNotificationsStore.Builder()
@@ -170,6 +164,62 @@
         }
 
     @Test
+    @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+    fun promotedOngoingNotifications_flagOff_empty() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.promotedOngoingNotifications)
+
+            val promoted1 = activeNotificationModel(key = "notif1", isPromoted = true)
+            val notPromoted2 = activeNotificationModel(key = "notif2", isPromoted = false)
+
+            activeNotificationListRepository.activeNotifications.value =
+                ActiveNotificationsStore.Builder()
+                    .apply { addIndividualNotif(promoted1) }
+                    .apply { addIndividualNotif(notPromoted2) }
+                    .build()
+
+            assertThat(latest!!).isEmpty()
+        }
+
+    @Test
+    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+    fun promotedOngoingNotifications_nonePromoted_empty() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.promotedOngoingNotifications)
+
+            activeNotificationListRepository.activeNotifications.value =
+                ActiveNotificationsStore.Builder()
+                    .apply { activeNotificationModel(key = "notif1", isPromoted = false) }
+                    .apply { activeNotificationModel(key = "notif2", isPromoted = false) }
+                    .apply { activeNotificationModel(key = "notif3", isPromoted = false) }
+                    .build()
+
+            assertThat(latest!!).isEmpty()
+        }
+
+    @Test
+    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+    fun promotedOngoingNotifications_somePromoted_hasOnlyPromoted() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.promotedOngoingNotifications)
+
+            val promoted1 = activeNotificationModel(key = "notif1", isPromoted = true)
+            val notPromoted2 = activeNotificationModel(key = "notif2", isPromoted = false)
+            val notPromoted3 = activeNotificationModel(key = "notif3", isPromoted = false)
+            val promoted4 = activeNotificationModel(key = "notif4", isPromoted = true)
+
+            activeNotificationListRepository.activeNotifications.value =
+                ActiveNotificationsStore.Builder()
+                    .apply { addIndividualNotif(promoted1) }
+                    .apply { addIndividualNotif(notPromoted2) }
+                    .apply { addIndividualNotif(notPromoted3) }
+                    .apply { addIndividualNotif(promoted4) }
+                    .build()
+
+            assertThat(latest!!).containsExactly(promoted1, promoted4)
+        }
+
+    @Test
     fun areAnyNotificationsPresent_isTrue() =
         testScope.runTest {
             val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
index 572a0c1..183f901 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
@@ -16,22 +16,25 @@
 package com.android.systemui.statusbar.notification.domain.interactor
 
 import android.app.Notification
-import android.os.Bundle
+import android.app.Notification.FLAG_PROMOTED_ONGOING
+import android.platform.test.annotations.EnableFlags
 import android.service.notification.StatusBarNotification
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.statusbar.RankingBuilder
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
+import com.android.systemui.statusbar.notification.promoted.promotedNotificationsProvider
 import com.android.systemui.statusbar.notification.shared.byKey
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -39,16 +42,16 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class RenderNotificationsListInteractorTest : SysuiTestCase() {
-    private val backgroundDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(backgroundDispatcher)
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
-    private val notifsRepository = ActiveNotificationListRepository()
-    private val notifsInteractor =
-        ActiveNotificationsInteractor(notifsRepository, backgroundDispatcher)
+    private val notifsRepository = kosmos.activeNotificationListRepository
+    private val notifsInteractor = kosmos.activeNotificationsInteractor
     private val underTest =
         RenderNotificationListInteractor(
             notifsRepository,
             sectionStyleProvider = mock(),
+            promotedNotificationsProvider = kosmos.promotedNotificationsProvider,
         )
 
     @Test
@@ -85,12 +88,7 @@
 
             assertThat(ranks)
                 .containsExactlyEntriesIn(
-                    mapOf(
-                        "single" to 0,
-                        "summary" to 1,
-                        "child0" to 2,
-                        "child1" to 3,
-                    )
+                    mapOf("single" to 0, "summary" to 1, "child0" to 2, "child1" to 3)
                 )
         }
 
@@ -126,6 +124,53 @@
 
             assertThat(actual).containsAtLeastEntriesIn(expected)
         }
+
+    @Test
+    @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+    fun setRenderList_setsPromotionStatus() =
+        testScope.runTest {
+            val actual by collectLastValue(notifsInteractor.topLevelRepresentativeNotifications)
+
+            val notPromoted1 = mockNotificationEntry("key1", flag = null)
+            val promoted2 = mockNotificationEntry("key2", flag = FLAG_PROMOTED_ONGOING)
+
+            underTest.setRenderedList(listOf(notPromoted1, promoted2))
+
+            assertThat(actual!!.size).isEqualTo(2)
+
+            val first = actual!![0]
+            assertThat(first.key).isEqualTo("key1")
+            assertThat(first.isPromoted).isFalse()
+
+            val second = actual!![1]
+            assertThat(second.key).isEqualTo("key2")
+            assertThat(second.isPromoted).isTrue()
+        }
+
+    private fun mockNotificationEntry(
+        key: String,
+        rank: Int = 0,
+        flag: Int? = null,
+    ): NotificationEntry {
+        val nBuilder = Notification.Builder(context, "a")
+        if (flag != null) {
+            nBuilder.setFlag(flag, true)
+        }
+        val notification = nBuilder.build()
+
+        val mockSbn =
+            mock<StatusBarNotification>() {
+                whenever(this.notification).thenReturn(notification)
+                whenever(packageName).thenReturn("com.android")
+            }
+        return mock<NotificationEntry> {
+            whenever(this.key).thenReturn(key)
+            whenever(this.icons).thenReturn(mock())
+            whenever(this.representativeEntry).thenReturn(this)
+            whenever(this.ranking).thenReturn(RankingBuilder().setRank(rank).build())
+            whenever(this.sbn).thenReturn(mockSbn)
+        }
+    }
 }
 
 private fun mockGroupEntry(
@@ -139,19 +184,3 @@
         whenever(this.children).thenReturn(children)
     }
 }
-
-private fun mockNotificationEntry(key: String, rank: Int = 0): NotificationEntry {
-    val mockNotification = mock<Notification> { this.extras = Bundle() }
-    val mockSbn =
-        mock<StatusBarNotification>() {
-            whenever(notification).thenReturn(mockNotification)
-            whenever(packageName).thenReturn("com.android")
-        }
-    return mock<NotificationEntry> {
-        whenever(this.key).thenReturn(key)
-        whenever(this.icons).thenReturn(mock())
-        whenever(this.representativeEntry).thenReturn(this)
-        whenever(this.ranking).thenReturn(RankingBuilder().setRank(rank).build())
-        whenever(this.sbn).thenReturn(mockSbn)
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/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/promoted/PromotedNotificationsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderTest.kt
new file mode 100644
index 0000000..a9dbe63
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted
+
+import android.app.Notification
+import android.app.Notification.FLAG_PROMOTED_ONGOING
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+
+@SmallTest
+class PromotedNotificationsProviderTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
+    private val underTest = kosmos.promotedNotificationsProvider
+
+    @Test
+    @DisableFlags(PromotedNotificationUi.FLAG_NAME)
+    fun shouldPromote_uiFlagOff_false() {
+        val entry = createNotification(FLAG_PROMOTED_ONGOING)
+
+        assertThat(underTest.shouldPromote(entry)).isFalse()
+    }
+
+    @Test
+    @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+    fun shouldPromote_uiFlagOn_notifDoesNotHaveFlag_false() {
+        val entry = createNotification(flag = null)
+
+        assertThat(underTest.shouldPromote(entry)).isFalse()
+    }
+
+    @Test
+    @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+    fun shouldPromote_uiFlagOn_notifHasFlag_true() {
+        val entry = createNotification(FLAG_PROMOTED_ONGOING)
+
+        assertThat(underTest.shouldPromote(entry)).isTrue()
+    }
+
+    private fun createNotification(flag: Int? = null): NotificationEntry {
+        val n = Notification.Builder(context, "a")
+        if (flag != null) {
+            n.setFlag(flag, true)
+        }
+
+        return NotificationEntryBuilder().setNotification(n.build()).build()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
new file mode 100644
index 0000000..a1b63b1
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
@@ -0,0 +1,648 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.row
+
+import android.R
+import android.app.AppOpsManager
+import android.app.INotificationManager
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Intent
+import android.content.pm.LauncherApps
+import android.content.pm.PackageManager
+import android.content.pm.ShortcutManager
+import android.graphics.Color
+import android.os.Binder
+import android.os.UserManager
+import android.os.fakeExecutorHandler
+import android.platform.test.flag.junit.FlagsParameterization
+import android.provider.Settings
+import android.service.notification.NotificationListenerService
+import android.testing.TestableLooper.RunWithLooper
+import android.util.ArraySet
+import android.view.View
+import android.view.accessibility.AccessibilityManager
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.statusbar.IStatusBarService
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.PowerInteractorFactory.create
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.scene.data.repository.setSceneTransition
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.statusbar.NotificationEntryHelper
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.AssistantFeedbackController
+import com.android.systemui.statusbar.notification.NotificationActivityStarter
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.testKosmos
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.wmshell.BubblesManager
+import java.util.Optional
+import kotlin.test.assertNotNull
+import kotlin.test.fail
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.invocation.InvocationOnMock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+/** Tests for [NotificationGutsManager]. */
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+@RunWithLooper
+class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase() {
+    private val testNotificationChannel =
+        NotificationChannel(
+            TEST_CHANNEL_ID,
+            TEST_CHANNEL_ID,
+            NotificationManager.IMPORTANCE_DEFAULT,
+        )
+
+    private val kosmos = testKosmos()
+
+    private val testScope = kosmos.testScope
+    private val javaAdapter = JavaAdapter(testScope.backgroundScope)
+    private val executor = kosmos.fakeExecutor
+    private val handler = kosmos.fakeExecutorHandler
+    private lateinit var helper: NotificationTestHelper
+    private lateinit var gutsManager: NotificationGutsManager
+
+    @get:Rule val rule: MockitoRule = MockitoJUnit.rule()
+
+    @Mock private lateinit var metricsLogger: MetricsLogger
+    @Mock private lateinit var onUserInteractionCallback: OnUserInteractionCallback
+    @Mock private lateinit var presenter: NotificationPresenter
+    @Mock private lateinit var notificationActivityStarter: NotificationActivityStarter
+    @Mock private lateinit var notificationListContainer: NotificationListContainer
+    @Mock
+    private lateinit var onSettingsClickListener: NotificationGutsManager.OnSettingsClickListener
+    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock private lateinit var accessibilityManager: AccessibilityManager
+    @Mock private lateinit var highPriorityProvider: HighPriorityProvider
+    @Mock private lateinit var iNotificationManager: INotificationManager
+    @Mock private lateinit var barService: IStatusBarService
+    @Mock private lateinit var launcherApps: LauncherApps
+    @Mock private lateinit var shortcutManager: ShortcutManager
+    @Mock private lateinit var channelEditorDialogController: ChannelEditorDialogController
+    @Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier
+    @Mock private lateinit var contextTracker: UserContextProvider
+    @Mock private lateinit var bubblesManager: BubblesManager
+    @Mock private lateinit var shadeController: ShadeController
+    @Mock private lateinit var peopleSpaceWidgetManager: PeopleSpaceWidgetManager
+    @Mock private lateinit var assistantFeedbackController: AssistantFeedbackController
+    @Mock private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var headsUpManager: HeadsUpManager
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var userManager: UserManager
+
+    private lateinit var windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor
+
+    companion object {
+        private const val TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId"
+
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
+    @Before
+    fun setUp() {
+        allowTestableLooperAsMainThread()
+        helper = NotificationTestHelper(mContext, mDependency)
+        whenever(accessibilityManager.isTouchExplorationEnabled).thenReturn(false)
+
+        windowRootViewVisibilityInteractor =
+            WindowRootViewVisibilityInteractor(
+                testScope.backgroundScope,
+                WindowRootViewVisibilityRepository(barService, executor),
+                FakeKeyguardRepository(),
+                headsUpManager,
+                create().powerInteractor,
+                kosmos.activeNotificationsInteractor,
+            ) {
+                kosmos.sceneInteractor
+            }
+
+        gutsManager =
+            NotificationGutsManager(
+                mContext,
+                handler,
+                handler,
+                javaAdapter,
+                accessibilityManager,
+                highPriorityProvider,
+                iNotificationManager,
+                userManager,
+                peopleSpaceWidgetManager,
+                launcherApps,
+                shortcutManager,
+                channelEditorDialogController,
+                contextTracker,
+                assistantFeedbackController,
+                Optional.of(bubblesManager),
+                UiEventLoggerFake(),
+                onUserInteractionCallback,
+                shadeController,
+                windowRootViewVisibilityInteractor,
+                notificationLockscreenUserManager,
+                statusBarStateController,
+                barService,
+                deviceProvisionedController,
+                metricsLogger,
+                headsUpManager,
+                activityStarter,
+            )
+        gutsManager.setUpWithPresenter(
+            presenter,
+            notificationListContainer,
+            onSettingsClickListener,
+        )
+        gutsManager.setNotificationActivityStarter(notificationActivityStarter)
+        gutsManager.start()
+    }
+
+    @Test
+    fun testOpenAndCloseGuts() {
+        val guts = spy(NotificationGuts(mContext))
+        whenever(guts.post(any())).thenAnswer { invocation: InvocationOnMock ->
+            handler.post(((invocation.arguments[0] as Runnable)))
+            null
+        }
+
+        // Test doesn't support animation since the guts view is not attached.
+        doNothing().whenever(guts).openControls(anyInt(), anyInt(), anyBoolean(), any())
+
+        val realRow = createTestNotificationRow()
+        val menuItem = createTestMenuItem(realRow)
+
+        val row = spy(realRow)
+        whenever(row.windowToken).thenReturn(Binder())
+        whenever(row.guts).thenReturn(guts)
+
+        assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
+        assertEquals(View.INVISIBLE.toLong(), guts.visibility.toLong())
+        executor.runAllReady()
+        verify(guts).openControls(anyInt(), anyInt(), anyBoolean(), any<Runnable>())
+        verify(headsUpManager).setGutsShown(realRow.entry, true)
+
+        assertEquals(View.VISIBLE.toLong(), guts.visibility.toLong())
+        gutsManager.closeAndSaveGuts(false, false, true, 0, 0, false)
+
+        verify(guts).closeControls(anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean())
+        verify(row, times(1)).setGutsView(any<MenuItem>())
+        executor.runAllReady()
+        verify(headsUpManager).setGutsShown(realRow.entry, false)
+    }
+
+    @Test
+    fun testLockscreenShadeVisible_visible_gutsNotClosed() =
+        testScope.runTest {
+            // First, start out lockscreen or shade as not visible
+            windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false)
+            runCurrent()
+
+            val guts: NotificationGuts = mock()
+            gutsManager.exposedGuts = guts
+
+            // WHEN the lockscreen or shade becomes visible
+            windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+            runCurrent()
+
+            // THEN the guts are not closed
+            verify(guts, never()).removeCallbacks(any())
+            verify(guts, never())
+                .closeControls(anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean())
+        }
+
+    @Test
+    @DisableSceneContainer
+    fun testLockscreenShadeVisible_notVisible_gutsClosed() =
+        testScope.runTest {
+            // First, start out lockscreen or shade as visible
+            windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+            runCurrent()
+
+            val guts: NotificationGuts = mock()
+            gutsManager.exposedGuts = guts
+
+            // WHEN the lockscreen or shade is no longer visible
+            windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false)
+            runCurrent()
+
+            // THEN the guts are closed
+            verify(guts).removeCallbacks(null)
+            verify(guts)
+                .closeControls(
+                    /* leavebehinds = */ eq(true),
+                    /* controls = */ eq(true),
+                    /* x = */ anyInt(),
+                    /* y = */ anyInt(),
+                    /* force = */ eq(true),
+                )
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun testShadeVisible_notVisible_gutsClosed() =
+        testScope.runTest {
+            // First, start with shade as visible
+            kosmos.setSceneTransition(Idle(Scenes.Shade))
+            runCurrent()
+
+            val guts: NotificationGuts = mock()
+            gutsManager.exposedGuts = guts
+
+            // WHEN the shade is no longer visible
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
+            runCurrent()
+
+            // THEN the guts are closed
+            verify(guts).removeCallbacks(null)
+            verify(guts)
+                .closeControls(
+                    /* leavebehinds = */ eq(true),
+                    /* controls = */ eq(true),
+                    /* x = */ anyInt(),
+                    /* y = */ anyInt(),
+                    /* force = */ eq(true),
+                )
+        }
+
+    @Test
+    @DisableSceneContainer
+    fun testLockscreenShadeVisible_notVisible_listContainerReset() =
+        testScope.runTest {
+            // First, start out lockscreen or shade as visible
+            windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+            runCurrent()
+            clearInvocations(notificationListContainer)
+
+            // WHEN the lockscreen or shade is no longer visible
+            windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false)
+            runCurrent()
+
+            // THEN the list container is reset
+            verify(notificationListContainer).resetExposedMenuView(anyBoolean(), anyBoolean())
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun testShadeVisible_notVisible_listContainerReset() =
+        testScope.runTest {
+            // First, start with shade as visible
+            kosmos.setSceneTransition(Idle(Scenes.Shade))
+            runCurrent()
+            clearInvocations(notificationListContainer)
+
+            // WHEN the shade is no longer visible
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
+            runCurrent()
+
+            // THEN the list container is reset
+            verify(notificationListContainer).resetExposedMenuView(anyBoolean(), anyBoolean())
+        }
+
+    @Test
+    fun testChangeDensityOrFontScale() {
+        val guts = spy(NotificationGuts(mContext))
+        whenever(guts.post(any())).thenAnswer { invocation: InvocationOnMock ->
+            handler.post(((invocation.arguments[0] as Runnable)))
+            null
+        }
+
+        // Test doesn't support animation since the guts view is not attached.
+        doNothing().whenever(guts).openControls(anyInt(), anyInt(), anyBoolean(), any<Runnable>())
+
+        val realRow = createTestNotificationRow()
+        val menuItem = createTestMenuItem(realRow)
+
+        val row = spy(realRow)
+
+        whenever(row.windowToken).thenReturn(Binder())
+        whenever(row.guts).thenReturn(guts)
+        doNothing().whenever(row).ensureGutsInflated()
+
+        val realEntry = realRow.entry
+        val entry = spy(realEntry)
+
+        whenever(entry.row).thenReturn(row)
+        whenever(entry.guts).thenReturn(guts)
+
+        assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
+        executor.runAllReady()
+        verify(guts).openControls(anyInt(), anyInt(), anyBoolean(), any<Runnable>())
+
+        // called once by mGutsManager.bindGuts() in mGutsManager.openGuts()
+        verify(row).setGutsView(any<MenuItem>())
+
+        row.onDensityOrFontScaleChanged()
+        gutsManager.onDensityOrFontScaleChanged(entry)
+
+        executor.runAllReady()
+
+        gutsManager.closeAndSaveGuts(false, false, false, 0, 0, false)
+
+        verify(guts).closeControls(anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean())
+
+        // called again by mGutsManager.bindGuts(), in mGutsManager.onDensityOrFontScaleChanged()
+        verify(row, times(2)).setGutsView(any<MenuItem>())
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_camera() {
+        val row = createTestNotificationRow()
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_CAMERA)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, row)
+        val captor = argumentCaptor<Intent>()
+        verify(notificationActivityStarter, times(1))
+            .startNotificationGutsIntent(captor.capture(), anyInt(), eq(row))
+        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.lastValue.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_mic() {
+        val row = createTestNotificationRow()
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_RECORD_AUDIO)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, row)
+        val captor = argumentCaptor<Intent>()
+        verify(notificationActivityStarter, times(1))
+            .startNotificationGutsIntent(captor.capture(), anyInt(), eq(row))
+        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.lastValue.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_camera_mic() {
+        val row = createTestNotificationRow()
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_CAMERA)
+        ops.add(AppOpsManager.OP_RECORD_AUDIO)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, row)
+        val captor = argumentCaptor<Intent>()
+        verify(notificationActivityStarter, times(1))
+            .startNotificationGutsIntent(captor.capture(), anyInt(), eq(row))
+        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.lastValue.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_overlay() {
+        val row = createTestNotificationRow()
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, row)
+        val captor = argumentCaptor<Intent>()
+        verify(notificationActivityStarter, times(1))
+            .startNotificationGutsIntent(captor.capture(), anyInt(), eq(row))
+        assertEquals(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION, captor.lastValue.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_camera_mic_overlay() {
+        val row = createTestNotificationRow()
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_CAMERA)
+        ops.add(AppOpsManager.OP_RECORD_AUDIO)
+        ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, row)
+        val captor = argumentCaptor<Intent>()
+        verify(notificationActivityStarter, times(1))
+            .startNotificationGutsIntent(captor.capture(), anyInt(), eq(row))
+        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.lastValue.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_camera_overlay() {
+        val row = createTestNotificationRow()
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_CAMERA)
+        ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, row)
+        val captor = argumentCaptor<Intent>()
+        verify(notificationActivityStarter, times(1))
+            .startNotificationGutsIntent(captor.capture(), anyInt(), eq(row))
+        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.lastValue.action)
+    }
+
+    @Test
+    fun testAppOpsSettingsIntent_mic_overlay() {
+        val row = createTestNotificationRow()
+        val ops = ArraySet<Int>()
+        ops.add(AppOpsManager.OP_RECORD_AUDIO)
+        ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+        gutsManager.startAppOpsSettingsActivity("", 0, ops, row)
+        val captor = argumentCaptor<Intent>()
+        verify(notificationActivityStarter, times(1))
+            .startNotificationGutsIntent(captor.capture(), anyInt(), eq(row))
+        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.lastValue.action)
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun testInitializeNotificationInfoView_highPriority() {
+        val notificationInfoView: NotificationInfo = mock()
+        val row = spy(helper.createRow())
+        val entry = row.entry
+        NotificationEntryHelper.modifyRanking(entry)
+            .setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE)
+            .setImportance(NotificationManager.IMPORTANCE_HIGH)
+            .build()
+
+        whenever(row.isNonblockable).thenReturn(false)
+        whenever(highPriorityProvider.isHighPriority(entry)).thenReturn(true)
+        val statusBarNotification = entry.sbn
+        gutsManager.initializeNotificationInfo(row, notificationInfoView)
+
+        verify(notificationInfoView)
+            .bindNotification(
+                any<PackageManager>(),
+                any<INotificationManager>(),
+                eq(onUserInteractionCallback),
+                eq(channelEditorDialogController),
+                eq(statusBarNotification.packageName),
+                any<NotificationChannel>(),
+                eq(entry),
+                any<NotificationInfo.OnSettingsClickListener>(),
+                any<NotificationInfo.OnAppSettingsClickListener>(),
+                any<UiEventLogger>(),
+                /* isDeviceProvisioned = */ eq(false),
+                /* isNonblockable = */ eq(false),
+                /* wasShownHighPriority = */ eq(true),
+                eq(assistantFeedbackController),
+                eq(metricsLogger),
+            )
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun testInitializeNotificationInfoView_PassesAlongProvisionedState() {
+        val notificationInfoView: NotificationInfo = mock()
+        val row = spy(helper.createRow())
+        NotificationEntryHelper.modifyRanking(row.entry)
+            .setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE)
+            .build()
+        whenever(row.isNonblockable).thenReturn(false)
+        val statusBarNotification = row.entry.sbn
+        val entry = row.entry
+
+        whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+
+        gutsManager.initializeNotificationInfo(row, notificationInfoView)
+
+        verify(notificationInfoView)
+            .bindNotification(
+                any<PackageManager>(),
+                any<INotificationManager>(),
+                eq(onUserInteractionCallback),
+                eq(channelEditorDialogController),
+                eq(statusBarNotification.packageName),
+                any<NotificationChannel>(),
+                eq(entry),
+                any<NotificationInfo.OnSettingsClickListener>(),
+                any<NotificationInfo.OnAppSettingsClickListener>(),
+                any<UiEventLogger>(),
+                /* isDeviceProvisioned = */ eq(true),
+                /* isNonblockable = */ eq(false),
+                /* wasShownHighPriority = */ eq(false),
+                eq(assistantFeedbackController),
+                eq(metricsLogger),
+            )
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun testInitializeNotificationInfoView_withInitialAction() {
+        val notificationInfoView: NotificationInfo = mock()
+        val row = spy(helper.createRow())
+        NotificationEntryHelper.modifyRanking(row.entry)
+            .setUserSentiment(NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE)
+            .build()
+        whenever(row.isNonblockable).thenReturn(false)
+        val statusBarNotification = row.entry.sbn
+        val entry = row.entry
+
+        gutsManager.initializeNotificationInfo(row, notificationInfoView)
+
+        verify(notificationInfoView)
+            .bindNotification(
+                any<PackageManager>(),
+                any<INotificationManager>(),
+                eq(onUserInteractionCallback),
+                eq(channelEditorDialogController),
+                eq(statusBarNotification.packageName),
+                any<NotificationChannel>(),
+                eq(entry),
+                any<NotificationInfo.OnSettingsClickListener>(),
+                any<NotificationInfo.OnAppSettingsClickListener>(),
+                any<UiEventLogger>(),
+                /* isDeviceProvisioned = */ eq(false),
+                /* isNonblockable = */ eq(false),
+                /* wasShownHighPriority = */ eq(false),
+                eq(assistantFeedbackController),
+                eq(metricsLogger),
+            )
+    }
+
+    private fun createTestNotificationRow(): ExpandableNotificationRow {
+        val nb =
+            Notification.Builder(mContext, testNotificationChannel.id)
+                .setContentTitle("foo")
+                .setColorized(true)
+                .setColor(Color.RED)
+                .setFlag(Notification.FLAG_CAN_COLORIZE, true)
+                .setSmallIcon(R.drawable.sym_def_app_icon)
+
+        try {
+            val row = helper.createRow(nb.build())
+            NotificationEntryHelper.modifyRanking(row.entry)
+                .setChannel(testNotificationChannel)
+                .build()
+            return row
+        } catch (e: Exception) {
+            fail()
+        }
+    }
+
+    private fun createTestMenuItem(
+        row: ExpandableNotificationRow
+    ): NotificationMenuRowPlugin.MenuItem {
+        val menuRow: NotificationMenuRowPlugin =
+            NotificationMenuRow(mContext, peopleNotificationIdentifier)
+        menuRow.createMenu(row, row.entry.sbn)
+
+        val menuItem = menuRow.getLongpressMenuItem(mContext)
+        assertNotNull(menuItem)
+        return menuItem
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 1ef4007..b2a485c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -4,8 +4,8 @@
 import android.content.pm.PackageManager
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
 import android.widget.FrameLayout
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
 import com.android.systemui.Flags
@@ -16,7 +16,9 @@
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
 import com.android.systemui.statusbar.NotificationShelf
 import com.android.systemui.statusbar.StatusBarState
@@ -47,10 +49,12 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class StackScrollAlgorithmTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
 
     @JvmField @Rule var expect: Expect = Expect.create()
 
@@ -99,6 +103,18 @@
     private val bigGap = notifSectionDividerGap
     private val smallGap = px(R.dimen.notification_section_divider_height_lockscreen)
 
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
     @Before
     fun setUp() {
         Assume.assumeFalse(isTv())
@@ -614,7 +630,7 @@
 
     @Test
     fun resetViewStates_isOnKeyguard_viewBecomesTransparent() {
-        ambientState.setStatusBarState(StatusBarState.KEYGUARD)
+        ambientState.fakeShowingStackOnLockscreen()
         ambientState.hideAmount = 0.25f
         whenever(notificationRow.isHeadsUpState).thenReturn(true)
 
@@ -676,7 +692,7 @@
         whenever(row1.isHeadsUpState).thenReturn(true)
         whenever(row2.isHeadsUpState).thenReturn(false)
 
-        ambientState.setStatusBarState(StatusBarState.KEYGUARD)
+        ambientState.fakeShowingStackOnLockscreen()
         ambientState.hideAmount = 0.25f
         ambientState.dozeAmount = 0.33f
         notificationShelf.viewState.hidden = true
@@ -729,6 +745,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     fun resetViewStates_noSpaceForFooter_footerHidden() {
         ambientState.isShadeExpanded = true
         ambientState.stackEndHeight = 0f // no space for the footer in the stack
@@ -740,6 +757,20 @@
     }
 
     @Test
+    @EnableSceneContainer
+    fun resetViewStates_noSpaceForFooter_footerHidden_withSceneContainer() {
+        ambientState.isShadeExpanded = true
+        ambientState.stackTop = 0f
+        ambientState.stackCutoff = 100f
+        val footerView = mockFooterView(height = 200) // no space for the footer in the stack
+        hostView.addView(footerView)
+
+        stackScrollAlgorithm.resetViewStates(ambientState, 0)
+
+        assertThat((footerView.viewState as FooterViewState).hideContent).isTrue()
+    }
+
+    @Test
     fun resetViewStates_clearAllInProgress_hasNonClearableRow_footerVisible() {
         whenever(notificationRow.canViewBeCleared()).thenReturn(false)
         ambientState.isClearAllInProgress = true
@@ -1552,3 +1583,19 @@
         whenever(viewState).thenReturn(ExpandableViewState())
     }
 }
+
+private fun mockFooterView(height: Int): FooterView {
+    return mock(FooterView::class.java).apply {
+        whenever(viewState).thenReturn(FooterViewState())
+        whenever(intrinsicHeight).thenReturn(height)
+    }
+}
+
+private fun AmbientState.fakeShowingStackOnLockscreen() {
+    if (SceneContainerFlag.isEnabled) {
+        isShowingStackOnLockscreen = true
+        lockscreenStackFadeInProgress = 1f // stack is fully opaque
+    } else {
+        setStatusBarState(StatusBarState.KEYGUARD)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
index 4762527..d665b31 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
@@ -24,7 +24,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.power.data.repository.FakePowerRepository
 import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -75,9 +75,9 @@
             configurationController,
             context,
             testScope.backgroundScope,
-            mock()
+            mock(),
         )
-    private val configurationInteractor = ConfigurationInteractor(configurationRepository)
+    private val configurationInteractor = ConfigurationInteractorImpl(configurationRepository)
 
     private val unfoldTransitionRepository =
         UnfoldTransitionRepositoryImpl(Optional.of(unfoldTransitionProgressProvider))
@@ -103,7 +103,7 @@
                 unfoldTransitionInteractor,
                 configurationInteractor,
                 animationStatus,
-                powerInteractor
+                powerInteractor,
             )
     }
 
@@ -140,7 +140,7 @@
             updateDisplay(
                 width = INITIAL_DISPLAY_HEIGHT,
                 height = INITIAL_DISPLAY_WIDTH,
-                rotation = ROTATION_90
+                rotation = ROTATION_90,
             )
             runCurrent()
 
@@ -284,7 +284,7 @@
     private fun updateDisplay(
         width: Int = INITIAL_DISPLAY_WIDTH,
         height: Int = INITIAL_DISPLAY_HEIGHT,
-        @Surface.Rotation rotation: Int = ROTATION_0
+        @Surface.Rotation rotation: Int = ROTATION_0,
     ) {
         configuration.windowConfiguration.maxBounds.set(Rect(0, 0, width, height))
         configuration.windowConfiguration.displayRotation = rotation
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/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/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
index e9d88cc..122cfdd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
@@ -16,16 +16,22 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
 import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.setSceneTransition
 import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
 import com.android.systemui.testKosmos
@@ -33,10 +39,12 @@
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class NotificationLoggerViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class NotificationLoggerViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
 
     private val kosmos = testKosmos()
 
@@ -46,9 +54,22 @@
     private val powerInteractor = kosmos.powerInteractor
     private val windowRootViewVisibilityRepository = kosmos.windowRootViewVisibilityRepository
 
-    private val underTest = kosmos.notificationListLoggerViewModel
+    private val underTest by lazy { kosmos.notificationListLoggerViewModel }
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
 
     @Test
+    @DisableSceneContainer
     fun isLockscreenOrShadeInteractive_deviceActiveAndShadeIsInteractive_true() =
         testScope.runTest {
             powerInteractor.setAwakeForTest()
@@ -60,6 +81,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun isLockscreenOrShadeInteractive_deviceIsAsleepAndShadeIsInteractive_false() =
         testScope.runTest {
             powerInteractor.setAsleepForTest()
@@ -71,6 +93,7 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun isLockscreenOrShadeInteractive_deviceActiveAndShadeIsNotInteractive_false() =
         testScope.runTest {
             powerInteractor.setAwakeForTest()
@@ -82,6 +105,54 @@
         }
 
     @Test
+    @EnableSceneContainer
+    fun isLockscreenOrShadeInteractive_deviceAwakeAndShadeIsDisplayed_true() =
+        testScope.runTest {
+            powerInteractor.setAwakeForTest()
+            kosmos.setSceneTransition(Idle(Scenes.Shade))
+
+            val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun isLockscreenOrShadeInteractive_deviceAwakeAndLockScreenIsDisplayed_true() =
+        testScope.runTest {
+            powerInteractor.setAwakeForTest()
+            kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
+
+            val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+            assertThat(actual).isTrue()
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun isLockscreenOrShadeInteractive_deviceIsAsleepOnLockscreen_false() =
+        testScope.runTest {
+            powerInteractor.setAsleepForTest()
+            kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
+
+            val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun isLockscreenOrShadeInteractive_deviceActiveAndShadeIsClosed() =
+        testScope.runTest {
+            powerInteractor.setAwakeForTest()
+            kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+            val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+            assertThat(actual).isFalse()
+        }
+
+    @Test
     fun activeNotifications_hasNotifications() =
         testScope.runTest {
             activeNotificationListRepository.setActiveNotifs(5)
@@ -135,6 +206,7 @@
 
             assertThat(isOnLockScreen).isTrue()
         }
+
     @Test
     fun isOnLockScreen_false() =
         testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index 5052a00..198821f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -44,6 +44,8 @@
 import com.android.systemui.doze.DozeScreenState;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -59,6 +61,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Answers;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
@@ -88,6 +91,8 @@
     @Mock private ConfigurationController mConfigurationController;
     @Mock private UserTracker mUserTracker;
     @Mock private DozeInteractor mDozeInteractor;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     @Captor private ArgumentCaptor<BatteryStateChangeCallback> mBatteryStateChangeCallback;
 
     /**
@@ -134,6 +139,7 @@
             mStatusBarStateController,
             mUserTracker,
             mDozeInteractor,
+            mKeyguardTransitionInteractor,
             secureSettings
         );
 
@@ -286,6 +292,18 @@
         assertTrue(mDozeParameters.shouldControlScreenOff());
     }
 
+    @Test
+    public void shouldDelayDisplayDozeTransition_True_WhenTransitioningToAod() {
+        setShouldControlUnlockedScreenOffForTest(false);
+        when(mScreenOffAnimationController.shouldDelayDisplayDozeTransition()).thenReturn(false);
+        when(mKeyguardTransitionInteractor.getTransitionState().getValue().getTo())
+                .thenReturn(KeyguardState.LOCKSCREEN);
+        assertFalse(mDozeParameters.shouldDelayDisplayDozeTransition());
+
+        when(mKeyguardTransitionInteractor.getTransitionState().getValue().getTo())
+                .thenReturn(KeyguardState.AOD);
+        assertTrue(mDozeParameters.shouldDelayDisplayDozeTransition());
+    }
 
     @Test
     public void keyguardVisibility_changesControlScreenOffAnimation() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
deleted file mode 100644
index 157f818..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ /dev/null
@@ -1,845 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-
-import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
-import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO;
-
-import static com.android.systemui.Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND;
-import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-import static com.android.systemui.statusbar.StatusBarState.SHADE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.provider.Settings;
-import android.testing.TestableLooper;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.CarrierTextController;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.keyguard.logging.KeyguardLogger;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.battery.BatteryMeterViewController;
-import com.android.systemui.flags.DisableSceneContainer;
-import com.android.systemui.flags.EnableSceneContainer;
-import com.android.systemui.kosmos.KosmosJavaAdapter;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.res.R;
-import com.android.systemui.shade.ShadeViewStateProvider;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore;
-import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
-import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
-import com.android.systemui.statusbar.phone.ui.TintedIconManager;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.SecureSettings;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@TestableLooper.RunWithLooper
-public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
-    @Mock
-    private CarrierTextController mCarrierTextController;
-    @Mock
-    private ConfigurationController mConfigurationController;
-    @Mock
-    private SystemStatusAnimationScheduler mAnimationScheduler;
-    @Mock
-    private BatteryController mBatteryController;
-    @Mock
-    private UserInfoController mUserInfoController;
-    @Mock
-    private StatusBarIconController mStatusBarIconController;
-    @Mock
-    private TintedIconManager.Factory mIconManagerFactory;
-    @Mock
-    private TintedIconManager mIconManager;
-    @Mock
-    private BatteryMeterViewController mBatteryMeterViewController;
-    @Mock
-    private KeyguardStateController mKeyguardStateController;
-    @Mock
-    private KeyguardBypassController mKeyguardBypassController;
-    @Mock
-    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock
-    private BiometricUnlockController mBiometricUnlockController;
-    @Mock
-    private SysuiStatusBarStateController mStatusBarStateController;
-    @Mock
-    private StatusBarContentInsetsProvider mStatusBarContentInsetsProvider;
-    @Mock
-    private StatusBarContentInsetsProviderStore mStatusBarContentInsetsProviderStore;
-    @Mock
-    private UserManager mUserManager;
-    @Mock
-    private StatusBarUserChipViewModel mStatusBarUserChipViewModel;
-    @Captor
-    private ArgumentCaptor<ConfigurationListener> mConfigurationListenerCaptor;
-    @Captor
-    private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor;
-    @Mock private SecureSettings mSecureSettings;
-    @Mock private CommandQueue mCommandQueue;
-    @Mock private KeyguardLogger mLogger;
-    @Mock private StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory;
-
-    private TestShadeViewStateProvider mShadeViewStateProvider;
-    private KeyguardStatusBarView mKeyguardStatusBarView;
-    private KeyguardStatusBarViewController mController;
-    private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
-    private final FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock());
-    private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
-
-    @Before
-    public void setup() throws Exception {
-        mShadeViewStateProvider = new TestShadeViewStateProvider();
-
-        MockitoAnnotations.initMocks(this);
-        when(mStatusBarContentInsetsProviderStore.getDefaultDisplay())
-                .thenReturn(mStatusBarContentInsetsProvider);
-        when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
-
-        allowTestableLooperAsMainThread();
-        TestableLooper.get(this).runWithLooper(() -> {
-            mKeyguardStatusBarView =
-                    spy((KeyguardStatusBarView) LayoutInflater.from(mContext)
-                            .inflate(R.layout.keyguard_status_bar, null));
-            when(mKeyguardStatusBarView.getDisplay()).thenReturn(mContext.getDisplay());
-        });
-
-        mController = createController();
-    }
-
-    private KeyguardStatusBarViewController createController() {
-        return new KeyguardStatusBarViewController(
-                mKeyguardStatusBarView,
-                mCarrierTextController,
-                mConfigurationController,
-                mAnimationScheduler,
-                mBatteryController,
-                mUserInfoController,
-                mStatusBarIconController,
-                mIconManagerFactory,
-                mBatteryMeterViewController,
-                mShadeViewStateProvider,
-                mKeyguardStateController,
-                mKeyguardBypassController,
-                mKeyguardUpdateMonitor,
-                mKosmos.getKeyguardStatusBarViewModel(),
-                mBiometricUnlockController,
-                mStatusBarStateController,
-                mStatusBarContentInsetsProviderStore,
-                mUserManager,
-                mStatusBarUserChipViewModel,
-                mSecureSettings,
-                mCommandQueue,
-                mFakeExecutor,
-                mBackgroundExecutor,
-                mLogger,
-                mStatusOverlayHoverListenerFactory,
-                mKosmos.getCommunalSceneInteractor()
-        );
-    }
-
-    @Test
-    @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
-    public void onViewAttached_updateUserSwitcherFlagEnabled_callbacksRegistered() {
-        mController.onViewAttached();
-
-        runAllScheduled();
-        verify(mConfigurationController).addCallback(any());
-        verify(mAnimationScheduler).addCallback(any());
-        verify(mUserInfoController).addCallback(any());
-        verify(mCommandQueue).addCallback(any());
-        verify(mStatusBarIconController).addIconGroup(any());
-        verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
-    }
-
-    @Test
-    @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
-    public void onViewAttached_updateUserSwitcherFlagDisabled_callbacksRegistered() {
-        mController.onViewAttached();
-
-        verify(mConfigurationController).addCallback(any());
-        verify(mAnimationScheduler).addCallback(any());
-        verify(mUserInfoController).addCallback(any());
-        verify(mCommandQueue).addCallback(any());
-        verify(mStatusBarIconController).addIconGroup(any());
-        verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
-    }
-
-    @Test
-    @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
-    public void
-            onConfigurationChanged_updateUserSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
-        mController.onViewAttached();
-        runAllScheduled();
-        verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture());
-        clearInvocations(mUserManager);
-        clearInvocations(mKeyguardStatusBarView);
-
-        mConfigurationListenerCaptor.getValue().onConfigChanged(null);
-
-        runAllScheduled();
-        verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
-        verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
-    }
-
-    @Test
-    @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
-    public void
-            onConfigurationChanged_updateUserSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
-        mController.onViewAttached();
-        verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture());
-        clearInvocations(mUserManager);
-        clearInvocations(mKeyguardStatusBarView);
-
-        mConfigurationListenerCaptor.getValue().onConfigChanged(null);
-        verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
-        verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
-    }
-
-    @Test
-    @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
-    public void
-            onKeyguardVisibilityChanged_userSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
-        mController.onViewAttached();
-        runAllScheduled();
-        verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture());
-        clearInvocations(mUserManager);
-        clearInvocations(mKeyguardStatusBarView);
-
-        mKeyguardCallbackCaptor.getValue().onKeyguardVisibilityChanged(true);
-
-        runAllScheduled();
-        verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
-        verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
-    }
-
-    @Test
-    @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
-    public void
-            onKeyguardVisibilityChanged_userSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
-        mController.onViewAttached();
-        verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture());
-        clearInvocations(mUserManager);
-        clearInvocations(mKeyguardStatusBarView);
-
-        mKeyguardCallbackCaptor.getValue().onKeyguardVisibilityChanged(true);
-        verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
-        verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
-    }
-
-    @Test
-    public void onViewDetached_callbacksUnregistered() {
-        // Set everything up first.
-        mController.onViewAttached();
-
-        mController.onViewDetached();
-
-        verify(mConfigurationController).removeCallback(any());
-        verify(mAnimationScheduler).removeCallback(any());
-        verify(mUserInfoController).removeCallback(any());
-        verify(mCommandQueue).removeCallback(any());
-        verify(mStatusBarIconController).removeIconGroup(any());
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void onViewReAttached_flagOff_iconManagerNotReRegistered() {
-        mController.onViewAttached();
-        mController.onViewDetached();
-        reset(mStatusBarIconController);
-
-        mController.onViewAttached();
-
-        verify(mStatusBarIconController, never()).addIconGroup(any());
-    }
-
-    @Test
-    @EnableSceneContainer
-    public void onViewReAttached_flagOn_iconManagerReRegistered() {
-        mController.onViewAttached();
-        mController.onViewDetached();
-        reset(mStatusBarIconController);
-
-        mController.onViewAttached();
-
-        verify(mStatusBarIconController).addIconGroup(any());
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void setBatteryListening_true_callbackAdded() {
-        mController.setBatteryListening(true);
-
-        verify(mBatteryController).addCallback(any());
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void setBatteryListening_false_callbackRemoved() {
-        // First set to true so that we know setting to false is a change in state.
-        mController.setBatteryListening(true);
-
-        mController.setBatteryListening(false);
-
-        verify(mBatteryController).removeCallback(any());
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void setBatteryListening_trueThenTrue_callbackAddedOnce() {
-        mController.setBatteryListening(true);
-        mController.setBatteryListening(true);
-
-        verify(mBatteryController).addCallback(any());
-    }
-
-    @Test
-    @EnableSceneContainer
-    public void setBatteryListening_true_flagOn_callbackNotAdded() {
-        mController.setBatteryListening(true);
-
-        verify(mBatteryController, never()).addCallback(any());
-    }
-
-    @Test
-    public void updateTopClipping_viewClippingUpdated() {
-        int viewTop = 20;
-        mKeyguardStatusBarView.setTop(viewTop);
-        int notificationPanelTop = 30;
-
-        mController.updateTopClipping(notificationPanelTop);
-
-        assertThat(mKeyguardStatusBarView.getClipBounds().top).isEqualTo(
-                notificationPanelTop - viewTop);
-    }
-
-    @Test
-    public void setNotTopClipping_viewClippingUpdatedToZero() {
-        // Start out with some amount of top clipping.
-        mController.updateTopClipping(50);
-        assertThat(mKeyguardStatusBarView.getClipBounds().top).isGreaterThan(0);
-
-        mController.setNoTopClipping();
-
-        assertThat(mKeyguardStatusBarView.getClipBounds().top).isEqualTo(0);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_alphaAndVisibilityGiven_viewUpdated() {
-        // Verify the initial values so we know the method triggers changes.
-        assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(1f);
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-
-        float newAlpha = 0.5f;
-        int newVisibility = View.INVISIBLE;
-        mController.updateViewState(newAlpha, newVisibility);
-
-        assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(newAlpha);
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(newVisibility);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_paramVisibleButIsDisabled_viewIsInvisible() {
-        mController.onViewAttached();
-        setDisableSystemIcons(true);
-
-        mController.updateViewState(1f, View.VISIBLE);
-
-        // Since we're disabled, we stay invisible
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_notKeyguardState_nothingUpdated() {
-        mController.onViewAttached();
-        updateStateToNotKeyguard();
-
-        float oldAlpha = mKeyguardStatusBarView.getAlpha();
-
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(oldAlpha);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_bypassEnabledAndShouldListenForFace_viewHidden() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-
-        when(mKeyguardUpdateMonitor.shouldListenForFace()).thenReturn(true);
-        when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
-        onFinishedGoingToSleep();
-
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_bypassNotEnabled_viewShown() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        when(mKeyguardUpdateMonitor.shouldListenForFace()).thenReturn(true);
-        when(mKeyguardBypassController.getBypassEnabled()).thenReturn(false);
-        onFinishedGoingToSleep();
-
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_shouldNotListenForFace_viewShown() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        when(mKeyguardUpdateMonitor.shouldListenForFace()).thenReturn(false);
-        when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
-        onFinishedGoingToSleep();
-
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_panelExpandedHeightZero_viewHidden() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        mShadeViewStateProvider.setPanelViewExpandedHeight(0);
-
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_dragProgressOne_viewHidden() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        mShadeViewStateProvider.setLockscreenShadeDragProgress(1f);
-
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_disableSystemInfoFalse_viewShown() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-        setDisableSystemInfo(false);
-
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_disableSystemInfoTrue_viewHidden() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-        setDisableSystemInfo(true);
-
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_disableSystemIconsFalse_viewShown() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-        setDisableSystemIcons(false);
-
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_disableSystemIconsTrue_viewHidden() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-        setDisableSystemIcons(true);
-
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_dozingTrue_flagOff_viewHidden() {
-        mController.init();
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        mController.setDozing(true);
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateViewState_dozingFalse_flagOff_viewShown() {
-        mController.init();
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        mController.setDozing(false);
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    @EnableSceneContainer
-    public void updateViewState_flagOn_doesNothing() {
-        mController.init();
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        mKeyguardStatusBarView.setVisibility(View.GONE);
-        mKeyguardStatusBarView.setAlpha(0.456f);
-
-        mController.updateViewState();
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.GONE);
-        assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(0.456f);
-    }
-
-    @Test
-    @EnableSceneContainer
-    public void updateViewStateWithAlphaAndVis_flagOn_doesNothing() {
-        mController.init();
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        mKeyguardStatusBarView.setVisibility(View.GONE);
-        mKeyguardStatusBarView.setAlpha(0.456f);
-
-        mController.updateViewState(0.789f, View.VISIBLE);
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.GONE);
-        assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(0.456f);
-    }
-
-    @Test
-    @EnableSceneContainer
-    public void setAlpha_flagOn_doesNothing() {
-        mController.init();
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        mKeyguardStatusBarView.setAlpha(0.456f);
-
-        mController.setAlpha(0.123f);
-
-        assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(0.456f);
-    }
-
-    @Test
-    @EnableSceneContainer
-    public void setDozing_flagOn_doesNothing() {
-        mController.init();
-        mController.onViewAttached();
-        updateStateToKeyguard();
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-
-        mController.setDozing(true);
-        mController.updateViewState();
-
-        // setDozing(true) should typically cause the view to hide. But since the flag is on, we
-        // should ignore these set dozing calls and stay the same visibility.
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void setAlpha_explicitAlpha_setsExplicitAlpha() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        mController.setAlpha(0.5f);
-
-        assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(0.5f);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void setAlpha_explicitAlpha_thenMinusOneAlpha_setsAlphaBasedOnDefaultCriteria() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        mController.setAlpha(0.5f);
-        mController.setAlpha(-1f);
-
-        assertThat(mKeyguardStatusBarView.getAlpha()).isGreaterThan(0);
-        assertThat(mKeyguardStatusBarView.getAlpha()).isNotEqualTo(0.5f);
-    }
-
-    // TODO(b/195442899): Add more tests for #updateViewState once CLs are finalized.
-
-    @Test
-    @DisableSceneContainer
-    public void updateForHeadsUp_headsUpShouldBeVisible_viewHidden() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-        mKeyguardStatusBarView.setVisibility(View.VISIBLE);
-
-        mShadeViewStateProvider.setShouldHeadsUpBeVisible(true);
-        mController.updateForHeadsUp(/* animate= */ false);
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void updateForHeadsUp_headsUpShouldNotBeVisible_viewShown() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-
-        // Start with the opposite state.
-        mShadeViewStateProvider.setShouldHeadsUpBeVisible(true);
-        mController.updateForHeadsUp(/* animate= */ false);
-
-        mShadeViewStateProvider.setShouldHeadsUpBeVisible(false);
-        mController.updateForHeadsUp(/* animate= */ false);
-
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    public void testNewUserSwitcherDisablesAvatar_newUiOn() {
-        // GIVEN the status bar user switcher chip is enabled
-        when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(true);
-
-        // WHEN the controller is created
-        mController = createController();
-
-        // THEN keyguard status bar view avatar is disabled
-        assertThat(mKeyguardStatusBarView.isKeyguardUserAvatarEnabled()).isFalse();
-    }
-
-    @Test
-    public void testNewUserSwitcherDisablesAvatar_newUiOff() {
-        // GIVEN the status bar user switcher chip is disabled
-        when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(false);
-
-        // WHEN the controller is created
-        mController = createController();
-
-        // THEN keyguard status bar view avatar is enabled
-        assertThat(mKeyguardStatusBarView.isKeyguardUserAvatarEnabled()).isTrue();
-    }
-
-    @Test
-    public void testBlockedIcons_obeysSettingForVibrateIcon_settingOff() {
-        String str = mContext.getString(com.android.internal.R.string.status_bar_volume);
-
-        // GIVEN the setting is off
-        when(mSecureSettings.getInt(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, 0))
-                .thenReturn(0);
-
-        // WHEN CollapsedStatusBarFragment builds the blocklist
-        mController.updateBlockedIcons();
-
-        // THEN status_bar_volume SHOULD be present in the list
-        boolean contains = mController.getBlockedIcons().contains(str);
-        assertTrue(contains);
-    }
-
-    @Test
-    public void testBlockedIcons_obeysSettingForVibrateIcon_settingOn() {
-        String str = mContext.getString(com.android.internal.R.string.status_bar_volume);
-
-        // GIVEN the setting is ON
-        when(mSecureSettings.getIntForUser(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, 0,
-                UserHandle.USER_CURRENT))
-                .thenReturn(1);
-
-        // WHEN CollapsedStatusBarFragment builds the blocklist
-        mController.updateBlockedIcons();
-
-        // THEN status_bar_volume SHOULD NOT be present in the list
-        boolean contains = mController.getBlockedIcons().contains(str);
-        assertFalse(contains);
-    }
-
-    private void updateStateToNotKeyguard() {
-        updateStatusBarState(SHADE);
-    }
-
-    private void updateStateToKeyguard() {
-        updateStatusBarState(KEYGUARD);
-    }
-
-    private void updateStatusBarState(int state) {
-        ArgumentCaptor<StatusBarStateController.StateListener> statusBarStateListenerCaptor =
-                ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
-        verify(mStatusBarStateController).addCallback(statusBarStateListenerCaptor.capture());
-        StatusBarStateController.StateListener callback = statusBarStateListenerCaptor.getValue();
-
-        callback.onStateChanged(state);
-    }
-
-    @Test
-    @DisableSceneContainer
-    public void animateKeyguardStatusBarIn_isDisabled_viewStillHidden() {
-        mController.onViewAttached();
-        updateStateToKeyguard();
-        setDisableSystemInfo(true);
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
-
-        mController.animateKeyguardStatusBarIn();
-
-        // Since we're disabled, we don't actually animate in and stay invisible
-        assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
-    }
-
-    /**
-     * Calls {@link com.android.keyguard.KeyguardUpdateMonitorCallback#onFinishedGoingToSleep(int)}
-     * to ensure values are updated properly.
-     */
-    private void onFinishedGoingToSleep() {
-        ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateCallbackCaptor =
-                ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
-        verify(mKeyguardUpdateMonitor).registerCallback(keyguardUpdateCallbackCaptor.capture());
-        KeyguardUpdateMonitorCallback callback = keyguardUpdateCallbackCaptor.getValue();
-
-        callback.onFinishedGoingToSleep(0);
-    }
-
-    private void setDisableSystemInfo(boolean disabled) {
-        CommandQueue.Callbacks callback = getCommandQueueCallback();
-        int disabled1 = disabled ? DISABLE_SYSTEM_INFO : 0;
-        callback.disable(mContext.getDisplayId(), disabled1, 0, false);
-    }
-
-    private void setDisableSystemIcons(boolean disabled) {
-        CommandQueue.Callbacks callback = getCommandQueueCallback();
-        int disabled2 = disabled ? DISABLE2_SYSTEM_ICONS : 0;
-        callback.disable(mContext.getDisplayId(), 0, disabled2, false);
-    }
-
-    private CommandQueue.Callbacks getCommandQueueCallback() {
-        ArgumentCaptor<CommandQueue.Callbacks> captor =
-                ArgumentCaptor.forClass(CommandQueue.Callbacks.class);
-        verify(mCommandQueue).addCallback(captor.capture());
-        return captor.getValue();
-    }
-
-    private void runAllScheduled() {
-        mBackgroundExecutor.runAllReady();
-        mFakeExecutor.runAllReady();
-    }
-
-    private static class TestShadeViewStateProvider
-            implements ShadeViewStateProvider {
-
-        TestShadeViewStateProvider() {}
-
-        private float mPanelViewExpandedHeight = 100f;
-        private boolean mShouldHeadsUpBeVisible = false;
-        private float mLockscreenShadeDragProgress = 0f;
-
-        @Override
-        public float getPanelViewExpandedHeight() {
-            return mPanelViewExpandedHeight;
-        }
-
-        @Override
-        public boolean shouldHeadsUpBeVisible() {
-            return mShouldHeadsUpBeVisible;
-        }
-
-        @Override
-        public float getLockscreenShadeDragProgress() {
-            return mLockscreenShadeDragProgress;
-        }
-
-        public void setPanelViewExpandedHeight(float panelViewExpandedHeight) {
-            this.mPanelViewExpandedHeight = panelViewExpandedHeight;
-        }
-
-        public void setShouldHeadsUpBeVisible(boolean shouldHeadsUpBeVisible) {
-            this.mShouldHeadsUpBeVisible = shouldHeadsUpBeVisible;
-        }
-
-        public void setLockscreenShadeDragProgress(float lockscreenShadeDragProgress) {
-            this.mLockscreenShadeDragProgress = lockscreenShadeDragProgress;
-        }
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
new file mode 100644
index 0000000..b815c6c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
@@ -0,0 +1,867 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.phone
+
+import android.app.StatusBarManager
+import android.graphics.Insets
+import android.os.UserHandle
+import android.os.UserManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.provider.Settings
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.ViewUtils
+import android.view.LayoutInflater
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.CarrierTextController
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.keyguard.logging.KeyguardLogger
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeViewStateProvider
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler
+import com.android.systemui.statusbar.phone.ui.StatusBarIconController
+import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.UserInfoController
+import com.android.systemui.statusbar.ui.viewmodel.keyguardStatusBarViewModel
+import com.android.systemui.statusbar.ui.viewmodel.statusBarUserChipViewModel
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper(setAsMainLooper = true)
+class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
+    private lateinit var kosmos: Kosmos
+    private lateinit var testScope: TestScope
+
+    @Mock private lateinit var carrierTextController: CarrierTextController
+
+    @Mock private lateinit var configurationController: ConfigurationController
+
+    @Mock private lateinit var animationScheduler: SystemStatusAnimationScheduler
+
+    @Mock private lateinit var batteryController: BatteryController
+
+    @Mock private lateinit var userInfoController: UserInfoController
+
+    @Mock private lateinit var statusBarIconController: StatusBarIconController
+
+    @Mock private lateinit var iconManagerFactory: TintedIconManager.Factory
+
+    @Mock private lateinit var iconManager: TintedIconManager
+
+    @Mock private lateinit var batteryMeterViewController: BatteryMeterViewController
+
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+
+    @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+    @Mock private lateinit var biometricUnlockController: BiometricUnlockController
+
+    @Mock
+    private lateinit var statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore
+
+    @Mock private lateinit var userManager: UserManager
+
+    @Captor
+    private lateinit var configurationListenerCaptor:
+        ArgumentCaptor<ConfigurationController.ConfigurationListener>
+
+    @Captor
+    private lateinit var keyguardCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
+
+    @Mock private lateinit var secureSettings: SecureSettings
+
+    @Mock private lateinit var commandQueue: CommandQueue
+
+    @Mock private lateinit var logger: KeyguardLogger
+
+    @Mock private lateinit var statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory
+
+    private lateinit var shadeViewStateProvider: TestShadeViewStateProvider
+
+    private lateinit var keyguardStatusBarView: KeyguardStatusBarView
+    private lateinit var controller: KeyguardStatusBarViewController
+    private val fakeExecutor = FakeExecutor(FakeSystemClock())
+    private val backgroundExecutor = FakeExecutor(FakeSystemClock())
+
+    private lateinit var looper: TestableLooper
+
+    @Before
+    @Throws(Exception::class)
+    fun setup() {
+        looper = TestableLooper.get(this)
+        kosmos = testKosmos()
+        testScope = kosmos.testScope
+        shadeViewStateProvider = TestShadeViewStateProvider()
+
+        Mockito.`when`(
+                kosmos.statusBarContentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()
+            )
+            .thenReturn(Insets.of(0, 0, 0, 0))
+
+        MockitoAnnotations.initMocks(this)
+
+        Mockito.`when`(iconManagerFactory.create(ArgumentMatchers.any(), ArgumentMatchers.any()))
+            .thenReturn(iconManager)
+        Mockito.`when`(statusBarContentInsetsProviderStore.defaultDisplay)
+            .thenReturn(kosmos.statusBarContentInsetsProvider)
+        allowTestableLooperAsMainThread()
+        looper.runWithLooper {
+            keyguardStatusBarView =
+                Mockito.spy(
+                    LayoutInflater.from(mContext).inflate(R.layout.keyguard_status_bar, null)
+                        as KeyguardStatusBarView
+                )
+            Mockito.`when`(keyguardStatusBarView.getDisplay()).thenReturn(mContext.display)
+        }
+
+        controller = createController()
+    }
+
+    private fun createController(): KeyguardStatusBarViewController {
+        return KeyguardStatusBarViewController(
+            kosmos.testDispatcher,
+            keyguardStatusBarView,
+            carrierTextController,
+            configurationController,
+            animationScheduler,
+            batteryController,
+            userInfoController,
+            statusBarIconController,
+            iconManagerFactory,
+            batteryMeterViewController,
+            shadeViewStateProvider,
+            keyguardStateController,
+            keyguardBypassController,
+            keyguardUpdateMonitor,
+            kosmos.keyguardStatusBarViewModel,
+            biometricUnlockController,
+            kosmos.statusBarStateController,
+            statusBarContentInsetsProviderStore,
+            userManager,
+            kosmos.statusBarUserChipViewModel,
+            secureSettings,
+            commandQueue,
+            fakeExecutor,
+            backgroundExecutor,
+            logger,
+            statusOverlayHoverListenerFactory,
+            kosmos.communalSceneInteractor,
+            kosmos.glanceableHubToLockscreenTransitionViewModel,
+            kosmos.lockscreenToGlanceableHubTransitionViewModel,
+        )
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    fun onViewAttached_updateUserSwitcherFlagEnabled_callbacksRegistered() {
+        controller.onViewAttached()
+
+        runAllScheduled()
+        Mockito.verify(configurationController).addCallback(ArgumentMatchers.any())
+        Mockito.verify(animationScheduler).addCallback(ArgumentMatchers.any())
+        Mockito.verify(userInfoController).addCallback(ArgumentMatchers.any())
+        Mockito.verify(commandQueue).addCallback(ArgumentMatchers.any())
+        Mockito.verify(statusBarIconController).addIconGroup(ArgumentMatchers.any())
+        Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    fun onViewAttached_updateUserSwitcherFlagDisabled_callbacksRegistered() {
+        controller.onViewAttached()
+
+        Mockito.verify(configurationController).addCallback(ArgumentMatchers.any())
+        Mockito.verify(animationScheduler).addCallback(ArgumentMatchers.any())
+        Mockito.verify(userInfoController).addCallback(ArgumentMatchers.any())
+        Mockito.verify(commandQueue).addCallback(ArgumentMatchers.any())
+        Mockito.verify(statusBarIconController).addIconGroup(ArgumentMatchers.any())
+        Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    fun onConfigurationChanged_updateUserSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
+        controller.onViewAttached()
+        runAllScheduled()
+        Mockito.verify(configurationController).addCallback(configurationListenerCaptor.capture())
+        Mockito.clearInvocations(userManager)
+        Mockito.clearInvocations(keyguardStatusBarView)
+
+        configurationListenerCaptor.value.onConfigChanged(null)
+
+        runAllScheduled()
+        Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+        Mockito.verify(keyguardStatusBarView).setUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    fun onConfigurationChanged_updateUserSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
+        controller.onViewAttached()
+        Mockito.verify(configurationController).addCallback(configurationListenerCaptor.capture())
+        Mockito.clearInvocations(userManager)
+        Mockito.clearInvocations(keyguardStatusBarView)
+
+        configurationListenerCaptor.value.onConfigChanged(null)
+        Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+        Mockito.verify(keyguardStatusBarView).setUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    fun onKeyguardVisibilityChanged_userSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
+        controller.onViewAttached()
+        runAllScheduled()
+        Mockito.verify(keyguardUpdateMonitor).registerCallback(keyguardCallbackCaptor.capture())
+        Mockito.clearInvocations(userManager)
+        Mockito.clearInvocations(keyguardStatusBarView)
+
+        keyguardCallbackCaptor.value.onKeyguardVisibilityChanged(true)
+
+        runAllScheduled()
+        Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+        Mockito.verify(keyguardStatusBarView).setUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+    fun onKeyguardVisibilityChanged_userSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
+        controller.onViewAttached()
+        Mockito.verify(keyguardUpdateMonitor).registerCallback(keyguardCallbackCaptor.capture())
+        Mockito.clearInvocations(userManager)
+        Mockito.clearInvocations(keyguardStatusBarView)
+
+        keyguardCallbackCaptor.value.onKeyguardVisibilityChanged(true)
+        Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+        Mockito.verify(keyguardStatusBarView).setUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+    }
+
+    @Test
+    fun onViewDetached_callbacksUnregistered() {
+        // Set everything up first.
+        controller.onViewAttached()
+
+        controller.onViewDetached()
+
+        Mockito.verify(configurationController).removeCallback(ArgumentMatchers.any())
+        Mockito.verify(animationScheduler).removeCallback(ArgumentMatchers.any())
+        Mockito.verify(userInfoController).removeCallback(ArgumentMatchers.any())
+        Mockito.verify(commandQueue).removeCallback(ArgumentMatchers.any())
+        Mockito.verify(statusBarIconController).removeIconGroup(ArgumentMatchers.any())
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun onViewReAttached_flagOff_iconManagerNotReRegistered() {
+        controller.onViewAttached()
+        controller.onViewDetached()
+        Mockito.reset(statusBarIconController)
+
+        controller.onViewAttached()
+
+        Mockito.verify(statusBarIconController, Mockito.never())
+            .addIconGroup(ArgumentMatchers.any())
+    }
+
+    @Test
+    @EnableSceneContainer
+    fun onViewReAttached_flagOn_iconManagerReRegistered() {
+        controller.onViewAttached()
+        controller.onViewDetached()
+        Mockito.reset(statusBarIconController)
+
+        controller.onViewAttached()
+
+        Mockito.verify(statusBarIconController).addIconGroup(ArgumentMatchers.any())
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun setBatteryListening_true_callbackAdded() {
+        controller.setBatteryListening(true)
+
+        Mockito.verify(batteryController).addCallback(ArgumentMatchers.any())
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun setBatteryListening_false_callbackRemoved() {
+        // First set to true so that we know setting to false is a change in state.
+        controller.setBatteryListening(true)
+
+        controller.setBatteryListening(false)
+
+        Mockito.verify(batteryController).removeCallback(ArgumentMatchers.any())
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun setBatteryListening_trueThenTrue_callbackAddedOnce() {
+        controller.setBatteryListening(true)
+        controller.setBatteryListening(true)
+
+        Mockito.verify(batteryController).addCallback(ArgumentMatchers.any())
+    }
+
+    @Test
+    @EnableSceneContainer
+    fun setBatteryListening_true_flagOn_callbackNotAdded() {
+        controller.setBatteryListening(true)
+
+        Mockito.verify(batteryController, Mockito.never()).addCallback(ArgumentMatchers.any())
+    }
+
+    @Test
+    fun updateTopClipping_viewClippingUpdated() {
+        val viewTop = 20
+        keyguardStatusBarView.top = viewTop
+        val notificationPanelTop = 30
+
+        controller.updateTopClipping(notificationPanelTop)
+
+        Truth.assertThat(keyguardStatusBarView.clipBounds.top)
+            .isEqualTo(notificationPanelTop - viewTop)
+    }
+
+    @Test
+    fun setNotTopClipping_viewClippingUpdatedToZero() {
+        // Start out with some amount of top clipping.
+        controller.updateTopClipping(50)
+        Truth.assertThat(keyguardStatusBarView.clipBounds.top).isGreaterThan(0)
+
+        controller.setNoTopClipping()
+
+        Truth.assertThat(keyguardStatusBarView.clipBounds.top).isEqualTo(0)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_alphaAndVisibilityGiven_viewUpdated() {
+        // Verify the initial values so we know the method triggers changes.
+        Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(1f)
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+
+        val newAlpha = 0.5f
+        val newVisibility = View.INVISIBLE
+        controller.updateViewState(newAlpha, newVisibility)
+
+        Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(newAlpha)
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(newVisibility)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_paramVisibleButIsDisabled_viewIsInvisible() {
+        controller.onViewAttached()
+        setDisableSystemIcons(true)
+
+        controller.updateViewState(1f, View.VISIBLE)
+
+        // Since we're disabled, we stay invisible
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_notKeyguardState_nothingUpdated() {
+        controller.onViewAttached()
+        updateStateToNotKeyguard()
+
+        val oldAlpha = keyguardStatusBarView.alpha
+
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(oldAlpha)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_bypassEnabledAndShouldListenForFace_viewHidden() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+
+        Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
+        Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(true)
+        onFinishedGoingToSleep()
+
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_bypassNotEnabled_viewShown() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
+        Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(false)
+        onFinishedGoingToSleep()
+
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_shouldNotListenForFace_viewShown() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(false)
+        Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(true)
+        onFinishedGoingToSleep()
+
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_panelExpandedHeightZero_viewHidden() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        shadeViewStateProvider.panelViewExpandedHeight = 0f
+
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_dragProgressOne_viewHidden() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        shadeViewStateProvider.lockscreenShadeDragProgress = 1f
+
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_disableSystemInfoFalse_viewShown() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+        setDisableSystemInfo(false)
+
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_disableSystemInfoTrue_viewHidden() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+        setDisableSystemInfo(true)
+
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_disableSystemIconsFalse_viewShown() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+        setDisableSystemIcons(false)
+
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_disableSystemIconsTrue_viewHidden() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+        setDisableSystemIcons(true)
+
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_dozingTrue_flagOff_viewHidden() {
+        controller.init()
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        controller.setDozing(true)
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateViewState_dozingFalse_flagOff_viewShown() {
+        controller.init()
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        controller.setDozing(false)
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    @EnableSceneContainer
+    fun updateViewState_flagOn_doesNothing() {
+        controller.init()
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        keyguardStatusBarView.visibility = View.GONE
+        keyguardStatusBarView.alpha = 0.456f
+
+        controller.updateViewState()
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.GONE)
+        Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.456f)
+    }
+
+    @Test
+    @EnableSceneContainer
+    fun updateViewStateWithAlphaAndVis_flagOn_doesNothing() {
+        controller.init()
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        keyguardStatusBarView.visibility = View.GONE
+        keyguardStatusBarView.alpha = 0.456f
+
+        controller.updateViewState(0.789f, View.VISIBLE)
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.GONE)
+        Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.456f)
+    }
+
+    @Test
+    @EnableSceneContainer
+    fun setAlpha_flagOn_doesNothing() {
+        controller.init()
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        keyguardStatusBarView.alpha = 0.456f
+
+        controller.setAlpha(0.123f)
+
+        Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.456f)
+    }
+
+    @Test
+    @EnableSceneContainer
+    fun setDozing_flagOn_doesNothing() {
+        controller.init()
+        controller.onViewAttached()
+        updateStateToKeyguard()
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+
+        controller.setDozing(true)
+        controller.updateViewState()
+
+        // setDozing(true) should typically cause the view to hide. But since the flag is on, we
+        // should ignore these set dozing calls and stay the same visibility.
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun setAlpha_explicitAlpha_setsExplicitAlpha() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        controller.setAlpha(0.5f)
+
+        Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.5f)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun setAlpha_explicitAlpha_thenMinusOneAlpha_setsAlphaBasedOnDefaultCriteria() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        controller.setAlpha(0.5f)
+        controller.setAlpha(-1f)
+
+        Truth.assertThat(keyguardStatusBarView.alpha).isGreaterThan(0)
+        Truth.assertThat(keyguardStatusBarView.alpha).isNotEqualTo(0.5f)
+    }
+
+    // TODO(b/195442899): Add more tests for #updateViewState once CLs are finalized.
+    @Test
+    @DisableSceneContainer
+    fun updateForHeadsUp_headsUpShouldBeVisible_viewHidden() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+        keyguardStatusBarView.visibility = View.VISIBLE
+
+        shadeViewStateProvider.setShouldHeadsUpBeVisible(true)
+        controller.updateForHeadsUp(/* animate= */ false)
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun updateForHeadsUp_headsUpShouldNotBeVisible_viewShown() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+
+        // Start with the opposite state.
+        shadeViewStateProvider.setShouldHeadsUpBeVisible(true)
+        controller.updateForHeadsUp(/* animate= */ false)
+
+        shadeViewStateProvider.setShouldHeadsUpBeVisible(false)
+        controller.updateForHeadsUp(/* animate= */ false)
+
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    fun testNewUserSwitcherDisablesAvatar_newUiOn() =
+        testScope.runTest {
+            // GIVEN the status bar user switcher chip is enabled
+            kosmos.fakeUserRepository.isStatusBarUserChipEnabled = true
+
+            // WHEN the controller is created
+            controller = createController()
+
+            // THEN keyguard status bar view avatar is disabled
+            Truth.assertThat(keyguardStatusBarView.isKeyguardUserAvatarEnabled).isFalse()
+        }
+
+    @Test
+    fun testNewUserSwitcherDisablesAvatar_newUiOff() {
+        // GIVEN the status bar user switcher chip is disabled
+        kosmos.fakeUserRepository.isStatusBarUserChipEnabled = false
+
+        // WHEN the controller is created
+        controller = createController()
+
+        // THEN keyguard status bar view avatar is enabled
+        Truth.assertThat(keyguardStatusBarView.isKeyguardUserAvatarEnabled).isTrue()
+    }
+
+    @Test
+    fun testBlockedIcons_obeysSettingForVibrateIcon_settingOff() {
+        val str = mContext.getString(com.android.internal.R.string.status_bar_volume)
+
+        // GIVEN the setting is off
+        Mockito.`when`(secureSettings.getInt(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, 0))
+            .thenReturn(0)
+
+        // WHEN CollapsedStatusBarFragment builds the blocklist
+        controller.updateBlockedIcons()
+
+        // THEN status_bar_volume SHOULD be present in the list
+        val contains = controller.blockedIcons.contains(str)
+        Assert.assertTrue(contains)
+    }
+
+    @Test
+    fun testBlockedIcons_obeysSettingForVibrateIcon_settingOn() {
+        val str = mContext.getString(com.android.internal.R.string.status_bar_volume)
+
+        // GIVEN the setting is ON
+        Mockito.`when`(
+                secureSettings.getIntForUser(
+                    Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
+                    0,
+                    UserHandle.USER_CURRENT,
+                )
+            )
+            .thenReturn(1)
+
+        // WHEN CollapsedStatusBarFragment builds the blocklist
+        controller.updateBlockedIcons()
+
+        // THEN status_bar_volume SHOULD NOT be present in the list
+        val contains = controller.blockedIcons.contains(str)
+        Assert.assertFalse(contains)
+    }
+
+    private fun updateStateToNotKeyguard() {
+        updateStatusBarState(StatusBarState.SHADE)
+    }
+
+    private fun updateStateToKeyguard() {
+        updateStatusBarState(StatusBarState.KEYGUARD)
+    }
+
+    private fun updateStatusBarState(state: Int) {
+        kosmos.statusBarStateController.setState(state)
+    }
+
+    @Test
+    @DisableSceneContainer
+    fun animateKeyguardStatusBarIn_isDisabled_viewStillHidden() {
+        controller.onViewAttached()
+        updateStateToKeyguard()
+        setDisableSystemInfo(true)
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+
+        controller.animateKeyguardStatusBarIn()
+
+        // Since we're disabled, we don't actually animate in and stay invisible
+        Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+    }
+
+    @Test
+    fun animateToGlanceableHub_affectsAlpha() =
+        testScope.runTest {
+            controller.init()
+            val transitionAlphaAmount = .5f
+            ViewUtils.attachView(keyguardStatusBarView)
+            looper.processAllMessages()
+            updateStateToKeyguard()
+            kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal)
+            runCurrent()
+            controller.updateCommunalAlphaTransition(transitionAlphaAmount)
+            Truth.assertThat(keyguardStatusBarView.getAlpha()).isEqualTo(transitionAlphaAmount)
+        }
+
+    @Test
+    fun animateToGlanceableHub_alphaResetOnCommunalNotShowing() =
+        testScope.runTest {
+            controller.init()
+            val transitionAlphaAmount = .5f
+            ViewUtils.attachView(keyguardStatusBarView)
+            looper.processAllMessages()
+            updateStateToKeyguard()
+            kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal)
+            runCurrent()
+            controller.updateCommunalAlphaTransition(transitionAlphaAmount)
+            kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Blank)
+            runCurrent()
+            Truth.assertThat(keyguardStatusBarView.getAlpha()).isNotEqualTo(transitionAlphaAmount)
+        }
+
+    /**
+     * Calls [com.android.keyguard.KeyguardUpdateMonitorCallback.onFinishedGoingToSleep] to ensure
+     * values are updated properly.
+     */
+    private fun onFinishedGoingToSleep() {
+        val keyguardUpdateCallbackCaptor =
+            ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
+        Mockito.verify(keyguardUpdateMonitor)
+            .registerCallback(keyguardUpdateCallbackCaptor.capture())
+        val callback = keyguardUpdateCallbackCaptor.value
+
+        callback.onFinishedGoingToSleep(0)
+    }
+
+    private fun setDisableSystemInfo(disabled: Boolean) {
+        val callback = commandQueueCallback
+        val disabled1 = if (disabled) StatusBarManager.DISABLE_SYSTEM_INFO else 0
+        callback.disable(mContext.displayId, disabled1, 0, false)
+    }
+
+    private fun setDisableSystemIcons(disabled: Boolean) {
+        val callback = commandQueueCallback
+        val disabled2 = if (disabled) StatusBarManager.DISABLE2_SYSTEM_ICONS else 0
+        callback.disable(mContext.displayId, 0, disabled2, false)
+    }
+
+    private val commandQueueCallback: CommandQueue.Callbacks
+        get() {
+            val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
+            Mockito.verify(commandQueue).addCallback(captor.capture())
+            return captor.value
+        }
+
+    private fun runAllScheduled() {
+        backgroundExecutor.runAllReady()
+        fakeExecutor.runAllReady()
+    }
+
+    private class TestShadeViewStateProvider : ShadeViewStateProvider {
+        override var panelViewExpandedHeight: Float = 100f
+        private var mShouldHeadsUpBeVisible = false
+        override var lockscreenShadeDragProgress: Float = 0f
+
+        override fun shouldHeadsUpBeVisible(): Boolean {
+            return mShouldHeadsUpBeVisible
+        }
+
+        fun setShouldHeadsUpBeVisible(shouldHeadsUpBeVisible: Boolean) {
+            this.mShouldHeadsUpBeVisible = shouldHeadsUpBeVisible
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index 88ec18d..9099334 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -48,12 +48,10 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.statusbar.data.model.StatusBarAppearance;
 import com.android.systemui.statusbar.data.model.StatusBarMode;
-import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModePerDisplayRepository;
 import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.util.kotlin.JavaAdapter;
 
 import kotlinx.coroutines.test.TestScope;
 
@@ -81,8 +79,8 @@
     private SysuiDarkIconDispatcher mStatusBarIconController;
     private LightBarController mLightBarController;
     private final TestScope mTestScope = TestScopeProvider.getTestScope();
-    private final FakeStatusBarModeRepository mStatusBarModeRepository =
-            new FakeStatusBarModeRepository();
+    private final FakeStatusBarModePerDisplayRepository mStatusBarModeRepository =
+            new FakeStatusBarModePerDisplayRepository();
 
     @Before
     public void setup() {
@@ -92,15 +90,16 @@
         mLightBarTransitionsController = mock(LightBarTransitionsController.class);
         when(mStatusBarIconController.getTransitionsController()).thenReturn(
                 mLightBarTransitionsController);
-        mLightBarController = new LightBarController(
-                mContext,
-                new JavaAdapter(mTestScope),
+        mLightBarController = new LightBarControllerImpl(
+                mContext.getDisplayId(),
+                mTestScope,
                 mStatusBarIconController,
                 mock(BatteryController.class),
                 mock(NavigationModeController.class),
                 mStatusBarModeRepository,
                 mock(DumpManager.class),
-                new FakeDisplayTracker(mContext));
+                mTestScope.getCoroutineContext(),
+                mock(BiometricUnlockController.class));
         mLightBarController.start();
     }
 
@@ -121,7 +120,7 @@
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds)
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -142,7 +141,7 @@
                 new AppearanceRegion(0 /* appearance */, secondBounds)
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -165,7 +164,7 @@
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds)
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -190,7 +189,7 @@
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, thirdBounds)
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -214,7 +213,7 @@
                 new AppearanceRegion(0 /* appearance */, secondBounds)
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -231,7 +230,7 @@
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1))
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -249,7 +248,7 @@
                 new AppearanceRegion(0, new Rect(0, 0, 1, 1))
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -266,7 +265,7 @@
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1))
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -276,7 +275,7 @@
         reset(mStatusBarIconController);
 
         // WHEN the same appearance regions but different status bar mode is sent
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.LIGHTS_OUT_TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -298,7 +297,7 @@
                 /* start= */ new Rect(0, 0, 10, 10),
                 /* end= */ new Rect(0, 0, 20, 20));
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         startingBounds,
@@ -311,7 +310,7 @@
         BoundsPair newBounds = new BoundsPair(
                 /* start= */ new Rect(0, 0, 30, 30),
                 /* end= */ new Rect(0, 0, 40, 40));
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         newBounds,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/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/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
index e0d9fac..110dec6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
@@ -36,7 +36,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
@@ -58,7 +57,6 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
-import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -144,6 +142,7 @@
         controller.start()
         controller.addCallback(mockOngoingCallListener)
         controller.setChipView(chipView)
+        onTeardown { controller.tearDownChipView() }
 
         val collectionListenerCaptor = ArgumentCaptor.forClass(NotifCollectionListener::class.java)
         verify(notificationCollection).addCollectionListener(collectionListenerCaptor.capture())
@@ -153,11 +152,6 @@
             .thenReturn(PROC_STATE_INVISIBLE)
     }
 
-    @After
-    fun tearDown() {
-        controller.tearDownChipView()
-    }
-
     @Test
     fun onEntryUpdated_isOngoingCallNotif_listenerAndRepoNotified() {
         val notification = NotificationEntryBuilder(createOngoingCallNotifEntry())
@@ -224,7 +218,7 @@
         notifCollectionListener.onEntryUpdated(notification.build())
         chipView.measure(
             View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
-            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
         )
 
         assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
@@ -241,7 +235,7 @@
         notifCollectionListener.onEntryUpdated(notification.build())
         chipView.measure(
             View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
-            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
         )
 
         assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
@@ -257,7 +251,7 @@
         notifCollectionListener.onEntryUpdated(notification.build())
         chipView.measure(
             View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
-            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
         )
 
         assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
@@ -668,7 +662,7 @@
 
     private fun createCallNotifEntry(
         callStyle: Notification.CallStyle,
-        nullContentIntent: Boolean = false
+        nullContentIntent: Boolean = false,
     ): NotificationEntry {
         val notificationEntryBuilder = NotificationEntryBuilder()
         notificationEntryBuilder.modifyNotification(context).style = callStyle
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
index fc1ea22..19d5a16 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
@@ -66,6 +66,7 @@
             logBuffer = FakeLogBuffer.Factory.create(),
             verboseLogBuffer = FakeLogBuffer.Factory.create(),
             systemClock,
+            context.resources,
         )
     private val demoDataSource =
         mock<DemoDeviceBasedSatelliteDataSource>().also {
@@ -80,7 +81,11 @@
                 )
         }
     private val demoImpl =
-        DemoDeviceBasedSatelliteRepository(demoDataSource, testScope.backgroundScope)
+        DemoDeviceBasedSatelliteRepository(
+            demoDataSource,
+            testScope.backgroundScope,
+            context.resources,
+        )
 
     private val underTest =
         DeviceBasedSatelliteRepositorySwitcher(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
index 8769389..a70881a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
@@ -58,7 +58,12 @@
                 whenever(it.satelliteEvents).thenReturn(fakeSatelliteEvents)
             }
 
-        underTest = DemoDeviceBasedSatelliteRepository(dataSource, testScope.backgroundScope)
+        underTest =
+            DemoDeviceBasedSatelliteRepository(
+                dataSource,
+                testScope.backgroundScope,
+                context.resources,
+            )
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
index 55460bd..41fa9e7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
@@ -28,4 +28,6 @@
     override val signalStrength = MutableStateFlow(0)
 
     override val isSatelliteAllowedForCurrentLocation = MutableStateFlow(false)
+
+    override var isOpportunisticSatelliteIconEnabled: Boolean = true
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index e7e4969..509aa7a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -172,6 +172,26 @@
         }
 
     @Test
+    fun icon_null_allOosAndConfigIsFalse() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.icon)
+
+            // GIVEN config for opportunistic icon is false
+            repo.isOpportunisticSatelliteIconEnabled = false
+
+            // GIVEN all icons are OOS
+            val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+            i1.isInService.value = false
+            i1.isEmergencyOnly.value = false
+
+            // GIVEN apm is disabled
+            airplaneModeRepository.setIsAirplaneMode(false)
+
+            // THEN icon is null because it is not allowed
+            assertThat(latest).isNull()
+        }
+
+    @Test
     fun icon_null_isEmergencyOnly() =
         testScope.runTest {
             val latest by collectLastValue(underTest.icon)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 53e033e..e7ca1dd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -51,6 +51,8 @@
 import android.os.Handler;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.provider.Settings;
 
 import androidx.annotation.VisibleForTesting;
@@ -561,6 +563,113 @@
     }
 
     @Test
+    @EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+    public void onWallpaperColorsChanged_homeWallpaper_shouldUpdateTheme() {
+        // Should ask for a new theme when the colors of the last applied wallpaper change
+        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+                Color.valueOf(Color.BLUE), null);
+
+        String jsonString =
+                "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+                        + "\"android.theme.customization.system_palette\":\"A16B00\","
+                        + "\"android.theme.customization.accent_color\":\"A16B00\","
+                        + "\"android.theme.customization.color_index\":\"2\"}";
+
+        when(mSecureSettings.getStringForUser(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+                .thenReturn(jsonString);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+                .thenReturn(1);
+        // Set LOCK wallpaper as the last applied one to verify that theme is no longer based on
+        // latest wallpaper
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+                .thenReturn(2);
+
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
+
+        ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+        verify(mSecureSettings).putStringForUser(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture(),
+                anyInt());
+
+        verify(mThemeOverlayApplier)
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+    }
+
+
+
+    @Test
+    @EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+    public void onWallpaperColorsChanged_homeWallpaperWithSameColor_shouldKeepThemeAndReapply() {
+        // Shouldn't ask for a new theme when the colors of the last applied wallpaper change
+        // with the same specified system palette one.
+        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+                Color.valueOf(0xffa16b00), null);
+
+        String jsonString =
+                "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+                        + "\"android.theme.customization.system_palette\":\"A16B00\","
+                        + "\"android.theme.customization.accent_color\":\"A16B00\","
+                        + "\"android.theme.customization.color_index\":\"2\"}";
+
+        when(mSecureSettings.getStringForUser(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+                .thenReturn(jsonString);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+                .thenReturn(1);
+        // Set LOCK wallpaper as the last applied one to verify that theme is no longer based on
+        // latest wallpaper
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+                .thenReturn(2);
+
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
+
+        ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+        verify(mSecureSettings, never()).putString(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture());
+
+        // Apply overlay by existing theme from secure setting
+        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+    }
+
+    @Test
+    @EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+    public void onWallpaperColorsChanged_lockWallpaper_shouldKeepTheme() {
+        // Should ask for a new theme when the colors of the last applied wallpaper change
+        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+                Color.valueOf(Color.BLUE), null);
+
+        String jsonString =
+                "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+                        + "\"android.theme.customization.system_palette\":\"A16B00\","
+                        + "\"android.theme.customization.accent_color\":\"A16B00\","
+                        + "\"android.theme.customization.color_index\":\"2\"}";
+
+        when(mSecureSettings.getStringForUser(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+                .thenReturn(jsonString);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+                .thenReturn(1);
+        // Set LOCK wallpaper as the last applied one to verify that theme is no longer based on
+        // latest wallpaper
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+                .thenReturn(2);
+
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_LOCK,
+                USER_SYSTEM);
+
+        ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+        verify(mSecureSettings, never()).putString(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture());
+
+        verify(mThemeOverlayApplier, never())
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+    }
+
+    @Test
+    @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
     public void onWallpaperColorsChanged_resetThemeWhenFromLatestWallpaper() {
         // Should ask for a new theme when the colors of the last applied wallpaper change
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
@@ -594,6 +703,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
     public void onWallpaperColorsChanged_keepThemeWhenFromLatestWallpaperAndSpecifiedColor() {
         // Shouldn't ask for a new theme when the colors of the last applied wallpaper change
         // with the same specified system palette one.
@@ -627,6 +737,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
     public void onWallpaperColorsChanged_keepThemeIfNotLatestWallpaper() {
         // Shouldn't ask for a new theme when the colors of the wallpaper that is not the last
         // applied one change
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
index f101539..09be93d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
@@ -24,7 +24,7 @@
 import com.android.internal.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
 import com.android.systemui.defaultDeviceState
 import com.android.systemui.deviceStateManager
 import com.android.systemui.display.data.repository.DeviceStateRepository
@@ -107,9 +107,9 @@
             configurationController,
             context,
             testScope.backgroundScope,
-            mock()
+            mock(),
         )
-    private val configurationInteractor = ConfigurationInteractor(configurationRepository)
+    private val configurationInteractor = ConfigurationInteractorImpl(configurationRepository)
     private val unfoldTransitionProgressProvider = FakeUnfoldTransitionProvider()
     private val unfoldTransitionRepository =
         UnfoldTransitionRepositoryImpl(Optional.of(unfoldTransitionProgressProvider))
@@ -145,7 +145,7 @@
                 testScope.backgroundScope,
                 displaySwitchLatencyLogger,
                 systemClock,
-                deviceStateManager
+                deviceStateManager,
             )
     }
 
@@ -174,7 +174,7 @@
                 DisplaySwitchLatencyEvent(
                     latencyMs = 250,
                     fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
-                    toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN
+                    toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN,
                 )
             assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
         }
@@ -200,7 +200,7 @@
                     testScope.backgroundScope,
                     displaySwitchLatencyLogger,
                     systemClock,
-                    deviceStateManager
+                    deviceStateManager,
                 )
             areAnimationEnabled.emit(true)
 
@@ -226,7 +226,7 @@
                 DisplaySwitchLatencyEvent(
                     latencyMs = 50,
                     fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
-                    toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN
+                    toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN,
                 )
             assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
         }
@@ -259,7 +259,7 @@
                 DisplaySwitchLatencyEvent(
                     latencyMs = 50,
                     fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
-                    toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN
+                    toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN,
                 )
             assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
         }
@@ -289,7 +289,7 @@
                 DisplaySwitchLatencyEvent(
                     latencyMs = 200,
                     fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN,
-                    toFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED
+                    toFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
                 )
             assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
         }
@@ -310,7 +310,7 @@
             lastWakefulnessEvent.emit(
                 WakefulnessModel(
                     internalWakefulnessState = WakefulnessState.ASLEEP,
-                    lastSleepReason = WakeSleepReason.FOLD
+                    lastSleepReason = WakeSleepReason.FOLD,
                 )
             )
             screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
@@ -326,7 +326,7 @@
                     latencyMs = 200,
                     fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN,
                     toFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
-                    toState = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__AOD
+                    toState = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__AOD,
                 )
             assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
         }
@@ -372,7 +372,7 @@
             lastWakefulnessEvent.emit(
                 WakefulnessModel(
                     internalWakefulnessState = WakefulnessState.ASLEEP,
-                    lastSleepReason = WakeSleepReason.FOLD
+                    lastSleepReason = WakeSleepReason.FOLD,
                 )
             )
             screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
@@ -385,7 +385,7 @@
                     latencyMs = 0,
                     fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN,
                     toFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
-                    toState = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__SCREEN_OFF
+                    toState = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__SCREEN_OFF,
                 )
             assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
index a0cfab4d..e88dbd2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
@@ -22,11 +22,10 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
 import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -42,70 +41,127 @@
 class UserAwareSecureSettingsRepositoryTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
-    private val dispatcher = kosmos.testDispatcher
     private val testScope = kosmos.testScope
     private val secureSettings = kosmos.fakeSettings
-    private val userRepository = Kosmos().fakeUserRepository
-    private lateinit var repository: UserAwareSecureSettingsRepository
+    private val userRepository = kosmos.fakeUserRepository
+    private lateinit var underTest: UserAwareSecureSettingsRepository
 
     @Before
     fun setup() {
-        repository =
-            UserAwareSecureSettingsRepositoryImpl(
-                secureSettings,
-                userRepository,
-                dispatcher,
-            )
+        underTest = kosmos.userAwareSecureSettingsRepository
+
         userRepository.setUserInfos(USER_INFOS)
-        setSettingValueForUser(enabled = true, userInfo = SETTING_ENABLED_USER)
-        setSettingValueForUser(enabled = false, userInfo = SETTING_DISABLED_USER)
+
+        secureSettings.putBoolForUser(BOOL_SETTING_NAME, true, USER_1.id)
+        secureSettings.putBoolForUser(BOOL_SETTING_NAME, false, USER_2.id)
+        secureSettings.putIntForUser(INT_SETTING_NAME, 1337, USER_1.id)
+        secureSettings.putIntForUser(INT_SETTING_NAME, 818, USER_2.id)
     }
 
     @Test
-    fun settingEnabledEmitsValueForCurrentUser() {
+    fun boolSetting_emitsInitialValue() {
         testScope.runTest {
-            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+            userRepository.setSelectedUserInfo(USER_1)
 
-            val enabled by collectLastValue(repository.boolSettingForActiveUser(SETTING_NAME))
+            val enabled by collectLastValue(underTest.boolSetting(BOOL_SETTING_NAME, false))
 
             assertThat(enabled).isTrue()
         }
     }
 
     @Test
-    fun settingEnabledEmitsNewValueWhenSettingChanges() {
+    fun boolSetting_whenSettingChanges_emitsNewValue() {
         testScope.runTest {
-            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
-            val enabled by collectValues(repository.boolSettingForActiveUser(SETTING_NAME))
+            userRepository.setSelectedUserInfo(USER_1)
+            val enabled by collectValues(underTest.boolSetting(BOOL_SETTING_NAME, false))
             runCurrent()
 
-            setSettingValueForUser(enabled = false, userInfo = SETTING_ENABLED_USER)
+            secureSettings.putBoolForUser(BOOL_SETTING_NAME, false, USER_1.id)
 
             assertThat(enabled).containsExactly(true, false).inOrder()
         }
     }
 
     @Test
-    fun settingEnabledEmitsValueForNewUserWhenUserChanges() {
+    fun boolSetting_whenWhenUserChanges_emitsNewValue() {
         testScope.runTest {
-            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
-            val enabled by collectLastValue(repository.boolSettingForActiveUser(SETTING_NAME))
+            userRepository.setSelectedUserInfo(USER_1)
+            val enabled by collectLastValue(underTest.boolSetting(BOOL_SETTING_NAME, false))
             runCurrent()
 
-            userRepository.setSelectedUserInfo(SETTING_DISABLED_USER)
+            userRepository.setSelectedUserInfo(USER_2)
 
             assertThat(enabled).isFalse()
         }
     }
 
-    private fun setSettingValueForUser(enabled: Boolean, userInfo: UserInfo) {
-        secureSettings.putBoolForUser(SETTING_NAME, enabled, userInfo.id)
+    @Test
+    fun intSetting_emitsInitialValue() {
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(USER_1)
+
+            val number by collectLastValue(underTest.intSetting(INT_SETTING_NAME, 0))
+
+            assertThat(number).isEqualTo(1337)
+        }
     }
 
+    @Test
+    fun intSetting_whenSettingChanges_emitsNewValue() {
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(USER_1)
+            val number by collectValues(underTest.intSetting(INT_SETTING_NAME, 0))
+            runCurrent()
+
+            secureSettings.putIntForUser(INT_SETTING_NAME, 1338, USER_1.id)
+
+            assertThat(number).containsExactly(1337, 1338).inOrder()
+        }
+    }
+
+    @Test
+    fun intSetting_whenWhenUserChanges_emitsNewValue() {
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(USER_1)
+            val number by collectLastValue(underTest.intSetting(INT_SETTING_NAME, 0))
+            runCurrent()
+
+            userRepository.setSelectedUserInfo(USER_2)
+
+            assertThat(number).isEqualTo(818)
+        }
+    }
+
+    @Test
+    fun getInt_returnsInitialValue() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(USER_1)
+
+            assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(1337)
+        }
+
+    @Test
+    fun getInt_whenSettingChanges_returnsNewValue() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(USER_1)
+            secureSettings.putIntForUser(INT_SETTING_NAME, 999, USER_1.id)
+
+            assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(999)
+        }
+
+    @Test
+    fun getInt_whenUserChanges_returnsThatUserValue() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(USER_2)
+
+            assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(818)
+        }
+
     private companion object {
-        const val SETTING_NAME = "SETTING_NAME"
-        val SETTING_ENABLED_USER = UserInfo(/* id= */ 0, "user1", /* flags= */ 0)
-        val SETTING_DISABLED_USER = UserInfo(/* id= */ 1, "user2", /* flags= */ 0)
-        val USER_INFOS = listOf(SETTING_ENABLED_USER, SETTING_DISABLED_USER)
+        const val BOOL_SETTING_NAME = "BOOL_SETTING_NAME"
+        const val INT_SETTING_NAME = "INT_SETTING_NAME"
+        val USER_1 = UserInfo(/* id= */ 0, "user1", /* flags= */ 0)
+        val USER_2 = UserInfo(/* id= */ 1, "user2", /* flags= */ 0)
+        val USER_INFOS = listOf(USER_1, USER_2)
     }
 }
diff --git a/packages/SystemUI/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/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 5792175..7d55169 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -28,6 +28,7 @@
 import java.io.PrintWriter
 import java.util.Locale
 import java.util.TimeZone
+import org.json.JSONArray
 import org.json.JSONObject
 
 /** Identifies a clock design */
@@ -61,7 +62,7 @@
 
     @ProtectedReturn("return new ClockPickerConfig(\"\", \"\", \"\", null);")
     /** Settings configuration parameters for the clock */
-    fun getClockPickerConfig(id: ClockId): ClockPickerConfig
+    fun getClockPickerConfig(settings: ClockSettings): ClockPickerConfig
 }
 
 /** Interface for controlling an active clock */
@@ -213,7 +214,53 @@
 
     /** Value to set this axis to */
     val value: Float,
-)
+) {
+    companion object {
+        private val KEY_AXIS_KEY = "key"
+        private val KEY_AXIS_VALUE = "value"
+
+        fun toJson(setting: ClockFontAxisSetting): JSONObject {
+            return JSONObject().apply {
+                put(KEY_AXIS_KEY, setting.key)
+                put(KEY_AXIS_VALUE, setting.value)
+            }
+        }
+
+        fun toJson(settings: List<ClockFontAxisSetting>): JSONArray {
+            return JSONArray().apply {
+                for (axis in settings) {
+                    put(toJson(axis))
+                }
+            }
+        }
+
+        fun fromJson(jsonObj: JSONObject): ClockFontAxisSetting {
+            return ClockFontAxisSetting(
+                key = jsonObj.getString(KEY_AXIS_KEY),
+                value = jsonObj.getDouble(KEY_AXIS_VALUE).toFloat(),
+            )
+        }
+
+        fun fromJson(jsonArray: JSONArray): List<ClockFontAxisSetting> {
+            val result = mutableListOf<ClockFontAxisSetting>()
+            for (i in 0..jsonArray.length() - 1) {
+                val obj = jsonArray.getJSONObject(i)
+                if (obj == null) continue
+                result.add(fromJson(obj))
+            }
+            return result
+        }
+
+        fun toFVar(settings: List<ClockFontAxisSetting>): String {
+            val sb = StringBuilder()
+            for (axis in settings) {
+                if (sb.length > 0) sb.append(", ")
+                sb.append("'${axis.key}' ${axis.value.toInt()}")
+            }
+            return sb.toString()
+        }
+    }
+}
 
 /** Methods which trigger various clock animations */
 @ProtectedInterface
@@ -350,6 +397,21 @@
     val description: String,
 ) {
     fun toSetting() = ClockFontAxisSetting(key, currentValue)
+
+    companion object {
+        fun merge(
+            fontAxes: List<ClockFontAxis>,
+            axisSettings: List<ClockFontAxisSetting>,
+        ): List<ClockFontAxis> {
+            val result = mutableListOf<ClockFontAxis>()
+            for (axis in fontAxes) {
+                val setting = axisSettings.firstOrNull { axis.key == it.key }
+                val output = setting?.let { axis.copy(currentValue = it.value) } ?: axis
+                result.add(output)
+            }
+            return result
+        }
+    }
 }
 
 /** Axis user interaction modes */
@@ -406,7 +468,7 @@
 data class ClockSettings(
     val clockId: ClockId? = null,
     val seedColor: Int? = null,
-    val axes: List<ClockFontAxisSetting>? = null,
+    val axes: List<ClockFontAxisSetting> = listOf(),
 ) {
     // Exclude metadata from equality checks
     var metadata: JSONObject = JSONObject()
@@ -415,38 +477,24 @@
         private val KEY_CLOCK_ID = "clockId"
         private val KEY_SEED_COLOR = "seedColor"
         private val KEY_METADATA = "metadata"
+        private val KEY_AXIS_LIST = "axes"
 
-        fun serialize(setting: ClockSettings?): String {
-            if (setting == null) {
-                return ""
+        fun toJson(setting: ClockSettings): JSONObject {
+            return JSONObject().apply {
+                put(KEY_CLOCK_ID, setting.clockId)
+                put(KEY_SEED_COLOR, setting.seedColor)
+                put(KEY_METADATA, setting.metadata)
+                put(KEY_AXIS_LIST, ClockFontAxisSetting.toJson(setting.axes))
             }
-
-            // TODO(b/364673977): Serialize axes
-
-            return JSONObject()
-                .put(KEY_CLOCK_ID, setting.clockId)
-                .put(KEY_SEED_COLOR, setting.seedColor)
-                .put(KEY_METADATA, setting.metadata)
-                .toString()
         }
 
-        fun deserialize(jsonStr: String?): ClockSettings? {
-            if (jsonStr.isNullOrEmpty()) {
-                return null
+        fun fromJson(json: JSONObject): ClockSettings {
+            val clockId = if (!json.isNull(KEY_CLOCK_ID)) json.getString(KEY_CLOCK_ID) else null
+            val seedColor = if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null
+            val axisList = json.optJSONArray(KEY_AXIS_LIST)?.let(ClockFontAxisSetting::fromJson)
+            return ClockSettings(clockId, seedColor, axisList ?: listOf()).apply {
+                metadata = json.optJSONObject(KEY_METADATA) ?: JSONObject()
             }
-
-            // TODO(b/364673977): Deserialize axes
-
-            val json = JSONObject(jsonStr)
-            val result =
-                ClockSettings(
-                    if (!json.isNull(KEY_CLOCK_ID)) json.getString(KEY_CLOCK_ID) else null,
-                    if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null,
-                )
-            if (!json.isNull(KEY_METADATA)) {
-                result.metadata = json.getJSONObject(KEY_METADATA)
-            }
-            return result
         }
     }
 }
diff --git a/packages/SystemUI/res/color/slider_active_track_color.xml b/packages/SystemUI/res/color/slider_active_track_color.xml
new file mode 100644
index 0000000..a5aa58d
--- /dev/null
+++ b/packages/SystemUI/res/color/slider_active_track_color.xml
@@ -0,0 +1,19 @@
+<?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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:color="?androidprv:attr/materialColorPrimary" android:state_enabled="true" />
+    <item android:color="?androidprv:attr/materialColorSurfaceContainerHighest" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/slider_inactive_track_color.xml b/packages/SystemUI/res/color/slider_inactive_track_color.xml
new file mode 100644
index 0000000..89aef77
--- /dev/null
+++ b/packages/SystemUI/res/color/slider_inactive_track_color.xml
@@ -0,0 +1,19 @@
+<?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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:color="?androidprv:attr/materialColorSurfaceContainerHighest" android:state_enabled="true" />
+    <item android:color="?androidprv:attr/materialColorPrimary" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/slider_thumb_color.xml b/packages/SystemUI/res/color/slider_thumb_color.xml
new file mode 100644
index 0000000..5206049
--- /dev/null
+++ b/packages/SystemUI/res/color/slider_thumb_color.xml
@@ -0,0 +1,20 @@
+<?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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:color="?androidprv:attr/materialColorSurfaceContainerHighest" android:state_enabled="false" />
+    <item android:color="?androidprv:attr/materialColorPrimary" />
+</selector>
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/drawable/volume_dialog_background_small_radius.xml b/packages/SystemUI/res/drawable/volume_dialog_background_small_radius.xml
new file mode 100644
index 0000000..7d79496
--- /dev/null
+++ b/packages/SystemUI/res/drawable/volume_dialog_background_small_radius.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:shape="rectangle">
+    <corners android:radius="@dimen/volume_dialog_background_square_corner_radius" />
+    <solid android:color="?androidprv:attr/materialColorSurface" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml b/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml
index df8521a..131201e 100644
--- a/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml
+++ b/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml
@@ -17,8 +17,8 @@
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:paddingMode="stack" >
     <size
-        android:height="@dimen/volume_ringer_item_size"
-        android:width="@dimen/volume_ringer_item_size" />
+        android:height="@dimen/volume_dialog_ringer_drawer_button_size"
+        android:width="@dimen/volume_dialog_ringer_drawer_button_size" />
     <solid android:color="?androidprv:attr/materialColorPrimary" />
-    <corners android:radius="360dp" />
+    <corners android:radius="@dimen/volume_dialog_ringer_selected_button_background_radius" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/volume_ringer_item_bg.xml b/packages/SystemUI/res/drawable/volume_ringer_item_bg.xml
index 7ddd57b7..a8c9818 100644
--- a/packages/SystemUI/res/drawable/volume_ringer_item_bg.xml
+++ b/packages/SystemUI/res/drawable/volume_ringer_item_bg.xml
@@ -16,7 +16,7 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:shape="rectangle" >
-    <size android:width="@dimen/volume_ringer_item_size" android:height="@dimen/volume_ringer_item_size"/>
+    <size android:width="@dimen/volume_dialog_ringer_drawer_button_size" android:height="@dimen/volume_dialog_ringer_drawer_button_size"/>
     <solid android:color="?androidprv:attr/materialColorSurfaceContainerHighest" />
-    <corners android:radius="@dimen/volume_ringer_item_radius" />
+    <corners android:radius="@dimen/volume_dialog_background_square_corner_radius" />
 </shape>
\ No newline at end of file
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_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 8b39e5e..e90f055f 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -40,9 +40,9 @@
         android:layout_height="wrap_content"
         android:background="@drawable/volume_dialog_background"
         android:divider="@drawable/volume_dialog_spacer"
+        android:paddingVertical="@dimen/volume_dialog_vertical_padding"
         android:gravity="center_horizontal"
         android:orientation="vertical"
-        android:paddingVertical="@dimen/volume_dialog_vertical_padding"
         android:showDividers="middle">
 
         <include layout="@layout/volume_ringer_drawer" />
diff --git a/packages/SystemUI/res/layout/volume_dialog_slider.xml b/packages/SystemUI/res/layout/volume_dialog_slider.xml
index 8acdd39..c1852b1 100644
--- a/packages/SystemUI/res/layout/volume_dialog_slider.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_slider.xml
@@ -18,10 +18,11 @@
     android:layout_height="@dimen/volume_dialog_slider_height">
 
     <com.google.android.material.slider.Slider
+        style="@style/SystemUI.Material3.Slider.Volume"
         android:id="@+id/volume_dialog_slider"
         android:layout_width="@dimen/volume_dialog_slider_height"
         android:layout_height="match_parent"
         android:layout_gravity="center"
         android:rotation="270"
-        android:theme="@style/Theme.MaterialComponents.DayNight" />
+        android:theme="@style/Theme.Material3.Light" />
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/volume_ringer_button.xml b/packages/SystemUI/res/layout/volume_ringer_button.xml
new file mode 100644
index 0000000..dc6780a
--- /dev/null
+++ b/packages/SystemUI/res/layout/volume_ringer_button.xml
@@ -0,0 +1,31 @@
+<?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.
+  -->
+<FrameLayout  xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content" >
+
+    <ImageButton
+        android:id="@+id/volume_drawer_button"
+        android:layout_width="@dimen/volume_dialog_ringer_drawer_button_size"
+        android:layout_height="@dimen/volume_dialog_ringer_drawer_button_size"
+        android:padding="@dimen/volume_dialog_ringer_drawer_button_icon_radius"
+        android:layout_marginBottom="@dimen/volume_dialog_components_spacing"
+        android:contentDescription="@string/volume_ringer_mode"
+        android:gravity="center"
+        android:src="@drawable/volume_ringer_item_bg"
+        android:background="@drawable/volume_ringer_item_bg"/>
+
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
index 94b55b1..7c266e6 100644
--- a/packages/SystemUI/res/layout/volume_ringer_drawer.xml
+++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
@@ -20,9 +20,8 @@
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:gravity="center"
-    android:paddingLeft="@dimen/volume_dialog_ringer_container_padding"
-    android:paddingTop="@dimen/volume_dialog_ringer_container_padding"
-    android:paddingRight="@dimen/volume_dialog_ringer_container_padding"
+    android:paddingLeft="@dimen/volume_dialog_ringer_horizontal_padding"
+    android:paddingRight="@dimen/volume_dialog_ringer_horizontal_padding"
     android:layoutDirection="ltr"
     android:clipToPadding="false"
     android:clipChildren="false"
@@ -31,7 +30,6 @@
     <!-- Drawer view, invisible by default. -->
     <FrameLayout
         android:id="@+id/volume_drawer_container"
-        android:alpha="0.0"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="vertical">
@@ -40,9 +38,8 @@
         <FrameLayout
             android:id="@+id/volume_drawer_selection_background"
             android:alpha="0.0"
-            android:layout_width="@dimen/volume_ringer_item_size"
-            android:layout_marginBottom="8dp"
-            android:layout_height="@dimen/volume_ringer_item_size"
+            android:layout_width="@dimen/volume_dialog_ringer_drawer_button_size"
+            android:layout_height="@dimen/volume_dialog_ringer_drawer_button_size"
             android:layout_gravity="bottom|right"
             android:background="@drawable/volume_drawer_selection_bg" />
 
@@ -52,64 +49,7 @@
             android:layout_height="wrap_content"
             android:orientation="vertical">
 
-            <FrameLayout
-                android:id="@+id/volume_drawer_vibrate"
-                android:layout_width="@dimen/volume_ringer_item_size"
-                android:layout_height="@dimen/volume_ringer_item_size"
-                android:layout_marginBottom="8dp"
-                android:contentDescription="@string/volume_ringer_hint_vibrate"
-                android:gravity="center"
-                android:background="@drawable/volume_ringer_item_bg">
-
-                <ImageView
-                    android:id="@+id/volume_drawer_vibrate_icon"
-                    android:layout_width="@dimen/volume_ringer_icon_size"
-                    android:layout_height="@dimen/volume_ringer_icon_size"
-                    android:layout_gravity="center"
-                    android:src="@drawable/ic_volume_ringer_vibrate"
-                    android:tint="?androidprv:attr/materialColorOnSurface" />
-
-            </FrameLayout>
-
-            <FrameLayout
-                android:id="@+id/volume_drawer_mute"
-                android:layout_width="@dimen/volume_ringer_item_size"
-                android:layout_height="@dimen/volume_ringer_item_size"
-                android:layout_marginBottom="8dp"
-                android:accessibilityTraversalAfter="@id/volume_drawer_vibrate"
-                android:contentDescription="@string/volume_ringer_hint_mute"
-                android:gravity="center"
-                android:background="@drawable/volume_ringer_item_bg">
-
-                <ImageView
-                    android:id="@+id/volume_drawer_mute_icon"
-                    android:layout_width="@dimen/volume_ringer_icon_size"
-                    android:layout_height="@dimen/volume_ringer_icon_size"
-                    android:layout_gravity="center"
-                    android:src="@drawable/ic_speaker_mute"
-                    android:tint="?androidprv:attr/materialColorOnSurface" />
-
-            </FrameLayout>
-
-            <FrameLayout
-                android:id="@+id/volume_drawer_normal"
-                android:layout_width="@dimen/volume_ringer_item_size"
-                android:layout_height="@dimen/volume_ringer_item_size"
-                android:layout_marginBottom="8dp"
-                android:accessibilityTraversalAfter="@id/volume_drawer_mute"
-                android:contentDescription="@string/volume_ringer_hint_unmute"
-                android:gravity="center"
-                android:background="@drawable/volume_ringer_item_bg">
-
-                <ImageView
-                    android:id="@+id/volume_drawer_normal_icon"
-                    android:layout_width="@dimen/volume_ringer_icon_size"
-                    android:layout_height="@dimen/volume_ringer_icon_size"
-                    android:layout_gravity="center"
-                    android:src="@drawable/ic_speaker_on"
-                    android:tint="?androidprv:attr/materialColorOnSurface" />
-
-            </FrameLayout>
+            <!-- add ringer buttons here -->
 
         </LinearLayout>
 
@@ -117,23 +57,16 @@
 
     <!-- The current ringer selection. When the drawer is opened, this animates to the corresponding
          position in the drawer. When the drawer is closed, it animates back. -->
-    <FrameLayout
-        android:id="@+id/volume_new_ringer_active_icon_container"
-        android:layout_width="@dimen/volume_ringer_item_size"
-        android:layout_height="@dimen/volume_ringer_item_size"
-        android:layout_marginBottom="8dp"
-        android:layout_gravity="bottom|right"
+    <ImageButton
+        android:id="@+id/volume_new_ringer_active_button"
+        android:layout_width="@dimen/volume_dialog_ringer_drawer_button_size"
+        android:layout_height="@dimen/volume_dialog_ringer_drawer_button_size"
+        android:layout_marginBottom="@dimen/volume_dialog_components_spacing"
+        android:background="@drawable/volume_drawer_selection_bg"
         android:contentDescription="@string/volume_ringer_change"
-        android:background="@drawable/volume_drawer_selection_bg">
-
-        <ImageView
-            android:id="@+id/volume_new_ringer_active_icon"
-            android:layout_width="@dimen/volume_ringer_icon_size"
-            android:layout_height="@dimen/volume_ringer_icon_size"
-            android:layout_gravity="center"
-            android:tint="?androidprv:attr/materialColorOnPrimary"
-            android:src="@drawable/ic_volume_media" />
-
-    </FrameLayout>
+        android:gravity="center"
+        android:padding="@dimen/volume_dialog_ringer_horizontal_padding"
+        android:src="@drawable/ic_volume_media"
+        android:tint="?androidprv:attr/materialColorOnPrimary" />
 
 </FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 9ec6106..c091cbf 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Neem jou skerm op?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Neem een app op"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Neem hele skerm op"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Wanneer jy jou hele skerm opneem, word enigiets wat op jou skerm wys, opgeneem. Wees dus versigtig met dinge soos wagwoorde, betalingbesonderhede, boodskappe, foto’s, en oudio en video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Wanneer jy ’n app opneem, word enigiets wat in daardie app gewys of gespeel word, opgeneem. Wees dus versigtig met dinge soos wagwoorde, betalingbesonderhede, boodskappe, foto’s, en oudio en video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Neem skerm op"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Huidige app"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Toeganklikheid"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Kortpadsleutels"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Soekkortpaaie"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Geen soekresultate nie"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Vou ikoon in"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Vou ikoon uit"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"of"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Sleephandvatsel"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 598e224..af2971b 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"ማያ ገፅዎን ይቀዳሉ?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"አንድ መተግበሪያ ቅዳ"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"መላው ማያ ገፅን ቅረጽ"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"መላው ማያ ገፅዎን በሚቀዱበት ጊዜ፣ በማያ ገፅዎ ላይ የሚታየው ማንኛውም ነገር ይቀዳል። ስለዚህ እንደ የይለፍ ቃላት፣ የክፍያ ዝርዝሮች፣ መልዕክቶች፣ ፎቶዎች እና ኦዲዮ እና ቪድዮ ላሉ ነገሮች ጥንቃቄ ያድርጉ።"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"መተግበሪያን ሲቀዱ በዚያ መተግበሪያ ውስጥ የሚታይ ወይም የሚጫወት ማንኛውም ነገር ይቀዳል። ስለዚህ እንደ የይለፍ ቃላት፣ የክፍያ ዝርዝሮች፣ መልዕክቶች፣ ፎቶዎች እና ኦዲዮ እና ቪድዮ ላሉ ነገሮች ጥንቃቄ ያድርጉ።"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"ማያ ገፅን ቅረጽ"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"የአሁን መተግበሪያ"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ተደራሽነት"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"የቁልፍ ሰሌዳ አቋራጮች"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"የፍለጋ አቋራጮች"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ምንም የፍለጋ ውጤቶች የሉም"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"መሰብሰቢያ አዶ"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"መዘርጊያ አዶ"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ወይም"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"መያዣ ይጎትቱ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 342c8c2..4ebac5a 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"هل تريد تسجيل محتوى الشاشة؟"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"تسجيل محتوى تطبيق واحد"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"تسجيل محتوى الشاشة بالكامل"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"أثناء تسجيل محتوى الشاشة بالكامل، يتم تسجيل كل المحتوى المعروض على شاشتك. لذا يُرجى توخي الحذر بشأن المعلومات، مثل كلمات المرور وتفاصيل الدفع والرسائل والصور وملفات الصوت والفيديو."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"أثناء تسجيل محتوى تطبيق، يتم تسجيل أي محتوى يتم عرضه أو تشغيله في ذلك التطبيق. لذا يُرجى توخي الحذر بشأن المعلومات، مثل كلمات المرور وتفاصيل الدفع والرسائل والصور وملفات الصوت والفيديو."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"تسجيل محتوى الشاشة"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"التطبيق الحالي"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"تسهيل الاستخدام"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"اختصارات لوحة المفاتيح"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"اختصارات طلبات البحث"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ما مِن نتائج بحث"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"رمز التصغير"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"رمز التوسيع"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"أو"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"مقبض السحب"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index b71db2e..85517f3 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"আপোনাৰ স্ক্ৰীনখন ৰেকৰ্ড কৰিবনে?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"এটা এপ্ ৰেকৰ্ড কৰক"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"গোটেই স্ক্ৰীনখন ৰেকৰ্ড কৰক"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"আপুনি গোটেই স্ক্ৰীনখন ৰেকৰ্ডিং কৰিলে, আপোনাৰ স্ক্ৰীনখনত দেখুওৱা যিকোনো বস্তু ৰেকৰ্ড কৰা হয়। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা, ফট’ আৰু অডিঅ’ আৰু ভিডিঅ’ৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"আপুনি কোনো এপ্ ৰেকৰ্ড কৰিলে, সেই এপত দেখুওৱা বা প্লে’ কৰা যিকোনো বস্তু ৰেকৰ্ড কৰা হয়। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা, ফট’ আৰু অডিঅ’ আৰু ভিডিঅ’ৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"স্ক্ৰীনখন ৰেকৰ্ড কৰক"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"আটাইবোৰ মচক"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"পৰিচালনা কৰক"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ইতিহাস"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"জাননীৰ ছেটিং"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"জাননীৰ ইতিহাস"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"নতুন"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"নীৰৱ"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"জাননীসমূহ"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"বৰ্তমানৰ এপ্‌"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"সাধ্য সুবিধা"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"কীব’ৰ্ডৰ শ্বৰ্টকাট"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"সন্ধানৰ শ্বৰ্টকাট"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"সন্ধানৰ কোনো ফলাফল নাই"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"সংকোচন কৰাৰ চিহ্ন"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"বিস্তাৰ কৰাৰ চিহ্ন"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"অথবা"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ড্ৰেগ হেণ্ডেল"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index d51c9aa..f08724a 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Ekran qeydə alınsın?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Bir tətbiqi qeydə alın"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Bütün ekranı qeydə alın"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Bütün ekranı qeydə alarkən ekranda göstərilən bütün kontent qeydə alınır. Parol, ödəniş detalları, mesaj, foto, habelə audio və video kimi məlumatlarla bağlı diqqətli olun."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Tətbiq qeydə aldıqda həmin tətbiqdə göstərilən və ya işə salınan bütün kontent qeydə alınır. Parol, ödəniş detalları, mesaj, foto, habelə audio və video kimi məlumatlarla bağlı diqqətli olun."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Ekranı qeydə alın"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Cari tətbiq"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Xüsusi imkanlar"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Klaviatura qısayolları"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Axtarış qısayolları"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Axtarış nəticəsi yoxdur"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"İkonanı yığcamlaşdırın"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"İkonanı genişləndirin"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"və ya"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Dəstəyi çəkin"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 881495f..f509691 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Želite da snimite ekran?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Snimi jednu aplikaciju"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Snimi ceo ekran"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Kada snimate ceo ekran, snima se sve što je na njemu. Zato pazite na lozinke, informacije o plaćanju, poruke, slike, audio i video sadržaj."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Kada snimate aplikaciju, snima se sav sadržaj koji se prikazuje ili pušta u njoj. Zato pazite na lozinke, informacije o plaćanju, poruke, slike, audio i video sadržaj."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Snimi ekran"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Obriši sve"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Upravljaj"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Istorija"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Podešavanja obaveštenja"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"Istorija obaveštenja"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Novo"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Nečujno"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Obaveštenja"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aktuelna aplikacija"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Pristupačnost"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Tasterske prečice"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Prečice pretrage"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nema rezultata pretrage"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona za skupljanje"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona za proširivanje"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ili"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Marker za prevlačenje"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 16bb70c..bc0e2d1 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Запісаць экран?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Запісаць адну праграму"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Запісаць змесціва ўсяго экрана"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Пры запісе ўсяго экрана запісваецца ўсё, што паказваецца на экране. Таму прадухіліце паказ пароляў, плацежных рэквізітаў, паведамленняў, фота, відэа і аўдыя."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Пры запісе праграмы запісваецца ўсё, што паказваецца або прайграецца ў гэтай праграме. Таму прадухіліце паказ пароляў, плацежных рэквізітаў, паведамленняў, фота, відэа і аўдыя."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Запісаць экран"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Бягучая праграма"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Спецыяльныя магчымасці"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Спалучэнні клавіш"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Пошук спалучэнняў клавіш"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Няма вынікаў пошуку"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Значок \"Згарнуць\""</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Значок \"Разгарнуць\""</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"або"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Маркер перацягвання"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 2b3a0b9..2ee1f5f 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Да се записва ли екранът?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Записване на едно приложение"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Записване на целия екран"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Когато записвате целия си екран, се записва всичко, което се показва на него. Затова бъдете внимателни с неща като пароли, подробности за начини на плащане, съобщения, снимки, аудио и видео."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Когато записвате приложение, се записва всичко, което се показва или възпроизвежда в него. Затова бъдете внимателни с неща като пароли, подробности за начини на плащане, съобщения, снимки, аудио и видео."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Записване на екрана"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Текущо приложение"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Достъпност"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Клавишни комбинации"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Търсете клавишни комбинации"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Няма резултати от търсенето"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Икона за свиване"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Икона за разгъване"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"или"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Манипулатор за преместване с плъзгане"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index f39a644..c593210 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"আপনার স্ক্রিন রেকর্ড করবেন?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"একটি অ্যাপ রেকর্ড করুন"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"সম্পূর্ণ স্ক্রিন রেকর্ড করুন"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"আপনার সম্পূর্ণ স্ক্রিন রেকর্ড করার সময়, আপনার স্ক্রিনে দেখানো সব কিছু রেকর্ড করা হয়। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ, ফটো এবং অডিও ও ভিডিওর মতো বিষয়ের ক্ষেত্রে সতর্ক থাকুন।"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"আপনি কোনও অ্যাপ রেকর্ড করার সময়, সেই অ্যাপে দেখানো বা চালানো সব কিছু রেকর্ড করা হয়। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ, ফটো এবং অডিও ও ভিডিওর মতো বিষয়ের ক্ষেত্রে সতর্ক থাকুন।"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"স্ক্রিন রেকর্ড করুন"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"সব মুছে দিন"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ম্যানেজ করুন"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ইতিহাস"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"বিজ্ঞপ্তি সেটিংস"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"বিজ্ঞপ্তির ইতিহাস"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"নতুন"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"আওয়াজ করবে না"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"বিজ্ঞপ্তি"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"বর্তমান অ্যাপ"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"অ্যাক্সেসিবিলিটি"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"কীবোর্ড শর্টকাট"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"সার্চ শর্টকাট"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"কোনও সার্চ ফলাফল নেই"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"আইকন আড়াল করুন"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"আইকন বড় করুন"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"অথবা"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"টেনে আনার হ্যান্ডেল"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index ec57c6a..38f4265 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Snimati ekran?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Snimaj jednu aplikaciju"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Snimaj cijeli ekran"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Kada snimate cijeli ekran, snimat će se sve što se prikazuje na ekranu. Stoga budite oprezni s informacijama kao što su lozinke, podaci o plaćanju, poruke, fotografije, zvukovi i videozapisi."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Kada snimate aplikaciju, snimat će se sve što se prikazuje ili reproducira u toj aplikaciji. Stoga budite oprezni s informacijama kao što su lozinke, podaci o plaćanju, poruke, fotografije, zvukovi i videozapisi."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Snimaj ekran"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Obriši sve"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Upravljajte"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historija"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Postavke obavještenja"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"Historija obavještenja"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Novo"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Nečujno"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Obavještenja"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Trenutna aplikacija"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Pristupačnost"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Prečice tastature"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Prečica pretraživanja"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nema rezultata pretraživanja"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona sužavanja"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona proširivanja"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ili"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ručica za prevlačenje"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 9c9ba90..bcaca5a 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Vols gravar la pantalla?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Grava una aplicació"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Grava tota la pantalla"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Quan graves tota la pantalla, es grava tot el que es mostra en pantalla. Per aquest motiu, ves amb compte amb elements com les contrasenyes, les dades de pagament, els missatges, les fotos, i l\'àudio i el vídeo."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Quan graves una aplicació, es grava tot el que es mostra o es reprodueix en aquesta aplicació. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges, les fotos, i l\'àudio i el vídeo."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Grava la pantalla"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aplicació actual"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibilitat"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Tecles de drecera"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Dreceres de cerca"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No hi ha cap resultat de la cerca"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Replega la icona"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Desplega la icona"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"o"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ansa per arrossegar"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 95f3699..76ae86d 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Pořídit nahrávku obrazovky?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Nahrát jednu aplikaci"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Nahrát celou obrazovku"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Při nahrávání celé obrazovky se zaznamenává veškerý obsah na obrazovce. Buďte proto opatrní, když jde o hesla, platební údaje, zprávy, fotografie, zvuk a video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Při nahrávání aplikace se zaznamenává všechno, co se v dané obrazovce zobrazuje nebo přehrává. Buďte proto opatrní, když jde o hesla, platební údaje, zprávy, fotografie, zvuk a video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Nahrát obrazovku"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aktuální aplikace"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Přístupnost"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Klávesové zkratky"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Vyhledat zkratky"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Žádné výsledky hledání"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona sbalení"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona rozbalení"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"nebo"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Úchyt pro přetažení"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 8266799..abcd4b8 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Vil du optage din skærm?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Optag én app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Optag hele skærmen"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Når du optager hele skærmen, bliver alt det, der vises på skærmen, optaget. Vær derfor forsigtig med ting såsom adgangskoder, betalingsoplysninger, beskeder, billeder, lyd og video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Når du optager en app, optages alt det, der vises eller afspilles i den pågældende app. Vær derfor forsigtig med ting såsom adgangskoder, betalingsoplysninger, beskeder, billeder, lyd og video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Optag skærm"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Ryd alle"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Administrer"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historik"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Indstillinger for notifikationer"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"Notifikationshistorik"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Nye"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Lydløs"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notifikationer"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aktuel app"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Hjælpefunktioner"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Tastaturgenveje"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Genveje til søgning"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Der er ingen søgeresultater"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikon for Skjul"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikon for Udvid"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"eller"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Håndtag"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index a4bc364..9799a932 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Bildschirm aufnehmen?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Einzelne App aufnehmen"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Gesamten Bildschirm aufnehmen"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Wenn du den gesamten Bildschirm aufnimmst, ist in der Aufnahme alles zu sehen, was auf dem Bildschirm angezeigt wird. Sei also vorsichtig mit Informationen wie Passwörtern, Zahlungsdetails, Nachrichten, Fotos sowie Audio- und Videoinhalten."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Wenn du eine App aufnimmst, ist in der Aufnahme alles zu sehen, was in dieser App angezeigt oder abgespielt wird. Sei also vorsichtig mit Informationen wie Passwörtern, Zahlungsdetails, Nachrichten, Fotos sowie Audio- und Videoinhalten."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Bildschirm aufnehmen"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aktuelle App"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Bedienungshilfen"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Tastenkürzel"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Tastenkürzel suchen"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Keine Suchergebnisse"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Symbol „Minimieren“"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Symbol „Maximieren“"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"oder"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ziehpunkt"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 278a8ea..7200a91 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Να γίνει εγγραφή της οθόνης σας;"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Εγγραφή μίας εφαρμογής"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Εγγραφή ολόκληρης της οθόνης"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Όταν κάνετε εγγραφή ολόκληρης της οθόνη σας, καταγράφεται οτιδήποτε εμφανίζεται σε αυτήν. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα, τις φωτογραφίες, τον ήχο και το βίντεο."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Όταν κάνετε εγγραφή μιας εφαρμογής, καταγράφεται οτιδήποτε εμφανίζεται ή αναπαράγεται στη συγκεκριμένη εφαρμογή. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα, τις φωτογραφίες, τον ήχο και το βίντεο."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Εγγραφή οθόνης"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Τρέχουσα εφαρμογή"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Προσβασιμότητα"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Συντομεύσεις πληκτρολογίου"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Συντομεύσεις αναζήτησης"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Κανένα αποτέλεσμα αναζήτησης"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Εικονίδιο σύμπτυξης"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Εικονίδιο ανάπτυξης"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ή"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Λαβή μεταφοράς"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 2fbd60a..1ecf4f1 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Record your screen?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Record one app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Record entire screen"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"When you\'re recording your entire screen, anything displayed on your screen is recorded. So, be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"When you\'re recording an app, anything displayed or played in that app is recorded. So, be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Record screen"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Current app"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibility"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Keyboard shortcuts"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Search shortcuts"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No search results"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Collapse icon"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Expand icon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"or"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Drag handle"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 23475fa..f775513 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Record your screen?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Record one app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Record entire screen"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"When you’re recording your entire screen, anything shown on your screen is recorded. So be careful with things like passwords, payment details, messages, photos, and audio and video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"When you’re recording an app, anything shown or played in that app is recorded. So be careful with things like passwords, payment details, messages, photos, and audio and video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Record screen"</string>
@@ -136,17 +138,14 @@
     <string name="screenrecord_stop_dialog_message_specific_app" msgid="5995770227684523244">"You\'re currently recording <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="screenrecord_stop_dialog_button" msgid="2883812564938194350">"Stop recording"</string>
     <string name="share_to_app_chip_accessibility_label" msgid="4210256229976947065">"Sharing screen"</string>
-    <!-- no translation found for share_to_app_chip_accessibility_label_generic (5517431657924536133) -->
-    <skip />
+    <string name="share_to_app_chip_accessibility_label_generic" msgid="5517431657924536133">"Sharing content"</string>
     <string name="share_to_app_stop_dialog_title" msgid="9212915050910250438">"Stop sharing screen?"</string>
-    <!-- no translation found for share_to_app_stop_dialog_title_generic (9079161538135843648) -->
-    <skip />
+    <string name="share_to_app_stop_dialog_title_generic" msgid="9079161538135843648">"Stop sharing?"</string>
     <string name="share_to_app_stop_dialog_message_entire_screen_with_host_app" msgid="522823522115375414">"You\'re currently sharing your entire screen with <xliff:g id="HOST_APP_NAME">%1$s</xliff:g>"</string>
     <string name="share_to_app_stop_dialog_message_entire_screen" msgid="5090115386271179270">"You\'re currently sharing your entire screen with an app"</string>
     <string name="share_to_app_stop_dialog_message_single_app_specific" msgid="5923772039347985172">"You\'re currently sharing <xliff:g id="APP_BEING_SHARED_NAME">%1$s</xliff:g>"</string>
     <string name="share_to_app_stop_dialog_message_single_app_generic" msgid="6681016774654578261">"You\'re currently sharing an app"</string>
-    <!-- no translation found for share_to_app_stop_dialog_message_generic (7622174291691249392) -->
-    <skip />
+    <string name="share_to_app_stop_dialog_message_generic" msgid="7622174291691249392">"You\'re currently sharing with an app"</string>
     <string name="share_to_app_stop_dialog_button" msgid="6334056916284230217">"Stop sharing"</string>
     <string name="cast_screen_to_other_device_chip_accessibility_label" msgid="4687917476203009885">"Casting screen"</string>
     <string name="cast_to_other_device_stop_dialog_title" msgid="7836517190930357326">"Stop casting?"</string>
@@ -521,10 +520,8 @@
     <string name="communal_widget_picker_title" msgid="1953369090475731663">"Lock screen widgets"</string>
     <string name="communal_widget_picker_description" msgid="490515450110487871">"Anyone can view widgets on your lock screen, even if your tablet\'s locked."</string>
     <string name="accessibility_action_label_unselect_widget" msgid="1041811747619468698">"unselect widget"</string>
-    <!-- no translation found for accessibility_action_label_shrink_widget (8259511040536438771) -->
-    <skip />
-    <!-- no translation found for accessibility_action_label_expand_widget (9190524260912211759) -->
-    <skip />
+    <string name="accessibility_action_label_shrink_widget" msgid="8259511040536438771">"Decrease height"</string>
+    <string name="accessibility_action_label_expand_widget" msgid="9190524260912211759">"Increase height"</string>
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Lock screen widgets"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"To open an app using a widget, you’ll need to verify it’s you. Also, keep in mind that anyone can view them, even when your tablet’s locked. Some widgets may not have been intended for your lock screen and may be unsafe to add here."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Got it"</string>
@@ -1411,9 +1408,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Current App"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibility"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Keyboard shortcuts"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Search shortcuts"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No search results"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Collapse icon"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Expand icon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"or"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Drag handle"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 2fbd60a..1ecf4f1 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Record your screen?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Record one app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Record entire screen"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"When you\'re recording your entire screen, anything displayed on your screen is recorded. So, be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"When you\'re recording an app, anything displayed or played in that app is recorded. So, be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Record screen"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Current app"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibility"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Keyboard shortcuts"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Search shortcuts"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No search results"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Collapse icon"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Expand icon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"or"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Drag handle"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 2fbd60a..1ecf4f1 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Record your screen?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Record one app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Record entire screen"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"When you\'re recording your entire screen, anything displayed on your screen is recorded. So, be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"When you\'re recording an app, anything displayed or played in that app is recorded. So, be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Record screen"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Current app"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibility"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Keyboard shortcuts"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Search shortcuts"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No search results"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Collapse icon"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Expand icon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"or"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Drag handle"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 9ea1540..77c2a63 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"¿Quieres grabar la pantalla?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Grabar una app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Grabar toda la pantalla"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Cuando grabes toda la pantalla, se grabará todo lo que se muestre en ella. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes, fotos, audios y videos."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Cuando grabes una app, se registrará todo lo que se muestre o reproduzca en ella. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes, fotos, audios y videos."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Grabar pantalla"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Cerrar todo"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Administrar"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Configuración de notificaciones"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"Historial de notificaciones"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Nuevo"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Silenciadas"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notificaciones"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"App actual"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accesibilidad"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Combinaciones de teclas"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Buscar combinaciones de teclas"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"La búsqueda no arrojó resultados"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ícono de contraer"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ícono de expandir"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"o"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Controlador de arrastre"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 16ffd44..81b1562 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"¿Grabar la pantalla?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Grabar una aplicación"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Grabar toda la pantalla"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Cuando grabas toda la pantalla, se graba todo lo que se muestre en ella. Debes tener cuidado con elementos como contraseñas, detalles de pagos, mensajes, fotos, audio y vídeo."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Cuando grabas una aplicación, se graba todo lo que se muestre o reproduzca en ella. Debes tener cuidado con elementos como contraseñas, detalles de pagos, mensajes, fotos, audio y vídeo."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Grabar pantalla"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aplicación en uso"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accesibilidad"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Combinaciones de teclas"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Atajos de búsqueda"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No hay resultados de búsqueda"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icono de contraer"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icono de desplegar"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"o"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Controlador de arrastre"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 5c9664b..56039da5 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Kas salvestada ekraanikuvast video?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Ühe rakenduse salvestamine"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Kogu ekraanikuva salvestamine"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Kui salvestate kogu ekraani, salvestatakse kõik ekraanil kuvatud andmed. Seega olge ettevaatlik selliste andmetega nagu paroolid, makseteave, sõnumid, fotod ning heli ja video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Kui salvestate rakendust, salvestatakse kõik, mida selles rakenduses näidatakse või esitatakse. Seega olge ettevaatlik selliste andmetega nagu paroolid, makseteave, sõnumid, fotod ning heli ja video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Ekraanikuva jäädvustamine"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Praegune rakendus"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Juurdepääsetavus"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Klaviatuuri otseteed"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Otsingu otseteed"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Otsingutulemused puuduvad"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ahendamisikoon"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Laiendamisikoon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"või"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Lohistamispide"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 31348b6..426d1d7 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Pantaila grabatu nahi duzu?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Grabatu aplikazio bat"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Grabatu pantaila osoa"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Pantaila osoa grabatzen ari zarenean, pantailan agertzen den guztia grabatzen da. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin, argazkiekin, audioekin eta bideoekin, besteak beste."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Aplikazio bat grabatzen ari zarenean, aplikazio horretan agertzen den edo bertan erreproduzitzen ari den guztia grabatzen da. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin, argazkiekin, audioekin eta bideoekin, besteak beste."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Grabatu pantaila"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Oraingo aplikazioa"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Erabilerraztasuna"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Lasterbideak"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Bilatu lasterbideak"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ez dago bilaketa-emaitzarik"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Tolesteko ikonoa"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Zabaltzeko ikonoa"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"edo"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Arrastatzeko kontrol-puntua"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index bc89200..b0d23aa 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"صفحه‌نمایش ضبط شود؟"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"ضبط یک برنامه"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"ضبط کل صفحه‌نمایش"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"وقتی کل صفحه‌نمایش را ضبط می‌کنید، هر چیزی که در صفحه‌نمایش نشان داده شود ضبط خواهد شد. درنتیجه مراقب چیزهایی مثل گذرواژه‌ها، جزئیات پرداخت، پیام‌ها، عکس‌ها، و صدا و تصویر باشید."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"وقتی برنامه‌ای را ضبط می‌کنید، هر چیزی که در آن برنامه نشان داده شود یا پخش شود ضبط خواهد شد. درنتیجه مراقب چیزهایی مثل گذرواژه‌ها، جزئیات پرداخت، پیام‌ها، عکس‌ها، و صدا و تصویر باشید."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"ضبط صفحه‌نمایش"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"پاک کردن همه موارد"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"مدیریت"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"سابقه"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"تنظیمات اعلان"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"سابقه اعلان"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"جدید"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"بی‌صدا"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"اعلان‌ها"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"برنامه فعلی"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"دسترس‌پذیری"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"میان‌برهای صفحه‌کلید"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"جستجوی میان‌برها"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"نتیجه‌ای برای جستجو پیدا نشد"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"نماد جمع کردن"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"نماد ازهم بازکردن"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"یا"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"دستگیره کشاندن"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index a40cbae..690228f 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Tallennetaanko näytön toimintaa?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Tallenna yhdestä sovelluksesta"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Tallenna koko näyttö"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Kun tallennat koko näyttöä, kaikki näytöllä näkyvä sisältö tallennetaan. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä, kuvia, audiota tai videoita."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Kun tallennat sovellusta, kaikki sovelluksessa näkyvä tai toistettu sisältö tallennetaan. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä, kuvia, audiota tai videoita."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Tallenna näyttö"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Nykyinen sovellus"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Saavutettavuus"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Pikanäppäimet"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pikahaut"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ei hakutuloksia"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Tiivistyskuvake"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Laajennuskuvake"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"tai"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Vetokahva"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 288be34..5ea58ce 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Enregistrer votre écran?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Enregistrer une appli"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Enregistrer l\'écran entier"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Lorsque vous enregistrez l\'intégralité de votre écran, tout ce qui s\'affiche sur votre écran est enregistré. Par conséquent, soyez prudent avec les mots de passe, les détails du mode de paiement, les messages, les photos et les contenus audio et vidéo."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Lorsque vous enregistrez une appli, tout ce qui est affiché ou lu dans cette appli est enregistré. Par conséquent, soyez prudent avec les mots de passe, les détails du mode de paiement, les messages, les photos et les contenus audio et vidéo."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Enregistrer l\'écran"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Appli actuelle"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibilité"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Raccourcis-clavier"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Recherchez des raccourcis"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Aucun résultat de recherche"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icône Réduire"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icône Développer"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Poignée de déplacement"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 9d9f3f9..2be31b1 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Enregistrer l\'écran ?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Enregistrer une appli"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Enregistrer tout l\'écran"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Lorsque vous enregistrez l\'intégralité de votre écran, tout ce qui s\'y affiche est enregistré. Faites donc attention aux éléments tels que les mots de passe, les détails du mode de paiement, les messages, les photos, et les contenus audio et vidéo."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Lorsque vous enregistrez une appli, tout ce qui est affiché ou lu dans celle-ci est enregistré. Faites donc attention aux éléments tels que les mots de passe, détails de mode de paiement, messages, photos et contenus audio et vidéo."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Enregistrer l\'écran"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Appli actuelle"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibilité"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Raccourcis clavier"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Raccourcis de recherche"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Aucun résultat de recherche"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icône Réduire"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icône Développer"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Poignée de déplacement"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 1430bc2..2bd0b30 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Queres gravar a túa pantalla?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Gravar unha aplicación"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Gravar pantalla completa"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Cando gravas a pantalla completa, recóllese todo o que se mostra nela. Recomendámosche que teñas coidado con determinada información, como os contrasinais, os detalles de pago, as mensaxes e as fotos, así como co contido de audio e de vídeo."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Cando gravas unha aplicación, recóllese todo o que se mostra ou reproduce nela. Recomendámosche que teñas coidado con determinada información, como os contrasinais, os detalles de pago, as mensaxes e as fotos, así como co contido de audio e de vídeo."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Gravar pantalla"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aplicación actual"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accesibilidade"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Atallos de teclado"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Atallos de busca"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Non hai resultados de busca"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icona de contraer"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icona de despregar"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Controlador de arrastre"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index bc2739f..becca8f 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"તમારી સ્ક્રીન રેકોર્ડ કરીએ?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"એક ઍપ રેકોર્ડ કરો"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"પૂર્ણ સ્ક્રીન રેકોર્ડ કરો"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"જ્યારે તમે તમારી પૂર્ણ સ્ક્રીન રેકોર્ડ કરી રહ્યાં હો, ત્યારે તમારી સ્ક્રીન પર બતાવવામાં આવતી હોય તેવી બધી વસ્તુ રેકોર્ડ કરવામાં આવે છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ, ફોટા અને ડિવાઇસ પર વાગી રહેલા ઑડિયો તથા વીડિયો જેવી બાબતોને લઈને સાવચેત રહો."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"જ્યારે તમે કોઈ ઍપને રેકોર્ડ કરી રહ્યાં હો, ત્યારે એ ઍપમાં બતાવવામાં કે ચલાવવામાં આવતી હોય તેવી બધી વસ્તુ રેકોર્ડ કરવામાં આવે છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ, ફોટા અને ડિવાઇસ પર વાગી રહેલા ઑડિયો તથા વીડિયો જેવી બાબતોને લઈને સાવચેત રહો."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"સ્ક્રીન રેકોર્ડ કરો"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"બધુ સાફ કરો"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"મેનેજ કરો"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ઇતિહાસ"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"નોટિફિકેશનના સેટિંગ"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"નોટિફિકેશનનો ઇતિહાસ"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"નવા"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"સાઇલન્ટ"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"નોટિફિકેશન"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"હાલની ઍપ"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ઍક્સેસિબિલિટી"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"કીબોર્ડ શૉર્ટકટ"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"શૉર્ટકટ શોધો"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"કોઈ શોધ પરિણામો નથી"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"\'નાનું કરો\'નું આઇકન"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"\'મોટું કરો\'નું આઇકન"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"અથવા"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ઑબ્જેક્ટ ખેંચવાનું હૅન્ડલ"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index f9a1fa5..a6fc05e 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"क्या आपको स्क्रीन रिकॉर्ड करनी है?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"एक ऐप्लिकेशन की रिकॉर्डिंग करें"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"पूरी स्क्रीन रिकॉर्ड करें"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"पूरी स्क्रीन रिकॉर्ड करते समय, स्क्रीन पर दिखने वाली हर चीज़ रिकॉर्ड की जाती है. इसलिए पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज,  डिवाइस पर चल रहे ऑडियो और वीडियो, और फ़ोटो जैसी चीज़ों को लेकर सावधानी बरतें."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"किसी ऐप्लिकेशन को रिकॉर्ड करने के दौरान, उस पर दिख रहा कॉन्टेंट या चल रहा मीडिया दूसरी स्क्रीन पर भी रिकॉर्ड होता है. इसलिए, रिकॉर्ड करते समय पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज, फ़ोटो, ऑडियो, और वीडियो को लेकर सावधानी बरतें."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"स्क्रीन रिकॉर्ड करें"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"मौजूदा ऐप्लिकेशन"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"सुलभता"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"कीबोर्ड शॉर्टकट"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"सर्च शॉर्टकट"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"खोज का कोई नतीजा नहीं मिला"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"छोटा करने का आइकॉन"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"बड़ा करने का आइकॉन"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"या"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"खींचकर छोड़ने वाला हैंडल"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 4a17fdc..cb7a193 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Želite li snimati zaslon?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Snimanje jedne aplikacije"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Snimanje cijelog zaslona"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Kad snimate cijeli zaslon, snima se sve što se prikazuje na zaslonu. Stoga pazite na stvari kao što su zaporke, podaci o plaćanju, poruke, fotografije te audio i videozapisi."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Kad snimate aplikaciju, snima se sve što se prikazuje ili reproducira u toj aplikaciji. Stoga pazite na stvari kao što su zaporke, podaci o plaćanju, poruke, fotografije te audio i videozapisi."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Snimanje zaslona"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Trenutačna aplikacija"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Pristupačnost"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Tipkovni prečaci"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Prečaci za pretraživanje"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nema rezultata pretraživanja"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona za sažimanje"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona za proširivanje"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ili"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Marker za povlačenje"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index ff5339f..b09947f 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Rögzíti a képernyőt?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Egyetlen alkalmazás rögzítése"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Teljes képernyő rögzítése"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"A teljes képernyő rögzítése esetén a képernyőn megjelenő minden tartalom rögzítésre kerül. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel, a fotókkal, valamint a hang- és videófelvételekkel."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Alkalmazás rögzítésekor az adott alkalmazásban megjelenített vagy lejátszott minden tartalom rögzítésre kerül. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel, a fotókkal, valamint a hang- és videófelvételekkel."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Képernyő rögzítése"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Jelenlegi alkalmazás"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Kisegítő lehetőségek"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Billentyűparancsok"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Billentyűparancsok keresése"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nincsenek keresési találatok"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Összecsukás ikon"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Kibontás ikon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"vagy"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Fogópont"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 8fb1347..174b526 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Տեսագրե՞լ ձեր էկրանը"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Տեսագրել մեկ հավելված"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Տեսագրել ամբողջ էկրանը"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Երբ դուք տեսագրում եք ամբողջ էկրանը, էկրանին ցուցադրվող ամեն ինչ տեսագրվում է։ Ուստի ուշադիր եղեք այնպիսի բաների հետ, ինչպիսիք են գաղտնաբառերը, վճարային տվյալները, հաղորդագրությունները, լուսանկարները, աուդիո և վիդեո բովանդակությունը։"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Երբ դուք որևէ հավելված եք տեսագրում, հավելվածում ցուցադրվող կամ նվագարկվող ամեն ինչ տեսագրվում է։ Ուստի ուշադիր եղեք այնպիսի բաների հետ, ինչպիսիք են գաղտնաբառերը, վճարային տվյալները, հաղորդագրությունները, լուսանկարները, աուդիո և վիդեո բովանդակությունը։"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Տեսագրել էկրանը"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Այս հավելվածը"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Հատուկ գործառույթներ"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Ստեղնային դյուրանցումներ"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Դյուրանցումների որոնում"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Որոնման արդյունքներ չկան"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ծալել պատկերակը"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ծավալել պատկերակը"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"կամ"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Տեղափոխման նշիչ"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 0b316be..e706b27 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Rekam layar Anda?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Rekam satu aplikasi"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Rekam seluruh layar"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Saat Anda merekam seluruh layar, semua hal yang ditampilkan di layar akan direkam. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, foto, audio, dan video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Jika Anda merekam aplikasi, semua hal yang ditampilkan atau diputar di aplikasi tersebut akan direkam. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, foto, audio, dan video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Rekam layar"</string>
@@ -409,7 +411,7 @@
     <string name="quick_settings_onehanded_label" msgid="2416537930246274991">"Mode satu tangan"</string>
     <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"Alat bantu dengar"</string>
     <string name="quick_settings_hearing_devices_connected" msgid="6519069502397037781">"Aktif"</string>
-    <string name="quick_settings_hearing_devices_disconnected" msgid="8907061223998176187">"Terputus"</string>
+    <string name="quick_settings_hearing_devices_disconnected" msgid="8907061223998176187">"Tidak terhubung"</string>
     <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"Alat bantu dengar"</string>
     <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Sambungkan perangkat baru"</string>
     <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Klik untuk menyambungkan perangkat baru"</string>
@@ -1190,7 +1192,7 @@
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 perangkat dipilih"</string>
     <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> perangkat dipilih"</string>
-    <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(terputus)"</string>
+    <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(tidak terhubung)"</string>
     <string name="media_output_dialog_connect_failed" msgid="3080972621975339387">"Tidak dapat beralih. Ketuk untuk mencoba lagi."</string>
     <string name="media_output_dialog_pairing_new" msgid="5098212763195577270">"Hubungkan perangkat"</string>
     <string name="media_output_dialog_launch_app_text" msgid="1527413319632586259">"Buka aplikasi untuk mentransmisikan sesi ini."</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aplikasi Saat Ini"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Aksesibilitas"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Pintasan keyboard"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pintasan penelusuran"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Tidak ada hasil penelusuran"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikon ciutkan"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikon luaskan"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"atau"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Handel geser"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 700ae58..26729106 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Viltu taka upp skjáinn?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Taka upp eitt forrit"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Taka upp allan skjáinn"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Þegar þú tekur upp allan skjáinn verður allt sem er sýnilegt á skjánum tekið upp. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð, myndir, hljóð og vídeó."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Þegar þú tekur upp forrit verður allt sem er sýnilegt eða spilað í forritinu tekið upp. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð, myndir, hljóð og vídeó."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Taka upp skjá"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Hreinsa allt"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Stjórna"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Ferill"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Tilkynningastillingar"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"Tilkynningaferill"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Nýtt"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Hljóðlaust"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Tilkynningar"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Núverandi forrit"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Aðgengi"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Flýtilyklar"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Leitarflýtileiðir"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Engar leitarniðurstöður"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Minnka tákn"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Stækka tákn"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"eða"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Dragkló"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index e7734fa..fd56c5b 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Registrare lo schermo?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Registra un\'app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Registra l\'intero schermo"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Quando registri l\'intero schermo, tutto ciò che viene mostrato sullo schermo viene registrato. Presta quindi attenzione a password, dati di pagamento, messaggi, foto, audio e video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Quando registri un\'app, tutto ciò che viene mostrato o riprodotto al suo interno viene registrato. Presta quindi attenzione a password, dati di pagamento, messaggi, foto, audio e video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Registra lo schermo"</string>
@@ -136,17 +138,14 @@
     <string name="screenrecord_stop_dialog_message_specific_app" msgid="5995770227684523244">"Al momento stai registrando <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="screenrecord_stop_dialog_button" msgid="2883812564938194350">"Interrompi registrazione"</string>
     <string name="share_to_app_chip_accessibility_label" msgid="4210256229976947065">"Condivisione dello schermo in corso"</string>
-    <!-- no translation found for share_to_app_chip_accessibility_label_generic (5517431657924536133) -->
-    <skip />
+    <string name="share_to_app_chip_accessibility_label_generic" msgid="5517431657924536133">"Condivisione di contenuti in corso…"</string>
     <string name="share_to_app_stop_dialog_title" msgid="9212915050910250438">"Vuoi interrompere la condivisione dello schermo?"</string>
-    <!-- no translation found for share_to_app_stop_dialog_title_generic (9079161538135843648) -->
-    <skip />
+    <string name="share_to_app_stop_dialog_title_generic" msgid="9079161538135843648">"Interrompere la condivisione?"</string>
     <string name="share_to_app_stop_dialog_message_entire_screen_with_host_app" msgid="522823522115375414">"Al momento stai condividendo l\'intero schermo con <xliff:g id="HOST_APP_NAME">%1$s</xliff:g>"</string>
     <string name="share_to_app_stop_dialog_message_entire_screen" msgid="5090115386271179270">"Al momento stai condividendo l\'intero schermo con un\'app"</string>
     <string name="share_to_app_stop_dialog_message_single_app_specific" msgid="5923772039347985172">"Al momento stai condividendo <xliff:g id="APP_BEING_SHARED_NAME">%1$s</xliff:g>"</string>
     <string name="share_to_app_stop_dialog_message_single_app_generic" msgid="6681016774654578261">"Al momento stai condividendo un\'app"</string>
-    <!-- no translation found for share_to_app_stop_dialog_message_generic (7622174291691249392) -->
-    <skip />
+    <string name="share_to_app_stop_dialog_message_generic" msgid="7622174291691249392">"Al momento stai condividendo contenuti con un\'app"</string>
     <string name="share_to_app_stop_dialog_button" msgid="6334056916284230217">"Interrompi condivisione"</string>
     <string name="cast_screen_to_other_device_chip_accessibility_label" msgid="4687917476203009885">"Trasmissione dello schermo in corso…"</string>
     <string name="cast_to_other_device_stop_dialog_title" msgid="7836517190930357326">"Vuoi interrompere la trasmissione?"</string>
@@ -521,10 +520,8 @@
     <string name="communal_widget_picker_title" msgid="1953369090475731663">"Widget della schermata di blocco"</string>
     <string name="communal_widget_picker_description" msgid="490515450110487871">"Chiunque può visualizzare i widget sulla tua schermata di blocco, anche se il tablet è bloccato."</string>
     <string name="accessibility_action_label_unselect_widget" msgid="1041811747619468698">"deseleziona widget"</string>
-    <!-- no translation found for accessibility_action_label_shrink_widget (8259511040536438771) -->
-    <skip />
-    <!-- no translation found for accessibility_action_label_expand_widget (9190524260912211759) -->
-    <skip />
+    <string name="accessibility_action_label_shrink_widget" msgid="8259511040536438771">"Riduci altezza"</string>
+    <string name="accessibility_action_label_expand_widget" msgid="9190524260912211759">"Aumenta altezza"</string>
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widget della schermata di blocco"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Per aprire un\'app utilizzando un widget, dovrai verificare la tua identità. Inoltre tieni presente che chiunque può vederlo, anche quando il tablet è bloccato. Alcuni widget potrebbero non essere stati progettati per la schermata di blocco e potrebbe non essere sicuro aggiungerli qui."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Ok"</string>
@@ -1413,9 +1410,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"App corrente"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibilità"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Scorciatoie da tastiera"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Scorciatoie per la ricerca"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nessun risultato di ricerca"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icona Comprimi"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icona Espandi"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"oppure"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Punto di trascinamento"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 553eaae..384c680 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"להקליט את המסך?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"הקלטה של אפליקציה אחת"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"הקלטה של כל המסך"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"כשמקליטים את כל המסך, כל מה שמופיע במסך מוקלט. מומלץ להיזהר עם סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"כשמקליטים אפליקציה, כל מה שרואים או מפעילים בה מוקלט. מומלץ להיזהר עם סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"הקלטת המסך"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ניקוי הכול"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ניהול"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"היסטוריה"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"הגדרות של התראות"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"היסטוריית ההתראות"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"התראות חדשות"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"מצב שקט"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"התראות"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"האפליקציה הנוכחית"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"נגישות"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"מקשי קיצור"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"קיצורי דרך לחיפוש"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"אין תוצאות חיפוש"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"סמל הכיווץ"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"סמל ההרחבה"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"או"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"נקודת האחיזה לגרירה"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index a6fffee..a0a3b2b 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"画面を録画しますか?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"1 つのアプリを録画"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"画面全体を録画"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"画面全体を録画すると、画面に表示されるものがすべて録画されます。パスワード、お支払いの詳細、メッセージ、写真、音声、動画などの情報にご注意ください。"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"アプリを録画すると、そのアプリで表示または再生される内容がすべて録画されます。パスワード、お支払いの詳細、メッセージ、写真、音声、動画などの情報にご注意ください。"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"画面を録画"</string>
@@ -1411,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"現在のアプリ"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ユーザー補助"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"キーボード ショートカット"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"検索ショートカット"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"検索結果がありません"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"閉じるアイコン"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"開くアイコン"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"または"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ドラッグ ハンドル"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 9608253..471957e 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"გსურთ თქვენი ეკრანის ჩაწერა?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"ერთი აპის ჩაწერა"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"მთლიანი ეკრანის ჩაწერა"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"მთლიანი ეკრანის ჩაწერისას ჩაიწერება ყველაფერი, რაც თქვენს ეკრანზე გამოჩნდება. ამიტომ სიფრთხილე გამოიჩინეთ ისეთ ინფორმაციასთან, როგორიცაა პაროლები, გადახდის დეტალები, შეტყობინებები, ფოტოები, აუდიო და ვიდეო."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"აპის ჩაწერისას ჩაიწერება ყველაფერი, რაც ამ აპში გამოჩნდება ან დაიკვრება. ამიტომ სიფრთხილე გამოიჩინეთ ისეთ ინფორმაციასთან, როგორიცაა პაროლები, გადახდის დეტალები, შეტყობინებები, ფოტოები, აუდიო და ვიდეო."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"ეკრანის ჩაწერა"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"მიმდინარე აპი"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"მისაწვდომობა"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"კლავიატურის მალსახმობები"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ძიების მალსახმობები"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ძიების შედეგები არ არის"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ხატულის ჩაკეცვა"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ხატულის გაფართოება"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ან"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"სახელური ჩავლებისთვის"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 0456fa8..3dbfd86 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Қолданба экранын жазасыз ба?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Бір қолданба экранын жазу"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Бүкіл экранды жазу"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Бүкіл экранды жазған кезде, онда көрінетін барлық нәрсе жазылады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізген кезде сақ болыңыз."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Қолданбаны жазған кезде, онда көрінетін не ойнатылатын барлық нәрсе жазылады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізген кезде сақ болыңыз."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Экранды жазу"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Қолданыстағы қолданба"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Арнайы мүмкіндіктер"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Перне тіркесімдері"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Іздеу жылдам пәрмендері"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Іздеу нәтижелері жоқ."</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Жию белгішесі"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Жаю белгішесі"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"немесе"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Сүйрейтін тетік"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 3c33531..b2c8977 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"ថត​អេក្រង់​របស់អ្នកឬ?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"ថត​កម្មវិធី​ទោល"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"ថតអេក្រង់ទាំងមូល"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"នៅពេល​អ្នកកំពុង​ថតអេក្រង់​ទាំងមូល​របស់អ្នក អ្វីគ្រប់យ៉ាង​ដែលបង្ហាញ​នៅលើ​អេក្រង់​របស់អ្នក​ត្រូវបាន​ថត។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះអ្វីៗដូចជា ពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ សារ រូបថត ព្រមទាំងសំឡេង និងវីដេអូ។"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"នៅពេលអ្នក​កំពុង​ថតកម្មវិធី​ណាមួយ អ្វីគ្រប់យ៉ាង​ដែលបង្ហាញ ឬចាក់​នៅក្នុង​កម្មវិធីនោះ​ត្រូវបាន​ថត។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះអ្វីៗដូចជា ពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ សារ រូបថត ព្រមទាំងសំឡេង និងវីដេអូ។"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"ថត​អេក្រង់"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"កម្មវិធីបច្ចុប្បន្ន"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ភាពងាយស្រួល"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"ផ្លូវកាត់​ក្ដារ​ចុច"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ផ្លូវ​កាត់ការស្វែងរក"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"គ្មាន​លទ្ធផល​ស្វែងរក​ទេ"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"រូបតំណាង \"បង្រួម\""</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"រូបតំណាង \"ពង្រីក\""</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ឬ"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ដង​អូស"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index dca2e4b..20c3676 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"ನಿಮ್ಮ ಸ್ಕ್ರೀನ್‌ ಅನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಬೇಕೇ?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"ಒಂದು ಆ್ಯಪ್ ಅನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"ಸಂಪೂರ್ಣ ಸ್ಕ್ರೀನ್ ಅನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"ನಿಮ್ಮ ಸಂಪೂರ್ಣ ಸ್ಕ್ರೀನ್‌ ಅನ್ನು ನೀವು ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ, ನಿಮ್ಮ ಸ್ಕ್ರೀನ್‌ ಮೇಲೆ ಗೋಚರಿಸುವ ಎಲ್ಲವನ್ನೂ ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗುತ್ತದೆ. ಆದ್ದರಿಂದ ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು, ಫೋಟೋಗಳು ಮತ್ತು ಆಡಿಯೋ ಮತ್ತು ವೀಡಿಯೊದಂತಹ ವಿಷಯಗಳ ಬಗ್ಗೆ ಜಾಗರೂಕರಾಗಿರಿ."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"ನೀವು ಆ್ಯಪ್ ಅನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡುವಾಗ, ಆ ಆ್ಯಪ್‌ನಲ್ಲಿ ತೋರಿಸಿರುವ ಅಥವಾ ಪ್ಲೇ ಮಾಡಿದ ಎಲ್ಲವನ್ನೂ ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗುತ್ತದೆ. ಆದ್ದರಿಂದ ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು, ಫೋಟೋಗಳು ಮತ್ತು ಆಡಿಯೋ ಮತ್ತು ವೀಡಿಯೊದಂತಹ ವಿಷಯಗಳ ಬಗ್ಗೆ ಜಾಗರೂಕರಾಗಿರಿ."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿ"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ನಿರ್ವಹಿಸಿ"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ಇತಿಹಾಸ"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"ನೋಟಿಫಿಕೇಶನ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"ನೋಟಿಫಿಕೇಶನ್ ಇತಿಹಾಸ"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"ಹೊಸತು"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"ನಿಶ್ಶಬ್ದ"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"ಅಧಿಸೂಚನೆಗಳು"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"ಪ್ರಸ್ತುತ ಆ್ಯಪ್"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"ಕೀಬೋರ್ಡ್ ಶಾರ್ಟ್‌ಕಟ್‌ಗಳು"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ಹುಡುಕಾಟದ ಶಾರ್ಟ್‌ಕಟ್‌ಗಳು"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ಯಾವುದೇ ಹುಡುಕಾಟ ಫಲಿತಾಂಶಗಳಿಲ್ಲ"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ಕುಗ್ಗಿಸುವ ಐಕಾನ್"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ವಿಸ್ತೃತಗೊಳಿಸುವ ಐಕಾನ್"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ಅಥವಾ"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ಡ್ರ್ಯಾಗ್‌ ಹ್ಯಾಂಡಲ್‌"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 8674dc3..35dc245 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"화면을 녹화하시겠습니까?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"단일 앱 녹화"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"전체 화면 녹화"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"전체 화면을 녹화하면 화면에 표시되는 모든 항목이 녹화됩니다. 따라서 비밀번호, 결제 세부정보, 메시지, 사진, 오디오 및 동영상 등이 노출되지 않도록 주의하세요."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"앱을 녹화하면 앱에 표시되거나 앱에서 재생되는 모든 항목이 녹화됩니다. 따라서 비밀번호, 결제 세부정보, 메시지, 사진, 오디오 및 동영상 등이 노출되지 않도록 주의하세요."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"화면 녹화"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"모두 지우기"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"관리"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"기록"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"알림 설정"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"알림 기록"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"새 알림"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"무음"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"알림"</string>
@@ -1413,15 +1413,21 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"현재 앱"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"접근성"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"단축키"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"검색 바로가기"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"검색 결과 없음"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"접기 아이콘"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"확장 아이콘"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"또는"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"드래그 핸들"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"키보드 설정"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"키보드를 사용하여 이동"</string>
-    <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"단축키 알아보기"</string>
+    <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"단축키에 관해 알아보세요."</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"터치패드를 사용하여 이동"</string>
     <string name="launch_touchpad_tutorial_notification_content" msgid="7931085031240753226">"터치패드 동작 알아보기"</string>
     <string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"키보드와 터치패드를 사용하여 이동"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 363342a..72e86cc 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Экранды жаздырасызбы?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Бир колдонмону жаздыруу"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Бүтүндөй экранды жаздыруу"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Бүтүндөй экранды жаздырганда, андагы нерселердин баары видеого түшүп калат. Андыктан этият болуп, сырсөздөр, төлөм ыкмалары, билдирүүлөр, сүрөттөр, аудио жана видео материалдар сыяктуу купуя нерселерди көрсөтүп албаңыз."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Колдонмону жаздырганда ал колдонмодо көрсөтүлүп же ойнотулуп жаткан нерселер жаздырылат. Андыктан сырсөздөрдү, төлөмдүн чоо-жайын, билдирүүлөрдү, сүрөттөрдү, аудио жана видеону көрсөтүп албаңыз."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Экранды жаздыруу"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Учурдагы колдонмо"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Атайын мүмкүнчүлүктөр"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Ыкчам баскычтар"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Ыкчам баскычтарды издөө"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Эч нерсе табылган жок"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Жыйыштыруу сүрөтчөсү"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Жайып көрсөтүү сүрөтчөсү"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"же"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Cүйрөө маркери"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 54c2c6b..f6fe3cc 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"ບັນທຶກໜ້າຈໍຂອງທ່ານບໍ?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"ບັນທຶກແອັບດຽວ"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"ບັນທຶກໝົດໜ້າຈໍ"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"ເມື່ອທ່ານບັນທຶກໝົດໜ້າຈໍຂອງທ່ານ, ລະບົບຈະບັນທຶກທຸກສິ່ງທີ່ສະແດງຢູ່ໜ້າຈໍຂອງທ່ານ. ດັ່ງນັ້ນ, ໃຫ້ລະມັດລະວັງສິ່ງຕ່າງໆ ເຊັ່ນ: ລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ, ຮູບພາບ, ພ້ອມທັງສຽງ ແລະ ວິດີໂອ."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"ເມື່ອທ່ານບັນທຶກແອັບ, ລະບົບຈະບັນທຶກທຸກສິ່ງທີ່ສະແດງ ຫຼື ຫຼິ້ນຢູ່ໃນແອັບນັ້ນ. ດັ່ງນັ້ນ, ໃຫ້ລະມັດລະວັງສິ່ງຕ່າງໆ ເຊັ່ນ: ລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ, ຮູບພາບ, ພ້ອມທັງສຽງ ແລະ ວິດີໂອ."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"ບັນທຶກໜ້າຈໍ"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"ແອັບປັດຈຸບັນ"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ການຊ່ວຍເຂົ້າເຖິງ"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"ຄີລັດ"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ທາງລັດການຊອກຫາ"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ບໍ່ມີຜົນການຊອກຫາ"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ໄອຄອນຫຍໍ້ລົງ"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ໄອຄອນຂະຫຍາຍ"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ຫຼື"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ບ່ອນຈັບລາກ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index dca26a4..70a0063 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Įrašyti ekraną?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Įrašyti vieną programą"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Įrašyti visą ekraną"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Kai įrašote visą ekraną, įrašomas visas ekrane rodomas turinys. Todėl būkite atsargūs naudodami slaptažodžius, išsamią mokėjimo metodo informaciją, pranešimus, nuotraukas ir garso bei vaizdo įrašus."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Kai įrašote programą, įrašomas visas toje programoje rodomas ar leidžiamas turinys. Todėl būkite atsargūs naudodami slaptažodžius, išsamią mokėjimo metodo informaciją, pranešimus, nuotraukas ir garso bei vaizdo įrašus."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Įrašyti ekraną"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Esama programa"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Pritaikomumas"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Spartieji klavišai"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Paieškos šaukiniai"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nėra jokių paieškos rezultatų"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Sutraukimo piktograma"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Išskleidimo piktograma"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"arba"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Vilkimo rankenėlė"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index c5ba0ad..360afad 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Vai ierakstīt ekrānu?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Ierakstīt vienu lietotni"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Ierakstīt visu ekrānu"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Ierakstot visu ekrānu, viss, kas redzams ekrānā, tiek ierakstīts. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem, fotoattēliem un audio un video saturu."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Ierakstot lietotni, tiek ierakstīts viss attiecīgajā lietotnē rādītais vai atskaņotais. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem, fotoattēliem un audio un video saturu."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Ierakstīt ekrānu"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Pašreizējā lietotne"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Pieejamība"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Īsinājumtaustiņi"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Meklēšanas saīsnes"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nav meklēšanas rezultātu"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Sakļaušanas ikona"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Izvēršanas ikona"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"vai"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Vilkšanas turis"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 65e958e..ebd62a9 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Да се снима екранот?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Снимање на една апликација"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Снимање на целиот екран"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Додека го снимате целиот екран, сѐ што е прикажано на екранот се снима. Затоа, бидете внимателни со лозинките, деталите за плаќање, пораките, фотографиите и аудиото и видеото."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Додека снимате апликација, може да се сними сѐ што се прикажува или пушта во таа апликација. Затоа, бидете внимателни со лозинките, деталите за плаќање, пораките, фотографиите и аудиото и видеото."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Снимај го екранот"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Избриши сѐ"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Управувајте"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Историја"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Поставки за известувања"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"Историја на известувања"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Нов"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Безгласно"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Известувања"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Тековна апликација"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Пристапност"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Кратенки од тастатура"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Кратенки за пребарување"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Нема резултати од пребарување"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Икона за собирање"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Икона за проширување"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"или"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Рачка за влечење"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index e10ecff..6152fce 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"നിങ്ങളുടെ സ്ക്രീൻ റെക്കോർഡ് ചെയ്യണോ?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"ഒരു ആപ്പ് റെക്കോർഡ് ചെയ്യുക"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"സ്ക്രീൻ പൂർണ്ണമായി റെക്കോർഡ് ചെയ്യുക"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"നിങ്ങളുടെ സ്ക്രീൻ പൂർണ്ണമായി റെക്കോർഡ് ചെയ്യുമ്പോൾ, സ്ക്രീനിൽ ദൃശ്യമാകുന്ന എല്ലാം റെക്കോർഡ് ചെയ്യപ്പെടും. അതിനാൽ പാസ്‍വേഡുകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ, ഓഡിയോ, വീഡിയോ എന്നിവ പോലുള്ള കാര്യങ്ങൾ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"നിങ്ങളുടെ ആപ്പ് റെക്കോർഡ് ചെയ്യുമ്പോൾ, ആ ആപ്പിൽ കാണിക്കുന്നതോ പ്ലേ ചെയ്യുന്നതോ ആയ എല്ലാ കാര്യങ്ങളും റെക്കോർഡ് ചെയ്യപ്പെടും. അതിനാൽ പാസ്‍വേഡുകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ, ഓഡിയോ, വീഡിയോ എന്നിവ പോലുള്ള കാര്യങ്ങൾ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"സ്ക്രീൻ റെക്കോർഡ് ചെയ്യുക"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"എല്ലാം മായ്‌ക്കുക"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"മാനേജ് ചെയ്യുക"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ചരിത്രം"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"അറിയിപ്പ് ക്രമീകരണം"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"അറിയിപ്പ് ചരിത്രം"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"പുതിയത്"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"നിശബ്‌ദം"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"അറിയിപ്പുകൾ"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"നിലവിലെ ആപ്പ്"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ഉപയോഗസഹായി"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"കീബോഡ് കുറുക്കുവഴികൾ"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"തിരയൽ കുറുക്കുവഴികൾ"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"തിരയൽ ഫലങ്ങളൊന്നുമില്ല"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ചുരുക്കൽ ഐക്കൺ"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"വികസിപ്പിക്കൽ ഐക്കൺ"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"അല്ലെങ്കിൽ"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"വലിച്ചിടുന്നതിനുള്ള ഹാൻഡിൽ"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index cb5f054..aafc8c61 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Дэлгэцээ бичих үү?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Нэг аппыг бичих"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Бүтэн дэлгэцийг бичих"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Таныг бүтэн дэлгэцээ бичиж байхад дэлгэц дээр тань харуулж буй аливаа зүйлийг бичдэг. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж, зураг, аудио, видео зэрэг зүйлд болгоомжтой хандаарай."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Таныг апп бичиж байхад тухайн аппад харуулж эсвэл тоглуулж буй аливаа зүйлийг бичдэг. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж, зураг, аудио, видео зэрэг зүйлд болгоомжтой хандаарай."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Дэлгэцийг бичих"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Бүгдийг арилгах"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Удирдах"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Түүх"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Мэдэгдлийн тохиргоо"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"Мэдэгдлийн түүх"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Шинэ"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Чимээгүй"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Мэдэгдлүүд"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Одоогийн апп"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Хандалт"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Товчлуурын шууд холбоос"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Товчлолууд хайх"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ямар ч хайлтын илэрц байхгүй"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Хураах дүрс тэмдэг"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Дэлгэх дүрс тэмдэг"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"эсвэл"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Чирэх бариул"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 8aa0afa..fbbb167 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"तुमची स्क्रीन रेकॉर्ड करायची आहे का?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"एक अ‍ॅप रेकॉर्ड करा"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"पूर्ण स्क्रीन रेकॉर्ड करा"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"तुम्ही तुमची पूर्ण स्क्रीन रेकॉर्ड करता, तेव्हा तुमच्या स्क्रीनवर दाखवलेली कोणतीही गोष्टी रेकॉर्ड केली जाते. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज, फोटो आणि ऑडिओ व व्हिडिओ यांसारख्या गोष्टींबाबत सावधगिरी बाळगा."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"तुम्ही अ‍ॅप रेकॉर्ड करता, तेव्हा त्या अ‍ॅपमध्ये दाखवलेली किंवा प्ले केलेली कोणतीही गोष्ट रेकॉर्ड केली जाते. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज, फोटो आणि ऑडिओ व व्हिडिओ यांसारख्या गोष्टींबाबत सावधगिरी बाळगा."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"स्क्रीन रेकॉर्ड करा"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"सर्व साफ करा"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"व्यवस्थापित करा"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"नोटिफिकेशन सेटिंग्ज"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"नोटिफिकेशन इतिहास"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"नवीन"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"सायलंट"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"सूचना"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"सध्याचे अ‍ॅप"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"अ‍ॅक्सेसिबिलिटी"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"कीबोर्ड शॉर्टकट"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"शोधण्यासाठी शॉर्टकट"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"कोणतेही शोध परिणाम नाहीत"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"कोलॅप्स करा आयकन"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"विस्तार करा आयकन"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"किंवा"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ड्रॅग हॅंडल"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index d0071f4d..087c8a6 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Rakam skrin anda?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Rakam satu apl"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Rakam seluruh skrin"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Apabila anda merakam seluruh skrin anda, apa-apa sahaja yang dipaparkan pada skrin anda akan dirakam. Oleh hal yang demikian, berhati-hati dengan perkara seperti kata laluan, butiran pembayaran, mesej, foto dan audio serta video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Apabila anda merakam apl, apa-apa sahaja yang dipaparkan atau dimainkan dalam apl tersebut akan dirakam. Oleh hal yang demikian, berhati-hati dengan perkara seperti kata laluan, butiran pembayaran, mesej, foto dan audio serta video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Rakam skrin"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Apl Semasa"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Kebolehaksesan"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Pintasan papan kekunci"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pintasan carian"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Tiada hasil carian"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Kuncupkan ikon"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Kembangkan ikon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"atau"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Pemegang seret"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index fd4c147..d456bca 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"ဖန်သားပြင်ကို ရိုက်ကူးမလား။"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"အက်ပ်တစ်ခုကို ရိုက်ကူးရန်"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"ဖန်သားပြင်တစ်ခုလုံးကို ရိုက်ကူးရန်"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"သင့်ဖန်သားပြင်တစ်ခုလုံး ရိုက်ကူးနေချိန်တွင် ဖန်သားပြင်တွင် ပြထားသည့် အရာအားလုံးကို ရိုက်ကူးသည်။ စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ်၊ ဓာတ်ပုံ၊ အသံနှင့် ဗီဒီယိုကဲ့သို့ အရာများကို ဂရုစိုက်ပါ။"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"အက်ပ်ကို ရိုက်ကူးနေချိန်တွင် ယင်းအက်ပ်တွင် ပြထားသော (သို့) ဖွင့်ထားသော အရာအားလုံးကို ရိုက်ကူးသည်။ စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ်၊ ဓာတ်ပုံ၊ အသံနှင့် ဗီဒီယိုကဲ့သို့ အရာများကို ဂရုစိုက်ပါ။"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"ဖန်သားပြင်ကို ရိုက်ကူးရန်"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"လက်ရှိအက်ပ်"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"အများသုံးနိုင်မှု"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"လက်ကွက်ဖြတ်လမ်းများ"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ရှာဖွေစာလုံး ဖြတ်လမ်း"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ရှာဖွေမှုရလဒ် မရှိပါ"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"လျှော့ပြရန် သင်္ကေတ"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ပိုပြရန် သင်္ကေတ"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"သို့မဟုတ်"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ဖိဆွဲအထိန်း"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 1c30ca7..1fbb35b0 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Vil du ta opp skjermen?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Ta opp én app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Ta opp hele skjermen"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Når du tar opp hele skjermen, blir alt som vises på skjermen, tatt opp. Derfor bør du være forsiktig med for eksempel passord, betalingsopplysninger, meldinger, bilder, lyd og video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Når du tar opp en app, blir alt som vises eller spilles av i appen, tatt opp. Derfor bør du være forsiktig med for eksempel passord, betalingsopplysninger, meldinger, bilder, lyd og video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Ta opp skjermen"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aktiv app"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Tilgjengelighet"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Hurtigtaster"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Snarveier til søk"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ingen søkeresultater"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Skjul-ikon"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Vis-ikon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"eller"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Håndtak"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index f7ac880..381118a 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"तपाईंको स्क्रिन रेकर्ड गर्ने हो?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"एउटा एप रेकर्ड गर्नुहोस्"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"पूरै स्क्रिन रेकर्ड गर्नुहोस्"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"तपाईंले आफ्नो पूरै स्क्रिन रेकर्ड गरिरहेका बेला तपाईंको स्क्रिनमा देखाइने सबै सामग्री रेकर्ड गरिन्छ। त्यसैले पासवर्ड, भुक्तानीसम्बन्धी विवरण, म्यासेज, फोटो र अडियो तथा भिडियो जस्ता कुरा हेर्दा वा प्ले गर्दा सावधानी अपनाउनुहोला।"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"तपाईंले यो एप रेकर्ड गरिरहेका बेला यो एपमा देखाइने वा प्ले गरिने सबै सामग्री रेकर्ड गरिन्छ। त्यसैले पासवर्ड, भुक्तानीसम्बन्धी विवरण, म्यासेज, फोटो र अडियो तथा भिडियो जस्ता कुरा हेर्दा वा प्ले गर्दा सावधानी अपनाउनुहोला।"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"स्क्रिन रेकर्ड गर्नुहोस्"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"हालको एप"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"सर्वसुलभता"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"किबोर्डका सर्टकटहरू"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"खोजका सर्टकटहरू"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"कुनै पनि खोज परिणाम भेटिएन"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"\"कोल्याप्स गर्नुहोस्\" आइकन"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"\"एक्स्पान्ड गर्नुहोस्\" आइकन"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"वा"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ड्र्याग ह्यान्डल"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 4cea605..8ff59ee 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Je scherm opnemen?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Eén app opnemen"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Hele scherm opnemen"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Als je je hele scherm opneemt, wordt alles opgenomen wat op je scherm wordt getoond. Wees daarom voorzichtig met bijvoorbeeld wachtwoorden, betalingsgegevens, berichten, foto\'s, en audio en video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Als je een app opneemt, wordt alles opgenomen wat wordt getoond of afgespeeld in die app. Wees daarom voorzichtig met bijvoorbeeld wachtwoorden, betalingsgegevens, berichten, foto\'s, en audio en video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Scherm opnemen"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Alles wissen"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Beheren"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Geschiedenis"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Instellingen voor meldingen"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"Meldings­geschiedenis"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Nieuw"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Stil"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Meldingen"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Huidige app"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Toegankelijkheid"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Sneltoetsen"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Snelkoppelingen voor zoekopdrachten"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Geen zoekresultaten"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icoon voor samenvouwen"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icoon voor uitvouwen"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"of"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Handgreep voor slepen"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index bbbea61..6086440 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"ଆପଣଙ୍କ ସ୍କ୍ରିନକୁ ରେକର୍ଡ କରିବେ?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"ଗୋଟିଏ ଆପ ରେକର୍ଡ କରନ୍ତୁ"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"ସମ୍ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନ ରେକର୍ଡ କରନ୍ତୁ"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"ଆପଣ ଆପଣଙ୍କର ସମ୍ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନ ରେକର୍ଡ କରିବା ସମୟରେ, ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଦେଖାଯାଉଥିବା ସବୁକିଛି ରେକର୍ଡ ହୋଇଥାଏ। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ, ଫଟୋ ଏବଂ ଅଡିଓ ଓ ଭିଡିଓ ପରି ବିଷୟଗୁଡ଼ିକ ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"ଆପଣ ଏକ ଆପ ରେକର୍ଡ କରିବା ସମୟରେ, ସେହି ଆପରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ପ୍ଲେ ହେଉଥିବା ସବୁକିଛି ରେକର୍ଡ ହୋଇଥାଏ। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ, ଫଟୋ ଏବଂ ଅଡିଓ ଓ ଭିଡିଓ ପରି ବିଷୟଗୁଡ଼ିକ ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"ସ୍କ୍ରିନ ରେକର୍ଡ କରନ୍ତୁ"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"ବର୍ତ୍ତମାନର ଆପ"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ଆକ୍ସେସିବିଲିଟୀ"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"କୀବୋର୍ଡ ସର୍ଟକଟ"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ସର୍ଚ୍ଚ ସର୍ଟକଟ"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"କୌଣସି ସର୍ଚ୍ଚ ଫଳାଫଳ ନାହିଁ"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ଆଇକନକୁ ସଙ୍କୁଚିତ କରନ୍ତୁ"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ଆଇକନକୁ ବିସ୍ତାର କରନ୍ତୁ"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"କିମ୍ବା"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ଡ୍ରାଗ ହେଣ୍ଡେଲ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 0170a71..b99986a 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"ਕੀ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ ਨੂੰ ਰਿਕਾਰਡ ਕਰਨਾ ਹੈ?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"ਇੱਕ ਐਪ ਨੂੰ ਰਿਕਾਰਡ ਕਰੋ"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"ਪੂਰੀ ਸਕ੍ਰੀਨ ਨੂੰ ਰਿਕਾਰਡ ਕਰੋ"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"ਜਦੋਂ ਤੁਸੀਂ ਆਪਣੀ ਪੂਰੀ ਸਕ੍ਰੀਨ ਨੂੰ ਰਿਕਾਰਡ ਕਰ ਰਹੇ ਹੁੰਦੇ ਹੋ, ਤਾਂ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਖਾਈ ਜਾ ਰਹੀ ਹਰ ਚੀਜ਼ ਨੂੰ ਰਿਕਾਰਡ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ, ਫ਼ੋਟੋਆਂ ਅਤੇ ਨਾਲ ਹੀ ਆਡੀਓ ਅਤੇ ਵੀਡੀਓ ਵਰਗੀਆਂ ਚੀਜ਼ਾਂ ਵਾਸਤੇ ਸਾਵਧਾਨ ਰਹੋ।"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"ਜਦੋਂ ਤੁਸੀਂ ਕਿਸੇ ਐਪ ਨੂੰ ਰਿਕਾਰਡ ਕਰ ਰਹੇ ਹੁੰਦੇ ਹੋ, ਤਾਂ ਉਸ ਐਪ ਵਿੱਚ ਦਿਖਾਈ ਜਾਂ ਚਲਾਈ ਜਾ ਰਹੀ ਹਰ ਚੀਜ਼ ਨੂੰ ਰਿਕਾਰਡ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ, ਫ਼ੋਟੋਆਂ ਅਤੇ ਆਡੀਓ ਅਤੇ ਵੀਡੀਓ ਵਰਗੀਆਂ ਚੀਜ਼ਾਂ ਵਾਸਤੇ ਸਾਵਧਾਨ ਰਹੋ।"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡ ਕਰੋ"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ਸਭ ਕਲੀਅਰ ਕਰੋ"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ਇਤਿਹਾਸ"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"ਸੂਚਨਾ ਸੈਟਿੰਗਾਂ"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"ਸੂਚਨਾ ਇਤਿਹਾਸ"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"ਨਵੀਆਂ"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"ਸ਼ਾਂਤ"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"ਸੂਚਨਾਵਾਂ"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"ਮੌਜੂਦਾ ਐਪ"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ਪਹੁੰਚਯੋਗਤਾ"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"ਕੀ-ਬੋਰਡ ਸ਼ਾਰਟਕੱਟ"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ਖੋਜ ਸੰਬੰਧੀ ਸ਼ਾਰਟਕੱਟ"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ਕੋਈ ਖੋਜ ਨਤੀਜਾ ਨਹੀਂ ਮਿਲਿਆ"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ਪ੍ਰਤੀਕ ਨੂੰ ਸਮੇਟੋ"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ਪ੍ਰਤੀਕ ਦਾ ਵਿਸਤਾਰ ਕਰੋ"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ਜਾਂ"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ਘਸੀਟਣ ਵਾਲਾ ਹੈਂਡਲ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 33a743f..88177ac 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Nagrywać ekran?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Nagrywaj jedną aplikację"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Nagrywaj cały ekran"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Kiedy nagrywasz cały ekran, nagrane zostanie wszystko, co jest na nim widoczne. Dlatego uważaj na hasła, dane do płatności, wiadomości, zdjęcia, nagrania audio czy filmy."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Kiedy nagrywasz aplikację, wszystko, co jest w niej wyświetlane lub odtwarzane, zostaje nagrane. Dlatego zachowaj ostrożność w zakresie haseł, danych do płatności, wiadomości, zdjęć, audio i filmów."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Nagrywaj ekran"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Bieżąca aplikacja"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Ułatwienia dostępu"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Skróty klawiszowe"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Skróty do wyszukiwania"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Brak wyników wyszukiwania"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona zwijania"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona rozwijania"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"lub"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Uchwyt do przeciągania"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 8041760..e67c168 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Gravar a tela?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Gravar um app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Gravar a tela toda"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Quando você grava a tela toda, tudo o que aparece nela é registrado. Portanto, tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Quando você grava um app, todas as informações visíveis ou abertas nele ficam registradas. Portanto, tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Gravar a tela"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"App atual"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Acessibilidade"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Atalhos do teclado"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Atalhos de pesquisa"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nenhum resultado de pesquisa"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ícone \"Fechar\""</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ícone \"Abrir\""</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Alça de arrastar"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index f000998..30e16f0 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Gravar o ecrã?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Gravar uma app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Gravar o ecrã inteiro"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Quando está a gravar o ecrã inteiro, tudo o que é apresentado no ecrã é gravado. Por isso, tenha cuidado com, por exemplo, palavras-passe, detalhes de pagamento, mensagens, fotos, áudio e vídeo."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Quando está a gravar uma app, tudo o que é apresentado ou reproduzido nessa app é gravado. Por isso, tenha cuidado com, por exemplo, palavras-passe, detalhes de pagamento, mensagens, fotos, áudio e vídeo."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Gravar ecrã"</string>
@@ -136,17 +138,14 @@
     <string name="screenrecord_stop_dialog_message_specific_app" msgid="5995770227684523244">"Neste momento, está a gravar a app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="screenrecord_stop_dialog_button" msgid="2883812564938194350">"Parar gravação"</string>
     <string name="share_to_app_chip_accessibility_label" msgid="4210256229976947065">"A partilhar o ecrã"</string>
-    <!-- no translation found for share_to_app_chip_accessibility_label_generic (5517431657924536133) -->
-    <skip />
+    <string name="share_to_app_chip_accessibility_label_generic" msgid="5517431657924536133">"A partilhar conteúdo"</string>
     <string name="share_to_app_stop_dialog_title" msgid="9212915050910250438">"Parar a partilha do ecrã?"</string>
-    <!-- no translation found for share_to_app_stop_dialog_title_generic (9079161538135843648) -->
-    <skip />
+    <string name="share_to_app_stop_dialog_title_generic" msgid="9079161538135843648">"Parar a partilha?"</string>
     <string name="share_to_app_stop_dialog_message_entire_screen_with_host_app" msgid="522823522115375414">"Neste momento, está a partilhar todo o seu ecrã com a app <xliff:g id="HOST_APP_NAME">%1$s</xliff:g>"</string>
     <string name="share_to_app_stop_dialog_message_entire_screen" msgid="5090115386271179270">"Neste momento, está a partilhar todo o seu ecrã com uma app"</string>
     <string name="share_to_app_stop_dialog_message_single_app_specific" msgid="5923772039347985172">"Neste momento, está a partilhar a app <xliff:g id="APP_BEING_SHARED_NAME">%1$s</xliff:g>"</string>
     <string name="share_to_app_stop_dialog_message_single_app_generic" msgid="6681016774654578261">"Neste momento, está a partilhar uma app"</string>
-    <!-- no translation found for share_to_app_stop_dialog_message_generic (7622174291691249392) -->
-    <skip />
+    <string name="share_to_app_stop_dialog_message_generic" msgid="7622174291691249392">"Neste momento, está a partilhar com uma app"</string>
     <string name="share_to_app_stop_dialog_button" msgid="6334056916284230217">"Parar partilha"</string>
     <string name="cast_screen_to_other_device_chip_accessibility_label" msgid="4687917476203009885">"A transmitir o ecrã"</string>
     <string name="cast_to_other_device_stop_dialog_title" msgid="7836517190930357326">"Parar a transmissão?"</string>
@@ -521,10 +520,8 @@
     <string name="communal_widget_picker_title" msgid="1953369090475731663">"Widgets do ecrã de bloqueio"</string>
     <string name="communal_widget_picker_description" msgid="490515450110487871">"Todos podem pode ver widgets no ecrã de bloqueio, mesmo com o tablet bloqueado."</string>
     <string name="accessibility_action_label_unselect_widget" msgid="1041811747619468698">"desmarcar widget"</string>
-    <!-- no translation found for accessibility_action_label_shrink_widget (8259511040536438771) -->
-    <skip />
-    <!-- no translation found for accessibility_action_label_expand_widget (9190524260912211759) -->
-    <skip />
+    <string name="accessibility_action_label_shrink_widget" msgid="8259511040536438771">"Diminuir altura"</string>
+    <string name="accessibility_action_label_expand_widget" msgid="9190524260912211759">"Aumentar altura"</string>
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets do ecrã de bloqueio"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Para abrir uma app através de um widget, vai ter de validar a sua identidade. Além disso, tenha em atenção que qualquer pessoa pode ver os widgets, mesmo quando o tablet estiver bloqueado. Alguns widgets podem não se destinar ao ecrã de bloqueio e pode ser inseguro adicioná-los aqui."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
@@ -1413,9 +1410,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"App atual"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Acessibilidade"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Atalhos de teclado"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Atalhos de pesquisa"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nenhum resultado da pesquisa"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ícone de reduzir"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ícone de expandir"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Indicador para arrastar"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 8041760..e67c168 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Gravar a tela?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Gravar um app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Gravar a tela toda"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Quando você grava a tela toda, tudo o que aparece nela é registrado. Portanto, tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Quando você grava um app, todas as informações visíveis ou abertas nele ficam registradas. Portanto, tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Gravar a tela"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"App atual"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Acessibilidade"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Atalhos do teclado"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Atalhos de pesquisa"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nenhum resultado de pesquisa"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ícone \"Fechar\""</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ícone \"Abrir\""</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Alça de arrastar"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 57a2a0c..de7f8da 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Înregistrezi ecranul?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Înregistrează o aplicație"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Înregistrează tot ecranul"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Când înregistrezi întregul ecran, se înregistrează tot ce apare pe ecran. Prin urmare, ai grijă cu parolele, detaliile de plată, mesajele, fotografiile și conținutul audio și video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Când înregistrezi o aplicație, se înregistrează tot ce se afișează sau se redă în aplicație. Prin urmare, ai grijă cu parolele, detaliile de plată, mesajele, fotografiile și conținutul audio și video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Înregistrează ecranul"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aplicația actuală"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accesibilitate"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Comenzi rapide de la tastatură"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Comenzi directe de căutare"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Niciun rezultat al căutării"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Pictograma de restrângere"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Pictograma de extindere"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"sau"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ghidaj de tragere"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 507f709..a007685 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Начать запись экрана?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Записывать одно приложение"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Записывать весь экран"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Во время записи всего экрана все данные и действия, которые на нем показываются, попадают на видео. Поэтому будьте осторожны с паролями, сведениями о способах оплаты, сообщениями, фотографиями, аудио- и видеозаписями."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Во время записи приложения все данные и действия, которые показываются в его окне, попадают на видео. Поэтому будьте осторожны с паролями, сведениями о способах оплаты, сообщениями, фотографиями, аудио- и видеозаписями."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Запись экрана"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Очистить все"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Настроить"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"История"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Настройки уведомлений"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"История уведомлений"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Новое"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Без звука"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Уведомления"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Это приложение"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Специальные возможности"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Быстрые клавиши"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Найти быстрые клавиши"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ничего не найдено"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Значок \"Свернуть\""</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Значок \"Развернуть\""</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"или"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Маркер перемещения"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index fb9be30..ae9c4d0 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"ඔබේ තිරය පටිගත කරන්න ද?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"එක් යෙදුමක් පටිගත කරන්න"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"සම්පූර්ණ තිරය පටිගත කරන්න"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"ඔබ ඔබේ සම්පූර්ණ තිරය පටිගත කරන විට, ඔබේ තිරයේ පෙන්වන ඕනෑම දෙයක් වාර්තා වේ. ඒ නිසා මුරපද, ගෙවීම් විස්තර, පණිවුඩ, ඡායාරූප, සහ ශ්‍රව්‍ය සහ දෘශ්‍ය වැනි දේවල් පිළිබඳ ප්‍රවේශම් වන්න."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"ඔබ යෙදුමක් පටිගත කරන විට, එම යෙදුමේ පෙන්වන හෝ වාදනය කරන ඕනෑම දෙයක් වාර්තා වේ. ඒ නිසා මුරපද, ගෙවීම් විස්තර, පණිවුඩ, ඡායාරූප, සහ ශ්‍රව්‍ය සහ දෘශ්‍ය වැනි දේවල් පිළිබඳ ප්‍රවේශම් වන්න."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"තිරය පටිගත කරන්න"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"වත්මන් යෙදුම"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ප්‍රවේශ්‍යතාව"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"යතුරු පුවරු කෙටි මං"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"කෙටි මං සොයන්න"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"සෙවීම් ප්‍රතිඵල නැත"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"හැකුළුම් නිරූපකය"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"දිගහැරීම් නිරූපකය"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"හෝ"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ඇදීම් හැඬලය"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 42a2613..c223566 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Chcete nahrávať obrazovku?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Nahrávať jednu aplikáciu"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Nahrávať celú obrazovku"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Pri nahrávaní celej obrazovky sa zaznamená všetko, čo sa na nej zobrazuje. Preto venujte pozornosť položkám, ako sú heslá, platobné údaje, správy, fotky a zvuk či video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Pri nahrávaní aplikácie sa zaznamená všetko, čo sa v nej zobrazuje alebo prehráva. Preto venujte pozornosť položkám, ako sú heslá, platobné údaje, správy, fotky a zvuk či video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Nahrávať obrazovku"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Vymazať všetko"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Spravovať"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"História"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Nastavenia upozornení"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"História upozornení"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Nové"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Tichý"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Upozornenia"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aktuálna aplikácia"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Dostupnosť"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Klávesové skratky"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Vyhľadávacie odkazy"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Žiadne výsledky vyhľadávania"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona zbalenia"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona rozbalenia"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"alebo"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Presúvadlo"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 4ca7b24..15f1b3c 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Želite posneti zaslon?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Snemanje ene aplikacije"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Snemanje celotnega zaslona"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Pri snemanju celotnega zaslona se posname vse, kar je prikazano na zaslonu. Zato bodite previdni z gesli, podatki za plačilo, sporočili, fotografijami ter z zvokom in videom."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Pri snemanju aplikacije se posname vse, kar je prikazano ali predvajano v tej aplikaciji. Zato bodite previdni z gesli, podatki za plačilo, sporočili, fotografijami ter z zvokom in videom."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Snemanje zaslona"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Trenutna aplikacija"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Dostopnost"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Bližnjične tipke"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Bližnjice za iskanje"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ni rezultatov iskanja"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona za strnitev"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona za razširitev"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ali"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ročica za vlečenje"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index ddea921..7f74487 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Të regjistrohet ekrani?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Regjistro një aplikacion"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Regjistro të gjithë ekranin"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Kur regjistron të gjithë ekranin, regjistrohet çdo gjë e shfaqur në ekranin tënd. Prandaj, ki kujdes me gjërat si fjalëkalimet, detajet e pagesave, mesazhet, fotografitë, si dhe audion dhe videon."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Kur regjistron një aplikacion, regjistrohet çdo gjë që shfaqet ose luhet në atë aplikacion. Prandaj, ki kujdes me gjërat si fjalëkalimet, detajet e pagesave, mesazhet, fotografitë, si dhe audion dhe videon."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Regjistro ekranin"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aplikacioni aktual"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Qasshmëria"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Shkurtoret e tastierës"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Kërko për shkurtoret"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Asnjë rezultat kërkimi"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona e palosjes"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona e zgjerimit"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ose"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Doreza e zvarritjes"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 5936cd7..a1952d4 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Желите да снимите екран?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Сними једну апликацију"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Сними цео екран"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Када снимате цео екран, снима се све што је на њему. Зато пазите на лозинке, информације о плаћању, поруке, слике, аудио и видео садржај."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Када снимате апликацију, снима се сав садржај који се приказује или пушта у њој. Зато пазите на лозинке, информације о плаћању, поруке, слике, аудио и видео садржај."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Сними екран"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Обриши све"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Управљај"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Историја"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Подешавања обавештења"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"Историја обавештења"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Ново"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Нечујно"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Обавештења"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Актуелна апликација"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Приступачност"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Тастерске пречице"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Пречице претраге"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Нема резултата претраге"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Икона за скупљање"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Икона за проширивање"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"или"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Маркер за превлачење"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 45e150a..b898eaf 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Vill du spela in det som visas på skärmen?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Spela in en app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Spela in hela skärmen"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"När du spelar in hela skärmen spelas allt som visas på skärmen in. Var försiktig med sådant som lösenord, betalningsuppgifter, meddelanden, foton, ljud och video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"När du spelar in en app spelas allt som visas eller spelas upp i appen in. Var försiktig med sådant som lösenord, betalningsuppgifter, meddelanden, foton, ljud och video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Spela in skärmen"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aktuell app"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Tillgänglighet"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Kortkommandon"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Sökgenvägar"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Inga sökresultat"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikonen Komprimera"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikonen Utöka"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"eller"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Handtag"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 86ea7e4..8731337 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Ungependa kurekodi skrini yako?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Rekodi programu moja"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Rekodi skrini nzima"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Unaporekodi skrini yako nzima, chochote kinachoonyeshwa kwenye skrini yako kitarekodiwa. Kwa hivyo kuwa mwangalifu na vitu kama vile manenosiri, maelezo ya malipo, ujumbe, picha, sauti na video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Unaporekodi programu, chochote kinachoonyeshwa au kuchezwa kwenye programu hiyo kitarekodiwa. Kwa hivyo kuwa mwangalifu na vitu kama vile manenosiri, maelezo ya malipo, ujumbe, picha, sauti na video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Rekodi skrini"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Programu Inayotumika Sasa"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Ufikivu"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Mikato ya kibodi"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Njia mkato za kutafutia"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Hamna matokeo ya utafutaji"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Kunja aikoni"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Panua aikoni"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"au"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Aikoni ya buruta"</string>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 4007bb1..393631e 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -76,6 +76,14 @@
     <dimen name="large_dialog_width">472dp</dimen>
 
     <dimen name="large_screen_shade_header_height">42dp</dimen>
+
+    <!--
+    The horizontal distance between the shade overlay panel (both notifications and quick settings)
+    and the edge of the screen. On Compact screens in portrait orientation (< w600dp) this is
+    ignored in the shade layout, which takes up the full screen width without margins.
+    -->
+    <dimen name="shade_panel_margin_horizontal">24dp</dimen>
+
     <!-- start padding is smaller to account for status icon margins coming from drawable itself -->
     <dimen name="hover_system_icons_container_padding_start">3dp</dimen>
     <dimen name="hover_system_icons_container_padding_end">4dp</dimen>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 93b1864..b75a218 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"உங்கள் திரையை ரெக்கார்டு செய்யவா?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"ஓர் ஆப்ஸை ரெக்கார்டு செய்தல்"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"முழுத் திரையை ரெக்கார்டு செய்தல்"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"முழுத் திரையை நீங்கள் ரெக்கார்டு செய்யும்போது அதில் காட்டப்படும் அனைத்தும் ரெக்கார்டு செய்யப்படும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், படங்கள், ஆடியோ, வீடியோ போன்றவை குறித்துக் கவனத்துடன் இருங்கள்."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"ஓர் ஆப்ஸை ரெக்கார்டு செய்யும்போது அதில் காட்டப்படும் அல்லது பிளே செய்யப்படும் அனைத்தும் ரெக்கார்டு செய்யப்படும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், படங்கள், ஆடியோ, வீடியோ போன்றவை குறித்துக் கவனத்துடன் இருங்கள்."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"திரையை ரெக்கார்டு செய்"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"தற்போதைய ஆப்ஸ்"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"மாற்றுத்திறன் வசதி"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"கீபோர்டு ஷார்ட்கட்கள்"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"தேடல் ஷார்ட்கட்கள்"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"தேடல் முடிவுகள் இல்லை"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"சுருக்குவதற்கான ஐகான்"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"விரிவாக்குவதற்கான ஐகான்"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"அல்லது"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"இழுப்பதற்கான ஹேண்டில்"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 4a97651..d66c656 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"మీ స్క్రీన్‌ను రికార్డ్ చేయాలా?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"ఒక యాప్‌ను రికార్డ్ చేయండి"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"ఫుల్ స్క్రీన్‌ను రికార్డ్ చేయండి"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"మీ ఫుల్ స్క్రీన్‌ను మీరు రికార్డ్ చేసేటప్పుడు, మీ స్క్రీన్‌పై కనిపించేవన్నీ రికార్డ్ అవుతాయి. కాబట్టి పాస్‌వర్డ్‌లు, పేమెంట్ వివరాలు, మెసేజ్‌లు, ఫోటోలు, ఆడియో, ఇంకా వీడియో వంటి విషయాల్లో జాగ్రత్త వహించండి."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"మీరు యాప్‌ను రికార్డ్ చేసేటప్పుడు, సంబంధిత యాప్‌లో కనిపించేవన్నీ లేదా ప్లే అయ్యేవన్నీ రికార్డ్ అవుతాయి. కాబట్టి పాస్‌వర్డ్‌లు, పేమెంట్ వివరాలు, మెసేజ్‌లు, ఫోటోలు, ఆడియో, ఇంకా వీడియో వంటి విషయాల్లో జాగ్రత్త వహించండి."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"స్క్రీన్‌ను రికార్డ్ చేయండి"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"అన్నీ క్లియర్ చేయండి"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"మేనేజ్ చేయండి"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"హిస్టరీ"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"నోటిఫికేషన్ సెట్టింగ్‌లు"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"నోటిఫికేషన్ హిస్టరీ"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"కొత్తవి"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"నిశ్శబ్దం"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"నోటిఫికేషన్‌లు"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"ప్రస్తుత యాప్"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"యాక్సెసిబిలిటీ"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"కీబోర్డ్ షార్ట్‌కట్‌లు"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"సెర్చ్ షార్ట్‌కట్‌లు"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"సెర్చ్ ఫలితాలు ఏవీ లేవు"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"కుదించండి చిహ్నం"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"విస్తరించండి చిహ్నం"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"లేదా"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"లాగే హ్యాండిల్"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index ac027c7..b7d1a32 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"บันทึกหน้าจอไหม"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"บันทึกแอปเดียว"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"บันทึกทั้งหน้าจอ"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"ขณะบันทึกทั้งหน้าจอ ระบบจะบันทึกทุกสิ่งที่แสดงอยู่บนหน้าจอ ดังนั้นโปรดระวังสิ่งต่างๆ อย่างเช่นรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ รูปภาพ รวมถึงเสียงและวิดีโอ"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"ขณะบันทึกแอป ระบบจะบันทึกทุกสิ่งที่แสดงหรือเล่นอยู่ในแอปดังกล่าว ดังนั้นโปรดระวังสิ่งต่างๆ อย่างเช่นรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ รูปภาพ รวมถึงเสียงและวิดีโอ"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"บันทึกหน้าจอ"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"แอปปัจจุบัน"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"การช่วยเหลือพิเศษ"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"แป้นพิมพ์ลัด"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ค้นหาแป้นพิมพ์ลัด"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ไม่พบผลการค้นหา"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ไอคอนยุบ"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ไอคอนขยาย"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"หรือ"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"แฮนเดิลการลาก"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 09a0e040..a30a2f2 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"I-record ang iyong screen?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Mag-record ng isang app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"I-record ang buong screen"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Kapag nire-record mo ang iyong buong screen, nire-record ang anumang ipinapakita sa screen mo. Kaya mag-ingat sa mga bagay-bagay tulad ng mga password, detalye ng pagbabayad, mensahe, larawan, at audio at video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Kapag nagre-record ka ng app, nire-record ang anumang ipinapakita o pine-play sa app na iyon. Kaya mag-ingat sa mga bagay-bagay tulad ng mga password, detalye ng pagbabayad, mensahe, larawan, at audio at video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"I-record ang screen"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Kasalukuyang App"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibility"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Mga keyboard shortcut"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Mga shortcut ng paghahanap"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Walang resulta ng paghahanap"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"I-collapse ang icon"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"I-expand ang icon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"o"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Handle sa pag-drag"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index ae9cd18..c79dfcf 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Ekranınız kaydedilsin mi?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Bir uygulamayı kaydet"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Tüm ekranı kaydedin"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Tüm ekranınızı kaydettiğinizde ekranınızda gösterilen her şey kaydedilir. Bu nedenle şifre, ödeme ayrıntıları, mesaj, fotoğraf, ses ve video gibi öğeler konusunda dikkatli olun."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Bir uygulamayı kaydettiğinizde o uygulamada gösterilen veya oynatılan her şey kaydedilir. Bu nedenle şifre, ödeme ayrıntıları, mesaj, fotoğraf, ses ve video gibi öğeler konusunda dikkatli olun."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Ekranı kaydet"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Mevcut Uygulama"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Erişilebilirlik"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Klavye kısayolları"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Arama kısayolları"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Arama sonucu yok"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Daralt simgesi"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Genişlet simgesi"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"veya"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Sürükleme tutamacı"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 820d072..2808922 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Записати відео з екрана?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Записувати один додаток"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Записувати весь екран"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Коли ви записуєте вміст усього екрана, на відео потрапляє все, що на ньому відображається. Тому будьте уважні з паролями, повідомленнями, фотографіями, аудіо, відео, платіжною інформацією тощо."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Коли ви записуєте додаток, на відео потрапляє все, що відображається або відтворюється в ньому. Тому будьте уважні з паролями, повідомленнями, фотографіями, аудіо, відео, платіжною інформацією тощо."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Записувати вміст екрана"</string>
@@ -1413,15 +1415,21 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Поточний додаток"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Доступність"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Комбінації клавіш"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Комбінації клавіш для пошуку"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Нічого не знайдено"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Значок згортання"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Значок розгортання"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"або"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Маркер переміщення"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Налаштування клавіатури"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Навігація за допомогою клавіатури"</string>
-    <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Комбінації клавіш: докладніше"</string>
+    <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Дізнайтеся більше про комбінації клавіш"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Навігація за допомогою сенсорної панелі"</string>
     <string name="launch_touchpad_tutorial_notification_content" msgid="7931085031240753226">"Жести для сенсорної панелі: докладніше"</string>
     <string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"Навігація за допомогою клавіатури й сенсорної панелі"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index b98abbd..b72464ca 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"آپ کی اسکرین ریکارڈ کریں؟"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"ایک ایپ ریکارڈ کریں"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"پوری اسکرین کو ریکارڈ کریں"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"جب آپ اپنی پوری اسکرین کو ریکارڈ کر رہے ہوتے ہیں تو آپ کی اسکرین پر دکھائی گئی ہر چیز ریکارڈ کی جاتی ہے۔ لہذا، پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، تصاویر، ساتھ ہی آڈیو اور ویڈیو جیسی چیزوں کے سلسلے میں محتاط رہیں۔"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"جب آپ کسی ایپ کو ریکارڈ کر رہے ہوتے ہیں تو اس ایپ میں دکھائی گئی یا چلائی گئی ہر چیز ریکارڈ کی جاتی ہے۔ لہذا، پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، تصاویر، ساتھ ہی آڈیو اور ویڈیو جیسی چیزوں کے سلسلے میں محتاط رہیں۔"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"اسکرین ریکارڈ کریں"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"موجودہ ایپ"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ایکسیسبیلٹی"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"کی بورڈ شارٹ کٹس"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"تلاش کے شارٹ کٹس"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"تلاش کا کوئی نتیجہ نہیں ہے"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"آئیکن سکیڑیں"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"آئیکن پھیلائیں"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"یا"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"گھسیٹنے کا ہینڈل"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 3309772..7f6275c 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Ekran yozib olinsinmi?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Bitta ilovani yozib olish"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Butun ekranni yozib olish"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Butun ekranni yozib olishda ekranda koʻrsatilgan barcha axborotlar yozib olinadi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar, suratlar, audio va video chiqmasligi uchun ehtiyot boʻling."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Ilovani yozib olishda ilova koʻrsatilgan yoki ijro etilgan barcha axborotlar yozib olinadi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar, suratlar, audio va video chiqmasligi uchun ehtiyot boʻling."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Ekranni yozib olish"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Joriy ilova"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Qulayliklar"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Tezkor tugmalar"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Tezkor tugmalar qidiruvi"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Hech narsa topilmadi"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Yigʻish belgisi"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Yoyish belgisi"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"yoki"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Surish dastagi"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index f404a2b..388ebb8 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Ghi màn hình?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Ghi một ứng dụng"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Ghi toàn màn hình"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Khi bạn ghi toàn màn hình, mọi nội dung trên màn hình của bạn đều được ghi. Vì vậy, hãy thận trọng để không làm lộ thông tin như mật khẩu, thông tin thanh toán, tin nhắn, ảnh, âm thanh và video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Khi bạn ghi một ứng dụng, mọi nội dung xuất hiện hoặc phát trong ứng dụng đó sẽ đều được ghi. Vì vậy, hãy thận trọng để không làm lộ thông tin như mật khẩu, thông tin thanh toán, tin nhắn, ảnh, âm thanh và video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Ghi màn hình"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Xóa tất cả"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Quản lý"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Lịch sử"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Cài đặt thông báo"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"Nhật ký thông báo"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Mới"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Im lặng"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Thông báo"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Ứng dụng hiện tại"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Hỗ trợ tiếp cận"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Phím tắt"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Lối tắt tìm kiếm"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Không có kết quả tìm kiếm nào"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Biểu tượng Thu gọn"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Biểu tượng Mở rộng"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"hoặc"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Nút kéo"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 24c9f40..b463900 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"要录制屏幕吗?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"录制单个应用"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"录制整个屏幕"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"录制整个屏幕时,屏幕上显示的所有内容均会被录制。因此,请务必小心操作,谨防泄露密码、付款信息、消息、照片、音频、视频等。"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"录制单个应用时,该应用中显示或播放的所有内容均会被录制。因此,请务必小心操作,谨防泄露密码、付款信息、消息、照片、音频、视频等。"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"录制屏幕"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"当前应用"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"无障碍功能"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"键盘快捷键"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"搜索快捷键"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"无搜索结果"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"收起图标"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"展开图标"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"或"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"拖动手柄"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index e7ebe5c..9118591 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"要錄影螢幕畫面嗎?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"錄影一個應用程式"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"錄影整個螢幕畫面"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"當你錄影整個螢幕畫面時,系統會錄影螢幕畫面上顯示的任何內容。因此,請謹慎處理密碼、付款資料、訊息、相片、音訊和影片等。"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"當你錄影應用程式時,系統會錄影該應用程式中顯示或播放的任何內容。因此,請謹慎處理密碼、付款資料、訊息、相片、音訊和影片等。"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"錄影螢幕畫面"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"目前的應用程式"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"無障礙功能"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"鍵盤快速鍵"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"搜尋快速鍵"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"沒有相符的搜尋結果"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"收合圖示"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"展開圖示"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"或"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"拖曳控點"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 0f298dc..2189cbf 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"要錄製畫面嗎?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"錄製單一應用程式"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"錄製整個畫面"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"錄製整個畫面時,系統會錄下畫面上的所有內容。因此,請謹慎處理密碼、付款資料、訊息、相片和影音內容等資訊。"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"當你錄製應用程式畫面時,系統會錄下該應用程式顯示或播放的所有內容。因此,請謹慎處理密碼、付款資料、訊息、相片和影音內容等資訊。"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"錄製畫面"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"目前的應用程式"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"無障礙"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"鍵盤快速鍵"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"搜尋快速鍵"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"找不到相符的搜尋結果"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"收合圖示"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"展開圖示"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"或"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"拖曳控點"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 444ca04..05b5040 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Rekhoda isikrini sakho?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Rekhoda i-app eyodwa"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Rekhoda sonke isikrini"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Uma urekhoda sonke isikrini sakho, noma yini evela esikrinini iyarekhodwa. Ngakho-ke qaphela ngezinto ezifana namaphasiwedi, imininingwane yenkokhelo, imilayezo, izithombe, nomsindo nevidiyo."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Uma urekhoda i-app, noma yini evezwa noma edlala kuleyo app iyarekhodwa. Ngakho-ke qaphela ngezinto ezifana namaphasiwedi, imininingwane yenkokhelo, imilayezo, izithombe, nomsindo nevidiyo."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Rekhoda isikrini"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"I-App yamanje"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Ukufinyeleleka"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Izinqamuleli zekhibhodi"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Sesha izinqamuleli"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ayikho imiphumela yosesho"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Goqa isithonjana"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Nweba isithonjana"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"noma"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Hudula isibambi"</string>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index d4a52c3..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 -->
@@ -383,6 +383,10 @@
     <!-- Whether to show activity indicators in the status bar -->
     <bool name="config_showActivity">false</bool>
 
+    <!-- Whether to show the opportunistic satellite icon. When true, an icon will show to indicate
+         satellite capabilities when all other connections are out of service. -->
+    <bool name="config_showOpportunisticSatelliteIcon">true</bool>
+
     <!-- Whether or not to show the notification shelf that houses the icons of notifications that
      have been scrolled off-screen. -->
     <bool name="config_showNotificationShelf">true</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index de547ad..7fa2879 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -584,6 +584,12 @@
     -->
     <dimen name="shade_panel_width">412dp</dimen>
 
+    <!--
+    The horizontal distance between the shade overlay panel (both notifications and quick settings)
+    and the edge of the screen. This is zero only on Compact screens (< sw600dp).
+    -->
+    <dimen name="shade_panel_margin_horizontal">0dp</dimen>
+
     <dimen name="brightness_mirror_height">48dp</dimen>
 
     <dimen name="volume_dialog_panel_transparent_padding_right">8dp</dimen>
@@ -2058,7 +2064,7 @@
     <!-- Volume start -->
     <dimen name="volume_dialog_background_corner_radius">30dp</dimen>
     <dimen name="volume_dialog_width">60dp</dimen>
-    <dimen name="volume_dialog_vertical_padding">6dp</dimen>
+    <dimen name="volume_dialog_vertical_padding">10dp</dimen>
     <dimen name="volume_dialog_components_spacing">8dp</dimen>
     <dimen name="volume_dialog_floating_sliders_spacing">8dp</dimen>
     <dimen name="volume_dialog_floating_sliders_vertical_padding">10dp</dimen>
@@ -2072,10 +2078,10 @@
     <dimen name="volume_panel_slice_vertical_padding">8dp</dimen>
     <dimen name="volume_panel_slice_horizontal_padding">24dp</dimen>
 
-    <dimen name="volume_dialog_ringer_container_padding">10dp</dimen>
-    <dimen name="volume_ringer_item_size">40dp</dimen>
-    <dimen name="volume_ringer_icon_size">20dp</dimen>
-    <dimen name="volume_ringer_item_radius">12dp</dimen>
-    <dimen name="volume_dialog_ringer_item_margin_bottom">8dp</dimen>
+    <dimen name="volume_dialog_ringer_horizontal_padding">10dp</dimen>
+    <dimen name="volume_dialog_ringer_drawer_button_size">40dp</dimen>
+    <dimen name="volume_dialog_ringer_drawer_button_icon_radius">10dp</dimen>
+    <dimen name="volume_dialog_background_square_corner_radius">12dp</dimen>
+    <dimen name="volume_dialog_ringer_selected_button_background_radius">20dp</dimen>
     <!-- Volume end -->
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ab64ae5..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>
@@ -1797,6 +1800,7 @@
     <string name="volume_panel_spatial_audio_tracking">Head Tracking</string>
 
     <string name="volume_ringer_change">Tap to change ringer mode</string>
+    <string name="volume_ringer_mode">ringer mode</string>
 
     <!-- Hint for accessibility. For example: double tap to mute [CHAR_LIMIT=NONE] -->
     <string name="volume_ringer_hint_mute">mute</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index ab45b9f..7d071cd 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -557,6 +557,20 @@
         <item name="android:showWhenLocked">true</item>
     </style>
 
+    <style name="SystemUI.Material3.Slider.Volume">
+        <item name="trackHeight">40dp</item>
+        <item name="thumbHeight">52dp</item>
+    </style>
+
+    <style name="SystemUI.Material3.Slider" parent="@style/Widget.Material3.Slider">
+        <item name="labelStyle">@style/Widget.Material3.Slider.Label</item>
+        <item name="thumbColor">@color/slider_thumb_color</item>
+        <item name="tickColorActive">@color/slider_inactive_track_color</item>
+        <item name="tickColorInactive">@color/slider_active_track_color</item>
+        <item name="trackColorActive">@color/slider_active_track_color</item>
+        <item name="trackColorInactive">@color/slider_inactive_track_color</item>
+    </style>
+
     <style name="Theme.SystemUI.DayNightDialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"/>
 
     <style name="Theme.SystemUI.Dialog" parent="@style/Theme.SystemUI.DayNightDialog">
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/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 46e45aa..66b3e189 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -905,7 +905,18 @@
         return lp;
     }
 
-    private WindowManager.LayoutParams getWindowLayoutBaseParams() {
+    public static WindowManager.LayoutParams getWindowLayoutBaseParams() {
+        return getWindowLayoutBaseParams(/* excludeFromScreenshots= */ true);
+    }
+
+    /**
+     * Creates the base {@link WindowManager.LayoutParams} that are used for all decoration windows.
+     *
+     * @param excludeFromScreenshots whether to set the {@link
+     *     WindowManager.LayoutParams#PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY} flag.
+     */
+    public static WindowManager.LayoutParams getWindowLayoutBaseParams(
+            boolean excludeFromScreenshots) {
         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
@@ -921,7 +932,7 @@
         // FLAG_SLIPPERY can only be set by trusted overlays
         lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 
-        if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS) {
+        if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS && excludeFromScreenshots) {
             lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
index f8b445b..3cf400a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
@@ -38,7 +38,6 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
 import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IMagnificationConnection;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
@@ -138,10 +137,7 @@
                     mWindowMagnifierCallback,
                     mSysUiState,
                     mSecureSettings,
-                    scvhSupplier,
-                    new SfVsyncFrameCallbackProvider(),
-                    WindowManagerGlobal::getWindowSession,
-                    mViewCaptureAwareWindowManager);
+                    scvhSupplier);
         }
     }
 
@@ -407,7 +403,8 @@
         }
     }
 
-    boolean isMagnificationSettingsPanelShowing(int displayId) {
+    @MainThread
+    private boolean isMagnificationSettingsPanelShowing(int displayId) {
         final MagnificationSettingsController magnificationSettingsController =
                 mMagnificationSettingsSupplier.get(displayId);
         if (magnificationSettingsController != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 0883a06..7d5cf23 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -54,11 +54,8 @@
 import android.util.Size;
 import android.util.SparseArray;
 import android.util.TypedValue;
-import android.view.Choreographer;
 import android.view.Display;
 import android.view.Gravity;
-import android.view.IWindow;
-import android.view.IWindowSession;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -80,10 +77,8 @@
 import androidx.annotation.UiThread;
 import androidx.core.math.MathUtils;
 
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.accessibility.common.MagnificationConstants;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.systemui.Flags;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.res.R;
@@ -127,7 +122,6 @@
     private final SurfaceControl.Transaction mTransaction;
 
     private final WindowManager mWm;
-    private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
 
     private float mScale;
     private int mSettingsButtonIndex = MagnificationSize.DEFAULT;
@@ -219,11 +213,8 @@
     private int mMinWindowSize;
 
     private final WindowMagnificationAnimationController mAnimationController;
-    private final Supplier<IWindowSession> mGlobalWindowSessionSupplier;
-    private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
     private final MagnificationGestureDetector mGestureDetector;
     private int mBounceEffectDuration;
-    private final Choreographer.FrameCallback mMirrorViewGeometryVsyncCallback;
     private Locale mLocale;
     private NumberFormat mPercentFormat;
     private float mBounceEffectAnimationScale;
@@ -258,18 +249,11 @@
             @NonNull WindowMagnifierCallback callback,
             SysUiState sysUiState,
             SecureSettings secureSettings,
-            Supplier<SurfaceControlViewHost> scvhSupplier,
-            SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
-            Supplier<IWindowSession> globalWindowSessionSupplier,
-            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+            Supplier<SurfaceControlViewHost> scvhSupplier) {
         mContext = context;
         mHandler = handler;
         mAnimationController = animationController;
-        mAnimationController.setOnAnimationEndRunnable(() -> {
-            if (Flags.createWindowlessWindowMagnifier()) {
-                notifySourceBoundsChanged();
-            }
-        });
+        mAnimationController.setOnAnimationEndRunnable(this::notifySourceBoundsChanged);
         mAnimationController.setWindowMagnificationController(this);
         mWindowMagnifierCallback = callback;
         mSysUiState = sysUiState;
@@ -283,7 +267,6 @@
 
         mWm = context.getSystemService(WindowManager.class);
         mWindowBounds = new Rect(mWm.getCurrentWindowMetrics().getBounds());
-        mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
 
         mResources = mContext.getResources();
         mScale = secureSettings.getFloatForUser(
@@ -313,76 +296,31 @@
         mGestureDetector =
                 new MagnificationGestureDetector(mContext, handler, this);
         mWindowInsetChangeRunnable = this::onWindowInsetChanged;
-        mGlobalWindowSessionSupplier = globalWindowSessionSupplier;
-        mSfVsyncFrameProvider = sfVsyncFrameProvider;
 
         // Initialize listeners.
-        if (Flags.createWindowlessWindowMagnifier()) {
-            mMirrorViewRunnable = new Runnable() {
-                final Rect mPreviousBounds = new Rect();
+        mMirrorViewRunnable = new Runnable() {
+            final Rect mPreviousBounds = new Rect();
 
-                @Override
-                public void run() {
-                    if (mMirrorView != null) {
-                        if (mPreviousBounds.width() != mMirrorViewBounds.width()
-                                || mPreviousBounds.height() != mMirrorViewBounds.height()) {
-                            mMirrorView.setSystemGestureExclusionRects(Collections.singletonList(
-                                    new Rect(0, 0, mMirrorViewBounds.width(),
-                                            mMirrorViewBounds.height())));
-                            mPreviousBounds.set(mMirrorViewBounds);
-                        }
-                        updateSystemUIStateIfNeeded();
-                        mWindowMagnifierCallback.onWindowMagnifierBoundsChanged(
-                                mDisplayId, mMirrorViewBounds);
-                    }
-                }
-            };
-
-            mMirrorSurfaceViewLayoutChangeListener =
-                    (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
-                            mMirrorView.post(this::applyTapExcludeRegion);
-
-            mMirrorViewGeometryVsyncCallback = null;
-        } else {
-            mMirrorViewRunnable = () -> {
+            @Override
+            public void run() {
                 if (mMirrorView != null) {
-                    final Rect oldViewBounds = new Rect(mMirrorViewBounds);
-                    mMirrorView.getBoundsOnScreen(mMirrorViewBounds);
-                    if (oldViewBounds.width() != mMirrorViewBounds.width()
-                            || oldViewBounds.height() != mMirrorViewBounds.height()) {
+                    if (mPreviousBounds.width() != mMirrorViewBounds.width()
+                            || mPreviousBounds.height() != mMirrorViewBounds.height()) {
                         mMirrorView.setSystemGestureExclusionRects(Collections.singletonList(
-                                new Rect(0, 0,
-                                        mMirrorViewBounds.width(), mMirrorViewBounds.height())));
+                                new Rect(0, 0, mMirrorViewBounds.width(),
+                                        mMirrorViewBounds.height())));
+                        mPreviousBounds.set(mMirrorViewBounds);
                     }
                     updateSystemUIStateIfNeeded();
                     mWindowMagnifierCallback.onWindowMagnifierBoundsChanged(
                             mDisplayId, mMirrorViewBounds);
                 }
-            };
+            }
+        };
 
-            mMirrorSurfaceViewLayoutChangeListener =
-                    (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
-                            mMirrorView.post(this::applyTapExcludeRegion);
-
-            mMirrorViewGeometryVsyncCallback =
-                    l -> {
-                        if (isActivated() && mMirrorSurface != null && calculateSourceBounds(
-                                mMagnificationFrame, mScale)) {
-                            // The final destination for the magnification surface should be at 0,0
-                            // since the ViewRootImpl's position will change
-                            mTmpRect.set(0, 0, mMagnificationFrame.width(),
-                                    mMagnificationFrame.height());
-                            mTransaction.setGeometry(mMirrorSurface, mSourceBounds, mTmpRect,
-                                    Surface.ROTATION_0).apply();
-
-                            // Notify source bounds change when the magnifier is not animating.
-                            if (!mAnimationController.isAnimating()) {
-                                mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId,
-                                        mSourceBounds);
-                            }
-                        }
-                    };
-        }
+        mMirrorSurfaceViewLayoutChangeListener =
+                (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
+                        mMirrorView.post(this::applyTouchableRegion);
 
         mMirrorViewLayoutChangeListener =
                 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
@@ -463,7 +401,7 @@
 
         if (isActivated()) {
             updateDimensions();
-            applyTapExcludeRegion();
+            applyTouchableRegion();
         }
 
         if (!enable) {
@@ -513,9 +451,6 @@
         if (mMirrorView != null) {
             mHandler.removeCallbacks(mMirrorViewRunnable);
             mMirrorView.removeOnLayoutChangeListener(mMirrorViewLayoutChangeListener);
-            if (!Flags.createWindowlessWindowMagnifier()) {
-                mViewCaptureAwareWindowManager.removeView(mMirrorView);
-            }
             mMirrorView = null;
         }
 
@@ -624,11 +559,7 @@
         if (!isActivated()) return;
         LayoutParams params = (LayoutParams) mMirrorView.getLayoutParams();
         params.accessibilityTitle = getAccessibilityWindowTitle();
-        if (Flags.createWindowlessWindowMagnifier()) {
-            mSurfaceControlViewHost.relayout(params);
-        } else {
-            mWm.updateViewLayout(mMirrorView, params);
-        }
+        mSurfaceControlViewHost.relayout(params);
     }
 
     /**
@@ -678,62 +609,6 @@
         return (oldRotation - newRotation + 4) % 4 * 90;
     }
 
-    private void createMirrorWindow() {
-        if (Flags.createWindowlessWindowMagnifier()) {
-            createWindowlessMirrorWindow();
-            return;
-        }
-
-        // The window should be the size the mirrored surface will be but also add room for the
-        // border and the drag handle.
-        int windowWidth = mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin;
-        int windowHeight = mMagnificationFrame.height() + 2 * mMirrorSurfaceMargin;
-
-        LayoutParams params = new LayoutParams(
-                windowWidth, windowHeight,
-                LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,
-                LayoutParams.FLAG_NOT_TOUCH_MODAL
-                        | LayoutParams.FLAG_NOT_FOCUSABLE,
-                PixelFormat.TRANSPARENT);
-        params.gravity = Gravity.TOP | Gravity.LEFT;
-        params.x = mMagnificationFrame.left - mMirrorSurfaceMargin;
-        params.y = mMagnificationFrame.top - mMirrorSurfaceMargin;
-        params.layoutInDisplayCutoutMode = LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
-        params.receiveInsetsIgnoringZOrder = true;
-        params.setTitle(mContext.getString(R.string.magnification_window_title));
-        params.accessibilityTitle = getAccessibilityWindowTitle();
-
-        mMirrorView = LayoutInflater.from(mContext).inflate(R.layout.window_magnifier_view, null);
-        mMirrorSurfaceView = mMirrorView.findViewById(R.id.surface_view);
-
-        mMirrorBorderView = mMirrorView.findViewById(R.id.magnification_inner_border);
-
-        // Allow taps to go through to the mirror SurfaceView below.
-        mMirrorSurfaceView.addOnLayoutChangeListener(mMirrorSurfaceViewLayoutChangeListener);
-
-        mMirrorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
-                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
-                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
-                | View.SYSTEM_UI_FLAG_FULLSCREEN
-                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
-                | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
-        mMirrorView.addOnLayoutChangeListener(mMirrorViewLayoutChangeListener);
-        mMirrorView.setAccessibilityDelegate(new MirrorWindowA11yDelegate());
-        mMirrorView.setOnApplyWindowInsetsListener((v, insets) -> {
-            if (!mHandler.hasCallbacks(mWindowInsetChangeRunnable)) {
-                mHandler.post(mWindowInsetChangeRunnable);
-            }
-            return v.onApplyWindowInsets(insets);
-        });
-
-        mViewCaptureAwareWindowManager.addView(mMirrorView, params);
-
-        SurfaceHolder holder = mMirrorSurfaceView.getHolder();
-        holder.addCallback(this);
-        holder.setFormat(PixelFormat.RGBA_8888);
-        addDragTouchListeners();
-    }
-
     private void createWindowlessMirrorWindow() {
         // The window should be the size the mirrored surface will be but also add room for the
         // border and the drag handle.
@@ -802,62 +677,6 @@
         }
     }
 
-    private void applyTapExcludeRegion() {
-        if (Flags.createWindowlessWindowMagnifier()) {
-            applyTouchableRegion();
-            return;
-        }
-
-        // Sometimes this can get posted and run after deleteWindowMagnification() is called.
-        if (mMirrorView == null) return;
-
-        final Region tapExcludeRegion = calculateTapExclude();
-        final IWindow window = IWindow.Stub.asInterface(mMirrorView.getWindowToken());
-        try {
-            IWindowSession session = mGlobalWindowSessionSupplier.get();
-            session.updateTapExcludeRegion(window, tapExcludeRegion);
-        } catch (RemoteException e) {
-        }
-    }
-
-    private Region calculateTapExclude() {
-        Region regionInsideDragBorder = new Region(mBorderDragSize, mBorderDragSize,
-                mMirrorView.getWidth() - mBorderDragSize,
-                mMirrorView.getHeight() - mBorderDragSize);
-
-        Region tapExcludeRegion = new Region();
-
-        Rect dragArea = new Rect();
-        mDragView.getHitRect(dragArea);
-
-        Rect topLeftArea = new Rect();
-        mTopLeftCornerView.getHitRect(topLeftArea);
-
-        Rect topRightArea = new Rect();
-        mTopRightCornerView.getHitRect(topRightArea);
-
-        Rect bottomLeftArea = new Rect();
-        mBottomLeftCornerView.getHitRect(bottomLeftArea);
-
-        Rect bottomRightArea = new Rect();
-        mBottomRightCornerView.getHitRect(bottomRightArea);
-
-        Rect closeArea = new Rect();
-        mCloseView.getHitRect(closeArea);
-
-        // add tapExcludeRegion for Drag or close
-        tapExcludeRegion.op(dragArea, Region.Op.UNION);
-        tapExcludeRegion.op(topLeftArea, Region.Op.UNION);
-        tapExcludeRegion.op(topRightArea, Region.Op.UNION);
-        tapExcludeRegion.op(bottomLeftArea, Region.Op.UNION);
-        tapExcludeRegion.op(bottomRightArea, Region.Op.UNION);
-        tapExcludeRegion.op(closeArea, Region.Op.UNION);
-
-        regionInsideDragBorder.op(tapExcludeRegion, Region.Op.DIFFERENCE);
-
-        return regionInsideDragBorder;
-    }
-
     private void applyTouchableRegion() {
         // Sometimes this can get posted and run after deleteWindowMagnification() is called.
         if (mMirrorView == null) return;
@@ -1085,13 +904,8 @@
      * {@link #mMagnificationFrame}.
      */
     private void modifyWindowMagnification(boolean computeWindowSize) {
-        if (Flags.createWindowlessWindowMagnifier()) {
-            updateMirrorSurfaceGeometry();
-            updateWindowlessMirrorViewLayout(computeWindowSize);
-        } else {
-            mSfVsyncFrameProvider.postFrameCallback(mMirrorViewGeometryVsyncCallback);
-            updateMirrorViewLayout(computeWindowSize);
-        }
+        updateMirrorSurfaceGeometry();
+        updateWindowlessMirrorViewLayout(computeWindowSize);
     }
 
     /**
@@ -1169,58 +983,6 @@
         mMirrorViewRunnable.run();
     }
 
-    /**
-     * Updates the layout params of MirrorView based on the size of {@link #mMagnificationFrame}
-     * and translates MirrorView position when the view is moved close to the screen edges;
-     *
-     * @param computeWindowSize set to {@code true} to compute window size with
-     * {@link #mMagnificationFrame}.
-     */
-    private void updateMirrorViewLayout(boolean computeWindowSize) {
-        if (!isActivated()) {
-            return;
-        }
-        final int maxMirrorViewX = mWindowBounds.width() - mMirrorView.getWidth();
-        final int maxMirrorViewY = mWindowBounds.height() - mMirrorView.getHeight();
-
-        LayoutParams params =
-                (LayoutParams) mMirrorView.getLayoutParams();
-        params.x = mMagnificationFrame.left - mMirrorSurfaceMargin;
-        params.y = mMagnificationFrame.top - mMirrorSurfaceMargin;
-        if (computeWindowSize) {
-            params.width = mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin;
-            params.height = mMagnificationFrame.height() + 2 * mMirrorSurfaceMargin;
-        }
-
-        // Translates MirrorView position to make MirrorSurfaceView that is inside MirrorView
-        // able to move close to the screen edges.
-        final float translationX;
-        final float translationY;
-        if (params.x < 0) {
-            translationX = Math.max(params.x, -mOuterBorderSize);
-        } else if (params.x > maxMirrorViewX) {
-            translationX = Math.min(params.x - maxMirrorViewX, mOuterBorderSize);
-        } else {
-            translationX = 0;
-        }
-        if (params.y < 0) {
-            translationY = Math.max(params.y, -mOuterBorderSize);
-        } else if (params.y > maxMirrorViewY) {
-            translationY = Math.min(params.y - maxMirrorViewY, mOuterBorderSize);
-        } else {
-            translationY = 0;
-        }
-        mMirrorView.setTranslationX(translationX);
-        mMirrorView.setTranslationY(translationY);
-        mWm.updateViewLayout(mMirrorView, params);
-
-        // If they are not dragging the handle, we can move the drag handle immediately without
-        // disruption. But if they are dragging it, we avoid moving until the end of the drag.
-        if (!mIsDragging) {
-            mMirrorView.post(this::maybeRepositionButton);
-        }
-    }
-
     @Override
     public boolean onTouch(View v, MotionEvent event) {
         if (v == mDragView
@@ -1474,7 +1236,7 @@
         calculateMagnificationFrameBoundary();
         updateMagnificationFramePosition((int) offsetX, (int) offsetY);
         if (!isActivated()) {
-            createMirrorWindow();
+            createWindowlessMirrorWindow();
             showControls();
             applyResourcesValues();
         } else {
@@ -1766,7 +1528,7 @@
         if (newGravity != layoutParams.gravity) {
             layoutParams.gravity = newGravity;
             mDragView.setLayoutParams(layoutParams);
-            mDragView.post(this::applyTapExcludeRegion);
+            mDragView.post(this::applyTouchableRegion);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt
index 5414b62..39fd44a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt
@@ -63,6 +63,7 @@
     private val captioningManager: StateFlow<CaptioningManager?> =
         userRepository.selectedUser
             .map { userScopedCaptioningManagerProvider.forUser(it.userInfo.userHandle) }
+            .flowOn(backgroundCoroutineContext)
             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
 
     override val captioningModel: StateFlow<CaptioningModel?> =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 12b5fc0..b491c94 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -27,6 +27,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AlertDialog;
+import android.app.KeyguardManager;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.PixelFormat;
@@ -315,6 +316,16 @@
         mBiometricCallback = new BiometricCallback();
         mMSDLPlayer = msdlPlayer;
 
+        // Listener for when device locks from adaptive auth, dismiss prompt
+        getContext().getSystemService(KeyguardManager.class).addKeyguardLockedStateListener(
+                getContext().getMainExecutor(),
+                isKeyguardLocked -> {
+                    if (isKeyguardLocked) {
+                        onStartedGoingToSleep();
+                    }
+                }
+        );
+
         final BiometricModalities biometricModalities = new BiometricModalities(
                 Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
                 Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds));
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index ba51d02..68ec0f2d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -25,6 +25,7 @@
 import android.hardware.fingerprint.FingerprintSensorProperties
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
+import android.util.Log
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
 import com.android.systemui.biometrics.shared.model.toSensorStrength
@@ -91,13 +92,14 @@
                                 trySendWithFailureLogging(
                                     DEFAULT_PROPS,
                                     TAG,
-                                    "no registered sensors, use default props"
+                                    "no registered sensors, use default props",
                                 )
                             } else {
+                                Log.d(TAG, "onAllAuthenticatorsRegistered $sensors")
                                 trySendWithFailureLogging(
                                     sensors[0],
                                     TAG,
-                                    "update properties on authenticators registered"
+                                    "update properties on authenticators registered",
                                 )
                             }
                         }
@@ -160,7 +162,7 @@
                 FingerprintSensorProperties.TYPE_UNKNOWN,
                 false /* halControlsIllumination */,
                 true /* resetLockoutRequiresHardwareAuthToken */,
-                listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT)
+                listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT),
             )
         private val DEFAULT_PROPS =
             FingerprintSensorPropertiesInternal(
@@ -171,7 +173,7 @@
                 FingerprintSensorProperties.TYPE_UNKNOWN,
                 false /* halControlsIllumination */,
                 true /* resetLockoutRequiresHardwareAuthToken */,
-                listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT)
+                listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT),
             )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/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/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
index 18a7739..abbbd73 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
@@ -48,14 +48,14 @@
     private val authController: AuthController,
     private val selectedUserInteractor: SelectedUserInteractor,
     private val fingerprintManager: FingerprintManager?,
-    @Application scope: CoroutineScope
+    @Application scope: CoroutineScope,
 ) {
     private fun calculateIconSize(): Int {
         val pixelPitch = context.resources.getFloat(R.dimen.pixel_pitch)
         if (pixelPitch <= 0) {
             Log.e(
                 "UdfpsOverlayInteractor",
-                "invalid pixelPitch: $pixelPitch. Pixel pitch must be updated per device."
+                "invalid pixelPitch: $pixelPitch. Pixel pitch must be updated per device.",
             )
         }
         return (context.resources.getFloat(R.dimen.udfps_icon_size) / pixelPitch).toInt()
@@ -83,12 +83,11 @@
 
     /** Sets whether Udfps overlay should handle touches */
     fun setHandleTouches(shouldHandle: Boolean = true) {
-        if (authController.isUdfpsSupported
-                && shouldHandle != _shouldHandleTouches.value) {
+        if (authController.isUdfpsSupported && shouldHandle != _shouldHandleTouches.value) {
             fingerprintManager?.setIgnoreDisplayTouches(
                 requestId.value,
                 authController.udfpsProps!!.get(0).sensorId,
-                !shouldHandle
+                !shouldHandle,
             )
         }
         _shouldHandleTouches.value = shouldHandle
@@ -107,10 +106,11 @@
                         override fun onUdfpsLocationChanged(
                             udfpsOverlayParams: UdfpsOverlayParams
                         ) {
+                            Log.d(TAG, "udfpsOverlayParams updated $udfpsOverlayParams")
                             trySendWithFailureLogging(
                                 udfpsOverlayParams,
                                 TAG,
-                                "update udfpsOverlayParams"
+                                "update udfpsOverlayParams",
                             )
                         }
                     }
diff --git a/packages/SystemUI/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/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt
similarity index 68%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt
index 94b2bdf..ddd6bc9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.clipboardoverlay
 
-import com.android.systemui.kosmos.Kosmos
+/** Interface for listening to indication text changed from [ClipboardIndicationProvider]. */
+interface ClipboardIndicationCallback {
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+    /** Notifies the indication text changed. */
+    fun onIndicationTextChanged(text: CharSequence)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt
index 94b2bdf..be32723 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt
@@ -14,9 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.clipboardoverlay
 
-import com.android.systemui.kosmos.Kosmos
+/** Interface to provide the clipboard indication to be shown under the overlay. */
+interface ClipboardIndicationProvider {
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+    /**
+     * Gets the indication text async.
+     *
+     * @param callback callback for getting the indication text.
+     */
+    fun getIndicationText(callback: ClipboardIndicationCallback)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt
similarity index 65%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt
index 94b2bdf..da94d5b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt
@@ -14,9 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.clipboardoverlay
 
-import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+@SysUISingleton
+open class ClipboardIndicationProviderImpl @Inject constructor() : ClipboardIndicationProvider {
+
+    override fun getIndicationText(callback: ClipboardIndicationCallback) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 65c01ed..ac74784 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -21,6 +21,7 @@
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
 import static com.android.systemui.Flags.clipboardImageTimeout;
 import static com.android.systemui.Flags.clipboardSharedTransitions;
+import static com.android.systemui.Flags.showClipboardIndication;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER;
@@ -99,6 +100,7 @@
     private final ClipboardTransitionExecutor mTransitionExecutor;
 
     private final ClipboardOverlayView mView;
+    private final ClipboardIndicationProvider mClipboardIndicationProvider;
 
     private Runnable mOnSessionCompleteListener;
     private Runnable mOnRemoteCopyTapped;
@@ -173,6 +175,13 @@
                 }
             };
 
+    private ClipboardIndicationCallback mIndicationCallback = new ClipboardIndicationCallback() {
+        @Override
+        public void onIndicationTextChanged(@NonNull CharSequence text) {
+            mView.setIndicationText(text);
+        }
+    };
+
     @Inject
     public ClipboardOverlayController(@OverlayWindowContext Context context,
             ClipboardOverlayView clipboardOverlayView,
@@ -185,11 +194,13 @@
             @Background Executor bgExecutor,
             ClipboardImageLoader clipboardImageLoader,
             ClipboardTransitionExecutor transitionExecutor,
+            ClipboardIndicationProvider clipboardIndicationProvider,
             UiEventLogger uiEventLogger) {
         mContext = context;
         mBroadcastDispatcher = broadcastDispatcher;
         mClipboardImageLoader = clipboardImageLoader;
         mTransitionExecutor = transitionExecutor;
+        mClipboardIndicationProvider = clipboardIndicationProvider;
 
         mClipboardLogger = new ClipboardLogger(uiEventLogger);
 
@@ -288,6 +299,9 @@
         boolean shouldAnimate = !model.dataMatches(mClipboardModel) || wasExiting;
         mClipboardModel = model;
         mClipboardLogger.setClipSource(mClipboardModel.getSource());
+        if (showClipboardIndication()) {
+            mClipboardIndicationProvider.getIndicationText(mIndicationCallback);
+        }
         if (clipboardImageTimeout()) {
             if (shouldAnimate) {
                 reset();
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
index 1762d82..7e4d762 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -18,6 +18,8 @@
 
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
+import static com.android.systemui.Flags.showClipboardIndication;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -53,6 +55,7 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.core.view.ViewCompat;
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
 
@@ -103,6 +106,8 @@
     private View mShareChip;
     private View mRemoteCopyChip;
     private View mActionContainerBackground;
+    private View mIndicationContainer;
+    private TextView mIndicationText;
     private View mDismissButton;
     private LinearLayout mActionContainer;
     private ClipboardOverlayCallbacks mClipboardCallbacks;
@@ -136,6 +141,8 @@
         mShareChip = requireViewById(R.id.share_chip);
         mRemoteCopyChip = requireViewById(R.id.remote_copy_chip);
         mDismissButton = requireViewById(R.id.dismiss_button);
+        mIndicationContainer = requireViewById(R.id.indication_container);
+        mIndicationText = mIndicationContainer.findViewById(R.id.indication_text);
 
         bindDefaultActionChips();
 
@@ -208,6 +215,14 @@
         }
     }
 
+    void setIndicationText(CharSequence text) {
+        mIndicationText.setText(text);
+
+        // Set the visibility of clipboard indication based on the text is empty or not.
+        int visibility = text.isEmpty() ? View.GONE : View.VISIBLE;
+        mIndicationContainer.setVisibility(visibility);
+    }
+
     void setMinimized(boolean minimized) {
         if (minimized) {
             mMinimizedPreview.setVisibility(View.VISIBLE);
@@ -221,6 +236,18 @@
             mPreviewBorder.setVisibility(View.VISIBLE);
             mActionContainer.setVisibility(View.VISIBLE);
         }
+
+        if (showClipboardIndication()) {
+            // Adjust the margin of clipboard indication based on the minimized state.
+            int marginStart = minimized ? getResources().getDimensionPixelSize(
+                    R.dimen.overlay_action_container_margin_horizontal)
+                    : getResources().getDimensionPixelSize(
+                            R.dimen.overlay_action_container_minimum_edge_spacing);
+            ConstraintLayout.LayoutParams params =
+                    (ConstraintLayout.LayoutParams) mIndicationContainer.getLayoutParams();
+            params.setMarginStart(marginStart);
+            mIndicationContainer.setLayoutParams(params);
+        }
     }
 
     void setInsets(WindowInsets insets, int orientation) {
@@ -313,6 +340,7 @@
         setTranslationX(0);
         setAlpha(0);
         mActionContainerBackground.setVisibility(View.GONE);
+        mIndicationContainer.setVisibility(View.GONE);
         mDismissButton.setVisibility(View.GONE);
         mShareChip.setVisibility(View.GONE);
         mRemoteCopyChip.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlaySuppressionModule.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayOverrideModule.kt
similarity index 69%
rename from packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlaySuppressionModule.kt
rename to packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayOverrideModule.kt
index 527819c..c81f0d9 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlaySuppressionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayOverrideModule.kt
@@ -15,18 +15,26 @@
  */
 package com.android.systemui.clipboardoverlay.dagger
 
+import com.android.systemui.clipboardoverlay.ClipboardIndicationProvider
+import com.android.systemui.clipboardoverlay.ClipboardIndicationProviderImpl
 import com.android.systemui.clipboardoverlay.ClipboardOverlaySuppressionController
 import com.android.systemui.clipboardoverlay.ClipboardOverlaySuppressionControllerImpl
 import dagger.Binds
 import dagger.Module
 
-/** Dagger Module for code in the clipboard overlay package. */
+/** Dagger Module to provide default implementations which could be overridden. */
 @Module
-interface ClipboardOverlaySuppressionModule {
+interface ClipboardOverlayOverrideModule {
 
     /** Provides implementation for [ClipboardOverlaySuppressionController]. */
     @Binds
     fun provideClipboardOverlaySuppressionController(
         clipboardOverlaySuppressionControllerImpl: ClipboardOverlaySuppressionControllerImpl
     ): ClipboardOverlaySuppressionController
+
+    /** Provides implementation for [ClipboardIndicationProvider]. */
+    @Binds
+    fun provideClipboardIndicationProvider(
+        clipboardIndicationProviderImpl: ClipboardIndicationProviderImpl
+    ): ClipboardIndicationProvider
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
index d1c728c..1923880 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
@@ -18,19 +18,12 @@
 
 import com.android.systemui.common.data.repository.PackageChangeRepository
 import com.android.systemui.common.data.repository.PackageChangeRepositoryImpl
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
-import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
 import dagger.Binds
 import dagger.Module
 
 @Module
 abstract class CommonDataLayerModule {
     @Binds
-    abstract fun bindConfigurationRepository(
-        impl: ConfigurationRepositoryImpl
-    ): ConfigurationRepository
-
-    @Binds
     abstract fun bindPackageChangeRepository(
         impl: PackageChangeRepositoryImpl
     ): PackageChangeRepository
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationModule.kt
similarity index 67%
rename from packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt
rename to packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationModule.kt
index b36da3b..7f50e4a 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationModule.kt
@@ -17,6 +17,9 @@
 package com.android.systemui.common.ui
 
 import android.content.Context
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -28,24 +31,31 @@
 /**
  * Annotates elements that provide information from the global configuration.
  *
- * The global configuration is the one associted with the main display. Secondary displays will
+ * The global configuration is the one associated with the main display. Secondary displays will
  * apply override to the global configuration. Elements annotated with this shouldn't be used for
  * secondary displays.
  */
 @Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class GlobalConfig
 
 @Module
-interface ConfigurationStateModule {
+interface ConfigurationModule {
 
     /**
      * Deprecated: [ConfigurationState] should be injected only with the correct annotation. For
      * now, without annotation the global config associated state is provided.
      */
     @Binds
+    @Deprecated("Use the @GlobalConfig annotated one instead of this.")
     fun provideGlobalConfigurationState(
         @GlobalConfig configurationState: ConfigurationState
     ): ConfigurationState
 
+    @Binds
+    @Deprecated("Use the @GlobalConfig annotated one instead of this.")
+    fun provideDefaultConfigurationState(
+        @GlobalConfig configurationState: ConfigurationInteractor
+    ): ConfigurationInteractor
+
     companion object {
         @SysUISingleton
         @Provides
@@ -57,5 +67,14 @@
         ): ConfigurationState {
             return configStateFactory.create(context, configurationController)
         }
+
+        @SysUISingleton
+        @Provides
+        @GlobalConfig
+        fun provideGlobalConfigurationInteractor(
+            configurationRepository: ConfigurationRepository
+        ): ConfigurationInteractor {
+            return ConfigurationInteractorImpl(configurationRepository)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
index 2052c70..df89152 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
@@ -23,13 +23,17 @@
 import androidx.annotation.DimenRes
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.ui.GlobalConfig
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.wrapper.DisplayUtilsWrapper
 import dagger.Binds
 import dagger.Module
-import javax.inject.Inject
+import dagger.Provides
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
@@ -57,66 +61,62 @@
     fun getDimensionPixelSize(id: Int): Int
 }
 
-@SysUISingleton
 class ConfigurationRepositoryImpl
-@Inject
+@AssistedInject
 constructor(
-    private val configurationController: ConfigurationController,
-    private val context: Context,
+    @Assisted private val configurationController: ConfigurationController,
+    @Assisted private val context: Context,
     @Application private val scope: CoroutineScope,
     private val displayUtils: DisplayUtilsWrapper,
 ) : ConfigurationRepository {
     private val displayInfo = MutableStateFlow(DisplayInfo())
 
-    override val onAnyConfigurationChange: Flow<Unit> =
-        conflatedCallbackFlow {
-            val callback =
-                object : ConfigurationController.ConfigurationListener {
-                    override fun onUiModeChanged() {
-                        sendUpdate("ConfigurationRepository#onUiModeChanged")
-                    }
-
-                    override fun onThemeChanged() {
-                        sendUpdate("ConfigurationRepository#onThemeChanged")
-                    }
-
-                    override fun onConfigChanged(newConfig: Configuration) {
-                        sendUpdate("ConfigurationRepository#onConfigChanged")
-                    }
-
-                    fun sendUpdate(reason: String) {
-                        trySendWithFailureLogging(Unit, reason)
-                    }
+    override val onAnyConfigurationChange: Flow<Unit> = conflatedCallbackFlow {
+        val callback =
+            object : ConfigurationController.ConfigurationListener {
+                override fun onUiModeChanged() {
+                    sendUpdate("ConfigurationRepository#onUiModeChanged")
                 }
-            configurationController.addCallback(callback)
-            awaitClose { configurationController.removeCallback(callback) }
-        }
 
-    override val onConfigurationChange: Flow<Unit> =
-        conflatedCallbackFlow {
-            val callback =
-                object : ConfigurationController.ConfigurationListener {
-                    override fun onConfigChanged(newConfig: Configuration) {
-                        trySendWithFailureLogging(Unit, "ConfigurationRepository#onConfigChanged")
-                    }
+                override fun onThemeChanged() {
+                    sendUpdate("ConfigurationRepository#onThemeChanged")
                 }
-            configurationController.addCallback(callback)
-            awaitClose { configurationController.removeCallback(callback) }
-        }
 
-    override val configurationValues: Flow<Configuration> =
-            conflatedCallbackFlow {
-                val callback =
-                        object : ConfigurationController.ConfigurationListener {
-                            override fun onConfigChanged(newConfig: Configuration) {
-                                trySend(newConfig)
-                            }
-                        }
+                override fun onConfigChanged(newConfig: Configuration) {
+                    sendUpdate("ConfigurationRepository#onConfigChanged")
+                }
 
-                trySend(context.resources.configuration)
-                configurationController.addCallback(callback)
-                awaitClose { configurationController.removeCallback(callback) }
+                fun sendUpdate(reason: String) {
+                    trySendWithFailureLogging(Unit, reason)
+                }
             }
+        configurationController.addCallback(callback)
+        awaitClose { configurationController.removeCallback(callback) }
+    }
+
+    override val onConfigurationChange: Flow<Unit> = conflatedCallbackFlow {
+        val callback =
+            object : ConfigurationController.ConfigurationListener {
+                override fun onConfigChanged(newConfig: Configuration) {
+                    trySendWithFailureLogging(Unit, "ConfigurationRepository#onConfigChanged")
+                }
+            }
+        configurationController.addCallback(callback)
+        awaitClose { configurationController.removeCallback(callback) }
+    }
+
+    override val configurationValues: Flow<Configuration> = conflatedCallbackFlow {
+        val callback =
+            object : ConfigurationController.ConfigurationListener {
+                override fun onConfigChanged(newConfig: Configuration) {
+                    trySend(newConfig)
+                }
+            }
+
+        trySend(context.resources.configuration)
+        configurationController.addCallback(callback)
+        awaitClose { configurationController.removeCallback(callback) }
+    }
 
     override val scaleForResolution: StateFlow<Float> =
         onConfigurationChange
@@ -134,7 +134,7 @@
                     maxDisplayMode.physicalWidth,
                     maxDisplayMode.physicalHeight,
                     displayInfo.value.naturalWidth,
-                    displayInfo.value.naturalHeight
+                    displayInfo.value.naturalHeight,
                 )
             return if (scaleFactor == Float.POSITIVE_INFINITY) 1f else scaleFactor
         }
@@ -144,9 +144,40 @@
     override fun getDimensionPixelSize(@DimenRes id: Int): Int {
         return context.resources.getDimensionPixelSize(id)
     }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            context: Context,
+            configurationController: ConfigurationController,
+        ): ConfigurationRepositoryImpl
+    }
 }
 
 @Module
-interface ConfigurationRepositoryModule {
-    @Binds fun bindImpl(impl: ConfigurationRepositoryImpl): ConfigurationRepository
+abstract class ConfigurationRepositoryModule {
+
+    /**
+     * For compatibility reasons. Ideally, only the an annotated [ConfigurationRepository] should be
+     * injected.
+     */
+    @Binds
+    @Deprecated("Use the ConfigurationRepository annotated with @GlobalConfig instead.")
+    @SysUISingleton
+    abstract fun provideDefaultConfigRepository(
+        @GlobalConfig configurationRepository: ConfigurationRepository
+    ): ConfigurationRepository
+
+    companion object {
+        @Provides
+        @GlobalConfig
+        @SysUISingleton
+        fun provideGlobalConfigRepository(
+            context: Context,
+            @GlobalConfig configurationController: ConfigurationController,
+            factory: ConfigurationRepositoryImpl.Factory,
+        ): ConfigurationRepository {
+            return factory.create(context, configurationController)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
index adb1ee2..97a23e1 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
@@ -21,8 +21,6 @@
 import android.graphics.Rect
 import android.view.Surface
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
-import com.android.systemui.dagger.SysUISingleton
-import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -32,13 +30,52 @@
 import kotlinx.coroutines.flow.onStart
 
 /** Business logic related to configuration changes. */
-@SysUISingleton
-class ConfigurationInteractor @Inject constructor(private val repository: ConfigurationRepository) {
+interface ConfigurationInteractor {
     /**
      * Returns screen size adjusted to rotation, so returned screen size is stable across all
      * rotations
      */
-    private val Configuration.naturalScreenBounds: Rect
+    val Configuration.naturalScreenBounds: Rect
+
+    /** Returns the unadjusted screen size. */
+    val maxBounds: Flow<Rect>
+
+    /**
+     * Returns screen size adjusted to rotation, so returned screen sizes are stable across all
+     * rotations, could be useful if you need to react to screen resize (e.g. fold/unfold on
+     * foldable devices)
+     */
+    val naturalMaxBounds: Flow<Rect>
+
+    /**
+     * The layout direction. Will be either `View#LAYOUT_DIRECTION_LTR` or
+     * `View#LAYOUT_DIRECTION_RTL`.
+     */
+    val layoutDirection: Flow<Int>
+
+    /** Emit an event on any config change */
+    val onAnyConfigurationChange: Flow<Unit>
+
+    /** Emits the new configuration on any configuration change */
+    val configurationValues: Flow<Configuration>
+
+    /** Emits the current resolution scaling factor */
+    val scaleForResolution: Flow<Float>
+
+    /** Given [resourceId], emit the dimension pixel size on config change */
+    fun dimensionPixelSize(resourceId: Int): Flow<Int>
+
+    /** Emits the dimensional pixel size of the given resource, inverting it for RTL if necessary */
+    fun directionalDimensionPixelSize(originLayoutDirection: Int, resourceId: Int): Flow<Int>
+
+    /** Given a set of [resourceId]s, emit Map<ResourceId, DimensionPixelSize> on config change */
+    fun dimensionPixelSize(resourceIds: Set<Int>): Flow<Map<Int, Int>>
+}
+
+class ConfigurationInteractorImpl(private val repository: ConfigurationRepository) :
+    ConfigurationInteractor {
+
+    override val Configuration.naturalScreenBounds: Rect
         get() {
             val rotation = windowConfiguration.displayRotation
             val maxBounds = windowConfiguration.maxBounds
@@ -49,53 +86,40 @@
             }
         }
 
-    /** Returns the unadjusted screen size. */
-    val maxBounds: Flow<Rect> =
+    override val maxBounds: Flow<Rect> =
         repository.configurationValues
             .map { Rect(it.windowConfiguration.maxBounds) }
             .distinctUntilChanged()
 
-    /**
-     * Returns screen size adjusted to rotation, so returned screen sizes are stable across all
-     * rotations, could be useful if you need to react to screen resize (e.g. fold/unfold on
-     * foldable devices)
-     */
-    val naturalMaxBounds: Flow<Rect> =
+    override val naturalMaxBounds: Flow<Rect> =
         repository.configurationValues.map { it.naturalScreenBounds }.distinctUntilChanged()
 
-    /**
-     * The layout direction. Will be either `View#LAYOUT_DIRECTION_LTR` or
-     * `View#LAYOUT_DIRECTION_RTL`.
-     */
-    val layoutDirection: Flow<Int> =
+    override val layoutDirection: Flow<Int> =
         repository.configurationValues.map { it.layoutDirection }.distinctUntilChanged()
 
-    /** Given [resourceId], emit the dimension pixel size on config change */
-    fun dimensionPixelSize(resourceId: Int): Flow<Int> {
+    override fun dimensionPixelSize(resourceId: Int): Flow<Int> {
         return onAnyConfigurationChange.mapLatest { repository.getDimensionPixelSize(resourceId) }
     }
 
-    /** Emits the dimensional pixel size of the given resource, inverting it for RTL if necessary */
-    fun directionalDimensionPixelSize(originLayoutDirection: Int, resourceId: Int): Flow<Int> {
+    override fun directionalDimensionPixelSize(
+        originLayoutDirection: Int,
+        resourceId: Int,
+    ): Flow<Int> {
         return dimensionPixelSize(resourceId).combine(layoutDirection) { size, direction ->
             if (originLayoutDirection == direction) size else -size
         }
     }
 
-    /** Given a set of [resourceId]s, emit Map<ResourceId, DimensionPixelSize> on config change */
-    fun dimensionPixelSize(resourceIds: Set<Int>): Flow<Map<Int, Int>> {
+    override fun dimensionPixelSize(resourceIds: Set<Int>): Flow<Map<Int, Int>> {
         return onAnyConfigurationChange.mapLatest {
             resourceIds.associateWith { repository.getDimensionPixelSize(it) }
         }
     }
 
-    /** Emit an event on any config change */
-    val onAnyConfigurationChange: Flow<Unit> =
+    override val onAnyConfigurationChange: Flow<Unit> =
         repository.onAnyConfigurationChange.onStart { emit(Unit) }
 
-    /** Emits the new configuration on any configuration change */
-    val configurationValues: Flow<Configuration> = repository.configurationValues
+    override val configurationValues: Flow<Configuration> = repository.configurationValues
 
-    /** Emits the current resolution scaling factor */
-    val scaleForResolution: Flow<Float> = repository.scaleForResolution
+    override val scaleForResolution: Flow<Float> = repository.scaleForResolution
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index a33e0ac..44dd34a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -32,6 +32,8 @@
 import com.android.systemui.communal.shared.log.CommunalMetricsLogger
 import com.android.systemui.communal.shared.log.CommunalStatsLogProxyImpl
 import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelperImpl
 import com.android.systemui.communal.util.CommunalColors
 import com.android.systemui.communal.util.CommunalColorsImpl
 import com.android.systemui.communal.widgets.CommunalWidgetModule
@@ -65,6 +67,7 @@
             CommunalSettingsRepositoryModule::class,
             CommunalSmartspaceRepositoryModule::class,
             CommunalStartableModule::class,
+            GlanceableHubWidgetManagerModule::class,
         ]
 )
 interface CommunalModule {
@@ -91,6 +94,11 @@
         impl: CommunalSceneTransitionInteractor
     ): CoreStartable
 
+    @Binds
+    fun bindGlanceableHubMultiUserHelper(
+        impl: GlanceableHubMultiUserHelperImpl
+    ): GlanceableHubMultiUserHelper
+
     companion object {
         const val LOGGABLE_PREFIXES = "loggable_prefixes"
         const val LAUNCHER_PACKAGE = "launcher_package"
@@ -106,19 +114,14 @@
                     sceneKeys = listOf(CommunalScenes.Blank, CommunalScenes.Communal),
                     initialSceneKey = CommunalScenes.Blank,
                     navigationDistances =
-                        mapOf(
-                            CommunalScenes.Blank to 0,
-                            CommunalScenes.Communal to 1,
-                        ),
+                        mapOf(CommunalScenes.Blank to 0, CommunalScenes.Communal to 1),
                 )
             return SceneDataSourceDelegator(applicationScope, config)
         }
 
         @Provides
         @SysUISingleton
-        fun providesCommunalBackupUtils(
-            @Application context: Context,
-        ): CommunalBackupUtils {
+        fun providesCommunalBackupUtils(@Application context: Context): CommunalBackupUtils {
             return CommunalBackupUtils(context)
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
index 6cbf540..2d19b02 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.communal.CommunalSceneStartable
 import com.android.systemui.communal.log.CommunalLoggerStartable
 import com.android.systemui.communal.widgets.CommunalAppWidgetHostStartable
+import com.android.systemui.dagger.qualifiers.PerUser
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
@@ -48,6 +49,7 @@
 
     @Binds
     @IntoMap
+    @PerUser
     @ClassKey(CommunalAppWidgetHostStartable::class)
     fun bindCommunalAppWidgetHostStartable(impl: CommunalAppWidgetHostStartable): CoreStartable
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/GlanceableHubWidgetManagerModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/GlanceableHubWidgetManagerModule.kt
new file mode 100644
index 0000000..8c07583
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/GlanceableHubWidgetManagerModule.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.dagger
+
+import com.android.server.servicewatcher.ServiceWatcher.ServiceSupplier
+import com.android.systemui.communal.widgets.GlanceableHubWidgetManagerServiceInfo
+import com.android.systemui.communal.widgets.GlanceableHubWidgetManagerServiceSupplier
+import com.android.systemui.communal.widgets.GlanceableHubWidgetManagerServiceWatcherFactoryImpl
+import com.android.systemui.communal.widgets.ServiceWatcherFactory
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface GlanceableHubWidgetManagerModule {
+    @Binds
+    fun bindServiceSupplier(
+        impl: GlanceableHubWidgetManagerServiceSupplier
+    ): ServiceSupplier<GlanceableHubWidgetManagerServiceInfo?>
+
+    @Binds
+    fun bindServiceWatcherFactory(
+        impl: GlanceableHubWidgetManagerServiceWatcherFactoryImpl
+    ): ServiceWatcherFactory<GlanceableHubWidgetManagerServiceInfo?>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
index 17f4f0c..e72088f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.communal.data.db
 
 import android.content.Context
+import android.os.Process
 import android.util.Log
 import androidx.annotation.VisibleForTesting
 import androidx.room.Database
@@ -24,6 +25,7 @@
 import androidx.room.RoomDatabase
 import androidx.room.migration.Migration
 import androidx.sqlite.db.SupportSQLiteDatabase
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelperImpl
 import com.android.systemui.res.R
 
 @Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 4)
@@ -44,6 +46,11 @@
          *   new instance is created.
          */
         fun getInstance(context: Context, callback: Callback? = null): CommunalDatabase {
+            with(GlanceableHubMultiUserHelperImpl(Process.myUserHandle())) {
+                // Assert that the database is never accessed from a headless system user.
+                assertNotInHeadlessSystemUser()
+            }
+
             if (instance == null) {
                 instance =
                     Room.databaseBuilder(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index 258480e..3d40aa7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -25,6 +25,7 @@
 import androidx.room.Transaction
 import androidx.room.Update
 import androidx.sqlite.db.SupportSQLiteDatabase
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.communal.nano.CommunalHubState
 import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.widgets.CommunalWidgetHost
@@ -39,7 +40,6 @@
 import javax.inject.Provider
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * Callback that will be invoked when the Room database is created. Then the database will be
@@ -224,9 +224,9 @@
     ): Long {
         val widgets = getWidgetsNow()
 
-        // If rank is not specified, rank it last by finding the current maximum rank and increment
-        // by 1. If the new widget is the first widget, set the rank to 0.
-        val newRank = rank ?: widgets.keys.maxOfOrNull { it.rank + 1 } ?: 0
+        // If rank is not specified (null or less than 0), rank it last by finding the current
+        // maximum rank and increment by 1. If the new widget is the first widget, set rank to 0.
+        val newRank = rank?.takeIf { it >= 0 } ?: widgets.keys.maxOfOrNull { it.rank + 1 } ?: 0
 
         // Shift widgets after [rank], unless widget is added at the end.
         if (rank != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index e164587..29569f8 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -21,6 +21,7 @@
 import android.content.ComponentName
 import android.os.UserHandle
 import android.os.UserManager
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.Flags.communalWidgetResizing
 import com.android.systemui.common.data.repository.PackageChangeRepository
 import com.android.systemui.common.shared.model.PackageInstallSession
@@ -51,11 +52,10 @@
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** Encapsulates the state of widgets for communal mode. */
 interface CommunalWidgetRepository {
-    /** A flow of information about active communal widgets stored in database. */
+    /** A flow of the list of Glanceable Hub widgets ordered by rank. */
     val communalWidgets: Flow<List<CommunalWidgetContentModel>>
 
     /**
@@ -106,8 +106,12 @@
     fun resizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>)
 }
 
+/**
+ * The local implementation of the [CommunalWidgetRepository] that should be injected in a
+ * foreground user process.
+ */
 @SysUISingleton
-class CommunalWidgetRepositoryImpl
+class CommunalWidgetRepositoryLocalImpl
 @Inject
 constructor(
     private val appWidgetHost: CommunalAppWidgetHost,
@@ -123,7 +127,7 @@
     private val defaultWidgetPopulation: DefaultWidgetPopulation,
 ) : CommunalWidgetRepository {
     companion object {
-        const val TAG = "CommunalWidgetRepository"
+        const val TAG = "CommunalWidgetRepositoryLocalImpl"
     }
 
     private val logger = Logger(logBuffer, TAG)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
index b502fb1..824edcb 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
@@ -17,11 +17,24 @@
 
 package com.android.systemui.communal.data.repository
 
-import dagger.Binds
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
+import dagger.Lazy
 import dagger.Module
+import dagger.Provides
 
 @Module
 interface CommunalWidgetRepositoryModule {
-    @Binds
-    fun communalWidgetRepository(impl: CommunalWidgetRepositoryImpl): CommunalWidgetRepository
+    companion object {
+        @Provides
+        fun provideCommunalWidgetRepository(
+            localImpl: Lazy<CommunalWidgetRepositoryLocalImpl>,
+            remoteImpl: Lazy<CommunalWidgetRepositoryRemoteImpl>,
+            helper: GlanceableHubMultiUserHelper,
+        ): CommunalWidgetRepository {
+            // Provide an implementation based on the current user.
+            return if (helper.glanceableHubHsumFlagEnabled && helper.isInHeadlessSystemUser())
+                remoteImpl.get()
+            else localImpl.get()
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryRemoteImpl.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryRemoteImpl.kt
new file mode 100644
index 0000000..6682186
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryRemoteImpl.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.communal.data.repository
+
+import android.content.ComponentName
+import android.os.UserHandle
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
+import com.android.systemui.communal.widgets.GlanceableHubWidgetManager
+import com.android.systemui.communal.widgets.WidgetConfigurator
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
+
+/**
+ * The remote implementation of the [CommunalWidgetRepository] that should be injected in a headless
+ * system user process. This implementation receives widget data from and routes requests to the
+ * remote service in the foreground user.
+ */
+@SysUISingleton
+class CommunalWidgetRepositoryRemoteImpl
+@Inject
+constructor(
+    @Background private val bgScope: CoroutineScope,
+    private val glanceableHubWidgetManager: GlanceableHubWidgetManager,
+    glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
+) : CommunalWidgetRepository {
+
+    init {
+        // This is the implementation for the headless system user. For the foreground user
+        // implementation see [CommunalWidgetRepositoryLocalImpl].
+        glanceableHubMultiUserHelper.assertInHeadlessSystemUser()
+    }
+
+    override val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
+        glanceableHubWidgetManager.widgets
+
+    override fun addWidget(
+        provider: ComponentName,
+        user: UserHandle,
+        rank: Int?,
+        configurator: WidgetConfigurator?,
+    ) {
+        bgScope.launch { glanceableHubWidgetManager.addWidget(provider, user, rank, configurator) }
+    }
+
+    override fun deleteWidget(widgetId: Int) {
+        bgScope.launch { glanceableHubWidgetManager.deleteWidget(widgetId) }
+    }
+
+    override fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>) {
+        bgScope.launch { glanceableHubWidgetManager.updateWidgetOrder(widgetIdToRankMap) }
+    }
+
+    override fun resizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) {
+        bgScope.launch {
+            glanceableHubWidgetManager.resizeWidget(appWidgetId, spanY, widgetIdToRankMap)
+        }
+    }
+
+    override fun restoreWidgets(oldToNewWidgetIdMap: Map<Int, Int>) {
+        throw IllegalStateException("Restore widgets should be performed on a foreground user")
+    }
+
+    override fun abortRestoreWidgets() {
+        throw IllegalStateException("Restore widgets should be performed on a foreground user")
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index dc24805..602fe30 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -40,7 +40,6 @@
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.communal.shared.model.EditModeState
-import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
 import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.dagger.SysUISingleton
@@ -108,7 +107,6 @@
     keyguardInteractor: KeyguardInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     communalSettingsInteractor: CommunalSettingsInteractor,
-    private val appWidgetHost: CommunalAppWidgetHost,
     private val editWidgetsActivityStarter: EditWidgetsActivityStarter,
     private val userTracker: UserTracker,
     private val activityStarter: ActivityStarter,
@@ -451,7 +449,6 @@
                             appWidgetId = widget.appWidgetId,
                             rank = widget.rank,
                             providerInfo = widget.providerInfo,
-                            appWidgetHost = appWidgetHost,
                             inQuietMode = isQuietModeEnabled(widget.providerInfo.profile),
                             size = CommunalContentSize.toSize(widget.spanY),
                         )
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index 4c2c094..30f580e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -23,7 +23,6 @@
 import android.graphics.Bitmap
 import android.widget.RemoteViews
 import com.android.systemui.communal.shared.model.CommunalContentSize
-import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import java.util.UUID
 
 /** Encapsulates data for a communal content. */
@@ -60,7 +59,6 @@
             override val appWidgetId: Int,
             override val rank: Int,
             val providerInfo: AppWidgetProviderInfo,
-            val appWidgetHost: CommunalAppWidgetHost,
             val inQuietMode: Boolean,
             override val size: CommunalContentSize,
         ) : WidgetContent {
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.aidl
similarity index 86%
copy from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
copy to packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.aidl
index e21bf8f..a215698e 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.shared;
+package com.android.systemui.communal.shared.model;
 
-parcelable GroupedRecentTaskInfo;
\ No newline at end of file
+parcelable CommunalWidgetContentModel;
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
index 0c9ea78..f23347d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
@@ -19,21 +19,60 @@
 import android.appwidget.AppWidgetProviderInfo
 import android.content.ComponentName
 import android.graphics.Bitmap
+import android.os.Parcel
+import android.os.Parcelable
 import android.os.UserHandle
 
 /** Encapsulates data for a communal widget. */
-sealed interface CommunalWidgetContentModel {
+sealed interface CommunalWidgetContentModel : Parcelable {
     val appWidgetId: Int
     val rank: Int
     val spanY: Int
 
+    // Used for distinguishing subtypes when reading from a parcel.
+    val type: Int
+
     /** Widget is ready to display */
     data class Available(
         override val appWidgetId: Int,
         val providerInfo: AppWidgetProviderInfo,
         override val rank: Int,
         override val spanY: Int,
-    ) : CommunalWidgetContentModel
+    ) : CommunalWidgetContentModel {
+
+        override val type = TYPE_AVAILABLE
+
+        constructor(
+            parcel: Parcel
+        ) : this(
+            parcel.readInt(),
+            requireNotNull(parcel.readTypedObject(AppWidgetProviderInfo.CREATOR)),
+            parcel.readInt(),
+            parcel.readInt(),
+        )
+
+        override fun writeToParcel(parcel: Parcel, flags: Int) {
+            parcel.writeInt(type)
+            parcel.writeInt(appWidgetId)
+            parcel.writeTypedObject(providerInfo, flags)
+            parcel.writeInt(rank)
+            parcel.writeInt(spanY)
+        }
+
+        override fun describeContents(): Int {
+            return 0
+        }
+
+        companion object CREATOR : Parcelable.Creator<Available> {
+            override fun createFromParcel(parcel: Parcel): Available {
+                return Available(parcel)
+            }
+
+            override fun newArray(size: Int): Array<Available?> {
+                return arrayOfNulls(size)
+            }
+        }
+    }
 
     /** Widget is pending installation */
     data class Pending(
@@ -43,5 +82,61 @@
         val icon: Bitmap?,
         val user: UserHandle,
         override val spanY: Int,
-    ) : CommunalWidgetContentModel
+    ) : CommunalWidgetContentModel {
+
+        override val type = TYPE_PENDING
+
+        constructor(
+            parcel: Parcel
+        ) : this(
+            parcel.readInt(),
+            parcel.readInt(),
+            requireNotNull(parcel.readTypedObject(ComponentName.CREATOR)),
+            parcel.readTypedObject(Bitmap.CREATOR),
+            requireNotNull(parcel.readTypedObject(UserHandle.CREATOR)),
+            parcel.readInt(),
+        )
+
+        override fun writeToParcel(parcel: Parcel, flags: Int) {
+            parcel.writeInt(type)
+            parcel.writeInt(appWidgetId)
+            parcel.writeInt(rank)
+            parcel.writeTypedObject(componentName, flags)
+            parcel.writeTypedObject(icon, flags)
+            parcel.writeTypedObject(user, flags)
+            parcel.writeInt(spanY)
+        }
+
+        override fun describeContents(): Int {
+            return 0
+        }
+
+        companion object CREATOR : Parcelable.Creator<Pending> {
+            override fun createFromParcel(parcel: Parcel): Pending {
+                return Pending(parcel)
+            }
+
+            override fun newArray(size: Int): Array<Pending?> {
+                return arrayOfNulls(size)
+            }
+        }
+    }
+
+    // Used for distinguishing subtypes when reading from a parcel.
+    companion object CREATOR : Parcelable.Creator<CommunalWidgetContentModel> {
+        private const val TYPE_AVAILABLE = 0
+        private const val TYPE_PENDING = 1
+
+        override fun createFromParcel(parcel: Parcel): CommunalWidgetContentModel {
+            return when (val type = parcel.readInt()) {
+                TYPE_AVAILABLE -> Available(parcel)
+                TYPE_PENDING -> Pending(parcel)
+                else -> throw IllegalArgumentException("Unknown type: $type")
+            }
+        }
+
+        override fun newArray(size: Int): Array<CommunalWidgetContentModel?> {
+            return arrayOfNulls(size)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/GlanceableHubMultiUserHelper.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/GlanceableHubMultiUserHelper.kt
new file mode 100644
index 0000000..ef6a9ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/GlanceableHubMultiUserHelper.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.communal.shared.model
+
+import android.os.UserHandle
+import android.os.UserManager
+import com.android.systemui.Flags.secondaryUserWidgetHost
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/** Helper for multi-user / HSUM related functionality for the Glanceable Hub. */
+interface GlanceableHubMultiUserHelper {
+    /** Whether the Glanceable Hub in HSUM flag is enabled. */
+    val glanceableHubHsumFlagEnabled: Boolean
+
+    /** Whether the device is in headless system user mode. */
+    fun isHeadlessSystemUserMode(): Boolean
+
+    /** Whether the current process is running in the headless system user. */
+    fun isInHeadlessSystemUser(): Boolean
+
+    /**
+     * Asserts that the current process is running in the headless system user.
+     *
+     * Only throws an exception if [glanceableHubHsumFlagEnabled] is true.
+     */
+    @Throws(IllegalStateException::class) fun assertInHeadlessSystemUser()
+
+    /**
+     * Asserts that the current process is NOT running in the headless system user.
+     *
+     * Only throws an exception if [glanceableHubHsumFlagEnabled] is true.
+     */
+    @Throws(IllegalStateException::class) fun assertNotInHeadlessSystemUser()
+}
+
+@SysUISingleton
+class GlanceableHubMultiUserHelperImpl @Inject constructor(private val userHandle: UserHandle) :
+    GlanceableHubMultiUserHelper {
+
+    override val glanceableHubHsumFlagEnabled: Boolean = secondaryUserWidgetHost()
+
+    override fun isHeadlessSystemUserMode(): Boolean = UserManager.isHeadlessSystemUserMode()
+
+    override fun isInHeadlessSystemUser(): Boolean {
+        return isHeadlessSystemUserMode() && userHandle.isSystem
+    }
+
+    override fun assertInHeadlessSystemUser() {
+        if (glanceableHubHsumFlagEnabled) {
+            check(isInHeadlessSystemUser())
+        }
+    }
+
+    override fun assertNotInHeadlessSystemUser() {
+        if (glanceableHubHsumFlagEnabled) {
+            check(!isInHeadlessSystemUser())
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
index 012c844..b80e77c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
@@ -19,13 +19,13 @@
 import android.app.smartspace.SmartspaceConfig
 import android.app.smartspace.SmartspaceManager
 import android.app.smartspace.SmartspaceSession
-import android.content.Context
 import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_GLANCEABLE_HUB
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.smartspace.SmartspacePrecondition
 import com.android.systemui.smartspace.SmartspaceTargetFilter
 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN
@@ -42,8 +42,7 @@
 class CommunalSmartspaceController
 @Inject
 constructor(
-    private val context: Context,
-    private val smartspaceManager: SmartspaceManager?,
+    private val userTracker: UserTracker,
     private val execution: Execution,
     @Main private val uiExecutor: Executor,
     @Named(LOCKSCREEN_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition,
@@ -55,6 +54,7 @@
         private const val TAG = "CommunalSmartspaceCtrlr"
     }
 
+    private var userSmartspaceManager: SmartspaceManager? = null
     private var session: SmartspaceSession? = null
     private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
     private var targetFilter: SmartspaceTargetFilter? = optionalTargetFilter.orElse(null)
@@ -104,7 +104,11 @@
     }
 
     private fun connectSession() {
-        if (smartspaceManager == null) {
+        if (userSmartspaceManager == null) {
+            userSmartspaceManager =
+                userTracker.userContext.getSystemService(SmartspaceManager::class.java)
+        }
+        if (userSmartspaceManager == null) {
             return
         }
         if (plugin == null) {
@@ -119,11 +123,11 @@
         }
 
         val newSession =
-            smartspaceManager.createSmartspaceSession(
-                SmartspaceConfig.Builder(context, UI_SURFACE_GLANCEABLE_HUB).build()
+            userSmartspaceManager?.createSmartspaceSession(
+                SmartspaceConfig.Builder(userTracker.userContext, UI_SURFACE_GLANCEABLE_HUB).build()
             )
         Log.d(TAG, "Starting smartspace session for communal")
-        newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+        newSession?.addOnTargetsAvailableListener(uiExecutor, sessionListener)
         this.session = newSession
 
         plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
@@ -163,7 +167,7 @@
 
     private fun addAndRegisterListener(
         listener: SmartspaceTargetListener,
-        smartspaceDataPlugin: BcSmartspaceDataPlugin?
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?,
     ) {
         execution.assertIsMainThread()
         smartspaceDataPlugin?.registerListener(listener)
@@ -174,7 +178,7 @@
 
     private fun removeAndUnregisterListener(
         listener: SmartspaceTargetListener,
-        smartspaceDataPlugin: BcSmartspaceDataPlugin?
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?,
     ) {
         execution.assertIsMainThread()
         smartspaceDataPlugin?.unregisterListener(listener)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt
index bde5d0f..113375f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt
@@ -76,10 +76,10 @@
             floor(spans.toDouble() / resizeMultiple).toInt() * resizeMultiple
 
         val maxSpans: Int
-            get() = roundDownToMultiple(getSpansForPx(maxHeightPx))
+            get() = roundDownToMultiple(getSpansForPx(maxHeightPx)).coerceAtLeast(currentSpan)
 
         val minSpans: Int
-            get() = roundDownToMultiple(getSpansForPx(minHeightPx))
+            get() = roundDownToMultiple(getSpansForPx(minHeightPx)).coerceAtMost(currentSpan)
     }
 
     /** Check if widget can expanded based on current drag states */
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt
index d5d3a92..cc6007b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt
@@ -21,11 +21,14 @@
 import android.util.SizeF
 import com.android.app.tracing.coroutines.withContextTraced as withContext
 import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
 import com.android.systemui.communal.widgets.AppWidgetHostListenerDelegate
 import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import com.android.systemui.communal.widgets.CommunalAppWidgetHostView
+import com.android.systemui.communal.widgets.GlanceableHubWidgetManager
 import com.android.systemui.communal.widgets.WidgetInteractionHandler
 import com.android.systemui.dagger.qualifiers.UiBackground
+import dagger.Lazy
 import java.util.concurrent.Executor
 import javax.inject.Inject
 import kotlin.coroutines.CoroutineContext
@@ -36,9 +39,11 @@
 constructor(
     @UiBackground private val uiBgContext: CoroutineContext,
     @UiBackground private val uiBgExecutor: Executor,
-    private val appWidgetHost: CommunalAppWidgetHost,
+    private val appWidgetHostLazy: Lazy<CommunalAppWidgetHost>,
     private val interactionHandler: WidgetInteractionHandler,
     private val listenerFactory: AppWidgetHostListenerDelegate.Factory,
+    private val glanceableHubWidgetManagerLazy: Lazy<GlanceableHubWidgetManager>,
+    private val multiUserHelper: GlanceableHubMultiUserHelper,
 ) {
     suspend fun createWidget(
         context: Context,
@@ -51,9 +56,25 @@
                     setExecutor(uiBgExecutor)
                     setAppWidget(model.appWidgetId, model.providerInfo)
                 }
-            // Instead of setting the view as the listener directly, we wrap the view in a delegate
-            // which ensures the callbacks always get called on the main thread.
-            appWidgetHost.setListener(model.appWidgetId, listenerFactory.create(view))
+
+            if (
+                multiUserHelper.glanceableHubHsumFlagEnabled &&
+                    multiUserHelper.isInHeadlessSystemUser()
+            ) {
+                // If the widget view is created in the headless system user, the widget host lives
+                // remotely in the foreground user, and therefore the host listener needs to be
+                // registered through the widget manager.
+                with(glanceableHubWidgetManagerLazy.get()) {
+                    setAppWidgetHostListener(model.appWidgetId, listenerFactory.create(view))
+                }
+            } else {
+                // Instead of setting the view as the listener directly, we wrap the view in a
+                // delegate which ensures the callbacks always get called on the main thread.
+                with(appWidgetHostLazy.get()) {
+                    setListener(model.appWidgetId, listenerFactory.create(view))
+                }
+            }
+
             if (size != null) {
                 view.updateAppWidgetSize(
                     /* newOptions = */ Bundle(),
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
index 4f9ed2f..70e7947 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
@@ -18,6 +18,8 @@
 
 import android.appwidget.AppWidgetHost
 import android.content.Context
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import javax.annotation.concurrent.GuardedBy
@@ -25,7 +27,6 @@
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.asSharedFlow
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** Communal app widget host that creates a [CommunalAppWidgetHostView]. */
 class CommunalAppWidgetHost(
@@ -33,7 +34,14 @@
     private val backgroundScope: CoroutineScope,
     hostId: Int,
     logBuffer: LogBuffer,
+    glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
 ) : AppWidgetHost(context, hostId) {
+
+    init {
+        // The app widget host should never be accessed from a headless system user.
+        glanceableHubMultiUserHelper.assertNotInHeadlessSystemUser()
+    }
+
     private val logger = Logger(logBuffer, TAG)
 
     private val _appWidgetIdToRemove = MutableSharedFlow<Int>()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
index 301da51..dec7ba3 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
@@ -18,55 +18,118 @@
 
 import com.android.systemui.CoreStartable
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
 import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
 import com.android.systemui.util.kotlin.pairwise
 import com.android.systemui.util.kotlin.sample
+import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.dropWhile
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.withContext
 
+// Started per user, so make sure injections are lazy to avoid instantiating unnecessary
+// dependencies.
 @SysUISingleton
 class CommunalAppWidgetHostStartable
 @Inject
 constructor(
-    private val appWidgetHost: CommunalAppWidgetHost,
-    private val communalWidgetHost: CommunalWidgetHost,
-    private val communalInteractor: CommunalInteractor,
-    private val userTracker: UserTracker,
+    private val appWidgetHostLazy: Lazy<CommunalAppWidgetHost>,
+    private val communalWidgetHostLazy: Lazy<CommunalWidgetHost>,
+    private val communalInteractorLazy: Lazy<CommunalInteractor>,
+    private val communalSettingsInteractorLazy: Lazy<CommunalSettingsInteractor>,
+    private val keyguardInteractorLazy: Lazy<KeyguardInteractor>,
+    private val userTrackerLazy: Lazy<UserTracker>,
     @Background private val bgScope: CoroutineScope,
-    @Main private val uiDispatcher: CoroutineDispatcher
+    @Main private val uiDispatcher: CoroutineDispatcher,
+    private val glanceableHubWidgetManagerLazy: Lazy<GlanceableHubWidgetManager>,
+    private val glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
 ) : CoreStartable {
 
+    private val appWidgetHost by lazy { appWidgetHostLazy.get() }
+    private val communalWidgetHost by lazy { communalWidgetHostLazy.get() }
+    private val communalInteractor by lazy { communalInteractorLazy.get() }
+    private val communalSettingsInteractor by lazy { communalSettingsInteractorLazy.get() }
+    private val keyguardInteractor by lazy { keyguardInteractorLazy.get() }
+    private val userTracker by lazy { userTrackerLazy.get() }
+    private val glanceableHubWidgetManager by lazy { glanceableHubWidgetManagerLazy.get() }
+
     override fun start() {
-        anyOf(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen)
-            // Only trigger updates on state changes, ignoring the initial false value.
-            .pairwise(false)
-            .filter { (previous, new) -> previous != new }
-            .onEach { (_, shouldListen) -> updateAppWidgetHostActive(shouldListen) }
-            .sample(communalInteractor.communalWidgets, ::Pair)
-            .onEach { (withPrev, widgets) ->
-                val (_, isActive) = withPrev
-                // The validation is performed once the hub becomes active.
-                if (isActive) {
-                    validateWidgetsAndDeleteOrphaned(widgets)
+        if (
+            glanceableHubMultiUserHelper.glanceableHubHsumFlagEnabled &&
+                glanceableHubMultiUserHelper.isInHeadlessSystemUser()
+        ) {
+            onStartInHeadlessSystemUser()
+        } else {
+            onStartInForegroundUser()
+        }
+    }
+
+    private fun onStartInForegroundUser() {
+        // Make the host active when communal becomes available, and delete widgets whose user has
+        // been removed from the system.
+        // Skipped in HSUM, because lifecycle of the host is controlled by the
+        // [GlanceableHubWidgetManagerService], and widgets are stored per user so no more orphaned
+        // widgets.
+        if (
+            !glanceableHubMultiUserHelper.glanceableHubHsumFlagEnabled ||
+                !glanceableHubMultiUserHelper.isHeadlessSystemUserMode()
+        ) {
+            anyOf(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen)
+                // Only trigger updates on state changes, ignoring the initial false value.
+                .pairwise(false)
+                .filter { (previous, new) -> previous != new }
+                .onEach { (_, shouldListen) -> updateAppWidgetHostActive(shouldListen) }
+                .sample(communalInteractor.communalWidgets, ::Pair)
+                .onEach { (withPrev, widgets) ->
+                    val (_, isActive) = withPrev
+                    // The validation is performed once the hub becomes active.
+                    if (isActive) {
+                        validateWidgetsAndDeleteOrphaned(widgets)
+                    }
                 }
-            }
-            .launchIn(bgScope)
+                .launchIn(bgScope)
+        }
 
         appWidgetHost.appWidgetIdToRemove
             .onEach { appWidgetId -> communalInteractor.deleteWidget(id = appWidgetId) }
             .launchIn(bgScope)
     }
 
+    private fun onStartInHeadlessSystemUser() {
+        // Connection to the widget manager service in the foreground user should stay open as long
+        // as the Glanceable Hub is available.
+        allOf(
+                communalSettingsInteractor.isCommunalEnabled,
+                not(keyguardInteractor.isEncryptedOrLockdown),
+            )
+            .distinctUntilChanged()
+            // Drop the initial false
+            .dropWhile { !it }
+            .onEach { shouldRegister ->
+                if (shouldRegister) {
+                    glanceableHubWidgetManager.register()
+                } else {
+                    glanceableHubWidgetManager.unregister()
+                }
+            }
+            .launchIn(bgScope)
+    }
+
     private suspend fun updateAppWidgetHostActive(active: Boolean) =
         // Always ensure this is called on the main/ui thread.
         withContext(uiDispatcher) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt
index 0b8d977..8c745f5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt
@@ -26,6 +26,8 @@
 import android.os.UserHandle
 import android.widget.RemoteViews
 import androidx.annotation.WorkerThread
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
@@ -38,7 +40,6 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * Widget host that interacts with AppWidget service and host to bind and provide info for widgets
@@ -52,7 +53,14 @@
     private val appWidgetHost: CommunalAppWidgetHost,
     private val selectedUserInteractor: SelectedUserInteractor,
     @CommunalLog logBuffer: LogBuffer,
+    glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
 ) : CommunalAppWidgetHost.Observer {
+
+    init {
+        // The communal widget host should never be accessed from a headless system user.
+        glanceableHubMultiUserHelper.assertNotInHeadlessSystemUser()
+    }
+
     companion object {
         private const val TAG = "CommunalWidgetHost"
 
@@ -96,7 +104,7 @@
             bindWidget(
                 widgetId = id,
                 user = user ?: UserHandle(selectedUserInteractor.getSelectedUserId()),
-                provider = provider
+                provider = provider,
             )
         ) {
             logger.d("Successfully bound the widget $provider")
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
index f4962085..a235e7a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
@@ -20,6 +20,7 @@
 import android.appwidget.AppWidgetManager
 import android.content.Context
 import android.content.res.Resources
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -46,8 +47,15 @@
             @Application context: Context,
             @Background backgroundScope: CoroutineScope,
             @CommunalLog logBuffer: LogBuffer,
+            glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
         ): CommunalAppWidgetHost {
-            return CommunalAppWidgetHost(context, backgroundScope, APP_WIDGET_HOST_ID, logBuffer)
+            return CommunalAppWidgetHost(
+                context,
+                backgroundScope,
+                APP_WIDGET_HOST_ID,
+                logBuffer,
+                glanceableHubMultiUserHelper,
+            )
         }
 
         @SysUISingleton
@@ -58,6 +66,7 @@
             appWidgetHost: CommunalAppWidgetHost,
             selectedUserInteractor: SelectedUserInteractor,
             @CommunalLog logBuffer: LogBuffer,
+            glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
         ): CommunalWidgetHost {
             return CommunalWidgetHost(
                 applicationScope,
@@ -65,6 +74,7 @@
                 appWidgetHost,
                 selectedUserInteractor,
                 logBuffer,
+                glanceableHubMultiUserHelper,
             )
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index ca4bbc0..00c6209 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -33,6 +33,7 @@
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.ui.Modifier
 import androidx.lifecycle.lifecycleScope
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.compose.theme.PlatformTheme
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.Flags.communalEditWidgetsActivityFinishFix
@@ -44,6 +45,7 @@
 import com.android.systemui.communal.ui.view.layout.sections.CommunalAppWidgetSection
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
 import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
@@ -52,13 +54,13 @@
 import com.android.systemui.settings.UserTracker
 import javax.inject.Inject
 import kotlinx.coroutines.flow.first
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** An Activity for editing the widgets that appear in hub mode. */
 class EditWidgetsActivity
 @Inject
 constructor(
     private val communalViewModel: CommunalEditModeViewModel,
+    private val keyguardInteractor: KeyguardInteractor,
     private var windowManagerService: IWindowManager? = null,
     private val uiEventLogger: UiEventLogger,
     private val widgetConfiguratorFactory: WidgetConfigurationController.Factory,
@@ -223,6 +225,9 @@
                     communalViewModel.currentScene.first { it == CommunalScenes.Blank }
                 }
 
+                // Wait for dream to exit, if we were previously dreaming.
+                keyguardInteractor.isDreaming.first { !it }
+
                 communalViewModel.setEditModeState(EditModeState.SHOWING)
 
                 // Inform the ActivityController that we are now fully visible.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManager.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManager.kt
new file mode 100644
index 0000000..2f686fd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManager.kt
@@ -0,0 +1,240 @@
+/*
+ * 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.communal.widgets
+
+import android.appwidget.AppWidgetHost.AppWidgetHostListener
+import android.appwidget.AppWidgetProviderInfo
+import android.content.ComponentName
+import android.content.IntentSender
+import android.os.IBinder
+import android.os.OutcomeReceiver
+import android.os.RemoteException
+import android.os.UserHandle
+import android.widget.RemoteViews
+import com.android.server.servicewatcher.ServiceWatcher
+import com.android.server.servicewatcher.ServiceWatcher.ServiceListener
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
+import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IAppWidgetHostListener
+import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IConfigureWidgetCallback
+import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IGlanceableHubWidgetsListener
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.launch
+
+/**
+ * Manages updates to Glanceable Hub widgets and requests to edit them from the headless system
+ * user.
+ *
+ * It communicates with the remote [GlanceableHubWidgetManagerService] which runs in the foreground
+ * user, and abstracts the IPC details from the rest of the system.
+ */
+@SysUISingleton
+class GlanceableHubWidgetManager
+@Inject
+constructor(
+    @Background private val bgExecutor: Executor,
+    @Background private val bgScope: CoroutineScope,
+    glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
+    @CommunalLog logBuffer: LogBuffer,
+    serviceWatcherFactory: ServiceWatcherFactory<GlanceableHubWidgetManagerServiceInfo?>,
+) : ServiceListener<GlanceableHubWidgetManagerServiceInfo?> {
+
+    init {
+        // The manager should only be used in the headless system user.
+        glanceableHubMultiUserHelper.assertInHeadlessSystemUser()
+    }
+
+    private val logger = Logger(logBuffer, TAG)
+
+    private val serviceWatcher by lazy { serviceWatcherFactory.create(this) }
+
+    val widgets = conflatedCallbackFlow {
+        val callback =
+            object : IGlanceableHubWidgetsListener.Stub() {
+                override fun onWidgetsUpdated(widgets: List<CommunalWidgetContentModel>?) {
+                    trySend(widgets ?: emptyList())
+                }
+            }
+        runOnService { service -> service.addWidgetsListener(callback) }
+        awaitClose { runOnService { service -> service.removeWidgetsListener(callback) } }
+    }
+
+    fun register() {
+        serviceWatcher.register()
+    }
+
+    fun unregister() {
+        serviceWatcher.unregister()
+    }
+
+    override fun onBind(binder: IBinder?, serviceInfo: GlanceableHubWidgetManagerServiceInfo?) {
+        logger.i("Service bound")
+    }
+
+    override fun onUnbind() {
+        logger.i("Service unbound")
+    }
+
+    /** Requests the foreground user to set a [AppWidgetHostListener] for the given app widget. */
+    fun setAppWidgetHostListener(appWidgetId: Int, listener: AppWidgetHostListener) =
+        runOnService { service ->
+            service.setAppWidgetHostListener(appWidgetId, createIAppWidgetHostListener(listener))
+        }
+
+    /** Requests the foreground user to add a widget. */
+    fun addWidget(
+        provider: ComponentName,
+        user: UserHandle,
+        rank: Int?,
+        configurator: WidgetConfigurator?,
+    ) = runOnService { service ->
+        service.addWidget(provider, user, rank ?: -1, createIConfigureWidgetCallback(configurator))
+    }
+
+    /** Requests the foreground user to delete a widget. */
+    fun deleteWidget(appWidgetId: Int) = runOnService { service ->
+        service.deleteWidget(appWidgetId)
+    }
+
+    /** Requests the foreground user to update widget order. */
+    fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>) = runOnService { service ->
+        service.updateWidgetOrder(
+            widgetIdToRankMap.keys.toIntArray(),
+            widgetIdToRankMap.values.toIntArray(),
+        )
+    }
+
+    /** Requests the foreground user to resize a widget. */
+    fun resizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) =
+        runOnService { service ->
+            service.resizeWidget(
+                appWidgetId,
+                spanY,
+                widgetIdToRankMap.keys.toIntArray(),
+                widgetIdToRankMap.values.toIntArray(),
+            )
+        }
+
+    /**
+     * Requests the foreground user for the [IntentSender] to start a configuration activity for the
+     * widget.
+     *
+     * @param appWidgetId Id of the widget to configure.
+     * @param outcomeReceiver Callback for receiving the result or error.
+     * @param executor Executor to run the callback on.
+     */
+    fun getIntentSenderForConfigureActivity(
+        appWidgetId: Int,
+        outcomeReceiver: OutcomeReceiver<IntentSender?, Throwable>,
+        executor: Executor,
+    ) {
+        bgExecutor.execute {
+            serviceWatcher.runOnBinder(
+                object : ServiceWatcher.BinderOperation {
+                    override fun run(binder: IBinder?) {
+                        val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+                        try {
+                            val result = service.getIntentSenderForConfigureActivity(appWidgetId)
+                            executor.execute { outcomeReceiver.onResult(result) }
+                        } catch (e: RemoteException) {
+                            executor.execute { outcomeReceiver.onError(e) }
+                        }
+                    }
+
+                    override fun onError(t: Throwable?) {
+                        t?.let { executor.execute { outcomeReceiver.onError(t) } }
+                    }
+                }
+            )
+        }
+    }
+
+    private fun runOnService(block: (IGlanceableHubWidgetManagerService) -> Unit) {
+        bgExecutor.execute {
+            serviceWatcher.runOnBinder(
+                object : ServiceWatcher.BinderOperation {
+                    override fun run(binder: IBinder?) {
+                        block(IGlanceableHubWidgetManagerService.Stub.asInterface(binder))
+                    }
+
+                    override fun onError(t: Throwable?) {
+                        // TODO(b/375236794): handle failure in case service is unbound
+                    }
+                }
+            )
+        }
+    }
+
+    private fun createIAppWidgetHostListener(
+        listener: AppWidgetHostListener
+    ): IAppWidgetHostListener {
+        return object : IAppWidgetHostListener.Stub() {
+            override fun onUpdateProviderInfo(appWidget: AppWidgetProviderInfo?) {
+                listener.onUpdateProviderInfo(appWidget)
+            }
+
+            override fun updateAppWidget(views: RemoteViews?) {
+                listener.updateAppWidget(views)
+            }
+
+            override fun updateAppWidgetDeferred(packageName: String?, appWidgetId: Int) {
+                listener.updateAppWidgetDeferred(packageName, appWidgetId)
+            }
+
+            override fun onViewDataChanged(viewId: Int) {
+                listener.onViewDataChanged(viewId)
+            }
+        }
+    }
+
+    private fun createIConfigureWidgetCallback(
+        configurator: WidgetConfigurator?
+    ): IConfigureWidgetCallback? {
+        return configurator?.let {
+            object : IConfigureWidgetCallback.Stub() {
+                override fun onConfigureWidget(
+                    appWidgetId: Int,
+                    resultReceiver: IConfigureWidgetCallback.IResultReceiver?,
+                ) {
+                    bgScope.launch {
+                        val success = configurator.configureWidget(appWidgetId)
+                        try {
+                            resultReceiver?.onResult(success)
+                        } catch (e: RemoteException) {
+                            logger.e({ "Error reporting widget configuration result: $str1" }) {
+                                str1 = e.localizedMessage
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    companion object {
+        private const val TAG = "GlanceableHubWidgetManager"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt
new file mode 100644
index 0000000..0e43e2a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt
@@ -0,0 +1,391 @@
+/*
+ * 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.communal.widgets
+
+import android.appwidget.AppWidgetHost.AppWidgetHostListener
+import android.appwidget.AppWidgetProviderInfo
+import android.content.ComponentName
+import android.content.Intent
+import android.content.IntentSender
+import android.os.IBinder
+import android.os.RemoteCallbackList
+import android.os.RemoteException
+import android.os.UserHandle
+import android.widget.RemoteViews
+import androidx.lifecycle.LifecycleService
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.communal.data.repository.CommunalWidgetRepository
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
+import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IAppWidgetHostListener
+import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IConfigureWidgetCallback
+import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IGlanceableHubWidgetsListener
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
+import javax.inject.Inject
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+/**
+ * Service for the [GlanceableHubWidgetManager], which runs in a foreground user in Headless System
+ * User Mode (HSUM), manages widgets as the user who owns them, and communicates back to the
+ * headless system user, where these widgets are rendered.
+ */
+class GlanceableHubWidgetManagerService
+@Inject
+constructor(
+    private val widgetRepository: CommunalWidgetRepository,
+    private val appWidgetHost: CommunalAppWidgetHost,
+    private val communalWidgetHost: CommunalWidgetHost,
+    glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
+    @CommunalLog logBuffer: LogBuffer,
+) : LifecycleService() {
+
+    init {
+        // The service should only run in a foreground user.
+        glanceableHubMultiUserHelper.assertNotInHeadlessSystemUser()
+    }
+
+    private val logger = Logger(logBuffer, TAG)
+    private val widgetListenersRegistry = WidgetListenerRegistry()
+
+    override fun onCreate() {
+        super.onCreate()
+
+        logger.i("Service created")
+
+        communalWidgetHost.startObservingHost()
+        appWidgetHost.startListening()
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+
+        logger.i("Service destroyed")
+
+        appWidgetHost.stopListening()
+        communalWidgetHost.stopObservingHost()
+
+        // Cancel all widget listener jobs and unregister listeners
+        widgetListenersRegistry.kill()
+    }
+
+    override fun onBind(intent: Intent): IBinder? {
+        super.onBind(intent)
+        return WidgetManagerServiceBinder().asBinder()
+    }
+
+    private fun addWidgetsListenerInternal(listener: IGlanceableHubWidgetsListener?) {
+        if (listener == null) {
+            throw IllegalStateException("Listener cannot be null")
+        }
+
+        if (!listener.asBinder().isBinderAlive) {
+            throw IllegalStateException("Listener binder is dead")
+        }
+
+        val job =
+            widgetRepository.communalWidgets
+                .onEach { widgets ->
+                    try {
+                        listener.onWidgetsUpdated(widgets)
+                    } catch (e: RemoteException) {
+                        logger.e({ "Error pushing widget update: $str1" }) {
+                            str1 = e.localizedMessage
+                        }
+                    }
+                }
+                .launchIn(lifecycleScope)
+        widgetListenersRegistry.register(listener, job)
+    }
+
+    private fun removeWidgetsListenerInternal(listener: IGlanceableHubWidgetsListener?) {
+        if (listener == null) {
+            throw IllegalStateException("Listener cannot be null")
+        }
+
+        widgetListenersRegistry.unregister(listener)
+    }
+
+    private fun setAppWidgetHostListenerInternal(
+        appWidgetId: Int,
+        listener: IAppWidgetHostListener?,
+    ) {
+        if (listener == null) {
+            throw IllegalStateException("Listener cannot be null")
+        }
+
+        appWidgetHost.setListener(appWidgetId, createListener(listener))
+    }
+
+    private fun addWidgetInternal(
+        provider: ComponentName?,
+        user: UserHandle?,
+        rank: Int,
+        callback: IConfigureWidgetCallback?,
+    ) {
+        if (provider == null) {
+            throw IllegalStateException("Provider cannot be null")
+        }
+
+        if (user == null) {
+            throw IllegalStateException("User cannot be null")
+        }
+
+        val configurator =
+            callback?.let {
+                WidgetConfigurator { appWidgetId ->
+                    try {
+                        val result = CompletableDeferred<Boolean>()
+                        val resultReceiver =
+                            object : IConfigureWidgetCallback.IResultReceiver.Stub() {
+                                override fun onResult(success: Boolean) {
+                                    result.complete(success)
+                                }
+                            }
+
+                        callback.onConfigureWidget(appWidgetId, resultReceiver)
+                        result.await()
+                    } catch (e: RemoteException) {
+                        logger.e({ "Error configuring widget: $str1" }) {
+                            str1 = e.localizedMessage
+                        }
+                        false
+                    }
+                }
+            }
+        widgetRepository.addWidget(provider, user, rank, configurator)
+    }
+
+    private fun deleteWidgetInternal(appWidgetId: Int) {
+        widgetRepository.deleteWidget(appWidgetId)
+    }
+
+    private fun updateWidgetOrderInternal(appWidgetIds: IntArray?, ranks: IntArray?) {
+        if (appWidgetIds == null || ranks == null) {
+            throw IllegalStateException("appWidgetIds and ranks cannot be null")
+        }
+
+        if (appWidgetIds.size != ranks.size) {
+            throw IllegalStateException("appWidgetIds and ranks must be the same size")
+        }
+
+        widgetRepository.updateWidgetOrder(appWidgetIds.zip(ranks).toMap())
+    }
+
+    private fun resizeWidgetInternal(
+        appWidgetId: Int,
+        spanY: Int,
+        appWidgetIds: IntArray?,
+        ranks: IntArray?,
+    ) {
+        if (appWidgetIds == null || ranks == null) {
+            throw IllegalStateException("appWidgetIds and ranks cannot be null")
+        }
+
+        if (appWidgetIds.size != ranks.size) {
+            throw IllegalStateException("appWidgetIds and ranks must be the same size")
+        }
+
+        widgetRepository.resizeWidget(appWidgetId, spanY, appWidgetIds.zip(ranks).toMap())
+    }
+
+    private fun getIntentSenderForConfigureActivityInternal(appWidgetId: Int): IntentSender? {
+        return try {
+            appWidgetHost.getIntentSenderForConfigureActivity(appWidgetId, /* intentFlags= */ 0)
+        } catch (e: IntentSender.SendIntentException) {
+            logger.e({ "Error getting intent sender for configure activity" }) {
+                str1 = e.localizedMessage
+            }
+            null
+        }
+    }
+
+    private fun createListener(listener: IAppWidgetHostListener): AppWidgetHostListener {
+        return object : AppWidgetHostListener {
+            override fun onUpdateProviderInfo(appWidget: AppWidgetProviderInfo?) {
+                try {
+                    listener.onUpdateProviderInfo(appWidget)
+                } catch (e: RemoteException) {
+                    logger.e({ "Error pushing on update provider info: $str1" }) {
+                        str1 = e.localizedMessage
+                    }
+                }
+            }
+
+            override fun updateAppWidget(views: RemoteViews?) {
+                try {
+                    listener.updateAppWidget(views)
+                } catch (e: RemoteException) {
+                    logger.e({ "Error updating app widget: $str1" }) { str1 = e.localizedMessage }
+                }
+            }
+
+            override fun updateAppWidgetDeferred(packageName: String?, appWidgetId: Int) {
+                try {
+                    listener.updateAppWidgetDeferred(packageName, appWidgetId)
+                } catch (e: RemoteException) {
+                    logger.e({ "Error updating app widget deferred: $str1" }) {
+                        str1 = e.localizedMessage
+                    }
+                }
+            }
+
+            override fun onViewDataChanged(viewId: Int) {
+                try {
+                    listener.onViewDataChanged(viewId)
+                } catch (e: RemoteException) {
+                    logger.e({ "Error pushing on view data changed: $str1" }) {
+                        str1 = e.localizedMessage
+                    }
+                }
+            }
+        }
+    }
+
+    private inner class WidgetManagerServiceBinder : IGlanceableHubWidgetManagerService.Stub() {
+        override fun addWidgetsListener(listener: IGlanceableHubWidgetsListener?) {
+            val iden = clearCallingIdentity()
+
+            try {
+                addWidgetsListenerInternal(listener)
+            } finally {
+                restoreCallingIdentity(iden)
+            }
+        }
+
+        override fun removeWidgetsListener(listener: IGlanceableHubWidgetsListener?) {
+            val iden = clearCallingIdentity()
+
+            try {
+                removeWidgetsListenerInternal(listener)
+            } finally {
+                restoreCallingIdentity(iden)
+            }
+        }
+
+        override fun setAppWidgetHostListener(appWidgetId: Int, listener: IAppWidgetHostListener?) {
+            val iden = clearCallingIdentity()
+
+            try {
+                setAppWidgetHostListenerInternal(appWidgetId, listener)
+            } finally {
+                restoreCallingIdentity(iden)
+            }
+        }
+
+        override fun addWidget(
+            provider: ComponentName?,
+            user: UserHandle?,
+            rank: Int,
+            callback: IConfigureWidgetCallback?,
+        ) {
+            val iden = clearCallingIdentity()
+
+            try {
+                addWidgetInternal(provider, user, rank, callback)
+            } finally {
+                restoreCallingIdentity(iden)
+            }
+        }
+
+        override fun deleteWidget(appWidgetId: Int) {
+            val iden = clearCallingIdentity()
+
+            try {
+                deleteWidgetInternal(appWidgetId)
+            } finally {
+                restoreCallingIdentity(iden)
+            }
+        }
+
+        override fun updateWidgetOrder(appWidgetIds: IntArray?, ranks: IntArray?) {
+            val iden = clearCallingIdentity()
+
+            try {
+                updateWidgetOrderInternal(appWidgetIds, ranks)
+            } finally {
+                restoreCallingIdentity(iden)
+            }
+        }
+
+        override fun resizeWidget(
+            appWidgetId: Int,
+            spanY: Int,
+            appWidgetIds: IntArray?,
+            ranks: IntArray?,
+        ) {
+            val iden = clearCallingIdentity()
+
+            try {
+                resizeWidgetInternal(appWidgetId, spanY, appWidgetIds, ranks)
+            } finally {
+                restoreCallingIdentity(iden)
+            }
+        }
+
+        override fun getIntentSenderForConfigureActivity(appWidgetId: Int): IntentSender? {
+            val iden = clearCallingIdentity()
+
+            try {
+                return getIntentSenderForConfigureActivityInternal(appWidgetId)
+            } finally {
+                restoreCallingIdentity(iden)
+            }
+        }
+    }
+
+    /**
+     * Registry of widget listener binders, which handles canceling the job associated with a
+     * listener when it is unregistered, or when the binder is dead.
+     */
+    private class WidgetListenerRegistry : RemoteCallbackList<IGlanceableHubWidgetsListener>() {
+        private val jobs = mutableMapOf<IGlanceableHubWidgetsListener, Job>()
+
+        fun register(listener: IGlanceableHubWidgetsListener, job: Job) {
+            if (register(listener)) {
+                synchronized(jobs) { jobs[listener] = job }
+            } else {
+                job.cancel()
+            }
+        }
+
+        override fun unregister(listener: IGlanceableHubWidgetsListener?): Boolean {
+            synchronized(jobs) { jobs.remove(listener)?.cancel() }
+            return super.unregister(listener)
+        }
+
+        override fun onCallbackDied(listener: IGlanceableHubWidgetsListener?) {
+            synchronized(jobs) { jobs.remove(listener)?.cancel() }
+            super.onCallbackDied(listener)
+        }
+
+        override fun kill() {
+            synchronized(jobs) {
+                jobs.values.forEach { it.cancel() }
+                jobs.clear()
+            }
+            super.kill()
+        }
+    }
+
+    companion object {
+        private const val TAG = "GlanceableHubWidgetManagerService"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceInfo.kt
new file mode 100644
index 0000000..d527611
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceInfo.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Context.BIND_AUTO_CREATE
+import android.os.UserHandle
+import com.android.server.servicewatcher.ServiceWatcher
+
+/**
+ * Information about the [GlanceableHubWidgetManagerServiceInfo] used for binding with the
+ * [ServiceWatcher].
+ */
+class GlanceableHubWidgetManagerServiceInfo(context: Context, userHandle: UserHandle) :
+    ServiceWatcher.BoundServiceInfo(
+        /* action= */ null,
+        UserHandle.getUid(userHandle.identifier, UserHandle.getCallingAppId()),
+        ComponentName(context.packageName, GlanceableHubWidgetManagerService::class.java.name),
+        BIND_AUTO_CREATE,
+    )
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceSupplier.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceSupplier.kt
new file mode 100644
index 0000000..ed77e6f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceSupplier.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.communal.widgets
+
+import android.content.Context
+import com.android.server.servicewatcher.ServiceWatcher
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserTracker
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Supplies details about the [GlanceableHubWidgetManagerService] that the [ServiceWatcher] should
+ * bind to. Currently the service should only be bound if the current user is the main user.
+ */
+@SysUISingleton
+class GlanceableHubWidgetManagerServiceSupplier
+@Inject
+constructor(
+    @Application private val context: Context,
+    @Background private val bgExecutor: Executor,
+    private val userTracker: UserTracker,
+) : ServiceWatcher.ServiceSupplier<GlanceableHubWidgetManagerServiceInfo?>, UserTracker.Callback {
+
+    private var userAboutToSwitch = false
+    private var listener: ServiceWatcher.ServiceChangedListener? = null
+
+    override fun getServiceInfo(): GlanceableHubWidgetManagerServiceInfo? {
+        return GlanceableHubWidgetManagerServiceInfo(context, userTracker.userHandle)
+    }
+
+    override fun hasMatchingService(): Boolean {
+        // The service becomes unavailable immediately before a user switching is about to happen
+        // so that it is disconnected before the user process is terminated.
+        // It is also only available if the current user is the main user.
+        return !userAboutToSwitch && userTracker.userInfo.isMain
+    }
+
+    override fun register(listener: ServiceWatcher.ServiceChangedListener?) {
+        this.listener = listener
+        userTracker.addCallback(this, bgExecutor)
+    }
+
+    override fun unregister() {
+        listener = null
+        userTracker.removeCallback(this)
+    }
+
+    override fun onBeforeUserSwitching(newUser: Int) {
+        userAboutToSwitch = true
+        listener?.onServiceChanged()
+    }
+
+    override fun onUserChanged(newUser: Int, userContext: Context) {
+        userAboutToSwitch = false
+        listener?.onServiceChanged()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/IGlanceableHubWidgetManagerService.aidl b/packages/SystemUI/src/com/android/systemui/communal/widgets/IGlanceableHubWidgetManagerService.aidl
new file mode 100644
index 0000000..d71b230
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/IGlanceableHubWidgetManagerService.aidl
@@ -0,0 +1,67 @@
+package com.android.systemui.communal.widgets;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.IntentSender;
+import android.os.UserHandle;
+import android.widget.RemoteViews;
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel;
+import java.util.List;
+
+// Interface for the [GlanceableHubWidgetManagerService], which runs in a foreground user in HSUM
+// and communicates with the headless system user.
+interface IGlanceableHubWidgetManagerService {
+
+    // Adds a listener for updates of Glanceable Hub widgets.
+    oneway void addWidgetsListener(in IGlanceableHubWidgetsListener listener);
+
+    // Removes a listener for updates of Glanceable Hub widgets.
+    oneway void removeWidgetsListener(in IGlanceableHubWidgetsListener listener);
+
+    // Sets a listener for updates on a specific widget.
+    oneway void setAppWidgetHostListener(int appWidgetId, in IAppWidgetHostListener listener);
+
+    // Requests to add a widget in the Glanceable Hub.
+    oneway void addWidget(in ComponentName provider, in UserHandle user, int rank,
+        in IConfigureWidgetCallback callback);
+
+    // Requests to delete a widget from the Glanceable Hub.
+    oneway void deleteWidget(int appWidgetId);
+
+    // Requests to update the order of widgets in the Glanceable Hub.
+    oneway void updateWidgetOrder(in int[] appWidgetIds, in int[] ranks);
+
+    // Requests to resize a widget in the Glanceable Hub.
+    oneway void resizeWidget(int appWidgetId, int spanY, in int[] appWidgetIds, in int[] ranks);
+
+    // Returns the [IntentSender] for launching the configuration activity of the given widget.
+    IntentSender getIntentSenderForConfigureActivity(int appWidgetId);
+
+    // Listener for Glanceable Hub widget updates
+    oneway interface IGlanceableHubWidgetsListener {
+        // Called when widgets have updated.
+        void onWidgetsUpdated(in List<CommunalWidgetContentModel> widgets);
+    }
+
+    // Mirrors [AppWidgetHost#AppWidgetHostListener].
+    oneway interface IAppWidgetHostListener {
+        void onUpdateProviderInfo(in @nullable AppWidgetProviderInfo appWidget);
+
+        void updateAppWidget(in @nullable RemoteViews views);
+
+        void updateAppWidgetDeferred(in String packageName, int appWidgetId);
+
+        void onViewDataChanged(int viewId);
+    }
+
+    oneway interface IConfigureWidgetCallback {
+        // Called when the given widget should launch its configuration activity. The caller should
+        // report the result through the [IResultReceiver].
+        void onConfigureWidget(int appWidgetId, in IResultReceiver resultReceiver);
+
+        interface IResultReceiver {
+            // Called when the widget configuration operation returns a result.
+            void onResult(boolean success);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/ServiceWatcherFactory.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/ServiceWatcherFactory.kt
new file mode 100644
index 0000000..f79cc87
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/ServiceWatcherFactory.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.communal.widgets
+
+import android.content.Context
+import android.os.Handler
+import com.android.server.servicewatcher.ServiceWatcher
+import com.android.server.servicewatcher.ServiceWatcher.BoundServiceInfo
+import com.android.server.servicewatcher.ServiceWatcher.ServiceSupplier
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+
+/** Factory for creating a [ServiceWatcher]. */
+interface ServiceWatcherFactory<TBoundService : BoundServiceInfo?> {
+    fun create(listener: ServiceWatcher.ServiceListener<TBoundService?>): ServiceWatcher
+}
+
+/** Implementation of the [ServiceWatcherFactory] for the [GlanceableHubWidgetManagerService]. */
+@SysUISingleton
+class GlanceableHubWidgetManagerServiceWatcherFactoryImpl
+@Inject
+constructor(
+    @Application private val context: Context,
+    @Background private val handler: Handler,
+    private val supplier: ServiceSupplier<GlanceableHubWidgetManagerServiceInfo?>,
+) : ServiceWatcherFactory<GlanceableHubWidgetManagerServiceInfo?> {
+
+    override fun create(
+        listener: ServiceWatcher.ServiceListener<GlanceableHubWidgetManagerServiceInfo?>
+    ): ServiceWatcher {
+        return ServiceWatcher.create(
+            context,
+            handler,
+            GlanceableHubWidgetManagerService::class.java.simpleName,
+            supplier,
+            listener,
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt
index 3e68479..fddec56 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt
@@ -18,14 +18,19 @@
 
 import android.app.Activity
 import android.app.ActivityOptions
-import android.content.ActivityNotFoundException
+import android.content.IntentSender
+import android.os.OutcomeReceiver
 import android.window.SplashScreen
 import androidx.activity.ComponentActivity
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.util.nullableAtomicReference
+import dagger.Lazy
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import java.util.concurrent.Executor
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.withContext
@@ -38,14 +43,25 @@
 @AssistedInject
 constructor(
     @Assisted private val activity: ComponentActivity,
-    private val appWidgetHost: CommunalAppWidgetHost,
-    @Background private val bgDispatcher: CoroutineDispatcher
-) : WidgetConfigurator {
+    private val appWidgetHostLazy: Lazy<CommunalAppWidgetHost>,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    private val glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
+    private val glanceableHubWidgetManagerLazy: Lazy<GlanceableHubWidgetManager>,
+    @Main private val mainExecutor: Executor,
+) : WidgetConfigurator, OutcomeReceiver<IntentSender?, Throwable> {
     @AssistedFactory
     fun interface Factory {
         fun create(activity: ComponentActivity): WidgetConfigurationController
     }
 
+    private val activityOptions: ActivityOptions
+        get() =
+            ActivityOptions.makeBasic().apply {
+                pendingIntentBackgroundActivityStartMode =
+                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+                splashScreenStyle = SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR
+            }
+
     private var result: CompletableDeferred<Boolean>? by nullableAtomicReference()
 
     override suspend fun configureWidget(appWidgetId: Int): Boolean =
@@ -54,29 +70,64 @@
                 throw IllegalStateException("There is already a pending configuration")
             }
             result = CompletableDeferred()
-            val options =
-                ActivityOptions.makeBasic().apply {
-                    pendingIntentBackgroundActivityStartMode =
-                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
-                    splashScreenStyle = SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR
-                }
 
             try {
-                appWidgetHost.startAppWidgetConfigureActivityForResult(
-                    activity,
-                    appWidgetId,
-                    0,
-                    REQUEST_CODE,
-                    options.toBundle()
-                )
-            } catch (e: ActivityNotFoundException) {
+                if (
+                    !glanceableHubMultiUserHelper.glanceableHubHsumFlagEnabled ||
+                        !glanceableHubMultiUserHelper.isInHeadlessSystemUser()
+                ) {
+                    // Start configuration activity directly if we're running in a foreground user
+                    with(appWidgetHostLazy.get()) {
+                        startAppWidgetConfigureActivityForResult(
+                            activity,
+                            appWidgetId,
+                            0,
+                            REQUEST_CODE,
+                            activityOptions.toBundle(),
+                        )
+                    }
+                } else {
+                    with(glanceableHubWidgetManagerLazy.get()) {
+                        // Use service to get intent sender and start configuration activity
+                        // locally if running in a headless system user
+                        getIntentSenderForConfigureActivity(
+                            appWidgetId,
+                            outcomeReceiver = this@WidgetConfigurationController,
+                            mainExecutor,
+                        )
+                    }
+                }
+            } catch (_: Exception) {
                 setConfigurationResult(Activity.RESULT_CANCELED)
             }
-            val value = result?.await() ?: false
+            val value = result?.await() == true
             result = null
             return@withContext value
         }
 
+    // Called when an intent sender is returned, and the configuration activity should be started.
+    override fun onResult(intentSender: IntentSender?) {
+        if (intentSender == null) {
+            setConfigurationResult(Activity.RESULT_CANCELED)
+            return
+        }
+
+        activity.startIntentSenderForResult(
+            intentSender,
+            REQUEST_CODE,
+            /* fillInIntent = */ null,
+            /* flagsMask = */ 0,
+            /* flagsValues = */ 0,
+            /* extraFlags = */ 0,
+            activityOptions.toBundle(),
+        )
+    }
+
+    // Called when there is an error getting the intent sender.
+    override fun onError(e: Throwable) {
+        setConfigurationResult(Activity.RESULT_CANCELED)
+    }
+
     fun setConfigurationResult(resultCode: Int) {
         result?.complete(resultCode == Activity.RESULT_OK)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
index 904d5898..8f01775 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
@@ -19,6 +19,7 @@
 import android.app.Service;
 
 import com.android.systemui.SystemUIService;
+import com.android.systemui.communal.widgets.GlanceableHubWidgetManagerService;
 import com.android.systemui.doze.DozeService;
 import com.android.systemui.dreams.DreamOverlayService;
 import com.android.systemui.dump.SystemUIAuxiliaryDumpService;
@@ -93,4 +94,10 @@
     @ClassKey(IssueRecordingService.class)
     public abstract Service bindIssueRecordingService(IssueRecordingService service);
 
+    /** Inject into GlanceableHubWidgetManagerService */
+    @Binds
+    @IntoMap
+    @ClassKey(GlanceableHubWidgetManagerService.class)
+    public abstract Service bindGlanceableHubWidgetManagerService(
+            GlanceableHubWidgetManagerService service);
 }
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/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index cb649f2..4447dff 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -48,7 +48,8 @@
 import com.android.systemui.classifier.FalsingModule;
 import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
 import com.android.systemui.common.data.CommonDataLayerModule;
-import com.android.systemui.common.ui.ConfigurationStateModule;
+import com.android.systemui.common.ui.ConfigurationModule;
+import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryModule;
 import com.android.systemui.common.usagestats.data.CommonUsageStatsDataLayerModule;
 import com.android.systemui.communal.dagger.CommunalModule;
 import com.android.systemui.complication.dagger.ComplicationComponent;
@@ -211,7 +212,8 @@
         ClockRegistryModule.class,
         CommunalModule.class,
         CommonDataLayerModule.class,
-        ConfigurationStateModule.class,
+        ConfigurationModule.class,
+        ConfigurationRepositoryModule.class,
         CommonUsageStatsDataLayerModule.class,
         ConfigurationControllerModule.class,
         ConnectivityModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
index 9aa7fd1..78d8d8f 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
@@ -81,7 +81,7 @@
     override val viewId: Int,
     @DisplayCutout.BoundsPosition override val alignedBound1: Int,
     @DisplayCutout.BoundsPosition override val alignedBound2: Int,
-    private val layoutId: Int,
+    val layoutId: Int,
 ) : CornerDecorProvider() {
 
     override fun onReloadResAndMeasure(
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt
index 2bcfea8..45a5901 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt
@@ -16,20 +16,16 @@
 
 package com.android.systemui.dreams.homecontrols.service
 
-import android.content.ComponentName
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dreams.homecontrols.shared.IHomeControlsRemoteProxy
-import com.android.systemui.dreams.homecontrols.shared.IOnControlsSettingsChangeListener
+import com.android.systemui.dreams.homecontrols.shared.controlsSettings
 import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.util.kotlin.FlowDumperImpl
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -45,30 +41,8 @@
     @Assisted private val proxy: IHomeControlsRemoteProxy,
 ) : FlowDumperImpl(dumpManager) {
 
-    private companion object {
-        const val TAG = "HomeControlsRemoteProxy"
-    }
-
     val componentInfo: Flow<HomeControlsComponentInfo> =
-        conflatedCallbackFlow {
-                val listener =
-                    object : IOnControlsSettingsChangeListener.Stub() {
-                        override fun onControlsSettingsChanged(
-                            panelComponent: ComponentName?,
-                            allowTrivialControlsOnLockscreen: Boolean,
-                        ) {
-                            trySendWithFailureLogging(
-                                HomeControlsComponentInfo(
-                                    panelComponent,
-                                    allowTrivialControlsOnLockscreen,
-                                ),
-                                TAG,
-                            )
-                        }
-                    }
-                proxy.registerListenerForCurrentUser(listener)
-                awaitClose { proxy.unregisterListenerForCurrentUser(listener) }
-            }
+        proxy.controlsSettings
             .distinctUntilChanged()
             .stateIn(bgScope, SharingStarted.WhileSubscribed(), null)
             .dumpValue("componentInfo")
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/IHomeControlsRemoteProxyExt.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/IHomeControlsRemoteProxyExt.kt
new file mode 100644
index 0000000..2993ab5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/IHomeControlsRemoteProxyExt.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.homecontrols.shared
+
+import android.content.ComponentName
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+val IHomeControlsRemoteProxy.controlsSettings: Flow<HomeControlsComponentInfo>
+    get() = conflatedCallbackFlow {
+        val listener =
+            object : IOnControlsSettingsChangeListener.Stub() {
+                override fun onControlsSettingsChanged(
+                    panelComponent: ComponentName?,
+                    allowTrivialControlsOnLockscreen: Boolean,
+                ) {
+                    trySend(
+                        HomeControlsComponentInfo(panelComponent, allowTrivialControlsOnLockscreen)
+                    )
+                }
+            }
+        registerListenerForCurrentUser(listener)
+        awaitClose { unregisterListenerForCurrentUser(listener) }
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt
index a65d216..6d1fd4d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt
@@ -57,6 +57,11 @@
         super.onBind(intent)
         return binder
     }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        binder.onDestroy()
+    }
 }
 
 class HomeControlsRemoteServiceBinder
@@ -148,6 +153,14 @@
         }
     }
 
+    fun onDestroy() {
+        logger.d("Service destroyed")
+        callbacks.kill()
+        callbackCount.set(0)
+        collectionJob?.cancel()
+        collectionJob = null
+    }
+
     @AssistedFactory
     interface Factory {
         fun create(lifecycleOwner: LifecycleOwner): HomeControlsRemoteServiceBinder
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
index 4a8c040..fd91389 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
@@ -20,7 +20,6 @@
 import android.app.smartspace.SmartspaceManager
 import android.app.smartspace.SmartspaceSession
 import android.app.smartspace.SmartspaceTarget
-import android.content.Context
 import android.graphics.Color
 import android.util.Log
 import android.view.View
@@ -31,6 +30,7 @@
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_DREAM
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.smartspace.SmartspacePrecondition
 import com.android.systemui.smartspace.SmartspaceTargetFilter
 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_DATA_PLUGIN
@@ -44,13 +44,12 @@
 import javax.inject.Inject
 import javax.inject.Named
 
-/**
- * Controller for managing the smartspace view on the dream
- */
+/** Controller for managing the smartspace view on the dream */
 @SysUISingleton
-class DreamSmartspaceController @Inject constructor(
-    private val context: Context,
-    private val smartspaceManager: SmartspaceManager?,
+class DreamSmartspaceController
+@Inject
+constructor(
+    private val userTracker: UserTracker,
     private val execution: Execution,
     @Main private val uiExecutor: Executor,
     private val smartspaceViewComponentFactory: SmartspaceViewComponent.Factory,
@@ -65,6 +64,7 @@
         private const val TAG = "DreamSmartspaceCtrlr"
     }
 
+    private var userSmartspaceManager: SmartspaceManager? = null
     private var session: SmartspaceSession? = null
     private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null)
     private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
@@ -78,66 +78,68 @@
     // Smartspace can be used on multiple displays, such as when the user casts their screen
     private var smartspaceViews = mutableSetOf<SmartspaceView>()
 
-    var preconditionListener = object : SmartspacePrecondition.Listener {
-        override fun onCriteriaChanged() {
-            reloadSmartspace()
+    var preconditionListener =
+        object : SmartspacePrecondition.Listener {
+            override fun onCriteriaChanged() {
+                reloadSmartspace()
+            }
         }
-    }
 
     init {
         precondition.addListener(preconditionListener)
     }
 
-    var filterListener = object : SmartspaceTargetFilter.Listener {
-        override fun onCriteriaChanged() {
-            reloadSmartspace()
+    var filterListener =
+        object : SmartspaceTargetFilter.Listener {
+            override fun onCriteriaChanged() {
+                reloadSmartspace()
+            }
         }
-    }
 
     init {
         targetFilter?.addListener(filterListener)
     }
 
-    var stateChangeListener = object : View.OnAttachStateChangeListener {
-        override fun onViewAttachedToWindow(v: View) {
-            val view = v as SmartspaceView
-            // Until there is dream color matching
-            view.setPrimaryTextColor(Color.WHITE)
-            smartspaceViews.add(view)
-            connectSession()
-            view.setDozeAmount(0f)
-        }
+    var stateChangeListener =
+        object : View.OnAttachStateChangeListener {
+            override fun onViewAttachedToWindow(v: View) {
+                val view = v as SmartspaceView
+                // Until there is dream color matching
+                view.setPrimaryTextColor(Color.WHITE)
+                smartspaceViews.add(view)
+                connectSession()
+                view.setDozeAmount(0f)
+            }
 
-        override fun onViewDetachedFromWindow(v: View) {
-            smartspaceViews.remove(v as SmartspaceView)
+            override fun onViewDetachedFromWindow(v: View) {
+                smartspaceViews.remove(v as SmartspaceView)
 
-            if (smartspaceViews.isEmpty()) {
-                disconnect()
+                if (smartspaceViews.isEmpty()) {
+                    disconnect()
+                }
             }
         }
-    }
 
-    private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
-        execution.assertIsMainThread()
+    private val sessionListener =
+        SmartspaceSession.OnTargetsAvailableListener { targets ->
+            execution.assertIsMainThread()
 
-        // The weather data plugin takes unfiltered targets and performs the filtering internally.
-        weatherPlugin?.onTargetsAvailable(targets)
+            // The weather data plugin takes unfiltered targets and performs the filtering
+            // internally.
+            weatherPlugin?.onTargetsAvailable(targets)
 
-        onTargetsAvailableUnfiltered(targets)
-        val filteredTargets = targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true }
-        plugin?.onTargetsAvailable(filteredTargets)
-    }
+            onTargetsAvailableUnfiltered(targets)
+            val filteredTargets =
+                targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true }
+            plugin?.onTargetsAvailable(filteredTargets)
+        }
 
-    /**
-     * Constructs the weather view with custom layout and connects it to the weather plugin.
-     */
+    /** Constructs the weather view with custom layout and connects it to the weather plugin. */
     fun buildAndConnectWeatherView(parent: ViewGroup, customView: View?): View? {
         return buildAndConnectViewWithPlugin(parent, weatherPlugin, customView)
     }
 
-    /**
-     * Constructs the smartspace view and connects it to the smartspace service.
-     */
+    /** Constructs the smartspace view and connects it to the smartspace service. */
     fun buildAndConnectView(parent: ViewGroup): View? {
         return buildAndConnectViewWithPlugin(parent, plugin, null)
     }
@@ -145,7 +147,7 @@
     private fun buildAndConnectViewWithPlugin(
         parent: ViewGroup,
         smartspaceDataPlugin: BcSmartspaceDataPlugin?,
-        customView: View?
+        customView: View?,
     ): View? {
         execution.assertIsMainThread()
 
@@ -163,12 +165,13 @@
     private fun buildView(
         parent: ViewGroup,
         smartspaceDataPlugin: BcSmartspaceDataPlugin?,
-        customView: View?
+        customView: View?,
     ): View? {
         return if (smartspaceDataPlugin != null) {
-            val view = smartspaceViewComponentFactory.create(parent, smartspaceDataPlugin,
-                stateChangeListener, customView)
-                .getView()
+            val view =
+                smartspaceViewComponentFactory
+                    .create(parent, smartspaceDataPlugin, stateChangeListener, customView)
+                    .getView()
             if (view !is View) {
                 return null
             }
@@ -179,12 +182,17 @@
     }
 
     private fun hasActiveSessionListeners(): Boolean {
-        return smartspaceViews.isNotEmpty() || listeners.isNotEmpty() ||
+        return smartspaceViews.isNotEmpty() ||
+            listeners.isNotEmpty() ||
             unfilteredListeners.isNotEmpty()
     }
 
     private fun connectSession() {
-        if (smartspaceManager == null) {
+        if (userSmartspaceManager == null) {
+            userSmartspaceManager =
+                userTracker.userContext.getSystemService(SmartspaceManager::class.java)
+        }
+        if (userSmartspaceManager == null) {
             return
         }
         if (plugin == null && weatherPlugin == null) {
@@ -198,25 +206,21 @@
             return
         }
 
-        val newSession = smartspaceManager.createSmartspaceSession(
-            SmartspaceConfig.Builder(context, UI_SURFACE_DREAM).build()
-        )
+        val newSession =
+            userSmartspaceManager?.createSmartspaceSession(
+                SmartspaceConfig.Builder(userTracker.userContext, UI_SURFACE_DREAM).build()
+            )
         Log.d(TAG, "Starting smartspace session for dream")
-        newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+        newSession?.addOnTargetsAvailableListener(uiExecutor, sessionListener)
         this.session = newSession
 
         weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
-        plugin?.registerSmartspaceEventNotifier {
-                e ->
-            session?.notifySmartspaceEvent(e)
-        }
+        plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
 
         reloadSmartspace()
     }
 
-    /**
-     * Disconnects the smartspace view from the smartspace service and cleans up any resources.
-     */
+    /** Disconnects the smartspace view from the smartspace service and cleans up any resources. */
     private fun disconnect() {
         if (hasActiveSessionListeners()) return
 
@@ -259,7 +263,7 @@
 
     private fun addAndRegisterListener(
         listener: SmartspaceTargetListener,
-        smartspaceDataPlugin: BcSmartspaceDataPlugin?
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?,
     ) {
         execution.assertIsMainThread()
         smartspaceDataPlugin?.registerListener(listener)
@@ -270,7 +274,7 @@
 
     private fun removeAndUnregisterListener(
         listener: SmartspaceTargetListener,
-        smartspaceDataPlugin: BcSmartspaceDataPlugin?
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?,
     ) {
         execution.assertIsMainThread()
         smartspaceDataPlugin?.unregisterListener(listener)
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index 3492365..b2fcc43 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -45,7 +45,7 @@
 
     /** See [registerCriticalDumpable]. */
     fun registerCriticalDumpable(module: Dumpable) {
-        registerCriticalDumpable(module::class.java.canonicalName, module)
+        registerCriticalDumpable(module::class.java.name, module)
     }
 
     /**
@@ -62,7 +62,7 @@
 
     /** See [registerNormalDumpable]. */
     fun registerNormalDumpable(module: Dumpable) {
-        registerNormalDumpable(module::class.java.canonicalName, module)
+        registerNormalDumpable(module::class.java.name, module)
     }
 
     /**
@@ -104,13 +104,10 @@
         dumpables[name] = DumpableEntry(module, name, priority)
     }
 
-    /**
-     * Same as the above override, but automatically uses the canonical class name as the dumpable
-     * name.
-     */
+    /** Same as the above override, but automatically uses the class name as the dumpable name. */
     @Synchronized
     fun registerDumpable(module: Dumpable) {
-        registerDumpable(module::class.java.canonicalName, module)
+        registerDumpable(module::class.java.name, module)
     }
 
     /** Unregisters a previously-registered dumpable. */
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
index 303f916..911331b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
@@ -42,7 +42,7 @@
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.GlobalSettings;
 
 import java.io.PrintWriter;
@@ -51,6 +51,7 @@
 import java.util.Objects;
 import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -74,7 +75,6 @@
     static final String TAG = "SysUIFlags";
 
     private final FlagManager mFlagManager;
-    private final Context mContext;
     private final GlobalSettings mGlobalSettings;
     private final Resources mResources;
     private final SystemPropertiesHelper mSystemProperties;
@@ -84,6 +84,8 @@
     private final Map<String, String> mStringFlagCache = new ConcurrentHashMap<>();
     private final Map<String, Integer> mIntFlagCache = new ConcurrentHashMap<>();
     private final Restarter mRestarter;
+    private final UserTracker mUserTracker;
+    private final Executor mMainExecutor;
 
     private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
             new ServerFlagReader.ChangeListener() {
@@ -118,6 +120,18 @@
                 }
             };
 
+    private final UserTracker.Callback mUserChangedCallback =
+            new UserTracker.Callback() {
+                @Override
+                public void onUserChanged(int newUser, @NonNull Context userContext) {
+                    mContext.unregisterReceiver(mReceiver);
+                    mContext = userContext;
+                    registerReceiver();
+                }
+            };
+
+    private Context mContext;
+
     @Inject
     public FeatureFlagsClassicDebug(
             FlagManager flagManager,
@@ -128,10 +142,11 @@
             ServerFlagReader serverFlagReader,
             @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags,
             Restarter restarter,
-            UserContextProvider userContextProvider) {
+            UserTracker userTracker,
+            @Main Executor executor) {
         mFlagManager = flagManager;
         if (classicFlagsMultiUser()) {
-            mContext = userContextProvider.createCurrentUserContext(context);
+            mContext = userTracker.createCurrentUserContext(context);
         } else {
             mContext = context;
         }
@@ -141,19 +156,28 @@
         mServerFlagReader = serverFlagReader;
         mAllFlags = allFlags;
         mRestarter = restarter;
+        mUserTracker = userTracker;
+        mMainExecutor = executor;
     }
 
     /** Call after construction to setup listeners. */
     void init() {
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(ACTION_SET_FLAG);
-        filter.addAction(ACTION_GET_FLAGS);
         mFlagManager.setOnSettingsChangedAction(
                 suppressRestart -> restartSystemUI(suppressRestart, "Settings changed"));
         mFlagManager.setClearCacheAction(this::removeFromCache);
+        registerReceiver();
+        if (classicFlagsMultiUser()) {
+            mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
+        }
+        mServerFlagReader.listenForChanges(mAllFlags.values(), mOnPropertiesChanged);
+    }
+
+    void registerReceiver() {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_SET_FLAG);
+        filter.addAction(ACTION_GET_FLAGS);
         mContext.registerReceiver(mReceiver, filter, null, null,
                 Context.RECEIVER_EXPORTED_UNAUDITED);
-        mServerFlagReader.listenForChanges(mAllFlags.values(), mOnPropertiesChanged);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/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/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
index a085887..12dd581 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
@@ -19,12 +19,14 @@
 import android.content.Context
 import android.graphics.drawable.Icon
 import android.hardware.input.InputManager
+import android.hardware.input.KeyGlyphMap
 import android.util.Log
 import android.view.InputDevice
 import android.view.KeyCharacterMap
 import android.view.KeyEvent
 import android.view.KeyboardShortcutGroup
 import android.view.KeyboardShortcutInfo
+import com.android.systemui.Flags.shortcutHelperKeyGlyph
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource
@@ -142,7 +144,10 @@
         return if (type == null) {
             null
         } else {
+            val keyGlyphMap =
+                if (shortcutHelperKeyGlyph()) inputManager.getKeyGlyphMap(inputDevice.id) else null
             toShortcutCategory(
+                keyGlyphMap,
                 inputDevice.keyCharacterMap,
                 type,
                 groups,
@@ -163,6 +168,7 @@
     }
 
     private fun toShortcutCategory(
+        keyGlyphMap: KeyGlyphMap?,
         keyCharacterMap: KeyCharacterMap,
         type: ShortcutCategoryType,
         shortcutGroups: List<KeyboardShortcutGroup>,
@@ -175,6 +181,7 @@
                     ShortcutSubCategory(
                         shortcutGroup.label.toString(),
                         toShortcuts(
+                            keyGlyphMap,
                             keyCharacterMap,
                             shortcutGroup.items,
                             keepIcons,
@@ -192,6 +199,7 @@
     }
 
     private fun toShortcuts(
+        keyGlyphMap: KeyGlyphMap?,
         keyCharacterMap: KeyCharacterMap,
         infoList: List<KeyboardShortcutInfo>,
         keepIcons: Boolean,
@@ -203,14 +211,16 @@
                 // keycode, or they could have a baseCharacter instead of a keycode.
                 it.keycode == KeyEvent.KEYCODE_UNKNOWN || supportedKeyCodes.contains(it.keycode)
             }
-            .mapNotNull { toShortcut(keyCharacterMap, it, keepIcons) }
+            .mapNotNull { toShortcut(keyGlyphMap, keyCharacterMap, it, keepIcons) }
 
     private fun toShortcut(
+        keyGlyphMap: KeyGlyphMap?,
         keyCharacterMap: KeyCharacterMap,
         shortcutInfo: KeyboardShortcutInfo,
         keepIcon: Boolean,
     ): Shortcut? {
-        val shortcutCommand = toShortcutCommand(keyCharacterMap, shortcutInfo) ?: return null
+        val shortcutCommand =
+            toShortcutCommand(keyGlyphMap, keyCharacterMap, shortcutInfo) ?: return null
         return Shortcut(
             label = shortcutInfo.label!!.toString(),
             icon = toShortcutIcon(keepIcon, shortcutInfo),
@@ -235,6 +245,7 @@
     }
 
     private fun toShortcutCommand(
+        keyGlyphMap: KeyGlyphMap?,
         keyCharacterMap: KeyCharacterMap,
         info: KeyboardShortcutInfo,
     ): ShortcutCommand? {
@@ -242,7 +253,7 @@
         var remainingModifiers = info.modifiers
         SUPPORTED_MODIFIERS.forEach { supportedModifier ->
             if ((supportedModifier and remainingModifiers) != 0) {
-                keys += toShortcutModifierKey(supportedModifier) ?: return null
+                keys += toShortcutModifierKey(keyGlyphMap, supportedModifier) ?: return null
                 // "Remove" the modifier from the remaining modifiers
                 remainingModifiers = remainingModifiers and supportedModifier.inv()
             }
@@ -253,7 +264,9 @@
             return null
         }
         if (info.keycode != 0 || info.baseCharacter > Char.MIN_VALUE) {
-            keys += toShortcutKey(keyCharacterMap, info.keycode, info.baseCharacter) ?: return null
+            keys +=
+                toShortcutKey(keyGlyphMap, keyCharacterMap, info.keycode, info.baseCharacter)
+                    ?: return null
         }
         if (keys.isEmpty()) {
             Log.wtf(TAG, "No keys for $info")
@@ -262,10 +275,15 @@
         return ShortcutCommand(keys)
     }
 
-    private fun toShortcutModifierKey(modifierMask: Int): ShortcutKey? {
+    private fun toShortcutModifierKey(keyGlyphMap: KeyGlyphMap?, modifierMask: Int): ShortcutKey? {
+        val modifierDrawable = keyGlyphMap?.getDrawableForModifierState(context, modifierMask)
+        if (modifierDrawable != null) {
+            return ShortcutKey.Icon.DrawableIcon(drawable = modifierDrawable)
+        }
+
         val iconResId = ShortcutHelperKeys.keyIcons[modifierMask]
         if (iconResId != null) {
-            return ShortcutKey.Icon(iconResId)
+            return ShortcutKey.Icon.ResIdIcon(iconResId)
         }
 
         val modifierLabel = ShortcutHelperKeys.modifierLabels[modifierMask]
@@ -277,13 +295,19 @@
     }
 
     private fun toShortcutKey(
+        keyGlyphMap: KeyGlyphMap?,
         keyCharacterMap: KeyCharacterMap,
         keyCode: Int,
         baseCharacter: Char = Char.MIN_VALUE,
     ): ShortcutKey? {
+        val keycodeDrawable = keyGlyphMap?.getDrawableForKeycode(context, keyCode)
+        if (keycodeDrawable != null) {
+            return ShortcutKey.Icon.DrawableIcon(drawable = keycodeDrawable)
+        }
+
         val iconResId = ShortcutHelperKeys.keyIcons[keyCode]
         if (iconResId != null) {
-            return ShortcutKey.Icon(iconResId)
+            return ShortcutKey.Icon.ResIdIcon(iconResId)
         }
         if (baseCharacter > Char.MIN_VALUE) {
             return ShortcutKey.Text(baseCharacter.uppercase())
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt
index e5b8096..28451ae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt
@@ -28,7 +28,7 @@
     }
 
     fun key(@DrawableRes drawableResId: Int) {
-        keys += ShortcutKey.Icon(drawableResId)
+        keys += ShortcutKey.Icon.ResIdIcon(drawableResId)
     }
 
     fun build() = ShortcutCommand(keys)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutKey.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutKey.kt
index 1abb78c..1a609ea 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutKey.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutKey.kt
@@ -16,10 +16,15 @@
 
 package com.android.systemui.keyboard.shortcut.shared.model
 
+import android.graphics.drawable.Drawable
 import androidx.annotation.DrawableRes
 
 sealed interface ShortcutKey {
     data class Text(val value: String) : ShortcutKey
 
-    data class Icon(@DrawableRes val drawableResId: Int) : ShortcutKey
+    sealed interface Icon : ShortcutKey {
+        data class ResIdIcon(@DrawableRes val drawableResId: Int) : Icon
+
+        data class DrawableIcon(val drawable: Drawable) : Icon
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index d537056..abddc70 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -647,7 +647,11 @@
 @Composable
 private fun BoxScope.ShortcutIconKey(key: ShortcutKey.Icon) {
     Icon(
-        painter = painterResource(key.drawableResId),
+        painter =
+            when (key) {
+                is ShortcutKey.Icon.ResIdIcon -> painterResource(key.drawableResId)
+                is ShortcutKey.Icon.DrawableIcon -> rememberDrawablePainter(drawable = key.drawable)
+            },
         contentDescription = null,
         modifier = Modifier.align(Alignment.Center).padding(6.dp),
     )
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
index 89cdd25..585f7ed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
@@ -33,13 +33,13 @@
 import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
 import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
 import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
+import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
-import javax.inject.Inject
 
 interface StickyKeysRepository {
     val stickyKeys: Flow<LinkedHashMap<ModifierKey, Locked>>
@@ -71,7 +71,7 @@
 
     override val settingEnabled: Flow<Boolean> =
         secureSettingsRepository
-            .boolSettingForActiveUser(SETTING_KEY, defaultValue = false)
+            .boolSetting(SETTING_KEY, defaultValue = false)
             .onEach { stickyKeysLogger.logNewSettingValue(it) }
             .flowOn(backgroundDispatcher)
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/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/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
index 032af94..2914cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -44,7 +44,7 @@
     private val keyguardStateController: KeyguardStateController,
     private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier,
     private val keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor,
-    private val keyguardTransitions: KeyguardTransitions
+    private val keyguardTransitions: KeyguardTransitions,
 ) {
 
     /**
@@ -108,27 +108,28 @@
      * Manager to effect the change.
      */
     fun setSurfaceBehindVisibility(visible: Boolean) {
-        if (isKeyguardGoingAway == visible) {
-            Log.d(TAG, "WmLockscreenVisibilityManager#setVisibility -> already visible=$visible")
+        if (isKeyguardGoingAway && visible) {
+            Log.d(TAG, "#setSurfaceBehindVisibility: already visible, ignoring")
             return
         }
 
         // The surface behind is always visible if the lockscreen is not showing, so we're already
         // visible.
         if (visible && isLockscreenShowing != true) {
-            Log.d(TAG, "#setVisibility -> already visible since the lockscreen isn't showing")
+            Log.d(TAG, "#setSurfaceBehindVisibility: ignoring since the lockscreen isn't showing")
             return
         }
 
-
-
         if (visible) {
             if (enableNewKeyguardShellTransitions) {
-                keyguardTransitions.startKeyguardTransition(false /* keyguardShowing */, false /* aodShowing */)
+                keyguardTransitions.startKeyguardTransition(
+                    false /* keyguardShowing */,
+                    false, /* aodShowing */
+                )
                 isKeyguardGoingAway = true
                 return
             }
-            // Make the surface visible behind the keyguard by calling keyguardGoingAway. The
+            // Make the surface behind the keyguard visible by calling keyguardGoingAway. The
             // lockscreen is still showing as well, allowing us to animate unlocked.
             Log.d(TAG, "ActivityTaskManagerService#keyguardGoingAway()")
             activityTaskManagerService.keyguardGoingAway(0)
@@ -153,7 +154,7 @@
         apps: Array<RemoteAnimationTarget>,
         wallpapers: Array<RemoteAnimationTarget>,
         nonApps: Array<RemoteAnimationTarget>,
-        finishedCallback: IRemoteAnimationFinishedCallback
+        finishedCallback: IRemoteAnimationFinishedCallback,
     ) {
         // Ensure that we've started a dismiss keyguard transition. WindowManager can start the
         // going away animation on its own, if an activity launches and then requests dismissing the
@@ -203,27 +204,25 @@
      */
     private fun setWmLockscreenState(
         lockscreenShowing: Boolean? = this.isLockscreenShowing,
-        aodVisible: Boolean = this.isAodVisible
+        aodVisible: Boolean = this.isAodVisible,
     ) {
-        Log.d(
-            TAG,
-            "#setWmLockscreenState(" +
-                "isLockscreenShowing=$lockscreenShowing, " +
-                "aodVisible=$aodVisible)."
-        )
-
         if (lockscreenShowing == null) {
             Log.d(
                 TAG,
                 "isAodVisible=$aodVisible, but lockscreenShowing=null. Waiting for" +
                     "non-null lockscreenShowing before calling ATMS#setLockScreenShown, which" +
-                    "will happen once KeyguardTransitionBootInteractor starts the boot transition."
+                    "will happen once KeyguardTransitionBootInteractor starts the boot transition.",
             )
             this.isAodVisible = aodVisible
             return
         }
 
         if (this.isLockscreenShowing == lockscreenShowing && this.isAodVisible == aodVisible) {
+            Log.d(
+                TAG,
+                "#setWmLockscreenState: lockscreenShowing=$lockscreenShowing and " +
+                    "isAodVisible=$aodVisible were both unchanged, not forwarding to ATMS.",
+            )
             return
         }
 
@@ -231,7 +230,7 @@
             TAG,
             "ATMS#setLockScreenShown(" +
                 "isLockscreenShowing=$lockscreenShowing, " +
-                "aodVisible=$aodVisible)."
+                "aodVisible=$aodVisible).",
         )
         if (enableNewKeyguardShellTransitions) {
             keyguardTransitions.startKeyguardTransition(lockscreenShowing, aodVisible)
@@ -247,7 +246,7 @@
             Log.d(
                 TAG,
                 "#endKeyguardGoingAwayAnimation() called when isKeyguardGoingAway=false. " +
-                    "Short-circuiting."
+                    "Short-circuiting.",
             )
             return
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/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 a1f6067..f473a82 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -18,7 +18,8 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.ObservableTransitionState.Idle
+import com.android.compose.animation.scene.ObservableTransitionState.Transition
 import com.android.systemui.Flags.transitionRaceCondition
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
@@ -30,6 +31,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
@@ -110,11 +112,8 @@
             }
             .distinctUntilChanged()
 
-    private val isDeviceEntered: Flow<Boolean> by lazy {
-        deviceEntryInteractor.get().isDeviceEntered
-    }
-
-    private val isDeviceNotEntered: Flow<Boolean> by lazy { isDeviceEntered.map { !it } }
+    private val isDeviceEntered by lazy { deviceEntryInteractor.get().isDeviceEntered }
+    private val isDeviceNotEntered by lazy { isDeviceEntered.map { !it } }
 
     /**
      * Surface visibility, which is either determined by the default visibility when not
@@ -124,32 +123,17 @@
     @OptIn(ExperimentalCoroutinesApi::class)
     val surfaceBehindVisibility: Flow<Boolean> =
         if (SceneContainerFlag.isEnabled) {
-                sceneInteractor.get().transitionState.flatMapLatestConflated { transitionState ->
-                    when (transitionState) {
-                        is ObservableTransitionState.Transition ->
-                            when {
-                                transitionState.fromContent == Scenes.Lockscreen &&
-                                    transitionState.toContent == Scenes.Gone ->
-                                    sceneInteractor
-                                        .get()
-                                        .isTransitionUserInputOngoing
-                                        .flatMapLatestConflated { isUserInputOngoing ->
-                                            if (isUserInputOngoing) {
-                                                isDeviceEntered
-                                            } else {
-                                                flowOf(true)
-                                            }
-                                        }
-                                transitionState.fromContent == Scenes.Bouncer &&
-                                    transitionState.toContent == Scenes.Gone ->
-                                    transitionState.progress.map { progress ->
-                                        progress >
-                                            FromPrimaryBouncerTransitionInteractor
-                                                .TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD
-                                    }
-                                else -> isDeviceEntered
+                sceneInteractor.get().transitionState.flatMapLatestConflated { state ->
+                    when {
+                        state.isTransitioning(from = Scenes.Lockscreen, to = Scenes.Gone) ->
+                            isDeviceEntered
+                        state.isTransitioning(from = Scenes.Bouncer, to = Scenes.Gone) ->
+                            (state as Transition).progress.map { progress ->
+                                progress >
+                                    FromPrimaryBouncerTransitionInteractor
+                                        .TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD
                             }
-                        is ObservableTransitionState.Idle -> isDeviceEntered
+                        else -> lockscreenVisibilityWithScenes.map { !it }
                     }
                 }
             } else {
@@ -219,6 +203,123 @@
         }
 
     /**
+     * Scenes that are part of the keyguard and are shown when the device is locked or when the
+     * keyguard still needs to be dismissed.
+     */
+    private val keyguardScenes = setOf(Scenes.Lockscreen, Scenes.Bouncer, Scenes.Communal)
+
+    /**
+     * Scenes that don't belong in the keyguard family and cannot show when the device is locked or
+     * when the keyguard still needs to be dismissed.
+     */
+    private val nonKeyguardScenes = setOf(Scenes.Gone)
+
+    /**
+     * Scenes that can show regardless of device lock or keyguard dismissal states. Other sources of
+     * state need to be consulted to know whether the device has been entered or not.
+     */
+    private val keyguardAgnosticScenes =
+        setOf(
+            Scenes.Shade,
+            Scenes.QuickSettings,
+            Overlays.NotificationsShade,
+            Overlays.QuickSettingsShade,
+        )
+
+    private val lockscreenVisibilityWithScenes =
+        combine(
+                sceneInteractor.get().transitionState.flatMapLatestConflated {
+                    when (it) {
+                        is Idle -> {
+                            when (it.currentScene) {
+                                in keyguardScenes -> flowOf(true)
+                                in nonKeyguardScenes -> flowOf(false)
+                                in keyguardAgnosticScenes -> isDeviceNotEntered
+                                else ->
+                                    throw IllegalStateException("Unknown scene: ${it.currentScene}")
+                            }
+                        }
+                        is Transition -> {
+                            when {
+                                it.isTransitioningSets(from = keyguardScenes) -> flowOf(true)
+                                it.isTransitioningSets(from = nonKeyguardScenes) -> flowOf(false)
+                                it.isTransitioningSets(from = keyguardAgnosticScenes) ->
+                                    isDeviceNotEntered
+                                else ->
+                                    throw IllegalStateException("Unknown scene: ${it.fromContent}")
+                            }
+                        }
+                    }
+                },
+                wakeToGoneInteractor.canWakeDirectlyToGone,
+                ::Pair,
+            )
+            .map { (lockscreenVisibilityByTransitionState, canWakeDirectlyToGone) ->
+                lockscreenVisibilityByTransitionState && !canWakeDirectlyToGone
+            }
+
+    private val lockscreenVisibilityLegacy =
+        combine(
+                transitionInteractor.currentKeyguardState,
+                wakeToGoneInteractor.canWakeDirectlyToGone,
+                ::Pair,
+            )
+            .sample(transitionInteractor.startedStepWithPrecedingStep, ::toTriple)
+            .map { (currentState, canWakeDirectlyToGone, startedWithPrev) ->
+                val startedFromStep = startedWithPrev.previousValue
+                val startedStep = startedWithPrev.newValue
+                val returningToGoneAfterCancellation =
+                    startedStep.to == KeyguardState.GONE &&
+                        startedFromStep.transitionState == TransitionState.CANCELED &&
+                        startedFromStep.from == KeyguardState.GONE
+
+                val transitionInfo =
+                    if (transitionRaceCondition()) {
+                        transitionRepository.currentTransitionInfo
+                    } else {
+                        transitionRepository.currentTransitionInfoInternal.value
+                    }
+                val wakingDirectlyToGone =
+                    deviceIsAsleepInState(transitionInfo.from) &&
+                        transitionInfo.to == KeyguardState.GONE
+
+                if (returningToGoneAfterCancellation || wakingDirectlyToGone) {
+                    // GONE -> AOD/DOZING (cancel) -> GONE is the camera launch transition,
+                    // which means we never want to show the lockscreen throughout the
+                    // transition. Same for waking directly to gone, due to the lockscreen being
+                    // disabled or because the device was woken back up before the lock timeout
+                    // duration elapsed.
+                    false
+                } else if (canWakeDirectlyToGone) {
+                    // Never show the lockscreen if we can wake directly to GONE. This means
+                    // that the lock timeout has not yet elapsed, or the keyguard is disabled.
+                    // In either case, we don't show the activity lock screen until one of those
+                    // conditions changes.
+                    false
+                } else if (
+                    currentState == KeyguardState.DREAMING &&
+                        deviceEntryInteractor.get().isUnlocked.value
+                ) {
+                    // Dreams dismiss keyguard and return to GONE if they can.
+                    false
+                } else if (
+                    startedWithPrev.newValue.from == KeyguardState.OCCLUDED &&
+                        startedWithPrev.newValue.to == KeyguardState.GONE
+                ) {
+                    // OCCLUDED -> GONE directly, without transiting a *_BOUNCER state, occurs
+                    // when an app uses intent flags to launch over an insecure keyguard without
+                    // dismissing it, and then manually requests keyguard dismissal while
+                    // OCCLUDED. This transition is not user-visible; the device unlocks in the
+                    // background and the app remains on top, while we're now GONE. In this case
+                    // we should simply tell WM that the lockscreen is no longer visible, and
+                    // *not* play the going away animation or related animations.
+                    false
+                } else {
+                    currentState != KeyguardState.GONE
+                }
+            }
+
+    /**
      * Whether the lockscreen is visible, from the Window Manager (WM) perspective.
      *
      * Note: This may briefly be true even if the lockscreen UI has animated out (alpha = 0f), as we
@@ -227,69 +328,11 @@
      */
     val lockscreenVisibility: Flow<Boolean> =
         if (SceneContainerFlag.isEnabled) {
-            isDeviceNotEntered
-        } else {
-            combine(
-                    transitionInteractor.currentKeyguardState,
-                    wakeToGoneInteractor.canWakeDirectlyToGone,
-                    ::Pair,
-                )
-                .sample(transitionInteractor.startedStepWithPrecedingStep, ::toTriple)
-                .map { (currentState, canWakeDirectlyToGone, startedWithPrev) ->
-                    val startedFromStep = startedWithPrev.previousValue
-                    val startedStep = startedWithPrev.newValue
-                    val returningToGoneAfterCancellation =
-                        startedStep.to == KeyguardState.GONE &&
-                            startedFromStep.transitionState == TransitionState.CANCELED &&
-                            startedFromStep.from == KeyguardState.GONE
-
-                    val transitionInfo =
-                        if (transitionRaceCondition()) {
-                            transitionRepository.currentTransitionInfo
-                        } else {
-                            transitionRepository.currentTransitionInfoInternal.value
-                        }
-                    val wakingDirectlyToGone =
-                        deviceIsAsleepInState(transitionInfo.from) &&
-                            transitionInfo.to == KeyguardState.GONE
-
-                    if (returningToGoneAfterCancellation || wakingDirectlyToGone) {
-                        // GONE -> AOD/DOZING (cancel) -> GONE is the camera launch transition,
-                        // which means we never want to show the lockscreen throughout the
-                        // transition. Same for waking directly to gone, due to the lockscreen being
-                        // disabled or because the device was woken back up before the lock timeout
-                        // duration elapsed.
-                        false
-                    } else if (canWakeDirectlyToGone) {
-                        // Never show the lockscreen if we can wake directly to GONE. This means
-                        // that the lock timeout has not yet elapsed, or the keyguard is disabled.
-                        // In either case, we don't show the activity lock screen until one of those
-                        // conditions changes.
-                        false
-                    } else if (
-                        currentState == KeyguardState.DREAMING &&
-                            deviceEntryInteractor.get().isUnlocked.value
-                    ) {
-                        // Dreams dismiss keyguard and return to GONE if they can.
-                        false
-                    } else if (
-                        startedWithPrev.newValue.from == KeyguardState.OCCLUDED &&
-                            startedWithPrev.newValue.to == KeyguardState.GONE
-                    ) {
-                        // OCCLUDED -> GONE directly, without transiting a *_BOUNCER state, occurs
-                        // when an app uses intent flags to launch over an insecure keyguard without
-                        // dismissing it, and then manually requests keyguard dismissal while
-                        // OCCLUDED. This transition is not user-visible; the device unlocks in the
-                        // background and the app remains on top, while we're now GONE. In this case
-                        // we should simply tell WM that the lockscreen is no longer visible, and
-                        // *not* play the going away animation or related animations.
-                        false
-                    } else {
-                        currentState != KeyguardState.GONE
-                    }
-                }
-                .distinctUntilChanged()
-        }
+                lockscreenVisibilityWithScenes
+            } else {
+                lockscreenVisibilityLegacy
+            }
+            .distinctUntilChanged()
 
     /**
      * Whether always-on-display (AOD) is visible when the lockscreen is visible, from window
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index be4bc23..6985615 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -182,9 +182,11 @@
             fgIconView.repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.STARTED) {
                     // Start with an empty state
+                    Log.d(TAG, "Initializing device entry fgIconView")
                     fgIconView.setImageState(StateSet.NOTHING, /* merge */ false)
                     launch("$TAG#fpIconView.viewModel") {
                         fgViewModel.viewModel.collect { viewModel ->
+                            Log.d(TAG, "Updating device entry icon image state $viewModel")
                             fgIconView.setImageState(
                                 view.getIconState(viewModel.type, viewModel.useAodVariant),
                                 /* merge */ false,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/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 67b009e..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 =
@@ -54,9 +55,7 @@
                 duration = TO_LOCKSCREEN_DURATION,
                 edge = Edge.create(from = Scenes.Communal, to = LOCKSCREEN),
             )
-            .setupWithoutSceneContainer(
-                edge = Edge.create(from = GLANCEABLE_HUB, to = LOCKSCREEN),
-            )
+            .setupWithoutSceneContainer(edge = Edge.create(from = GLANCEABLE_HUB, to = LOCKSCREEN))
 
     val keyguardAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
@@ -75,7 +74,7 @@
         configurationInteractor
             .directionalDimensionPixelSize(
                 LayoutDirection.LTR,
-                R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x
+                R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x,
             )
             .flatMapLatest { translatePx: Int ->
                 transitionAnimation.sharedFlowWithState(
@@ -87,7 +86,7 @@
                     // is cancelled.
                     onFinish = { 0f },
                     onCancel = { 0f },
-                    name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardTranslationX"
+                    name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardTranslationX",
                 )
             }
 
@@ -95,6 +94,8 @@
 
     val shortcutsAlpha: Flow<Float> = keyguardAlpha
 
+    val statusBarAlpha: Flow<Float> = keyguardAlpha
+
     val notificationTranslationX: Flow<Float> =
         keyguardTranslationX.map { it.value }.filterNotNull()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/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 378374e..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 =
@@ -54,9 +55,7 @@
                 duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
                 edge = Edge.create(from = LOCKSCREEN, to = Scenes.Communal),
             )
-            .setupWithoutSceneContainer(
-                edge = Edge.create(from = LOCKSCREEN, to = GLANCEABLE_HUB),
-            )
+            .setupWithoutSceneContainer(edge = Edge.create(from = LOCKSCREEN, to = GLANCEABLE_HUB))
 
     val keyguardAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
@@ -74,7 +73,7 @@
         configurationInteractor
             .directionalDimensionPixelSize(
                 LayoutDirection.LTR,
-                R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x
+                R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x,
             )
             .flatMapLatest { translatePx: Int ->
                 transitionAnimation.sharedFlowWithState(
@@ -86,7 +85,7 @@
                     onFinish = { 0f },
                     onCancel = { 0f },
                     interpolator = EMPHASIZED,
-                    name = "LOCKSCREEN->GLANCEABLE_HUB: keyguardTranslationX"
+                    name = "LOCKSCREEN->GLANCEABLE_HUB: keyguardTranslationX",
                 )
             }
 
@@ -94,6 +93,8 @@
 
     val shortcutsAlpha: Flow<Float> = keyguardAlpha
 
+    val statusBarAlpha: Flow<Float> = keyguardAlpha
+
     val notificationTranslationX: Flow<Float> =
         keyguardTranslationX.map { it.value }.filterNotNull()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/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
new file mode 100644
index 0000000..d38e507
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline
+
+import android.content.Context
+import android.graphics.drawable.Animatable
+import android.graphics.drawable.Drawable
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import androidx.annotation.WorkerThread
+import androidx.media.utils.MediaConstants
+import androidx.media3.common.Player
+import androidx.media3.session.CommandButton
+import androidx.media3.session.MediaController as Media3Controller
+import androidx.media3.session.SessionCommand
+import androidx.media3.session.SessionToken
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.graphics.ImageLoader
+import com.android.systemui.media.controls.shared.MediaControlDrawables
+import com.android.systemui.media.controls.shared.MediaLogger
+import com.android.systemui.media.controls.shared.model.MediaAction
+import com.android.systemui.media.controls.shared.model.MediaButton
+import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.SessionTokenFactory
+import com.android.systemui.res.R
+import com.android.systemui.util.Assert
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+private const val TAG = "Media3ActionFactory"
+
+@SysUISingleton
+class Media3ActionFactory
+@Inject
+constructor(
+    @Application val context: Context,
+    private val imageLoader: ImageLoader,
+    private val controllerFactory: MediaControllerFactory,
+    private val tokenFactory: SessionTokenFactory,
+    private val logger: MediaLogger,
+    @Background private val looper: Looper,
+    @Background private val handler: Handler,
+    @Background private val bgScope: CoroutineScope,
+) {
+
+    /**
+     * Generates action button info for this media session based on the Media3 session info
+     *
+     * @param packageName Package name for the media app
+     * @param controller The framework [MediaController] for the session
+     * @return The media action buttons, or null if the session token is null
+     */
+    suspend fun createActionsFromSession(
+        packageName: String,
+        sessionToken: MediaSession.Token,
+    ): MediaButton? {
+        // Get the Media3 controller using the legacy token
+        val token = tokenFactory.createTokenFromLegacy(sessionToken)
+        val m3controller = controllerFactory.create(token, looper)
+
+        // Build button info
+        val buttons = suspendCancellableCoroutine { continuation ->
+            // Media3Controller methods must always be called from a specific looper
+            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
+    }
+
+    /** This method must be called on the Media3 looper! */
+    @WorkerThread
+    private fun getMedia3Actions(
+        packageName: String,
+        m3controller: Media3Controller,
+        token: SessionToken,
+    ): MediaButton? {
+        Assert.isNotMainThread()
+
+        // First, get standard actions
+        val playOrPause =
+            if (m3controller.playbackState == Player.STATE_BUFFERING) {
+                // Spinner needs to be animating to render anything. Start it here.
+                val drawable =
+                    context.getDrawable(com.android.internal.R.drawable.progress_small_material)
+                (drawable as Animatable).start()
+                MediaAction(
+                    drawable,
+                    null, // no action to perform when clicked
+                    context.getString(R.string.controls_media_button_connecting),
+                    context.getDrawable(R.drawable.ic_media_connecting_container),
+                    // Specify a rebind id to prevent the spinner from restarting on later binds.
+                    com.android.internal.R.drawable.progress_small_material,
+                )
+            } else {
+                getStandardAction(m3controller, token, Player.COMMAND_PLAY_PAUSE)
+            }
+
+        val prevButton =
+            getStandardAction(
+                m3controller,
+                token,
+                Player.COMMAND_SEEK_TO_PREVIOUS,
+                Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM,
+            )
+        val nextButton =
+            getStandardAction(
+                m3controller,
+                token,
+                Player.COMMAND_SEEK_TO_NEXT,
+                Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM,
+            )
+
+        // Then, get custom actions
+        var customActions =
+            m3controller.customLayout
+                .asSequence()
+                .filter {
+                    it.isEnabled &&
+                        it.sessionCommand?.commandCode == SessionCommand.COMMAND_CODE_CUSTOM &&
+                        m3controller.isSessionCommandAvailable(it.sessionCommand!!)
+                }
+                .map { getCustomAction(packageName, token, it) }
+                .iterator()
+        fun nextCustomAction() = if (customActions.hasNext()) customActions.next() else null
+
+        // Finally, assign the remaining button slots: play/pause A B C D
+        // A = previous, else custom action (if not reserved)
+        // B = next, else custom action (if not reserved)
+        // C and D are always custom actions
+        val reservePrev =
+            m3controller.sessionExtras.getBoolean(
+                MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV,
+                false,
+            )
+        val reserveNext =
+            m3controller.sessionExtras.getBoolean(
+                MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT,
+                false,
+            )
+
+        val prevOrCustom =
+            prevButton
+                ?: if (reservePrev) {
+                    null
+                } else {
+                    nextCustomAction()
+                }
+
+        val nextOrCustom =
+            nextButton
+                ?: if (reserveNext) {
+                    null
+                } else {
+                    nextCustomAction()
+                }
+
+        return MediaButton(
+            playOrPause = playOrPause,
+            nextOrCustom = nextOrCustom,
+            prevOrCustom = prevOrCustom,
+            custom0 = nextCustomAction(),
+            custom1 = nextCustomAction(),
+            reserveNext = reserveNext,
+            reservePrev = reservePrev,
+        )
+    }
+
+    /**
+     * Create a [MediaAction] for a given command, if supported
+     *
+     * @param controller Media3 controller for the session
+     * @param commands Commands to check, in priority order
+     * @return A [MediaAction] representing the first supported command, or null if not supported
+     */
+    private fun getStandardAction(
+        controller: Media3Controller,
+        token: SessionToken,
+        vararg commands: @Player.Command Int,
+    ): MediaAction? {
+        for (command in commands) {
+            if (!controller.isCommandAvailable(command)) {
+                continue
+            }
+
+            return when (command) {
+                Player.COMMAND_PLAY_PAUSE -> {
+                    if (!controller.isPlaying) {
+                        MediaAction(
+                            context.getDrawable(R.drawable.ic_media_play),
+                            { executeAction(token, Player.COMMAND_PLAY_PAUSE) },
+                            context.getString(R.string.controls_media_button_play),
+                            context.getDrawable(R.drawable.ic_media_play_container),
+                        )
+                    } else {
+                        MediaAction(
+                            context.getDrawable(R.drawable.ic_media_pause),
+                            { executeAction(token, Player.COMMAND_PLAY_PAUSE) },
+                            context.getString(R.string.controls_media_button_pause),
+                            context.getDrawable(R.drawable.ic_media_pause_container),
+                        )
+                    }
+                }
+                else -> {
+                    MediaAction(
+                        icon = getIconForAction(command),
+                        action = { executeAction(token, command) },
+                        contentDescription = getDescriptionForAction(command),
+                        background = null,
+                    )
+                }
+            }
+        }
+        return null
+    }
+
+    /** Get a [MediaAction] representing a [CommandButton] */
+    private fun getCustomAction(
+        packageName: String,
+        token: SessionToken,
+        customAction: CommandButton,
+    ): MediaAction {
+        return MediaAction(
+            getIconForAction(customAction, packageName),
+            { executeAction(token, Player.COMMAND_INVALID, customAction) },
+            customAction.displayName,
+            null,
+        )
+    }
+
+    private fun getIconForAction(command: @Player.Command Int): Drawable? {
+        return when (command) {
+            Player.COMMAND_SEEK_TO_PREVIOUS -> MediaControlDrawables.getPrevIcon(context)
+            Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM -> MediaControlDrawables.getPrevIcon(context)
+            Player.COMMAND_SEEK_TO_NEXT -> MediaControlDrawables.getNextIcon(context)
+            Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM -> MediaControlDrawables.getNextIcon(context)
+            else -> {
+                Log.e(TAG, "Unknown icon for $command")
+                null
+            }
+        }
+    }
+
+    private fun getIconForAction(customAction: CommandButton, packageName: String): Drawable? {
+        val size = context.resources.getDimensionPixelSize(R.dimen.min_clickable_item_size)
+        // TODO(b/360196209): check customAction.icon field to use platform icons
+        if (customAction.iconResId != 0) {
+            val packageContext = context.createPackageContext(packageName, 0)
+            val source = ImageLoader.Res(customAction.iconResId, packageContext)
+            return runBlocking { imageLoader.loadDrawable(source, size, size) }
+        }
+
+        if (customAction.iconUri != null) {
+            val source = ImageLoader.Uri(customAction.iconUri!!)
+            return runBlocking { imageLoader.loadDrawable(source, size, size) }
+        }
+        return null
+    }
+
+    private fun getDescriptionForAction(command: @Player.Command Int): String? {
+        return when (command) {
+            Player.COMMAND_SEEK_TO_PREVIOUS ->
+                context.getString(R.string.controls_media_button_prev)
+            Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM ->
+                context.getString(R.string.controls_media_button_prev)
+            Player.COMMAND_SEEK_TO_NEXT -> context.getString(R.string.controls_media_button_next)
+            Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM ->
+                context.getString(R.string.controls_media_button_next)
+            else -> {
+                Log.e(TAG, "Unknown content description for $command")
+                null
+            }
+        }
+    }
+
+    private fun executeAction(
+        token: SessionToken,
+        command: Int,
+        customAction: CommandButton? = null,
+    ) {
+        bgScope.launch {
+            val controller = controllerFactory.create(token, looper)
+            handler.post {
+                try {
+                    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?.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()
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
index 591a9cc..a176e0c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
@@ -84,6 +84,7 @@
     private val mediaFlags: MediaFlags,
     private val imageLoader: ImageLoader,
     private val statusBarManager: StatusBarManager,
+    private val media3ActionFactory: Media3ActionFactory,
 ) {
     private val mediaProcessingJobs = ConcurrentHashMap<String, Job>()
 
@@ -364,7 +365,7 @@
             )
         }
 
-    private fun createActionsFromState(
+    private suspend fun createActionsFromState(
         packageName: String,
         controller: MediaController,
         user: UserHandle,
@@ -373,6 +374,12 @@
             return null
         }
 
+        if (mediaFlags.areMedia3ActionsEnabled(packageName, user)) {
+            return media3ActionFactory.createActionsFromSession(
+                packageName,
+                controller.sessionToken,
+            )
+        }
         return createActionsFromState(context, packageName, controller)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt
index 2bdee67..beb4d41 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt
@@ -141,6 +141,7 @@
     new: MediaData,
     old: MediaData,
 ): Boolean {
+    // TODO(b/360196209): account for actions generated from media3
     val oldState = MediaController(context, old.token!!).playbackState
     return if (
         new.semanticActions == null &&
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
index 88c47ba..0b598c1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
@@ -140,6 +140,10 @@
         )
     }
 
+    fun logMedia3UnsupportedCommand(command: String) {
+        buffer.log(TAG, LogLevel.DEBUG, { str1 = command }, { "Unsupported media3 command $str1" })
+    }
+
     companion object {
         private const val TAG = "MediaLog"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java
deleted file mode 100644
index 6caf5c2..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media.controls.util;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-
-import javax.inject.Inject;
-
-/**
- * Testable wrapper around {@link MediaController} constructor.
- */
-public class MediaControllerFactory {
-
-    private final Context mContext;
-
-    @Inject
-    public MediaControllerFactory(Context context) {
-        mContext = context;
-    }
-
-    /**
-     * Creates a new MediaController from a session's token.
-     *
-     * @param token The token for the session. This value must never be null.
-     */
-    public MediaController create(@NonNull MediaSession.Token token) {
-        return new MediaController(mContext, token);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt
new file mode 100644
index 0000000..d815852
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.os.Looper
+import androidx.concurrent.futures.await
+import androidx.media3.session.MediaController as Media3Controller
+import androidx.media3.session.SessionToken
+import javax.inject.Inject
+
+/** Testable wrapper for media controller construction */
+open class MediaControllerFactory @Inject constructor(private val context: Context) {
+    /**
+     * Creates a new [MediaController] from the framework session token.
+     *
+     * @param token The token for the session. This value must never be null.
+     */
+    open fun create(token: MediaSession.Token): MediaController {
+        return MediaController(context, token)
+    }
+
+    /**
+     * Creates a new [Media3Controller] from 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)
+            .buildAsync()
+            .await()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index d4af1b5..ac60c47 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -18,9 +18,10 @@
 
 import android.app.StatusBarManager
 import android.os.UserHandle
+import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
+import com.android.systemui.flags.Flags as FlagsClassic
 import javax.inject.Inject
 
 @SysUISingleton
@@ -29,22 +30,29 @@
      * Check whether media control actions should be based on PlaybackState instead of notification
      */
     fun areMediaSessionActionsEnabled(packageName: String, user: UserHandle): Boolean {
-        // Allow global override with flag
         return StatusBarManager.useMediaSessionActionsForApp(packageName, user)
     }
 
+    /** Check whether media control actions should be derived from Media3 controller */
+    fun areMedia3ActionsEnabled(packageName: String, user: UserHandle): Boolean {
+        val compatFlag = StatusBarManager.useMedia3ControllerForApp(packageName, user)
+        val featureFlag = Flags.mediaControlsButtonMedia3()
+        return featureFlag && compatFlag
+    }
+
     /**
      * If true, keep active media controls for the lifetime of the MediaSession, regardless of
      * whether the underlying notification was dismissed
      */
-    fun isRetainingPlayersEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_SESSIONS)
+    fun isRetainingPlayersEnabled() = featureFlags.isEnabled(FlagsClassic.MEDIA_RETAIN_SESSIONS)
 
     /** Check whether to get progress information for resume players */
-    fun isResumeProgressEnabled() = featureFlags.isEnabled(Flags.MEDIA_RESUME_PROGRESS)
+    fun isResumeProgressEnabled() = featureFlags.isEnabled(FlagsClassic.MEDIA_RESUME_PROGRESS)
 
     /** If true, do not automatically dismiss the recommendation card */
-    fun isPersistentSsCardEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_RECOMMENDATIONS)
+    fun isPersistentSsCardEnabled() =
+        featureFlags.isEnabled(FlagsClassic.MEDIA_RETAIN_RECOMMENDATIONS)
 
     /** Check whether we allow remote media to generate resume controls */
-    fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME)
+    fun isRemoteResumeAllowed() = featureFlags.isEnabled(FlagsClassic.MEDIA_REMOTE_RESUME)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/SessionTokenFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/SessionTokenFactory.kt
new file mode 100644
index 0000000..b289fd4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/SessionTokenFactory.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaSession
+import androidx.concurrent.futures.await
+import androidx.media3.session.SessionToken
+import javax.inject.Inject
+
+/** Testable wrapper for [SessionToken] creation */
+open class SessionTokenFactory @Inject constructor(private val context: Context) {
+    /** Create a new [SessionToken] from the framework [MediaSession.Token] */
+    open suspend fun createTokenFromLegacy(token: MediaSession.Token): SessionToken {
+        return SessionToken.createSessionToken(context, token).await()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/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 c70cd0a..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;
@@ -131,7 +130,9 @@
 
         // This activity is launched directly by an app, or system server. System server provides
         // the package name through the intent if so.
-        if (mPackageName == null) {
+        if (mPackageName == null || (
+                com.android.systemui.Flags.mediaProjectionRequestAttributionFix()
+                        && getCallingPackage() == null)) {
             if (launchingIntent.hasExtra(EXTRA_PACKAGE_REUSING_GRANTED_CONSENT)) {
                 mPackageName = launchingIntent.getStringExtra(
                         EXTRA_PACKAGE_REUSING_GRANTED_CONSENT);
@@ -185,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/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
index 96c0cac..40613c0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
@@ -149,6 +149,7 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.data.repository.LightBarControllerStore;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -258,8 +259,7 @@
     private boolean mTransientShownFromGestureOnSystemBar;
     private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
     private LightBarController mLightBarController;
-    private final LightBarController mMainLightBarController;
-    private final LightBarController.Factory mLightBarControllerFactory;
+    private final LightBarControllerStore mLightBarControllerStore;
     private AutoHideController mAutoHideController;
     private final AutoHideController mMainAutoHideController;
     private final AutoHideController.Factory mAutoHideControllerFactory;
@@ -580,8 +580,7 @@
             @Background Executor bgExecutor,
             UiEventLogger uiEventLogger,
             NavBarHelper navBarHelper,
-            LightBarController mainLightBarController,
-            LightBarController.Factory lightBarControllerFactory,
+            LightBarControllerStore lightBarControllerStore,
             AutoHideController mainAutoHideController,
             AutoHideController.Factory autoHideControllerFactory,
             Optional<TelecomManager> telecomManagerOptional,
@@ -628,8 +627,7 @@
         mUiEventLogger = uiEventLogger;
         mNavBarHelper = navBarHelper;
         mNotificationShadeDepthController = notificationShadeDepthController;
-        mMainLightBarController = mainLightBarController;
-        mLightBarControllerFactory = lightBarControllerFactory;
+        mLightBarControllerStore = lightBarControllerStore;
         mMainAutoHideController = mainAutoHideController;
         mAutoHideControllerFactory = autoHideControllerFactory;
         mTelecomManagerOptional = telecomManagerOptional;
@@ -842,8 +840,7 @@
         // Unfortunately, we still need it because status bar needs LightBarController
         // before notifications creation. We cannot directly use getLightBarController()
         // from NavigationBarFragment directly.
-        LightBarController lightBarController = mIsOnDefaultDisplay
-                ? mMainLightBarController : mLightBarControllerFactory.create(mContext);
+        LightBarController lightBarController = mLightBarControllerStore.forDisplay(mDisplayId);
         setLightBarController(lightBarController);
 
         // TODO(b/118592525): to support multi-display, we start to add something which is
diff --git a/packages/SystemUI/src/com/android/systemui/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/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
index 1c9cb3d..fef5a74 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
@@ -25,6 +25,7 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.coroutineScope
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.internal.logging.MetricsLogger
 import com.android.systemui.animation.Expandable
 import com.android.systemui.dagger.qualifiers.Background
@@ -48,7 +49,6 @@
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
 import javax.inject.Inject
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.runBlocking
 
 class ModesTile
@@ -120,8 +120,7 @@
         tileState = tileMapper.map(config, model)
         state?.apply {
             this.state = tileState.activationState.legacyState
-            val tileStateIcon = tileState.icon()
-            icon = tileStateIcon?.asQSTileIcon() ?: ResourceIcon.get(ICON_RES_ID)
+            icon = tileState.icon?.asQSTileIcon() ?: ResourceIcon.get(ICON_RES_ID)
             label = tileLabel
             secondaryLabel = tileState.secondaryLabel
             contentDescription = tileState.contentDescription
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/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/airplane/domain/AirplaneModeMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
index 9fb1d46..d67057a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
@@ -30,10 +30,8 @@
 /** Maps [AirplaneModeTileModel] to [QSTileState]. */
 class AirplaneModeMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    val theme: Theme,
-) : QSTileDataToStateMapper<AirplaneModeTileModel> {
+constructor(@Main private val resources: Resources, val theme: Theme) :
+    QSTileDataToStateMapper<AirplaneModeTileModel> {
 
     override fun map(config: QSTileConfig, data: AirplaneModeTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
@@ -43,16 +41,7 @@
                 } else {
                     R.drawable.qs_airplane_icon_off
                 }
-
-            icon = {
-                Icon.Loaded(
-                    resources.getDrawable(
-                        iconRes!!,
-                        theme,
-                    ),
-                    contentDescription = null
-                )
-            }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
             if (data.isEnabled) {
                 activationState = QSTileState.ActivationState.ACTIVE
                 secondaryLabel = resources.getStringArray(R.array.tile_states_airplane)[2]
@@ -62,9 +51,6 @@
             }
             contentDescription = label
             supportedActions =
-                setOf(
-                    QSTileState.UserAction.CLICK,
-                    QSTileState.UserAction.LONG_CLICK,
-                )
+                setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
         }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
index f088943..7322b8d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
@@ -45,6 +45,7 @@
         val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E HH:mm")
         val formatterDateOnly: DateTimeFormatter = DateTimeFormatter.ofPattern("E MMM d")
     }
+
     override fun map(config: QSTileConfig, data: AlarmTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             when (data) {
@@ -54,13 +55,13 @@
                     val alarmDateTime =
                         LocalDateTime.ofInstant(
                             Instant.ofEpochMilli(data.alarmClockInfo.triggerTime),
-                            TimeZone.getDefault().toZoneId()
+                            TimeZone.getDefault().toZoneId(),
                         )
 
                     val nowDateTime =
                         LocalDateTime.ofInstant(
                             Instant.ofEpochMilli(clock.currentTimeMillis()),
-                            TimeZone.getDefault().toZoneId()
+                            TimeZone.getDefault().toZoneId(),
                         )
 
                     // Edge case: If it's 8:00:30 right now and alarm is requested for next week at
@@ -84,7 +85,7 @@
                 }
             }
             iconRes = R.drawable.ic_alarm
-            icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
             sideViewIcon = QSTileState.SideViewIcon.Chevron
             contentDescription = label
             supportedActions = setOf(QSTileState.UserAction.CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
index bcf0935..5b30e8d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
@@ -29,10 +29,8 @@
 /** Maps [BatterySaverTileModel] to [QSTileState]. */
 open class BatterySaverTileMapper
 @Inject
-constructor(
-    @Main protected val resources: Resources,
-    private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<BatterySaverTileModel> {
+constructor(@Main protected val resources: Resources, private val theme: Resources.Theme) :
+    QSTileDataToStateMapper<BatterySaverTileModel> {
 
     override fun map(config: QSTileConfig, data: BatterySaverTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
@@ -41,8 +39,7 @@
             iconRes =
                 if (data.isPowerSaving) R.drawable.qs_battery_saver_icon_on
                 else R.drawable.qs_battery_saver_icon_off
-            icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
-
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
             sideViewIcon = QSTileState.SideViewIcon.None
 
             if (data.isPluggedIn) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
index cad7c65..7c90b3d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.colorcorrection.domain
 
 import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
 import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel
@@ -28,17 +29,14 @@
 /** Maps [ColorCorrectionTileModel] to [QSTileState]. */
 class ColorCorrectionTileMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<ColorCorrectionTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+    QSTileDataToStateMapper<ColorCorrectionTileModel> {
 
     override fun map(config: QSTileConfig, data: ColorCorrectionTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             val subtitleArray = resources.getStringArray(R.array.tile_states_color_correction)
-
             iconRes = R.drawable.ic_qs_color_correction
-
+            icon = Icon.Loaded(resources.getDrawable(R.drawable.ic_qs_color_correction)!!, null)
             if (data.isEnabled) {
                 activationState = QSTileState.ActivationState.ACTIVE
                 secondaryLabel = subtitleArray[2]
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt
index 984228d..60aa4ea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt
@@ -35,10 +35,8 @@
 @SysUISingleton
 class CustomTileMapper
 @Inject
-constructor(
-    private val context: Context,
-    private val uriGrantsManager: IUriGrantsManager,
-) : QSTileDataToStateMapper<CustomTileDataModel> {
+constructor(private val context: Context, private val uriGrantsManager: IUriGrantsManager) :
+    QSTileDataToStateMapper<CustomTileDataModel> {
 
     override fun map(config: QSTileConfig, data: CustomTileDataModel): QSTileState {
         val userContext =
@@ -50,7 +48,7 @@
 
         val iconResult =
             if (userContext != null) {
-                getIconProvider(
+                getIcon(
                     userContext = userContext,
                     icon = data.tile.icon,
                     callingAppUid = data.callingAppUid,
@@ -58,16 +56,16 @@
                     defaultIcon = data.defaultTileIcon,
                 )
             } else {
-                IconResult({ null }, true)
+                IconResult(null, true)
             }
 
-        return QSTileState.build(iconResult.iconProvider, data.tile.label) {
+        return QSTileState.build(iconResult.icon, data.tile.label) {
             var tileState: Int = data.tile.state
             if (data.hasPendingBind) {
                 tileState = Tile.STATE_UNAVAILABLE
             }
 
-            icon = iconResult.iconProvider
+            icon = iconResult.icon
             activationState =
                 if (iconResult.failedToLoad) {
                     QSTileState.ActivationState.UNAVAILABLE
@@ -102,7 +100,7 @@
     }
 
     @SuppressLint("MissingPermission") // android.permission.INTERACT_ACROSS_USERS_FULL
-    private fun getIconProvider(
+    private fun getIcon(
         userContext: Context,
         icon: android.graphics.drawable.Icon?,
         callingAppUid: Int,
@@ -123,17 +121,12 @@
                 null
             } ?: defaultIcon?.loadDrawable(userContext)
         return IconResult(
-            {
-                drawable?.constantState?.newDrawable()?.let {
-                    Icon.Loaded(it, contentDescription = null)
-                }
+            drawable?.constantState?.newDrawable()?.let {
+                Icon.Loaded(it, contentDescription = null)
             },
             failedToLoad,
         )
     }
 
-    class IconResult(
-        val iconProvider: () -> Icon?,
-        val failedToLoad: Boolean,
-    )
+    class IconResult(val icon: Icon?, val failedToLoad: Boolean)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
index d7d6124..7e557eb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
@@ -30,10 +30,8 @@
 /** Maps [FlashlightTileModel] to [QSTileState]. */
 class FlashlightMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Theme,
-) : QSTileDataToStateMapper<FlashlightTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Theme) :
+    QSTileDataToStateMapper<FlashlightTileModel> {
 
     override fun map(config: QSTileConfig, data: FlashlightTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
@@ -43,15 +41,8 @@
                 } else {
                     R.drawable.qs_flashlight_icon_off
                 }
-            val icon =
-                Icon.Loaded(
-                    resources.getDrawable(
-                        iconRes!!,
-                        theme,
-                    ),
-                    contentDescription = null
-                )
-            this.icon = { icon }
+
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
 
             contentDescription = label
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
index 6b4dda1..9d44fc6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
@@ -29,23 +29,13 @@
 /** Maps [FontScalingTileModel] to [QSTileState]. */
 class FontScalingTileMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<FontScalingTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+    QSTileDataToStateMapper<FontScalingTileModel> {
 
     override fun map(config: QSTileConfig, data: FontScalingTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             iconRes = R.drawable.ic_qs_font_scaling
-            val icon =
-                Icon.Loaded(
-                    resources.getDrawable(
-                        iconRes!!,
-                        theme,
-                    ),
-                    contentDescription = null
-                )
-            this.icon = { icon }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
             contentDescription = label
             activationState = QSTileState.ActivationState.ACTIVE
             sideViewIcon = QSTileState.SideViewIcon.Chevron
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
index 8dd611f..c3ac1f8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
@@ -36,9 +36,7 @@
         QSTileState.build(resources, theme, config.uiConfig) {
             label = resources.getString(R.string.quick_settings_hearing_devices_label)
             iconRes = R.drawable.qs_hearing_devices_icon
-            val loadedIcon =
-                Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
-            icon = { loadedIcon }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
             sideViewIcon = QSTileState.SideViewIcon.Chevron
             contentDescription = label
             if (data.isAnyActiveHearingDevice) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
index bb0b9b7..fc94585 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
@@ -61,28 +61,26 @@
             when (val dataIcon = data.icon) {
                 is InternetTileIconModel.ResourceId -> {
                     iconRes = dataIcon.resId
-                    icon = {
+                    icon =
                         Icon.Loaded(
                             resources.getDrawable(dataIcon.resId, theme),
                             contentDescription = null,
                         )
-                    }
                 }
 
                 is InternetTileIconModel.Cellular -> {
                     val signalDrawable = SignalDrawable(context, handler)
                     signalDrawable.setLevel(dataIcon.level)
-                    icon = { Icon.Loaded(signalDrawable, contentDescription = null) }
+                    icon = Icon.Loaded(signalDrawable, contentDescription = null)
                 }
 
                 is InternetTileIconModel.Satellite -> {
                     iconRes = dataIcon.resourceIcon.res // level is inferred from res
-                    icon = {
+                    icon =
                         Icon.Loaded(
                             resources.getDrawable(dataIcon.resourceIcon.res, theme),
                             contentDescription = null,
                         )
-                    }
                 }
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
index 40aee65..3692c35 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
@@ -30,10 +30,8 @@
 /** Maps [ColorInversionTileModel] to [QSTileState]. */
 class ColorInversionTileMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Theme,
-) : QSTileDataToStateMapper<ColorInversionTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Theme) :
+    QSTileDataToStateMapper<ColorInversionTileModel> {
     override fun map(config: QSTileConfig, data: ColorInversionTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             val subtitleArray = resources.getStringArray(R.array.tile_states_inversion)
@@ -47,7 +45,7 @@
                 secondaryLabel = subtitleArray[1]
                 iconRes = R.drawable.qs_invert_colors_icon_off
             }
-            icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
             contentDescription = label
             supportedActions =
                 setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
index ff931b3..3fe2a77 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
@@ -28,21 +28,26 @@
 
 class IssueRecordingMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Theme,
-) : QSTileDataToStateMapper<IssueRecordingModel> {
+constructor(@Main private val resources: Resources, private val theme: Theme) :
+    QSTileDataToStateMapper<IssueRecordingModel> {
     override fun map(config: QSTileConfig, data: IssueRecordingModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
-            if (data.isRecording) {
-                activationState = QSTileState.ActivationState.ACTIVE
-                secondaryLabel = resources.getString(R.string.qs_record_issue_stop)
-                icon = { Icon.Resource(R.drawable.qs_record_issue_icon_on, null) }
-            } else {
-                icon = { Icon.Resource(R.drawable.qs_record_issue_icon_off, null) }
-                activationState = QSTileState.ActivationState.INACTIVE
-                secondaryLabel = resources.getString(R.string.qs_record_issue_start)
-            }
+            icon =
+                if (data.isRecording) {
+                    activationState = QSTileState.ActivationState.ACTIVE
+                    secondaryLabel = resources.getString(R.string.qs_record_issue_stop)
+                    Icon.Loaded(
+                        resources.getDrawable(R.drawable.qs_record_issue_icon_on, theme),
+                        null,
+                    )
+                } else {
+                    activationState = QSTileState.ActivationState.INACTIVE
+                    secondaryLabel = resources.getString(R.string.qs_record_issue_start)
+                    Icon.Loaded(
+                        resources.getDrawable(R.drawable.qs_record_issue_icon_off, theme),
+                        null,
+                    )
+                }
             supportedActions = setOf(QSTileState.UserAction.CLICK)
             contentDescription = "$label, $secondaryLabel"
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
index d58f5ab..08432f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
@@ -30,10 +30,8 @@
 /** Maps [LocationTileModel] to [QSTileState]. */
 class LocationTileMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Theme,
-) : QSTileDataToStateMapper<LocationTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Theme) :
+    QSTileDataToStateMapper<LocationTileModel> {
 
     override fun map(config: QSTileConfig, data: LocationTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
@@ -43,17 +41,9 @@
                 } else {
                     R.drawable.qs_location_icon_off
                 }
-            val icon =
-                Icon.Loaded(
-                    resources.getDrawable(
-                        iconRes!!,
-                        theme,
-                    ),
-                    contentDescription = null
-                )
-            this.icon = { icon }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
 
-            this.label = resources.getString(R.string.quick_settings_location_label)
+            label = resources.getString(R.string.quick_settings_location_label)
 
             if (data.isEnabled) {
                 activationState = QSTileState.ActivationState.ACTIVE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
index 69da313..4a64313 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -30,14 +30,12 @@
 
 class ModesTileMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    val theme: Resources.Theme,
-) : QSTileDataToStateMapper<ModesTileModel> {
+constructor(@Main private val resources: Resources, val theme: Resources.Theme) :
+    QSTileDataToStateMapper<ModesTileModel> {
     override fun map(config: QSTileConfig, data: ModesTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             iconRes = data.iconResId
-            icon = { data.icon }
+            icon = data.icon
             activationState =
                 if (data.isActivated) {
                     QSTileState.ActivationState.ACTIVE
@@ -47,10 +45,7 @@
             secondaryLabel = getModesStatus(data, resources)
             contentDescription = "$label. $secondaryLabel"
             supportedActions =
-                setOf(
-                    QSTileState.UserAction.CLICK,
-                    QSTileState.UserAction.LONG_CLICK,
-                )
+                setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
             sideViewIcon = QSTileState.SideViewIcon.Chevron
             expandedAccessibilityClass = Button::class
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
index bcf7cc7..081a03c7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
@@ -57,9 +57,8 @@
                 activationState = QSTileState.ActivationState.INACTIVE
                 iconRes = R.drawable.qs_nightlight_icon_off
             }
-            val loadedIcon =
-                Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
-            icon = { loadedIcon }
+
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
 
             secondaryLabel = getSecondaryLabel(data, resources)
 
@@ -70,7 +69,7 @@
 
     private fun getSecondaryLabel(
         data: NightDisplayTileModel,
-        resources: Resources
+        resources: Resources,
     ): CharSequence? {
         when (data) {
             is NightDisplayTileModel.AutoModeTwilight -> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/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/onehanded/ui/OneHandedModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
index 4080996..8e5d0d4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
@@ -29,17 +29,15 @@
 /** Maps [OneHandedModeTileModel] to [QSTileState]. */
 class OneHandedModeTileMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<OneHandedModeTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+    QSTileDataToStateMapper<OneHandedModeTileModel> {
 
     override fun map(config: QSTileConfig, data: OneHandedModeTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             val subtitleArray = resources.getStringArray(R.array.tile_states_onehanded)
             label = resources.getString(R.string.quick_settings_onehanded_label)
             iconRes = com.android.internal.R.drawable.ic_qs_one_handed_mode
-            icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
             if (data.isEnabled) {
                 activationState = QSTileState.ActivationState.ACTIVE
                 secondaryLabel = subtitleArray[2]
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
index 8231742..5c6351e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
@@ -29,17 +29,15 @@
 /** Maps [QRCodeScannerTileModel] to [QSTileState]. */
 class QRCodeScannerTileMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<QRCodeScannerTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+    QSTileDataToStateMapper<QRCodeScannerTileModel> {
 
     override fun map(config: QSTileConfig, data: QRCodeScannerTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             label = resources.getString(R.string.qr_code_scanner_title)
             contentDescription = label
             iconRes = R.drawable.ic_qr_code_scanner
-            icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
             sideViewIcon = QSTileState.SideViewIcon.Chevron
             supportedActions = setOf(QSTileState.UserAction.CLICK)
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
index 85ee022..fe77fe6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
@@ -30,10 +30,8 @@
 /** Maps [ReduceBrightColorsTileModel] to [QSTileState]. */
 class ReduceBrightColorsTileMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<ReduceBrightColorsTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+    QSTileDataToStateMapper<ReduceBrightColorsTileModel> {
 
     override fun map(config: QSTileConfig, data: ReduceBrightColorsTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
@@ -50,12 +48,7 @@
                     resources
                         .getStringArray(R.array.tile_states_reduce_brightness)[Tile.STATE_INACTIVE]
             }
-            icon = {
-                Icon.Loaded(
-                    drawable = resources.getDrawable(iconRes!!, theme),
-                    contentDescription = null
-                )
-            }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
             label =
                 resources.getString(com.android.internal.R.string.reduce_bright_colors_feature_name)
             contentDescription = label
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
index 33dc6ed..9a003ff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
@@ -36,36 +36,33 @@
     @Main private val resources: Resources,
     private val theme: Resources.Theme,
     private val devicePostureController: DevicePostureController,
-    private val deviceStateManager: DeviceStateManager
+    private val deviceStateManager: DeviceStateManager,
 ) : QSTileDataToStateMapper<RotationLockTileModel> {
     override fun map(config: QSTileConfig, data: RotationLockTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
-            this.label = resources.getString(R.string.quick_settings_rotation_unlocked_label)
-            this.contentDescription =
-                resources.getString(R.string.accessibility_quick_settings_rotation)
+            label = resources.getString(R.string.quick_settings_rotation_unlocked_label)
+            contentDescription = resources.getString(R.string.accessibility_quick_settings_rotation)
 
             if (data.isRotationLocked) {
                 activationState = QSTileState.ActivationState.INACTIVE
-                this.secondaryLabel = EMPTY_SECONDARY_STRING
+                secondaryLabel = EMPTY_SECONDARY_STRING
                 iconRes = R.drawable.qs_auto_rotate_icon_off
             } else {
                 activationState = QSTileState.ActivationState.ACTIVE
-                this.secondaryLabel =
+                secondaryLabel =
                     if (data.isCameraRotationEnabled) {
                         resources.getString(R.string.rotation_lock_camera_rotation_on)
                     } else {
                         EMPTY_SECONDARY_STRING
                     }
-                this.iconRes = R.drawable.qs_auto_rotate_icon_on
+                iconRes = R.drawable.qs_auto_rotate_icon_on
             }
-            this.icon = {
-                Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
-            }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
             if (isDeviceFoldable(resources, deviceStateManager)) {
-                this.secondaryLabel = getSecondaryLabelWithPosture(this.activationState)
+                secondaryLabel = getSecondaryLabelWithPosture(activationState)
             }
-            this.stateDescription = this.secondaryLabel
-            this.sideViewIcon = QSTileState.SideViewIcon.None
+            stateDescription = secondaryLabel
+            sideViewIcon = QSTileState.SideViewIcon.None
             supportedActions =
                 setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
         }
@@ -86,7 +83,7 @@
         return resources.getString(
             R.string.rotation_tile_with_posture_secondary_label_template,
             stateName,
-            posture
+            posture,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
index 888bba87..08196bb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
@@ -29,10 +29,8 @@
 /** Maps [DataSaverTileModel] to [QSTileState]. */
 class DataSaverTileMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<DataSaverTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+    QSTileDataToStateMapper<DataSaverTileModel> {
     override fun map(config: QSTileConfig, data: DataSaverTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             with(data) {
@@ -45,9 +43,7 @@
                     iconRes = R.drawable.qs_data_saver_icon_off
                     secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[1]
                 }
-                val loadedIcon =
-                    Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
-                icon = { loadedIcon }
+                icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
                 contentDescription = label
                 supportedActions =
                     setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/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/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
index e74e77f..ba06de9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
@@ -30,10 +30,8 @@
 /** Maps [ScreenRecordModel] to [QSTileState]. */
 class ScreenRecordTileMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<ScreenRecordModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+    QSTileDataToStateMapper<ScreenRecordModel> {
     override fun map(config: QSTileConfig, data: ScreenRecordModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
             label = resources.getString(R.string.quick_settings_screen_record_label)
@@ -43,24 +41,12 @@
                 is ScreenRecordModel.Recording -> {
                     activationState = QSTileState.ActivationState.ACTIVE
                     iconRes = R.drawable.qs_screen_record_icon_on
-                    val loadedIcon =
-                        Icon.Loaded(
-                            resources.getDrawable(iconRes!!, theme),
-                            contentDescription = null
-                        )
-                    icon = { loadedIcon }
                     sideViewIcon = QSTileState.SideViewIcon.None
                     secondaryLabel = resources.getString(R.string.quick_settings_screen_record_stop)
                 }
                 is ScreenRecordModel.Starting -> {
                     activationState = QSTileState.ActivationState.ACTIVE
                     iconRes = R.drawable.qs_screen_record_icon_on
-                    val loadedIcon =
-                        Icon.Loaded(
-                            resources.getDrawable(iconRes!!, theme),
-                            contentDescription = null
-                        )
-                    icon = { loadedIcon }
                     val countDown = data.countdownSeconds
                     sideViewIcon = QSTileState.SideViewIcon.None
                     secondaryLabel = String.format("%d...", countDown)
@@ -68,17 +54,13 @@
                 is ScreenRecordModel.DoingNothing -> {
                     activationState = QSTileState.ActivationState.INACTIVE
                     iconRes = R.drawable.qs_screen_record_icon_off
-                    val loadedIcon =
-                        Icon.Loaded(
-                            resources.getDrawable(iconRes!!, theme),
-                            contentDescription = null
-                        )
-                    icon = { loadedIcon }
                     sideViewIcon = QSTileState.SideViewIcon.Chevron // tapping will open dialog
                     secondaryLabel =
                         resources.getString(R.string.quick_settings_screen_record_start)
                 }
             }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+
             contentDescription =
                 if (TextUtils.isEmpty(secondaryLabel)) label
                 else TextUtils.concat(label, ", ", secondaryLabel)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
index 597cf27..b4cfec4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
@@ -51,8 +51,7 @@
             supportedActions =
                 setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
             iconRes = sensorPrivacyTileResources.getIconRes(data.isBlocked)
-            icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
-
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
             sideViewIcon = QSTileState.SideViewIcon.None
 
             if (data.isBlocked) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
index f29c745d..eda8e5c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
@@ -34,14 +34,13 @@
 /** Maps [UiModeNightTileModel] to [QSTileState]. */
 class UiModeNightTileMapper
 @Inject
-constructor(
-    @Main private val resources: Resources,
-    private val theme: Theme,
-) : QSTileDataToStateMapper<UiModeNightTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Theme) :
+    QSTileDataToStateMapper<UiModeNightTileModel> {
     companion object {
         val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a")
         val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")
     }
+
     override fun map(config: QSTileConfig, data: UiModeNightTileModel): QSTileState =
         with(data) {
             QSTileState.build(resources, theme, config.uiConfig) {
@@ -76,7 +75,7 @@
                                 if (isNightMode)
                                     R.string.quick_settings_dark_mode_secondary_label_until
                                 else R.string.quick_settings_dark_mode_secondary_label_on_at,
-                                formatter.format(time)
+                                formatter.format(time),
                             )
                     } else if (
                         nightModeCustomType == UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME
@@ -121,9 +120,7 @@
                     if (activationState == QSTileState.ActivationState.ACTIVE)
                         R.drawable.qs_light_dark_theme_icon_on
                     else R.drawable.qs_light_dark_theme_icon_off
-                val loadedIcon =
-                    Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
-                icon = { loadedIcon }
+                icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
 
                 supportedActions =
                     if (activationState == QSTileState.ActivationState.UNAVAILABLE)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
index eee95b7..a1bc8a8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
@@ -42,9 +42,7 @@
             label = getTileLabel()!!
             contentDescription = label
             iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status
-            icon = {
-                Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
-            }
+            icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
 
             when (data) {
                 is WorkModeTileModel.HasActiveProfile -> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
index 549f0a7..8394be5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
@@ -35,7 +35,7 @@
  * // TODO(b/http://b/299909989): Clean up legacy mappings after the transition
  */
 data class QSTileState(
-    val icon: () -> Icon?,
+    val icon: Icon?,
     val iconRes: Int?,
     val label: CharSequence,
     val activationState: ActivationState,
@@ -54,21 +54,18 @@
             resources: Resources,
             theme: Theme,
             config: QSTileUIConfig,
-            builder: Builder.() -> Unit
+            builder: Builder.() -> Unit,
         ): QSTileState {
             val iconDrawable = resources.getDrawable(config.iconRes, theme)
             return build(
-                { Icon.Loaded(iconDrawable, null) },
+                Icon.Loaded(iconDrawable, null),
                 resources.getString(config.labelRes),
                 builder,
             )
         }
 
-        fun build(
-            icon: () -> Icon?,
-            label: CharSequence,
-            builder: Builder.() -> Unit
-        ): QSTileState = Builder(icon, label).apply { builder() }.build()
+        fun build(icon: Icon?, label: CharSequence, builder: Builder.() -> Unit): QSTileState =
+            Builder(icon, label).apply { builder() }.build()
     }
 
     enum class ActivationState(val legacyState: Int) {
@@ -117,10 +114,7 @@
         data object None : SideViewIcon
     }
 
-    class Builder(
-        var icon: () -> Icon?,
-        var label: CharSequence,
-    ) {
+    class Builder(var icon: Icon?, var label: CharSequence) {
         var iconRes: Int? = null
         var activationState: ActivationState = ActivationState.INACTIVE
         var secondaryLabel: CharSequence? = null
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index f89745f..35b1b96 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.os.UserHandle
 import android.util.Log
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.internal.logging.InstanceId
 import com.android.systemui.Dumpable
 import com.android.systemui.animation.Expandable
@@ -42,7 +43,6 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.takeWhile
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 // TODO(b/http://b/299909989): Use QSTileViewModel directly after the rollout
 class QSTileViewModelAdapter
@@ -223,7 +223,7 @@
         fun mapState(
             context: Context,
             viewModelState: QSTileState,
-            config: QSTileConfig
+            config: QSTileConfig,
         ): QSTile.State =
             // we have to use QSTile.BooleanState to support different side icons
             // which are bound to instanceof QSTile.BooleanState in QSTileView.
@@ -241,7 +241,7 @@
                     viewModelState.supportedActions.contains(QSTileState.UserAction.TOGGLE_CLICK)
 
                 icon =
-                    when (val stateIcon = viewModelState.icon()) {
+                    when (val stateIcon = viewModelState.icon) {
                         is Icon.Loaded ->
                             if (viewModelState.iconRes == null) DrawableIcon(stateIcon.drawable)
                             else DrawableIconWithRes(stateIcon.drawable, viewModelState.iconRes)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index 6d5bf32..d4adcdd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -22,6 +22,7 @@
 import android.view.View
 import androidx.annotation.VisibleForTesting
 import androidx.asynclayoutinflater.view.AsyncLayoutInflater
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.settingslib.applications.InterestingConfigChanges
 import com.android.systemui.Dumpable
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
@@ -36,6 +37,7 @@
 import com.android.systemui.qs.dagger.QSSceneComponent
 import com.android.systemui.res.R
 import com.android.systemui.settings.brightness.MirrorController
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.util.kotlin.sample
@@ -57,7 +59,6 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.flow.update
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.withContext
 
 // TODO(307945185) Split View concerns into a ViewBinder
@@ -206,7 +207,7 @@
     dumpManager: DumpManager,
     @Main private val mainDispatcher: CoroutineDispatcher,
     @Application applicationScope: CoroutineScope,
-    private val configurationInteractor: ConfigurationInteractor,
+    @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor,
     private val asyncLayoutInflaterFactory: (Context) -> AsyncLayoutInflater,
 ) : QSContainerController, QSSceneAdapter, Dumpable {
 
@@ -219,7 +220,7 @@
         dumpManager: DumpManager,
         @Main dispatcher: CoroutineDispatcher,
         @Application scope: CoroutineScope,
-        configurationInteractor: ConfigurationInteractor,
+        @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
     ) : this(
         qsSceneComponentFactory,
         qsImplProvider,
@@ -256,7 +257,7 @@
             .stateIn(
                 applicationScope,
                 SharingStarted.WhileSubscribed(),
-                customizerState.value.isShowing
+                customizerState.value.isShowing,
             )
     override val customizerAnimationDuration: StateFlow<Int> =
         customizerState
diff --git a/packages/SystemUI/src/com/android/systemui/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 5229acc..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
@@ -19,6 +19,7 @@
 package com.android.systemui.scene.domain.startable
 
 import android.app.StatusBarManager
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
 import com.android.internal.logging.UiEventLogger
@@ -45,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
@@ -96,13 +96,11 @@
 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
 import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * Hooks up business logic that manipulates the state of the [SceneInteractor] for the system UI
@@ -138,7 +136,6 @@
     private val uiEventLogger: UiEventLogger,
     private val sceneBackInteractor: SceneBackInteractor,
     private val shadeSessionStorage: SessionStorage,
-    private val windowMgrLockscreenVisInteractor: WindowManagerLockscreenVisibilityInteractor,
     private val keyguardEnabledInteractor: KeyguardEnabledInteractor,
     private val dismissCallbackRegistry: DismissCallbackRegistry,
     private val statusBarStateController: SysuiStatusBarStateController,
@@ -213,7 +210,6 @@
     /** Updates the visibility of the scene container. */
     private fun hydrateVisibility() {
         applicationScope.launch {
-            // TODO(b/296114544): Combine with some global hun state to make it visible!
             deviceProvisioningInteractor.isDeviceProvisioned
                 .flatMapLatest { isAllowedToBeVisible ->
                     if (isAllowedToBeVisible) {
@@ -271,27 +267,6 @@
         handleDeviceUnlockStatus()
         handlePowerState()
         handleShadeTouchability()
-        handleSurfaceBehindKeyguardVisibility()
-    }
-
-    private fun handleSurfaceBehindKeyguardVisibility() {
-        applicationScope.launch {
-            sceneInteractor.currentScene.collectLatest { currentScene ->
-                if (currentScene == Scenes.Lockscreen) {
-                    // Wait for the screen to be on
-                    powerInteractor.isAwake.first { it }
-                    // Wait for surface to become visible
-                    windowMgrLockscreenVisInteractor.surfaceBehindVisibility.first { it }
-                    // Make sure the device is actually unlocked before force-changing the scene
-                    deviceUnlockedInteractor.deviceUnlockStatus.first { it.isUnlocked }
-                    // Override the current transition, if any, by forcing the scene to Gone
-                    sceneInteractor.changeScene(
-                        toScene = Scenes.Gone,
-                        loggingReason = "surface behind keyguard is visible",
-                    )
-                }
-            }
-        }
     }
 
     private fun handleBouncerImeVisibility() {
@@ -300,7 +275,7 @@
             bouncerInteractor.onImeHiddenByUser.collectLatest {
                 if (sceneInteractor.currentScene.value == Scenes.Bouncer) {
                     sceneInteractor.changeScene(
-                        toScene = Scenes.Lockscreen, // TODO(b/336581871): add sceneState?
+                        toScene = Scenes.Lockscreen,
                         loggingReason = "IME hidden",
                     )
                 }
@@ -318,7 +293,6 @@
                     when {
                         isAnySimLocked -> {
                             switchToScene(
-                                // TODO(b/336581871): add sceneState?
                                 targetSceneKey = Scenes.Bouncer,
                                 loggingReason = "Need to authenticate locked SIM card.",
                             )
@@ -326,7 +300,6 @@
                         unlockStatus.isUnlocked &&
                             deviceEntryInteractor.canSwipeToEnter.value == false -> {
                             switchToScene(
-                                // TODO(b/336581871): add sceneState?
                                 targetSceneKey = Scenes.Gone,
                                 loggingReason =
                                     "All SIM cards unlocked and device already unlocked and " +
@@ -335,7 +308,6 @@
                         }
                         else -> {
                             switchToScene(
-                                // TODO(b/336581871): add sceneState?
                                 targetSceneKey = Scenes.Lockscreen,
                                 loggingReason =
                                     "All SIM cards unlocked and device still locked" +
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
index cd38ca6..7d7cab4 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -77,6 +77,11 @@
         }
     }
 
+    override fun onTouchEvent(event: MotionEvent?): Boolean {
+        event?.let { motionEventHandler?.onEmptySpaceMotionEvent(it) }
+        return super.onTouchEvent(event)
+    }
+
     companion object {
         private const val TAG = "SceneWindowRootView"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index 82f65cf..32d5cb4 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.scene.ui.composable.Overlay
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
@@ -48,7 +49,6 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** Models UI state for the scene container. */
 class SceneContainerViewModel
@@ -58,6 +58,7 @@
     private val falsingInteractor: FalsingInteractor,
     private val powerInteractor: PowerInteractor,
     shadeInteractor: ShadeInteractor,
+    private val remoteInputInteractor: RemoteInputInteractor,
     private val splitEdgeDetector: SplitEdgeDetector,
     private val logger: SceneLogger,
     hapticsViewModelFactory: SceneContainerHapticsViewModel.Factory,
@@ -101,6 +102,10 @@
                         this@SceneContainerViewModel.onMotionEvent(motionEvent)
                     }
 
+                    override fun onEmptySpaceMotionEvent(motionEvent: MotionEvent) {
+                        this@SceneContainerViewModel.onEmptySpaceMotionEvent(motionEvent)
+                    }
+
                     override fun onMotionEventComplete() {
                         this@SceneContainerViewModel.onMotionEventComplete()
                     }
@@ -147,6 +152,23 @@
     }
 
     /**
+     * Notifies that a [MotionEvent] has propagated through the entire [SharedNotificationContainer]
+     * and Composable scene container hierarchy without being handled.
+     *
+     * Call this after the [MotionEvent] has finished propagating through the UI hierarchy.
+     */
+    fun onEmptySpaceMotionEvent(event: MotionEvent) {
+        // check if the touch is outside the window and if remote input is active.
+        // If true, close any active remote inputs.
+        if (
+            event.action == MotionEvent.ACTION_OUTSIDE &&
+                (remoteInputInteractor.isRemoteInputActive as StateFlow).value
+        ) {
+            remoteInputInteractor.closeRemoteInputs()
+        }
+    }
+
+    /**
      * Notifies that a scene container user interaction has begun.
      *
      * This is a user interaction that has reached the Composable hierarchy of the scene container,
@@ -263,6 +285,9 @@
         /** Notifies that a [MotionEvent] has occurred. */
         fun onMotionEvent(motionEvent: MotionEvent)
 
+        /** Notifies that a [MotionEvent] has occurred outside the root window. */
+        fun onEmptySpaceMotionEvent(motionEvent: MotionEvent)
+
         /**
          * Notifies that the previous [MotionEvent] reported by [onMotionEvent] has finished
          * processing.
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/screenshot/data/model/DisplayContentModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
index 2048b7c..137b4fd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.screenshot.data.model
 
 import android.app.ActivityTaskManager.RootTaskInfo
+import com.android.systemui.screenshot.policy.childTasksTopDown
 
 /** Information about the tasks on a display. */
 data class DisplayContentModel(
@@ -27,3 +28,5 @@
     /** A list of root tasks on the display, ordered from top to bottom along the z-axis */
     val rootTasks: List<RootTaskInfo>,
 )
+
+fun DisplayContentModel.allTasks() = rootTasks.asSequence().flatMap { it.childTasksTopDown() }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt
index 5e2b576..2a4fe3e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt
@@ -16,15 +16,17 @@
 
 package com.android.systemui.screenshot.policy
 
-import android.content.ComponentName
 import android.os.UserHandle
 
-/** The parameters dictated by a [CapturePolicy], used to adjust alter screenshot request. */
 data class CaptureParameters(
-    /** How should the content be captured? */
+    /** Describes how the image should be obtained. */
     val type: CaptureType,
-    /** The focused or top component at the time of the screenshot. */
-    val component: ComponentName?,
-    /** Which user should receive the screenshot file? */
+    /** Which user to receive the image. */
     val owner: UserHandle,
+    /**
+     * The task which represents the main content or focal point of the screenshot. This is the task
+     * used for retrieval of [AssistContent][android.app.assist.AssistContent] as well as
+     * [Scroll Capture][android.view.IWindowManager.requestScrollCapture].
+     */
+    val contentTask: TaskReference,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt
index 0fb5366..73ff566 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt
@@ -22,7 +22,7 @@
 fun interface CapturePolicy {
     /**
      * Test the policy against the current display task state. If the policy applies, Returns a
-     * [PolicyResult.Matched] containing [CaptureParameters] used to alter the request.
+     * [PolicyResult.Matched] containing [LegacyCaptureParameters] used to alter the request.
      */
     suspend fun check(content: DisplayContentModel): PolicyResult
 
@@ -35,7 +35,7 @@
             /** Why the policy matched. */
             val reason: String,
             /** Details on how to modify the screen capture request. */
-            val parameters: CaptureParameters,
+            val parameters: LegacyCaptureParameters,
         ) : PolicyResult
 
         /** The policy rules do not match the given display content and do not apply. */
@@ -43,7 +43,7 @@
             /** The name of the policy rule which matched. */
             val policy: String,
             /** Why the policy did not match. */
-            val reason: String
+            val reason: String,
         ) : PolicyResult
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt
index 9455201..34c85110 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt
@@ -20,12 +20,10 @@
 
 /** What to capture */
 sealed interface CaptureType {
+
     /** Capture the entire screen contents. */
     data class FullScreen(val displayId: Int) : CaptureType
 
     /** Capture the contents of the task only. */
     data class IsolatedTask(val taskId: Int, val taskBounds: Rect?) : CaptureType
-
-    data class RootTask(val parentTaskId: Int, val taskBounds: Rect?, val childTaskIds: List<Int>) :
-        CaptureType
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/LegacyCaptureParameters.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/LegacyCaptureParameters.kt
new file mode 100644
index 0000000..4b697b2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/LegacyCaptureParameters.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import android.content.ComponentName
+import android.os.UserHandle
+
+/** The parameters dictated by a [CapturePolicy], used to adjust alter screenshot request. */
+data class LegacyCaptureParameters(
+    /** How should the content be captured? */
+    val type: CaptureType,
+    /** The focused or top component at the time of the screenshot. */
+    val component: ComponentName?,
+    /** Which user should receive the screenshot file? */
+    val owner: UserHandle,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
index e840668..a84cdf40 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
@@ -80,6 +80,7 @@
                     Log.i(TAG, "$result")
                     return modify(original, result.parameters)
                 }
+
                 is NotMatched -> Log.i(TAG, "$result")
             }
         }
@@ -89,7 +90,45 @@
     }
 
     /** Produce a new [ScreenshotData] using [CaptureParameters] */
-    suspend fun modify(original: ScreenshotData, updates: CaptureParameters): ScreenshotData {
+    suspend fun modify(original: ScreenshotData, params: CaptureParameters): ScreenshotData {
+        Log.d(TAG, "[modify] CaptureParameters = $params")
+        // Update and apply bitmap capture depending on the parameters.
+        when (val type = params.type) {
+            is IsolatedTask -> {
+                Log.i(TAG, "Capturing task snapshot: $params")
+
+                val taskSnapshot =
+                    capture.captureTask(type.taskId) ?: error("Failed to capture task")
+
+                return original.copy(
+                    type = TAKE_SCREENSHOT_PROVIDED_IMAGE,
+                    bitmap = taskSnapshot,
+                    userHandle = params.owner,
+                    taskId = params.contentTask.taskId,
+                    topComponent = params.contentTask.component,
+                    originalScreenBounds = type.taskBounds,
+                )
+            }
+
+            is FullScreen -> {
+                Log.i(TAG, "Capturing screenshot: $params")
+
+                val screenshot =
+                    captureDisplay(type.displayId) ?: error("Failed to capture screenshot")
+                return original.copy(
+                    type = TAKE_SCREENSHOT_FULLSCREEN,
+                    bitmap = screenshot,
+                    userHandle = params.owner,
+                    topComponent = params.contentTask.component,
+                    originalScreenBounds = Rect(0, 0, screenshot.width, screenshot.height),
+                    taskId = params.contentTask.taskId,
+                )
+            }
+        }
+    }
+
+    /** Produce a new [ScreenshotData] using [LegacyCaptureParameters] */
+    suspend fun modify(original: ScreenshotData, updates: LegacyCaptureParameters): ScreenshotData {
         Log.d(TAG, "[modify] CaptureParameters = $updates")
         // Update and apply bitmap capture depending on the parameters.
         val updated =
@@ -102,14 +141,7 @@
                         type.taskId,
                         type.taskBounds,
                     )
-                is CaptureType.RootTask ->
-                    replaceWithTaskSnapshot(
-                        original,
-                        updates.component,
-                        updates.owner,
-                        type.parentTaskId,
-                        type.taskBounds,
-                    )
+
                 is FullScreen ->
                     replaceWithScreenshot(
                         original,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
index 1945c25..3857ba3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
@@ -31,11 +31,8 @@
  *
  * Parameters: Capture the whole screen, owned by the private user.
  */
-class PrivateProfilePolicy
-@Inject
-constructor(
-    private val profileTypes: ProfileTypeRepository,
-) : CapturePolicy {
+class PrivateProfilePolicy @Inject constructor(private val profileTypes: ProfileTypeRepository) :
+    CapturePolicy {
     override suspend fun check(content: DisplayContentModel): PolicyResult {
         // The systemUI notification shade isn't a private profile app, skip.
         if (content.systemUiState.shadeExpanded) {
@@ -47,25 +44,23 @@
             content.rootTasks
                 .filter { it.isVisible }
                 .firstNotNullOfOrNull { root ->
-                    root
-                        .childTasksTopDown()
-                        .firstOrNull {
-                            profileTypes.getProfileType(it.userId) == ProfileType.PRIVATE
-                        }
-                }
-                ?: return NotMatched(policy = NAME, reason = NO_VISIBLE_TASKS)
+                    root.childTasksTopDown().firstOrNull {
+                        profileTypes.getProfileType(it.userId) == ProfileType.PRIVATE
+                    }
+                } ?: return NotMatched(policy = NAME, reason = NO_VISIBLE_TASKS)
 
         // If matched, return parameters needed to modify the request.
         return Matched(
             policy = NAME,
             reason = PRIVATE_TASK_VISIBLE,
-            CaptureParameters(
+            LegacyCaptureParameters(
                 type = FullScreen(content.displayId),
                 component = content.rootTasks.first { it.isVisible }.topActivity,
                 owner = UserHandle.of(childTask.userId),
-            )
+            ),
         )
     }
+
     companion object {
         const val NAME = "PrivateProfile"
         const val SHADE_EXPANDED = "Notification shade is expanded"
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
index dd39f92..c43e929 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.screenshot.data.model.ChildTaskModel
 
 /** The child tasks of A RootTaskInfo as [ChildTaskModel] in top-down (z-index ascending) order. */
-internal fun RootTaskInfo.childTasksTopDown(): Sequence<ChildTaskModel> {
+fun RootTaskInfo.childTasksTopDown(): Sequence<ChildTaskModel> {
     return ((childTaskIds.size - 1) downTo 0).asSequence().map { index ->
         ChildTaskModel(
             childTaskIds[index],
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicy.kt
index 9967aff..5213579 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicy.kt
@@ -20,18 +20,16 @@
 import android.app.WindowConfiguration
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
-import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
 import android.content.ComponentName
+import android.graphics.Rect
 import android.os.UserHandle
 import android.util.Log
 import com.android.systemui.screenshot.data.model.DisplayContentModel
-import com.android.systemui.screenshot.data.model.ProfileType
 import com.android.systemui.screenshot.data.model.ProfileType.PRIVATE
 import com.android.systemui.screenshot.data.model.ProfileType.WORK
 import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
 import com.android.systemui.screenshot.policy.CaptureType.FullScreen
 import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
-import com.android.systemui.screenshot.policy.CaptureType.RootTask
 import javax.inject.Inject
 
 private const val TAG = "ScreenshotPolicy"
@@ -39,7 +37,7 @@
 /** Determines what to capture and which user owns the output. */
 class ScreenshotPolicy @Inject constructor(private val profileTypes: ProfileTypeRepository) {
     /**
-     * Apply the policy to the content, resulting in [CaptureParameters].
+     * Apply the policy to the content, resulting in [LegacyCaptureParameters].
      *
      * @param content the content of the display
      * @param defaultComponent the component associated with the screenshot by default
@@ -53,7 +51,7 @@
         val defaultFullScreen by lazy {
             CaptureParameters(
                 type = FullScreen(displayId = content.displayId),
-                component = defaultComponent,
+                contentTask = TaskReference(-1, defaultComponent, defaultOwner, Rect()),
                 owner = defaultOwner,
             )
         }
@@ -70,32 +68,47 @@
             } ?: return defaultFullScreen
 
         Log.d(TAG, "topRootTask: $topRootTask")
-        val rootTaskOwners = topRootTask.childTaskUserIds.distinct()
 
-        // Special case: Only WORK in top root task which is full-screen or maximized freeform
+        // When:
+        // * there is one or more child task
+        // * all owned by the same user
+        // * this user is a work profile
+        // * the root task is fullscreen or freeform-maximized
+        //
+        // Then:
+        // the result will be a task snapshot instead of a full screen capture. If there is more
+        // than one child task, the root task will be snapshot to include any/all child tasks. This
+        // is intended to cover split-screen mode.
+        val rootTaskOwners = topRootTask.childTaskUserIds.distinct()
         if (
             rootTaskOwners.size == 1 &&
                 profileTypes.getProfileType(rootTaskOwners.single()) == WORK &&
                 (topRootTask.isFullScreen() || topRootTask.isMaximizedFreeform())
         ) {
+            val topChildTask = topRootTask.childTasksTopDown().first()
+
+            // If there is more than one task, capture the parent to include both.
             val type =
                 if (topRootTask.childTaskCount() > 1) {
-                    RootTask(
-                        parentTaskId = topRootTask.taskId,
-                        taskBounds = topRootTask.bounds,
-                        childTaskIds = topRootTask.childTasksTopDown().map { it.id }.toList(),
-                    )
+                    IsolatedTask(taskId = topRootTask.taskId, taskBounds = topRootTask.bounds)
                 } else {
-                    IsolatedTask(
-                        taskId = topRootTask.childTasksTopDown().first().id,
-                        taskBounds = topRootTask.bounds,
-                    )
+                    // Otherwise capture the single task, and use its bounds.
+                    IsolatedTask(taskId = topChildTask.id, taskBounds = topChildTask.bounds)
                 }
-            // Capture the RootTask (and all children)
+
+            // The content task (the focus of the screenshot) must represent a single task
+            // containing an activity, so always reference the top child task here. The owner
+            // of the screenshot here is always the same as well.
             return CaptureParameters(
                 type = type,
-                component = topRootTask.topActivity,
-                owner = UserHandle.of(rootTaskOwners.single()),
+                contentTask =
+                    TaskReference(
+                        taskId = topChildTask.id,
+                        component = topRootTask.topActivity ?: defaultComponent,
+                        owner = UserHandle.of(topChildTask.userId),
+                        bounds = topChildTask.bounds,
+                    ),
+                owner = UserHandle.of(topChildTask.userId),
             )
         }
 
@@ -105,26 +118,36 @@
         val visibleChildTasks =
             content.rootTasks.filter { it.isVisible }.flatMap { it.childTasksTopDown() }
 
+        // Don't target a PIP window as the screenshot "content", it should only be used
+        // to determine ownership (above).
+        val contentTask =
+            content.rootTasks
+                .filter {
+                    it.isVisible && it.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED
+                }
+                .flatMap { it.childTasksTopDown() }
+                .first()
+
         val allVisibleProfileTypes =
             visibleChildTasks
                 .map { it.userId }
                 .distinct()
                 .associate { profileTypes.getProfileType(it) to UserHandle.of(it) }
 
-        // If any visible content belongs to the private profile user -> private profile
-        // otherwise the personal user (including partial screen work content).
-        val ownerHandle =
-            allVisibleProfileTypes[PRIVATE]
-                ?: allVisibleProfileTypes[ProfileType.NONE]
-                ?: defaultOwner
-
-        // Attribute to the component of top-most task owned by this user (or fallback to default)
-        val topComponent =
-            visibleChildTasks.firstOrNull { it.userId == ownerHandle.identifier }?.componentName
+        // If any task is visible and owned by a PRIVATE profile user, the screenshot is assigned
+        // to that user. Work profile has been handled above so it is not considered here. Fallback
+        // to the default user which is the primary "current" user ('aka' personal "profile").
+        val ownerHandle = allVisibleProfileTypes[PRIVATE] ?: defaultOwner
 
         return CaptureParameters(
             type = FullScreen(content.displayId),
-            component = topComponent ?: topRootTask.topActivity ?: defaultComponent,
+            contentTask =
+                TaskReference(
+                    taskId = contentTask.id,
+                    component = contentTask.componentName,
+                    owner = UserHandle.of(contentTask.userId),
+                    bounds = contentTask.bounds,
+                ),
             owner = ownerHandle,
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/TaskReference.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/TaskReference.kt
new file mode 100644
index 0000000..04f5b1e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/TaskReference.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import android.content.ComponentName
+import android.graphics.Rect
+import android.os.UserHandle
+
+data class TaskReference(
+    /** The id of the task. */
+    val taskId: Int,
+    /** The component name of the task. */
+    val component: ComponentName?,
+    /** The owner of the task. */
+    val owner: UserHandle,
+    /** The bounds of the task. */
+    val bounds: Rect,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
index cf90c0a..109c1cb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
@@ -68,7 +68,7 @@
         return PolicyResult.Matched(
             policy = NAME,
             reason = WORK_TASK_IS_TOP,
-            CaptureParameters(
+            LegacyCaptureParameters(
                 type = IsolatedTask(taskId = childTask.id, taskBounds = childTask.bounds),
                 component = childTask.componentName ?: rootTask.topActivity,
                 owner = UserHandle.of(childTask.userId),
diff --git a/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
deleted file mode 100644
index 6199a83..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.settings
-
-import android.content.ContentResolver
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
-import com.android.systemui.shared.settings.data.repository.SecureSettingsRepositoryImpl
-import dagger.Module
-import dagger.Provides
-import kotlinx.coroutines.CoroutineDispatcher
-
-@Module
-object SecureSettingsRepositoryModule {
-    @JvmStatic
-    @Provides
-    @SysUISingleton
-    fun provideSecureSettingsRepository(
-        contentResolver: ContentResolver,
-        @Background backgroundDispatcher: CoroutineDispatcher,
-    ): SecureSettingsRepository =
-        SecureSettingsRepositoryImpl(contentResolver, backgroundDispatcher)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/SystemSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/settings/SystemSettingsRepositoryModule.kt
deleted file mode 100644
index 02ce74a..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/SystemSettingsRepositoryModule.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.settings
-
-import android.content.ContentResolver
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository
-import com.android.systemui.shared.settings.data.repository.SystemSettingsRepositoryImpl
-import dagger.Module
-import dagger.Provides
-import kotlinx.coroutines.CoroutineDispatcher
-
-@Module
-object SystemSettingsRepositoryModule {
-    @JvmStatic
-    @Provides
-    @SysUISingleton
-    fun provideSystemSettingsRepository(
-        contentResolver: ContentResolver,
-        @Background backgroundDispatcher: CoroutineDispatcher,
-    ): SystemSettingsRepository =
-        SystemSettingsRepositoryImpl(contentResolver, backgroundDispatcher)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/settings/UserSettingsRepositoryModule.kt
new file mode 100644
index 0000000..3d7b2ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserSettingsRepositoryModule.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings
+
+import android.content.ContentResolver
+import com.android.systemui.Flags
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepositoryImpl
+import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository
+import com.android.systemui.shared.settings.data.repository.SystemSettingsRepositoryImpl
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SystemSettings
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
+import com.android.systemui.util.settings.repository.UserAwareSystemSettingsRepository
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
+
+@Module
+object UserSettingsRepositoryModule {
+    @JvmStatic
+    @Provides
+    @SysUISingleton
+    fun provideSecureSettingsRepository(
+        secureSettings: Lazy<SecureSettings>,
+        userRepository: Lazy<UserRepository>,
+        contentResolver: Lazy<ContentResolver>,
+        @Background backgroundDispatcher: CoroutineDispatcher,
+        @Background backgroundContext: CoroutineContext,
+    ): SecureSettingsRepository {
+        return if (Flags.userAwareSettingsRepositories()) {
+            UserAwareSecureSettingsRepository(
+                secureSettings.get(),
+                userRepository.get(),
+                backgroundDispatcher,
+                backgroundContext,
+            )
+        } else {
+            SecureSettingsRepositoryImpl(contentResolver.get(), backgroundDispatcher)
+        }
+    }
+
+    @JvmStatic
+    @Provides
+    @SysUISingleton
+    fun provideSystemSettingsRepository(
+        systemSettings: Lazy<SystemSettings>,
+        userRepository: Lazy<UserRepository>,
+        contentResolver: Lazy<ContentResolver>,
+        @Background backgroundDispatcher: CoroutineDispatcher,
+        @Background backgroundContext: CoroutineContext,
+    ): SystemSettingsRepository {
+        return if (Flags.userAwareSettingsRepositories()) {
+            UserAwareSystemSettingsRepository(
+                systemSettings.get(),
+                userRepository.get(),
+                backgroundDispatcher,
+                backgroundContext,
+            )
+        } else {
+            SystemSettingsRepositoryImpl(contentResolver.get(), backgroundDispatcher)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
index 6e63446..1776a2c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
@@ -18,14 +18,14 @@
 
 import android.content.Context
 import android.view.ViewGroup
-import com.android.systemui.res.R
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.StatusBarState.SHADE
-import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
+import com.android.systemui.res.R
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
+import com.android.systemui.statusbar.StatusBarState.SHADE
+import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
 import com.android.systemui.unfold.SysUIUnfoldScope
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
 import javax.inject.Inject
@@ -39,8 +39,10 @@
     progressProvider: NaturalRotationUnfoldProgressProvider,
 ) {
 
-    private val filterShade: () -> Boolean = { statusBarStateController.getState() == SHADE ||
-        statusBarStateController.getState() == SHADE_LOCKED }
+    private val filterShade: () -> Boolean = {
+        statusBarStateController.getState() == SHADE ||
+            statusBarStateController.getState() == SHADE_LOCKED
+    }
 
     private val translateAnimator by lazy {
         UnfoldConstantTranslateAnimator(
@@ -48,21 +50,23 @@
                 setOf(
                     ViewIdToTranslate(R.id.quick_settings_panel, START, filterShade),
                     ViewIdToTranslate(R.id.qs_footer_actions, START, filterShade),
-                    ViewIdToTranslate(R.id.notification_stack_scroller, END, filterShade)),
-            progressProvider = progressProvider)
+                    ViewIdToTranslate(R.id.notification_stack_scroller, END, filterShade),
+                ),
+            progressProvider = progressProvider,
+        )
     }
 
     private val translateAnimatorStatusBar by lazy {
         UnfoldConstantTranslateAnimator(
             viewsIdToTranslate =
-            setOf(
-                ViewIdToTranslate(R.id.shade_header_system_icons, END, filterShade),
-                ViewIdToTranslate(R.id.privacy_container, END, filterShade),
-                ViewIdToTranslate(R.id.carrier_group, END, filterShade),
-                ViewIdToTranslate(R.id.clock, START, filterShade),
-                ViewIdToTranslate(R.id.date, START, filterShade)
-            ),
-            progressProvider = progressProvider
+                setOf(
+                    ViewIdToTranslate(R.id.shade_header_system_icons, END, filterShade),
+                    ViewIdToTranslate(R.id.privacy_container, END, filterShade),
+                    ViewIdToTranslate(R.id.carrier_group, END, filterShade),
+                    ViewIdToTranslate(R.id.clock, START, filterShade),
+                    ViewIdToTranslate(R.id.date, START, filterShade),
+                ),
+            progressProvider = progressProvider,
         )
     }
 
@@ -73,10 +77,7 @@
         val splitShadeStatusBarViewGroup: ViewGroup? =
             root.findViewById(R.id.split_shade_status_bar)
         if (splitShadeStatusBarViewGroup != null) {
-            translateAnimatorStatusBar.init(
-                splitShadeStatusBarViewGroup,
-                translationMax
-            )
+            translateAnimatorStatusBar.init(splitShadeStatusBarViewGroup, translationMax)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 0e82bf8..c15c8f9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2053,6 +2053,9 @@
         }
         if (mQsController.getExpanded()) {
             mQsController.flingQs(0, FLING_COLLAPSE);
+        } else if (mBarState == KEYGUARD) {
+            mLockscreenShadeTransitionController.goToLockedShade(
+                    /* expandedView= */null, /* needsQSAnimation= */false);
         } else {
             expand(true /* animate */);
         }
@@ -3109,7 +3112,7 @@
         if (isTracking()) {
             onTrackingStopped(true);
         }
-        if (isExpanded() && !mQsController.getExpanded()) {
+        if (isExpanded() && mBarState != KEYGUARD && !mQsController.getExpanded()) {
             mShadeLog.d("Status Bar was long pressed. Expanding to QS.");
             expandToQs();
         } else {
@@ -5091,13 +5094,6 @@
             }
             boolean handled = mHeadsUpTouchHelper.onTouchEvent(event);
 
-            if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && mQsController.handleTouch(
-                    event, isFullyCollapsed(), isShadeOrQsHeightAnimationRunning())) {
-                if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
-                    mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
-                }
-                return true;
-            }
             // This touch session has already resulted in shade expansion. Ignore everything else.
             if (ShadeExpandsOnStatusBarLongPress.isEnabled()
                     && event.getActionMasked() != MotionEvent.ACTION_DOWN
@@ -5105,6 +5101,13 @@
                 mShadeLog.d("Touch has same down time as Status Bar long press. Ignoring.");
                 return false;
             }
+            if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && mQsController.handleTouch(
+                    event, isFullyCollapsed(), isShadeOrQsHeightAnimationRunning())) {
+                if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
+                    mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
+                }
+                return true;
+            }
             if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
                 mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
                 handled = true;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index be2bf82..f2c3906 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -88,6 +88,7 @@
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 /**
  * Controller for {@link NotificationShadeWindowView}.
@@ -193,7 +194,7 @@
             PrimaryBouncerInteractor primaryBouncerInteractor,
             AlternateBouncerInteractor alternateBouncerInteractor,
             BouncerViewBinder bouncerViewBinder,
-            @ShadeDisplayAware ConfigurationForwarder configurationForwarder,
+            @ShadeDisplayAware Provider<ConfigurationForwarder> configurationForwarder,
             BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor) {
         mLockscreenShadeTransitionController = transitionController;
         mFalsingCollector = falsingCollector;
@@ -257,7 +258,7 @@
         }
 
         if (ShadeWindowGoesAround.isEnabled()) {
-            mView.setConfigurationForwarder(configurationForwarder);
+            mView.setConfigurationForwarder(configurationForwarder.get());
         }
         dumpManager.registerDumpable(this);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index 49fa80c..5b06ad2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.shade
 
 import android.view.MotionEvent
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.assist.AssistManager
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -39,7 +40,6 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.first
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.withContext
 
 /**
@@ -136,7 +136,6 @@
     }
 
     private fun animateCollapseShadeInternal() {
-        // TODO(b/336581871): add sceneState?
         shadeInteractor.collapseEitherShade(
             loggingReason = "ShadeController.animateCollapseShade",
             transitionKey = SlightlyFasterShadeCollapse,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index 51f1f81..42d4eff 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -20,7 +20,13 @@
 import android.content.res.Resources
 import android.view.LayoutInflater
 import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.common.ui.ConfigurationStateImpl
 import com.android.systemui.common.ui.GlobalConfig
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.res.R
 import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
@@ -79,12 +85,12 @@
     fun provideShadeWindowConfigurationController(
         @ShadeDisplayAware shadeContext: Context,
         factory: ConfigurationControllerImpl.Factory,
-        @GlobalConfig globalConfigConfigController: ConfigurationController,
+        @GlobalConfig globalConfigController: ConfigurationController,
     ): ConfigurationController {
         return if (ShadeWindowGoesAround.isEnabled) {
             factory.create(shadeContext)
         } else {
-            globalConfigConfigController
+            globalConfigController
         }
     }
 
@@ -92,13 +98,55 @@
     @ShadeDisplayAware
     @SysUISingleton
     fun provideShadeWindowConfigurationForwarder(
-        @ShadeDisplayAware shadeConfigurationController: ConfigurationController,
-        @GlobalConfig globalConfigController: ConfigurationController,
+        @ShadeDisplayAware shadeConfigurationController: ConfigurationController
     ): ConfigurationForwarder {
+        ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
+        return shadeConfigurationController
+    }
+
+    @SysUISingleton
+    @Provides
+    @ShadeDisplayAware
+    fun provideShadeDisplayAwareConfigurationState(
+        factory: ConfigurationStateImpl.Factory,
+        @ShadeDisplayAware configurationController: ConfigurationController,
+        @ShadeDisplayAware context: Context,
+        @GlobalConfig configurationState: ConfigurationState,
+    ): ConfigurationState {
         return if (ShadeWindowGoesAround.isEnabled) {
-            shadeConfigurationController
+            factory.create(context, configurationController)
         } else {
-            globalConfigController
+            configurationState
+        }
+    }
+
+    @SysUISingleton
+    @Provides
+    @ShadeDisplayAware
+    fun provideShadeDisplayAwareConfigurationRepository(
+        factory: ConfigurationRepositoryImpl.Factory,
+        @ShadeDisplayAware configurationController: ConfigurationController,
+        @ShadeDisplayAware context: Context,
+        @GlobalConfig globalConfigurationRepository: ConfigurationRepository,
+    ): ConfigurationRepository {
+        return if (ShadeWindowGoesAround.isEnabled) {
+            factory.create(context, configurationController)
+        } else {
+            globalConfigurationRepository
+        }
+    }
+
+    @SysUISingleton
+    @Provides
+    @ShadeDisplayAware
+    fun provideShadeAwareConfigurationInteractor(
+        @ShadeDisplayAware configurationRepository: ConfigurationRepository,
+        @GlobalConfig configurationInteractor: ConfigurationInteractor,
+    ): ConfigurationInteractor {
+        return if (ShadeWindowGoesAround.isEnabled) {
+            ConfigurationInteractorImpl(configurationRepository)
+        } else {
+            configurationInteractor
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 72a4650..2348a11 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -49,10 +49,7 @@
 import javax.inject.Provider
 
 /** Module for classes related to the notification shade. */
-@Module(
-    includes =
-        [StartShadeModule::class, ShadeViewProviderModule::class]
-)
+@Module(includes = [StartShadeModule::class, ShadeViewProviderModule::class])
 abstract class ShadeModule {
     companion object {
         @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt
rename to packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
index 6fb3ca5..ae36e81 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
@@ -25,7 +25,7 @@
 
 /** Accepts touch events, detects long press, and calls ShadeViewController#onStatusBarLongPress. */
 @SysUISingleton
-class LongPressGestureDetector
+class StatusBarLongPressGestureDetector
 @Inject
 constructor(context: Context, val shadeViewController: ShadeViewController) {
     val gestureDetector =
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index 5629938..ef62d2d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -15,9 +15,7 @@
  */
 package com.android.systemui.shade.data.repository
 
-import android.content.Context
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -182,8 +180,7 @@
 
 /** Business logic for shade interactions */
 @SysUISingleton
-class ShadeRepositoryImpl @Inject constructor() :
-    ShadeRepository {
+class ShadeRepositoryImpl @Inject constructor() : ShadeRepository {
     private val _qsExpansion = MutableStateFlow(0f)
     @Deprecated("Use ShadeInteractor.qsExpansion instead")
     override val qsExpansion: StateFlow<Float> = _qsExpansion.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
index 3a483f4..f151307 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
@@ -41,7 +41,6 @@
                 } else {
                     Scenes.Shade
                 }
-            // TODO(b/336581871): add sceneState?
             sceneInteractor.changeScene(key, "animateCollapseQs")
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
index e5d08a0..44f2911 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.shade.domain.startable
 
 import android.content.Context
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.CoreStartable
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
@@ -42,7 +43,6 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onStart
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 @SysUISingleton
 class ShadeStartable
@@ -51,7 +51,7 @@
     @Application private val applicationScope: CoroutineScope,
     @ShadeDisplayAware private val context: Context,
     @ShadeTouchLog private val touchLog: LogBuffer,
-    private val configurationRepository: ConfigurationRepository,
+    @ShadeDisplayAware private val configurationRepository: ConfigurationRepository,
     private val shadeRepository: ShadeRepository,
     private val splitShadeStateController: SplitShadeStateController,
     private val scrimShadeTransitionController: ScrimShadeTransitionController,
diff --git a/packages/SystemUI/src/com/android/systemui/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/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
index 30f564f..3a24ec9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
@@ -61,15 +61,6 @@
             return row.getEntry().getSbn().getNotification();
         }
     };
-    private static final ResultApplicator GREY_APPLICATOR = new ResultApplicator() {
-        @Override
-        public void apply(View parent, View view, boolean apply, boolean reset) {
-            CachingIconView icon = view.findViewById(com.android.internal.R.id.icon);
-            if (icon != null) {
-                icon.setGrayedOut(apply);
-            }
-        }
-    };
 
     private final ExpandableNotificationRow mRow;
     private final ArrayList<Processor> mProcessors = new ArrayList<>();
@@ -78,14 +69,18 @@
     public NotificationGroupingUtil(ExpandableNotificationRow row) {
         mRow = row;
 
-        final IconComparator iconVisibilityComparator = new IconComparator(mRow) {
+        final IconComparator iconVisibilityComparator = new IconComparator() {
             public boolean compare(View parent, View child, Object parentData,
                     Object childData) {
+                if (Flags.notificationsRedesignAppIcons() && mRow.isShowingAppIcon()) {
+                    // Icon is always the same when we're showing the app icon.
+                    return true;
+                }
                 return hasSameIcon(parentData, childData)
                         && hasSameColor(parentData, childData);
             }
         };
-        final IconComparator greyComparator = new IconComparator(mRow) {
+        final IconComparator greyComparator = new IconComparator() {
             public boolean compare(View parent, View child, Object parentData,
                     Object childData) {
                 if (Flags.notificationsRedesignAppIcons() && mRow.isShowingAppIcon()) {
@@ -95,6 +90,19 @@
                         || hasSameColor(parentData, childData);
             }
         };
+        final ResultApplicator greyApplicator = new ResultApplicator() {
+            @Override
+            public void apply(View parent, View view, boolean apply, boolean reset) {
+                if (Flags.notificationsRedesignAppIcons() && mRow.isShowingAppIcon()) {
+                    // Do nothing.
+                    return;
+                }
+                CachingIconView icon = view.findViewById(com.android.internal.R.id.icon);
+                if (icon != null) {
+                    icon.setGrayedOut(apply);
+                }
+            }
+        };
 
         // To hide the icons if they are the same and the color is the same
         mProcessors.add(new Processor(mRow,
@@ -107,7 +115,7 @@
                 com.android.internal.R.id.status_bar_latest_event_content,
                 ICON_EXTRACTOR,
                 greyComparator,
-                GREY_APPLICATOR));
+                greyApplicator));
         // To show the large icon on the left side instead if all the small icons are the same
         mProcessors.add(new Processor(mRow,
                 com.android.internal.R.id.status_bar_latest_event_content,
@@ -319,13 +327,14 @@
 
     private interface ViewComparator {
         /**
-         * @param parent the view with the given id in the group header
-         * @param child the view with the given id in the child notification
+         * @param parent     the view with the given id in the group header
+         * @param child      the view with the given id in the child notification
          * @param parentData optional data for the parent
-         * @param childData optional data for the child
+         * @param childData  optional data for the child
          * @return whether to views are the same
          */
         boolean compare(View parent, View child, Object parentData, Object childData);
+
         boolean isEmpty(View view);
     }
 
@@ -368,21 +377,12 @@
     }
 
     private abstract static class IconComparator implements ViewComparator {
-        private final ExpandableNotificationRow mRow;
-
-        IconComparator(ExpandableNotificationRow row) {
-            mRow = row;
-        }
-
         @Override
         public boolean compare(View parent, View child, Object parentData, Object childData) {
             return false;
         }
 
         protected boolean hasSameIcon(Object parentData, Object childData) {
-            if (Flags.notificationsRedesignAppIcons() && mRow.isShowingAppIcon()) {
-                return true;
-            }
             Icon parentIcon = getIcon((Notification) parentData);
             Icon childIcon = getIcon((Notification) childData);
             return parentIcon.sameAs(childIcon);
@@ -420,9 +420,9 @@
     private interface ResultApplicator {
         /**
          * @param parent the root view of the child notification
-         * @param view the view with the given id in the child notification
-         * @param apply whether the state should be applied or removed
-         * @param reset if [de]application is the result of a reset
+         * @param view   the view with the given id in the child notification
+         * @param apply  whether the state should be applied or removed
+         * @param reset  if [de]application is the result of a reset
          */
         void apply(View parent, View view, boolean apply, boolean reset);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 9b1e782..fdc1c0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -53,6 +53,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
@@ -686,6 +687,7 @@
     }
 
     public void checkRemoteInputOutside(MotionEvent event) {
+        SceneContainerFlag.assertInLegacyMode();
         if (event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar
                 && event.getX() == 0 && event.getY() == 0  // a touch outside both bars
                 && isRemoteInputActive()) {
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/CommandQueueInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
index 57c8bc6..614f0f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
@@ -46,7 +46,7 @@
 ) : CoreStartable {
 
     override fun start() {
-        StatusBarSimpleFragment.assertInNewMode()
+        StatusBarConnectedDisplays.assertInNewMode()
         val result: RegisterStatusBarResult =
             try {
                 barService.registerStatusBar(commandQueue)
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 e115922..84c7ab2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
@@ -17,11 +17,14 @@
 package com.android.systemui.statusbar.core
 
 import android.view.Display
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.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
 import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
@@ -29,7 +32,6 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.onStart
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * Responsible for creating and starting the status bar components for each display. Also does it
@@ -48,6 +50,8 @@
     private val initializerStore: StatusBarInitializerStore,
     private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
     private val statusBarInitializerStore: StatusBarInitializerStore,
+    private val privacyDotWindowControllerStore: PrivacyDotWindowControllerStore,
+    private val lightBarControllerStore: LightBarControllerStore,
 ) : CoreStartable {
 
     init {
@@ -71,6 +75,15 @@
         val displayId = display.displayId
         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) {
@@ -89,4 +102,12 @@
     private fun createAndStartInitializerForDisplay(displayId: Int) {
         statusBarInitializerStore.forDisplay(displayId).start()
     }
+
+    private fun startPrivacyDotForDisplay(displayId: Int) {
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            // For the default display, privacy dot is started via ScreenDecorations
+            return
+        }
+        privacyDotWindowControllerStore.forDisplay(displayId).start()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
index 3abbc6e..f441fd6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
 import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewUpdatedListener
+import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository
 import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
 import com.android.systemui.statusbar.phone.PhoneStatusBarView
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
@@ -71,7 +72,10 @@
     }
 
     interface Factory {
-        fun create(statusBarWindowController: StatusBarWindowController): StatusBarInitializer
+        fun create(
+            statusBarWindowController: StatusBarWindowController,
+            statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository,
+        ): StatusBarInitializer
     }
 }
 
@@ -79,6 +83,7 @@
 @AssistedInject
 constructor(
     @Assisted private val statusBarWindowController: StatusBarWindowController,
+    @Assisted private val statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository,
     private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>,
     private val statusBarRootFactory: StatusBarRootFactory,
     private val componentFactory: HomeStatusBarComponent.Factory,
@@ -127,7 +132,7 @@
                 val phoneStatusBarView = cv.findViewById<PhoneStatusBarView>(R.id.status_bar)
                 component =
                     componentFactory.create(phoneStatusBarView).also { component ->
-                        // CollapsedStatusBarFragment used to be responsible initializting
+                        // CollapsedStatusBarFragment used to be responsible initializing
                         component.init()
 
                         statusBarViewUpdatedListener?.onStatusBarViewUpdated(
@@ -135,8 +140,12 @@
                             component.phoneStatusBarTransitions,
                         )
 
-                        creationListeners.forEach { listener ->
-                            listener.onStatusBarViewInitialized(component)
+                        if (StatusBarConnectedDisplays.isEnabled) {
+                            statusBarModePerDisplayRepository.onStatusBarViewInitialized(component)
+                        } else {
+                            creationListeners.forEach { listener ->
+                                listener.onStatusBarViewInitialized(component)
+                            }
                         }
                     }
             }
@@ -184,7 +193,8 @@
     @AssistedFactory
     interface Factory : StatusBarInitializer.Factory {
         override fun create(
-            statusBarWindowController: StatusBarWindowController
+            statusBarWindowController: StatusBarWindowController,
+            statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository,
         ): StatusBarInitializerImpl
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
index 041f0b0..4f815c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.display.data.repository.PerDisplayStore
 import com.android.systemui.display.data.repository.PerDisplayStoreImpl
 import com.android.systemui.display.data.repository.SingleDisplayStore
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -37,6 +38,7 @@
     displayRepository: DisplayRepository,
     private val factory: StatusBarInitializer.Factory,
     private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
+    private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
 ) :
     StatusBarInitializerStore,
     PerDisplayStoreImpl<StatusBarInitializer>(backgroundApplicationScope, displayRepository) {
@@ -47,7 +49,8 @@
 
     override fun createInstanceForDisplay(displayId: Int): StatusBarInitializer {
         return factory.create(
-            statusBarWindowController = statusBarWindowControllerStore.forDisplay(displayId)
+            statusBarWindowController = statusBarWindowControllerStore.forDisplay(displayId),
+            statusBarModePerDisplayRepository = statusBarModeRepositoryStore.forDisplay(displayId),
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index f65ae67..4341200 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.data.StatusBarDataLayerModule
+import com.android.systemui.statusbar.data.repository.LightBarControllerStore
 import com.android.systemui.statusbar.phone.LightBarController
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProviderImpl
@@ -55,26 +56,26 @@
  *   [com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule], etc.).
  */
 @Module(includes = [StatusBarDataLayerModule::class, SystemBarUtilsProxyImpl.Module::class])
-abstract class StatusBarModule {
+interface StatusBarModule {
 
     @Binds
     @IntoMap
     @ClassKey(OngoingCallController::class)
-    abstract fun bindOngoingCallController(impl: OngoingCallController): CoreStartable
+    fun bindOngoingCallController(impl: OngoingCallController): CoreStartable
 
     @Binds
     @IntoMap
     @ClassKey(LightBarController::class)
-    abstract fun bindLightBarController(impl: LightBarController): CoreStartable
+    fun lightBarControllerAsCoreStartable(controller: LightBarController): CoreStartable
 
     @Binds
     @IntoMap
     @ClassKey(StatusBarSignalPolicy::class)
-    abstract fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable
+    fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable
 
     @Binds
     @SysUISingleton
-    abstract fun statusBarWindowControllerFactory(
+    fun statusBarWindowControllerFactory(
         implFactory: StatusBarWindowControllerImpl.Factory
     ): StatusBarWindowController.Factory
 
@@ -82,6 +83,12 @@
 
         @Provides
         @SysUISingleton
+        fun lightBarController(store: LightBarControllerStore): LightBarController {
+            return store.defaultDisplay
+        }
+
+        @Provides
+        @SysUISingleton
         fun windowControllerStore(
             multiDisplayImplLazy: Lazy<MultiDisplayStatusBarWindowControllerStore>,
             singleDisplayImplLazy: Lazy<SingleDisplayStatusBarWindowControllerStore>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
index c416bf7..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,23 +15,29 @@
  */
 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
 import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerModule
 import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStoreModule
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryModule
+import com.android.systemui.statusbar.data.repository.SystemEventChipAnimationControllerStoreModule
 import com.android.systemui.statusbar.phone.data.StatusBarPhoneDataLayerModule
 import dagger.Module
 
 @Module(
     includes =
         [
+            DarkIconDispatcherStoreModule::class,
             KeyguardStatusBarRepositoryModule::class,
+            LightBarControllerStoreModule::class,
             RemoteInputRepositoryModule::class,
             StatusBarConfigurationControllerModule::class,
             StatusBarContentInsetsProviderStoreModule::class,
             StatusBarModeRepositoryModule::class,
             StatusBarPhoneDataLayerModule::class,
+            SystemEventChipAnimationControllerStoreModule::class,
         ]
 )
 object StatusBarDataLayerModule
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
new file mode 100644
index 0000000..e498755
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.DisplayScopeRepository
+import com.android.systemui.display.data.repository.PerDisplayStore
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.statusbar.phone.LightBarController
+import com.android.systemui.statusbar.phone.LightBarControllerImpl
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** Provides per display instances of [LightBarController]. */
+interface LightBarControllerStore : PerDisplayStore<LightBarController>
+
+@SysUISingleton
+class LightBarControllerStoreImpl
+@Inject
+constructor(
+    @Background backgroundApplicationScope: CoroutineScope,
+    displayRepository: DisplayRepository,
+    private val factory: LightBarControllerImpl.Factory,
+    private val displayScopeRepository: DisplayScopeRepository,
+    private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
+    private val darkIconDispatcherStore: DarkIconDispatcherStore,
+) :
+    LightBarControllerStore,
+    PerDisplayStoreImpl<LightBarController>(backgroundApplicationScope, displayRepository) {
+
+    override fun createInstanceForDisplay(displayId: Int): LightBarController {
+        return factory
+            .create(
+                displayId,
+                displayScopeRepository.scopeForDisplay(displayId),
+                darkIconDispatcherStore.forDisplay(displayId),
+                statusBarModeRepositoryStore.forDisplay(displayId),
+            )
+            .also { it.start() }
+    }
+
+    override suspend fun onDisplayRemovalAction(instance: LightBarController) {
+        instance.stop()
+    }
+
+    override val instanceClass = LightBarController::class.java
+}
+
+@Module
+interface LightBarControllerStoreModule {
+
+    @Binds fun store(impl: LightBarControllerStoreImpl): LightBarControllerStore
+
+    @Binds
+    @IntoMap
+    @ClassKey(LightBarControllerStore::class)
+    fun storeAsCoreStartable(impl: LightBarControllerStoreImpl): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt
new file mode 100644
index 0000000..a1f5655
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt
@@ -0,0 +1,99 @@
+/*
+ * 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 android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+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.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.events.PrivacyDotWindowController
+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
+
+/** Providers per display instances of [PrivacyDotWindowController]. */
+interface PrivacyDotWindowControllerStore : PerDisplayStore<PrivacyDotWindowController>
+
+@SysUISingleton
+class PrivacyDotWindowControllerStoreImpl
+@Inject
+constructor(
+    @Background backgroundApplicationScope: CoroutineScope,
+    displayRepository: DisplayRepository,
+    private val windowControllerFactory: PrivacyDotWindowController.Factory,
+    private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
+    private val privacyDotViewControllerStore: PrivacyDotViewControllerStore,
+    private val viewCaptureAwareWindowManagerFactory: ViewCaptureAwareWindowManager.Factory,
+) :
+    PrivacyDotWindowControllerStore,
+    PerDisplayStoreImpl<PrivacyDotWindowController>(backgroundApplicationScope, displayRepository) {
+
+    init {
+        StatusBarConnectedDisplays.assertInNewMode()
+    }
+
+    override fun createInstanceForDisplay(displayId: Int): PrivacyDotWindowController {
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            throw IllegalArgumentException("This class should only be used for connected displays")
+        }
+        val displayWindowProperties =
+            displayWindowPropertiesRepository.get(displayId, TYPE_NAVIGATION_BAR_PANEL)
+        return windowControllerFactory.create(
+            displayId = displayId,
+            privacyDotViewController = privacyDotViewControllerStore.forDisplay(displayId),
+            viewCaptureAwareWindowManager =
+                viewCaptureAwareWindowManagerFactory.create(displayWindowProperties.windowManager),
+            inflater = displayWindowProperties.layoutInflater,
+        )
+    }
+
+    override val instanceClass = PrivacyDotWindowController::class.java
+}
+
+@Module
+interface PrivacyDotWindowControllerStoreModule {
+
+    @Binds fun store(impl: PrivacyDotWindowControllerStoreImpl): PrivacyDotWindowControllerStore
+
+    companion object {
+        @Provides
+        @SysUISingleton
+        @IntoMap
+        @ClassKey(PrivacyDotWindowControllerStore::class)
+        fun storeAsCoreStartable(
+            storeLazy: Lazy<PrivacyDotWindowControllerStoreImpl>
+        ): CoreStartable {
+            return if (StatusBarConnectedDisplays.isEnabled) {
+                storeLazy.get()
+            } else {
+                CoreStartable.NOP
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
index 9af4b8c..cd1e2ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
@@ -16,16 +16,21 @@
 
 package com.android.systemui.statusbar.data.repository
 
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.statusbar.NotificationRemoteInputManager
 import com.android.systemui.statusbar.RemoteInputController
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.stateIn
 
 /**
  * Repository used for tracking the state of notification remote input (e.g. when the user presses
@@ -42,30 +47,50 @@
     val remoteInputRowBottomBound: Flow<Float?>
 
     fun setRemoteInputRowBottomBound(bottom: Float?)
+
+    /** Close any active remote inputs */
+    fun closeRemoteInputs()
 }
 
 @SysUISingleton
 class RemoteInputRepositoryImpl
 @Inject
-constructor(private val notificationRemoteInputManager: NotificationRemoteInputManager) :
-    RemoteInputRepository {
-    override val isRemoteInputActive: Flow<Boolean> = conflatedCallbackFlow {
-        trySend(false) // initial value is false
+constructor(
+    @Application applicationScope: CoroutineScope,
+    private val notificationRemoteInputManager: NotificationRemoteInputManager,
+) : RemoteInputRepository {
+    private val _isRemoteInputActive = conflatedCallbackFlow {
         val callback =
             object : RemoteInputController.Callback {
                 override fun onRemoteInputActive(active: Boolean) {
                     trySend(active)
                 }
             }
+        trySend(notificationRemoteInputManager.isRemoteInputActive)
         notificationRemoteInputManager.addControllerCallback(callback)
         awaitClose { notificationRemoteInputManager.removeControllerCallback(callback) }
     }
 
+    override val isRemoteInputActive =
+        if (SceneContainerFlag.isEnabled) {
+            _isRemoteInputActive.stateIn(
+                applicationScope,
+                SharingStarted.WhileSubscribed(),
+                notificationRemoteInputManager.isRemoteInputActive,
+            )
+        } else {
+            _isRemoteInputActive
+        }
+
     override val remoteInputRowBottomBound = MutableStateFlow<Float?>(null)
 
     override fun setRemoteInputRowBottomBound(bottom: Float?) {
         remoteInputRowBottomBound.value = bottom
     }
+
+    override fun closeRemoteInputs() {
+        notificationRemoteInputManager.closeRemoteInputs()
+    }
 }
 
 @Module
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
index 44bee1d..cc91e2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
@@ -27,7 +27,7 @@
 import android.view.WindowInsetsController.Appearance
 import com.android.internal.statusbar.LetterboxDetails
 import com.android.internal.view.AppearanceRegion
-import com.android.systemui.Dumpable
+import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
@@ -59,7 +59,7 @@
  * Note: These status bar modes are status bar *window* states that are sent to us from
  * WindowManager, not determined internally.
  */
-interface StatusBarModePerDisplayRepository {
+interface StatusBarModePerDisplayRepository : OnStatusBarViewInitializedListener, CoreStartable {
     /**
      * True if the status bar window is showing transiently and will disappear soon, and false
      * otherwise. ("Otherwise" in this case means the status bar is persistently hidden OR
@@ -104,6 +104,12 @@
      *   determined internally instead.
      */
     fun clearTransient()
+
+    /**
+     * Called when the [StatusBarModePerDisplayRepository] should stop doing any work and clean up
+     * if needed.
+     */
+    fun stop()
 }
 
 class StatusBarModePerDisplayRepositoryImpl
@@ -114,7 +120,7 @@
     private val commandQueue: CommandQueue,
     private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator,
     ongoingCallRepository: OngoingCallRepository,
-) : StatusBarModePerDisplayRepository, OnStatusBarViewInitializedListener, Dumpable {
+) : StatusBarModePerDisplayRepository {
 
     private val commandQueueCallback =
         object : CommandQueue.Callbacks {
@@ -163,10 +169,14 @@
             }
         }
 
-    fun start() {
+    override fun start() {
         commandQueue.addCallback(commandQueueCallback)
     }
 
+    override fun stop() {
+        commandQueue.removeCallback(commandQueueCallback)
+    }
+
     private val _isTransientShown = MutableStateFlow(false)
     override val isTransientShown: StateFlow<Boolean> = _isTransientShown.asStateFlow()
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
index 2c9fa25..143e998 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
@@ -18,21 +18,54 @@
 
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.DisplayId
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.PerDisplayStore
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.core.StatusBarInitializer
 import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
 import dagger.Binds
+import dagger.Lazy
 import dagger.Module
+import dagger.Provides
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
 import dagger.multibindings.IntoSet
 import java.io.PrintWriter
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 
-interface StatusBarModeRepositoryStore {
-    val defaultDisplay: StatusBarModePerDisplayRepository
+interface StatusBarModeRepositoryStore : PerDisplayStore<StatusBarModePerDisplayRepository>
 
-    fun forDisplay(displayId: Int): StatusBarModePerDisplayRepository
+@SysUISingleton
+class MultiDisplayStatusBarModeRepositoryStore
+@Inject
+constructor(
+    @Background backgroundApplicationScope: CoroutineScope,
+    private val factory: StatusBarModePerDisplayRepositoryFactory,
+    displayRepository: DisplayRepository,
+) :
+    StatusBarModeRepositoryStore,
+    PerDisplayStoreImpl<StatusBarModePerDisplayRepository>(
+        backgroundApplicationScope,
+        displayRepository,
+    ) {
+
+    init {
+        StatusBarConnectedDisplays.assertInNewMode()
+    }
+
+    override fun createInstanceForDisplay(displayId: Int): StatusBarModePerDisplayRepository {
+        return factory.create(displayId).also { it.start() }
+    }
+
+    override suspend fun onDisplayRemovalAction(instance: StatusBarModePerDisplayRepository) {
+        instance.stop()
+    }
+
+    override val instanceClass = StatusBarModePerDisplayRepository::class.java
 }
 
 @SysUISingleton
@@ -47,10 +80,7 @@
     StatusBarInitializer.OnStatusBarViewInitializedListener {
     override val defaultDisplay = factory.create(displayId)
 
-    override fun forDisplay(displayId: Int) =
-        // TODO(b/369337087): implement per display status bar modes.
-        //  For now just use default display instance.
-        defaultDisplay
+    override fun forDisplay(displayId: Int) = defaultDisplay
 
     override fun start() {
         defaultDisplay.start()
@@ -66,17 +96,40 @@
 }
 
 @Module
-interface StatusBarModeRepositoryModule {
-    @Binds fun bindImpl(impl: StatusBarModeRepositoryImpl): StatusBarModeRepositoryStore
-
-    @Binds
-    @IntoMap
-    @ClassKey(StatusBarModeRepositoryStore::class)
-    fun bindCoreStartable(impl: StatusBarModeRepositoryImpl): CoreStartable
-
+abstract class StatusBarModeRepositoryModule {
     @Binds
     @IntoSet
-    fun bindViewInitListener(
+    abstract fun bindViewInitListener(
         impl: StatusBarModeRepositoryImpl
     ): StatusBarInitializer.OnStatusBarViewInitializedListener
+
+    companion object {
+        @Provides
+        @SysUISingleton
+        @IntoMap
+        @ClassKey(StatusBarModeRepositoryStore::class)
+        fun storeAsCoreStartable(
+            singleDisplayLazy: Lazy<StatusBarModeRepositoryImpl>,
+            multiDisplayLazy: Lazy<MultiDisplayStatusBarModeRepositoryStore>,
+        ): CoreStartable {
+            return if (StatusBarConnectedDisplays.isEnabled) {
+                multiDisplayLazy.get()
+            } else {
+                singleDisplayLazy.get()
+            }
+        }
+
+        @Provides
+        @SysUISingleton
+        fun store(
+            singleDisplayLazy: Lazy<StatusBarModeRepositoryImpl>,
+            multiDisplayLazy: Lazy<MultiDisplayStatusBarModeRepositoryStore>,
+        ): StatusBarModeRepositoryStore {
+            return if (StatusBarConnectedDisplays.isEnabled) {
+                multiDisplayLazy.get()
+            } else {
+                singleDisplayLazy.get()
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt
new file mode 100644
index 0000000..7760f58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.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.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.events.SystemEventChipAnimationController
+import com.android.systemui.statusbar.events.SystemEventChipAnimationControllerImpl
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
+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 [SystemEventChipAnimationController]. */
+interface SystemEventChipAnimationControllerStore :
+    PerDisplayStore<SystemEventChipAnimationController>
+
+@SysUISingleton
+class SystemEventChipAnimationControllerStoreImpl
+@Inject
+constructor(
+    @Background backgroundApplicationScope: CoroutineScope,
+    displayRepository: DisplayRepository,
+    private val factory: SystemEventChipAnimationControllerImpl.Factory,
+    private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
+    private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
+    private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore,
+) :
+    SystemEventChipAnimationControllerStore,
+    PerDisplayStoreImpl<SystemEventChipAnimationController>(
+        backgroundApplicationScope,
+        displayRepository,
+    ) {
+
+    init {
+        StatusBarConnectedDisplays.assertInNewMode()
+    }
+
+    override fun createInstanceForDisplay(displayId: Int): SystemEventChipAnimationController {
+        return factory.create(
+            displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR).context,
+            statusBarWindowControllerStore.forDisplay(displayId),
+            statusBarContentInsetsProviderStore.forDisplay(displayId),
+        )
+    }
+
+    override suspend fun onDisplayRemovalAction(instance: SystemEventChipAnimationController) {
+        instance.stop()
+    }
+
+    override val instanceClass = SystemEventChipAnimationController::class.java
+}
+
+@Module
+interface SystemEventChipAnimationControllerStoreModule {
+
+    @Binds
+    @SysUISingleton
+    fun store(
+        impl: SystemEventChipAnimationControllerStoreImpl
+    ): SystemEventChipAnimationControllerStore
+
+    companion object {
+        @Provides
+        @SysUISingleton
+        @IntoMap
+        @ClassKey(SystemEventChipAnimationControllerStore::class)
+        fun storeAsCoreStartable(
+            implLazy: Lazy<SystemEventChipAnimationControllerStoreImpl>
+        ): CoreStartable {
+            return if (StatusBarConnectedDisplays.isEnabled) {
+                implLazy.get()
+            } else {
+                CoreStartable.NOP
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
index b83b0cc..ea8c3e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
@@ -40,4 +40,9 @@
     fun setRemoteInputRowBottomBound(bottom: Float?) {
         remoteInputRepository.setRemoteInputRowBottomBound(bottom)
     }
+
+    /** Close any active remote inputs */
+    fun closeRemoteInputs() {
+        remoteInputRepository.closeRemoteInputs()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt
new file mode 100644
index 0000000..f2bb7b1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.events
+
+import androidx.core.animation.Animator
+import androidx.core.animation.AnimatorSet
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.data.repository.SystemEventChipAnimationControllerStore
+import javax.inject.Inject
+
+/**
+ * A [SystemEventChipAnimationController] that handles animations for multiple displays. It
+ * delegates the animation tasks to individual controllers for each display.
+ */
+@SysUISingleton
+class MultiDisplaySystemEventChipAnimationController
+@Inject
+constructor(
+    private val displayRepository: DisplayRepository,
+    private val controllerStore: SystemEventChipAnimationControllerStore,
+) : SystemEventChipAnimationController {
+
+    init {
+        StatusBarConnectedDisplays.assertInNewMode()
+    }
+
+    override fun prepareChipAnimation(viewCreator: ViewCreator) {
+        forEachController { it.prepareChipAnimation(viewCreator) }
+    }
+
+    override fun init() {
+        forEachController { it.init() }
+    }
+
+    override fun stop() {
+        forEachController { it.stop() }
+    }
+
+    override fun announceForAccessibility(contentDescriptions: String) {
+        forEachController { it.announceForAccessibility(contentDescriptions) }
+    }
+
+    override fun onSystemEventAnimationBegin(): Animator {
+        val animators = controllersForAllDisplays().map { it.onSystemEventAnimationBegin() }
+        return AnimatorSet().apply { playTogether(animators) }
+    }
+
+    override fun onSystemEventAnimationFinish(hasPersistentDot: Boolean): Animator {
+        val animators =
+            controllersForAllDisplays().map { it.onSystemEventAnimationFinish(hasPersistentDot) }
+        return AnimatorSet().apply { playTogether(animators) }
+    }
+
+    private fun forEachController(consumer: (SystemEventChipAnimationController) -> Unit) {
+        controllersForAllDisplays().forEach { consumer(it) }
+    }
+
+    private fun controllersForAllDisplays() =
+        displayRepository.displays.value.map { controllerStore.forDisplay(it.displayId) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt
new file mode 100644
index 0000000..9928ac6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.events
+
+import android.view.Display
+import android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM
+import android.view.DisplayCutout.BOUNDS_POSITION_LEFT
+import android.view.DisplayCutout.BOUNDS_POSITION_RIGHT
+import android.view.DisplayCutout.BOUNDS_POSITION_TOP
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager.LayoutParams.WRAP_CONTENT
+import android.widget.FrameLayout
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.systemui.ScreenDecorations
+import com.android.systemui.ScreenDecorationsThread
+import com.android.systemui.decor.DecorProvider
+import com.android.systemui.decor.PrivacyDotCornerDecorProviderImpl
+import com.android.systemui.decor.PrivacyDotDecorProviderFactory
+import com.android.systemui.statusbar.events.PrivacyDotCorner.BottomLeft
+import com.android.systemui.statusbar.events.PrivacyDotCorner.BottomRight
+import com.android.systemui.statusbar.events.PrivacyDotCorner.TopLeft
+import com.android.systemui.statusbar.events.PrivacyDotCorner.TopRight
+import com.android.systemui.util.containsExactly
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.Executor
+
+/**
+ * Responsible for adding the privacy dot to a window.
+ *
+ * It will create one window per corner (top left, top right, bottom left, bottom right), which are
+ * used dependant on the display's rotation.
+ */
+class PrivacyDotWindowController
+@AssistedInject
+constructor(
+    @Assisted private val displayId: Int,
+    @Assisted private val privacyDotViewController: PrivacyDotViewController,
+    @Assisted private val viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+    @Assisted private val inflater: LayoutInflater,
+    @ScreenDecorationsThread private val uiExecutor: Executor,
+    private val dotFactory: PrivacyDotDecorProviderFactory,
+) {
+
+    fun start() {
+        uiExecutor.execute { startOnUiThread() }
+    }
+
+    private fun startOnUiThread() {
+        val providers = dotFactory.providers
+
+        val topLeft = providers.inflate(BOUNDS_POSITION_TOP, BOUNDS_POSITION_LEFT)
+        val topRight = providers.inflate(BOUNDS_POSITION_TOP, BOUNDS_POSITION_RIGHT)
+        val bottomLeft = providers.inflate(BOUNDS_POSITION_BOTTOM, BOUNDS_POSITION_LEFT)
+        val bottomRight = providers.inflate(BOUNDS_POSITION_BOTTOM, BOUNDS_POSITION_RIGHT)
+
+        topLeft.addToWindow(TopLeft)
+        topRight.addToWindow(TopRight)
+        bottomLeft.addToWindow(BottomLeft)
+        bottomRight.addToWindow(BottomRight)
+
+        privacyDotViewController.initialize(topLeft, topRight, bottomLeft, bottomRight)
+    }
+
+    private fun List<DecorProvider>.inflate(alignedBound1: Int, alignedBound2: Int): View {
+        val provider =
+            first { it.alignedBounds.containsExactly(alignedBound1, alignedBound2) }
+                as PrivacyDotCornerDecorProviderImpl
+        return inflater.inflate(/* resource= */ provider.layoutId, /* root= */ null)
+    }
+
+    private fun View.addToWindow(corner: PrivacyDotCorner) {
+        val excludeFromScreenshots = displayId == Display.DEFAULT_DISPLAY
+        val params =
+            ScreenDecorations.getWindowLayoutBaseParams(excludeFromScreenshots).apply {
+                width = WRAP_CONTENT
+                height = WRAP_CONTENT
+                gravity = corner.rotatedCorner(context.display.rotation).gravity
+                title = "PrivacyDot${corner.title}$displayId"
+            }
+        // PrivacyDotViewController expects the dot view to have a FrameLayout parent.
+        val rootView = FrameLayout(context)
+        rootView.addView(this)
+        viewCaptureAwareWindowManager.addView(rootView, params)
+    }
+
+    @AssistedFactory
+    fun interface Factory {
+        fun create(
+            displayId: Int,
+            privacyDotViewController: PrivacyDotViewController,
+            viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+            inflater: LayoutInflater,
+        ): PrivacyDotWindowController
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
index b286605..1038ad4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -32,13 +32,16 @@
 import androidx.core.animation.ValueAnimator
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Default
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import com.android.systemui.util.animation.AnimationUtil.Companion.frames
+import dagger.Lazy
 import dagger.Module
 import dagger.Provides
 import dagger.assisted.Assisted
@@ -57,6 +60,8 @@
 
     fun init()
 
+    fun stop()
+
     /** Announces [contentDescriptions] for accessibility. */
     fun announceForAccessibility(contentDescriptions: String)
 
@@ -287,6 +292,26 @@
         return animSet
     }
 
+    private val statusBarContentInsetsChangedListener =
+        object : StatusBarContentInsetsChangedListener {
+            override fun onStatusBarContentInsetsChanged() {
+                val newContentArea =
+                    contentInsetsProvider.getStatusBarContentAreaForCurrentRotation()
+                updateDimens(newContentArea)
+
+                // If we are currently animating, we have to re-solve for the chip bounds. If
+                // we're not animating then [prepareChipAnimation] will take care of it for us.
+                currentAnimatedView?.let {
+                    updateChipBounds(it, newContentArea)
+                    // Since updateCurrentAnimatedView can only be called during an animation,
+                    // we have to create a no-op animator here to apply the new chip bounds.
+                    val animator = ValueAnimator.ofInt(0, 1).setDuration(0)
+                    animator.addUpdateListener { updateCurrentAnimatedView() }
+                    animator.start()
+                }
+            }
+        }
+
     override fun init() {
         initialized = true
         themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
@@ -303,28 +328,11 @@
 
         // Use contentInsetsProvider rather than configuration controller, since we only care
         // about status bar dimens
-        contentInsetsProvider.addCallback(
-            object : StatusBarContentInsetsChangedListener {
-                override fun onStatusBarContentInsetsChanged() {
-                    val newContentArea =
-                        contentInsetsProvider.getStatusBarContentAreaForCurrentRotation()
-                    updateDimens(newContentArea)
+        contentInsetsProvider.addCallback(statusBarContentInsetsChangedListener)
+    }
 
-                    // If we are currently animating, we have to re-solve for the chip bounds. If
-                    // we're
-                    // not animating then [prepareChipAnimation] will take care of it for us
-                    currentAnimatedView?.let {
-                        updateChipBounds(it, newContentArea)
-                        // Since updateCurrentAnimatedView can only be called during an animation,
-                        // we
-                        // have to create a dummy animator here to apply the new chip bounds
-                        val animator = ValueAnimator.ofInt(0, 1).setDuration(0)
-                        animator.addUpdateListener { updateCurrentAnimatedView() }
-                        animator.start()
-                    }
-                }
-            }
-        )
+    override fun stop() {
+        contentInsetsProvider.removeCallback(statusBarContentInsetsChangedListener)
     }
 
     override fun announceForAccessibility(contentDescriptions: String) {
@@ -418,7 +426,7 @@
     }
 
     @AssistedFactory
-    interface Factory {
+    fun interface Factory {
         fun create(
             context: Context,
             statusBarWindowController: StatusBarWindowController,
@@ -446,20 +454,36 @@
 private const val RIGHT = 2
 
 @Module
-object SystemEventChipAnimationControllerModule {
+interface SystemEventChipAnimationControllerModule {
 
-    @Provides
-    @SysUISingleton
-    fun controller(
-        factory: SystemEventChipAnimationControllerImpl.Factory,
-        context: Context,
-        statusBarWindowControllerStore: StatusBarWindowControllerStore,
-        contentInsetsProviderStore: StatusBarContentInsetsProviderStore,
-    ): SystemEventChipAnimationController {
-        return factory.create(
-            context,
-            statusBarWindowControllerStore.defaultDisplay,
-            contentInsetsProviderStore.defaultDisplay,
-        )
+    companion object {
+        @Provides
+        @Default
+        @SysUISingleton
+        fun defaultController(
+            factory: SystemEventChipAnimationControllerImpl.Factory,
+            context: Context,
+            statusBarWindowControllerStore: StatusBarWindowControllerStore,
+            contentInsetsProviderStore: StatusBarContentInsetsProviderStore,
+        ): SystemEventChipAnimationController {
+            return factory.create(
+                context,
+                statusBarWindowControllerStore.defaultDisplay,
+                contentInsetsProviderStore.defaultDisplay,
+            )
+        }
+
+        @Provides
+        @SysUISingleton
+        fun controller(
+            @Default defaultLazy: Lazy<SystemEventChipAnimationController>,
+            multiDisplayLazy: Lazy<MultiDisplaySystemEventChipAnimationController>,
+        ): SystemEventChipAnimationController {
+            return if (StatusBarConnectedDisplays.isEnabled) {
+                multiDisplayLazy.get()
+            } else {
+                defaultLazy.get()
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 77ec65b..9a779300 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -49,12 +49,12 @@
 @Inject
 constructor(
     private val launcherApps: LauncherApps,
-    private val conversationNotificationManager: ConversationNotificationManager
+    private val conversationNotificationManager: ConversationNotificationManager,
 ) {
     fun processNotification(
         entry: NotificationEntry,
         recoveredBuilder: Notification.Builder,
-        logger: NotificationRowContentBinderLogger
+        logger: NotificationRowContentBinderLogger,
     ): Notification.MessagingStyle? {
         val messagingStyle = recoveredBuilder.style as? Notification.MessagingStyle ?: return null
         messagingStyle.conversationType =
@@ -83,7 +83,7 @@
     private val notifCollection: CommonNotifCollection,
     private val bindEventManager: BindEventManager,
     private val headsUpManager: HeadsUpManager,
-    private val statusBarStateController: StatusBarStateController
+    private val statusBarStateController: StatusBarStateController,
 ) {
 
     private var isStatusBarExpanded = false
@@ -118,7 +118,8 @@
             .flatMap { layout -> layout.allViews.asSequence() }
             .flatMap { view ->
                 (view as? ConversationLayout)?.messagingGroups?.asSequence()
-                    ?: (view as? MessagingLayout)?.messagingGroups?.asSequence() ?: emptySequence()
+                    ?: (view as? MessagingLayout)?.messagingGroups?.asSequence()
+                    ?: emptySequence()
             }
             .flatMap { messagingGroup -> messagingGroup.messageContainer.children }
             .mapNotNull { view ->
@@ -144,7 +145,7 @@
     bindEventManager: BindEventManager,
     @ShadeDisplayAware private val context: Context,
     private val notifCollection: CommonNotifCollection,
-    @Main private val mainHandler: Handler
+    @Main private val mainHandler: Handler,
 ) {
     // Need this state to be thread safe, since it's accessed from the ui thread
     // (NotificationEntryListener) and a bg thread (NotificationRowContentBinder)
@@ -175,7 +176,7 @@
                             // the notif has been moved in the shade
                             mainHandler.postDelayed(
                                 { layout.setIsImportantConversation(important, true) },
-                                IMPORTANCE_ANIMATION_DELAY.toLong()
+                                IMPORTANCE_ANIMATION_DELAY.toLong(),
                             )
                         } else {
                             layout.setIsImportantConversation(important, false)
@@ -233,8 +234,7 @@
                     state?.run {
                         if (shouldIncrementUnread(recoveredBuilder)) unreadCount + 1
                         else unreadCount
-                    }
-                        ?: 1
+                    } ?: 1
                 ConversationState(newCount, entry.sbn.notification)
             }!!
             .unreadCount
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/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/collection/coordinator/RowAppearanceCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
index de868d4..df8e56e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
@@ -33,29 +33,30 @@
  * they are fully attached.
  */
 @CoordinatorScope
-class RowAppearanceCoordinator @Inject internal constructor(
+class RowAppearanceCoordinator
+@Inject
+internal constructor(
     @ShadeDisplayAware context: Context,
     private var mAssistantFeedbackController: AssistantFeedbackController,
-    private var mSectionStyleProvider: SectionStyleProvider
+    private var mSectionStyleProvider: SectionStyleProvider,
 ) : Coordinator {
 
     private var entryToExpand: NotificationEntry? = null
 
     /**
-     * `true` if notifications not part of a group should by default be rendered in their
-     * expanded state. If `false`, then only the first notification will be expanded if
-     * possible.
+     * `true` if notifications not part of a group should by default be rendered in their expanded
+     * state. If `false`, then only the first notification will be expanded if possible.
      */
     private val mAlwaysExpandNonGroupedNotification =
         context.resources.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications)
 
     /**
-     * `true` if the first non-group expandable notification should be expanded automatically
-     * when possible. If `false`, then the first non-group expandable notification should not
-     * be expanded.
+     * `true` if the first non-group expandable notification should be expanded automatically when
+     * possible. If `false`, then the first non-group expandable notification should not be
+     * expanded.
      */
     private val mAutoExpandFirstNotification =
-            context.resources.getBoolean(R.bool.config_autoExpandFirstNotification)
+        context.resources.getBoolean(R.bool.config_autoExpandFirstNotification)
 
     override fun attach(pipeline: NotifPipeline) {
         pipeline.addOnBeforeRenderListListener(::onBeforeRenderList)
@@ -63,17 +64,20 @@
     }
 
     private fun onBeforeRenderList(list: List<ListEntry>) {
-        entryToExpand = list.firstOrNull()?.representativeEntry?.takeIf { entry ->
-            !mSectionStyleProvider.isMinimizedSection(entry.section!!)
-        }
+        entryToExpand =
+            list.firstOrNull()?.representativeEntry?.takeIf { entry ->
+                !mSectionStyleProvider.isMinimizedSection(entry.section!!)
+            }
     }
 
     private fun onAfterRenderEntry(entry: NotificationEntry, controller: NotifRowController) {
         // If mAlwaysExpandNonGroupedNotification is false, then only expand the
         // very first notification if it's not a child of grouped notifications and when
         // mAutoExpandFirstNotification is true.
-        controller.setSystemExpanded(mAlwaysExpandNonGroupedNotification ||
-                (mAutoExpandFirstNotification && entry == entryToExpand))
+        controller.setSystemExpanded(
+            mAlwaysExpandNonGroupedNotification ||
+                (mAutoExpandFirstNotification && entry == entryToExpand)
+        )
         // Show/hide the feedback icon
         controller.setFeedbackIcon(mAssistantFeedbackController.getFeedbackIcon(entry))
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
index db778b80..2d1eccd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
@@ -17,10 +17,12 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import android.util.Log
+import com.android.app.tracing.traceSection
 import com.android.internal.widget.MessagingGroup
 import com.android.internal.widget.MessagingMessage
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener
 import com.android.systemui.statusbar.notification.ColorUpdateLogger
@@ -29,17 +31,17 @@
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.Compile
-import com.android.app.tracing.traceSection
-import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 /**
- * A coordinator which ensures that notifications within the new pipeline are correctly inflated
- * for the current uiMode and screen properties; additionally deferring those changes when a user
- * change is in progress until that process has completed.
+ * A coordinator which ensures that notifications within the new pipeline are correctly inflated for
+ * the current uiMode and screen properties; additionally deferring those changes when a user change
+ * is in progress until that process has completed.
  */
 @CoordinatorScope
-class ViewConfigCoordinator @Inject internal constructor(
+class ViewConfigCoordinator
+@Inject
+internal constructor(
     @ShadeDisplayAware private val mConfigurationController: ConfigurationController,
     private val mLockscreenUserManager: NotificationLockscreenUserManager,
     private val mGutsManager: NotificationGutsManager,
@@ -52,28 +54,32 @@
     private var mDispatchUiModeChangeOnUserSwitched = false
     private var mPipeline: NotifPipeline? = null
 
-    private val mKeyguardUpdateCallback = object : KeyguardUpdateMonitorCallback() {
-        override fun onUserSwitching(userId: Int) {
-            colorUpdateLogger.logTriggerEvent("VCC.mKeyguardUpdateCallback.onUserSwitching()")
-            log { "ViewConfigCoordinator.onUserSwitching(userId=$userId)" }
-            mIsSwitchingUser = true
+    private val mKeyguardUpdateCallback =
+        object : KeyguardUpdateMonitorCallback() {
+            override fun onUserSwitching(userId: Int) {
+                colorUpdateLogger.logTriggerEvent("VCC.mKeyguardUpdateCallback.onUserSwitching()")
+                log { "ViewConfigCoordinator.onUserSwitching(userId=$userId)" }
+                mIsSwitchingUser = true
+            }
+
+            override fun onUserSwitchComplete(userId: Int) {
+                colorUpdateLogger.logTriggerEvent(
+                    "VCC.mKeyguardUpdateCallback.onUserSwitchComplete()"
+                )
+                log { "ViewConfigCoordinator.onUserSwitchComplete(userId=$userId)" }
+                mIsSwitchingUser = false
+                applyChangesOnUserSwitched()
+            }
         }
 
-        override fun onUserSwitchComplete(userId: Int) {
-            colorUpdateLogger.logTriggerEvent("VCC.mKeyguardUpdateCallback.onUserSwitchComplete()")
-            log { "ViewConfigCoordinator.onUserSwitchComplete(userId=$userId)" }
-            mIsSwitchingUser = false
-            applyChangesOnUserSwitched()
+    private val mUserChangedListener =
+        object : UserChangedListener {
+            override fun onUserChanged(userId: Int) {
+                colorUpdateLogger.logTriggerEvent("VCC.mUserChangedListener.onUserChanged()")
+                log { "ViewConfigCoordinator.onUserChanged(userId=$userId)" }
+                applyChangesOnUserSwitched()
+            }
         }
-    }
-
-    private val mUserChangedListener = object : UserChangedListener {
-        override fun onUserChanged(userId: Int) {
-            colorUpdateLogger.logTriggerEvent("VCC.mUserChangedListener.onUserChanged()")
-            log { "ViewConfigCoordinator.onUserChanged(userId=$userId)" }
-            applyChangesOnUserSwitched()
-        }
-    }
 
     override fun attach(pipeline: NotifPipeline) {
         mPipeline = pipeline
@@ -87,8 +93,8 @@
         log {
             val keyguardIsSwitchingUser = mKeyguardUpdateMonitor.isSwitchingUser
             "ViewConfigCoordinator.onDensityOrFontScaleChanged()" +
-                    " isSwitchingUser=$mIsSwitchingUser" +
-                    " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser"
+                " isSwitchingUser=$mIsSwitchingUser" +
+                " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser"
         }
         MessagingMessage.dropCache()
         MessagingGroup.dropCache()
@@ -104,8 +110,8 @@
         log {
             val keyguardIsSwitchingUser = mKeyguardUpdateMonitor.isSwitchingUser
             "ViewConfigCoordinator.onUiModeChanged()" +
-                    " isSwitchingUser=$mIsSwitchingUser" +
-                    " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser"
+                " isSwitchingUser=$mIsSwitchingUser" +
+                " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser"
         }
         if (!mIsSwitchingUser) {
             updateNotificationsOnUiModeChanged()
@@ -132,13 +138,13 @@
     }
 
     private fun updateNotificationsOnUiModeChanged() {
-        colorUpdateLogger.logEvent("VCC.updateNotificationsOnUiModeChanged()",
-                "mode=" + mConfigurationController.nightModeName)
+        colorUpdateLogger.logEvent(
+            "VCC.updateNotificationsOnUiModeChanged()",
+            "mode=" + mConfigurationController.nightModeName,
+        )
         log { "ViewConfigCoordinator.updateNotificationsOnUiModeChanged()" }
         traceSection("updateNotifOnUiModeChanged") {
-            mPipeline?.allNotifs?.forEach { entry ->
-                entry.row?.onUiModeChanged()
-            }
+            mPipeline?.allNotifs?.forEach { entry -> entry.row?.onUiModeChanged() }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
index cab4c1c..3c838e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -18,6 +18,8 @@
 
 import android.content.Context
 import android.view.View
+import com.android.app.tracing.traceSection
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -26,8 +28,6 @@
 import com.android.systemui.statusbar.notification.collection.PipelineDumper
 import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
-import com.android.app.tracing.traceSection
-import com.android.systemui.shade.ShadeDisplayAware
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -36,7 +36,9 @@
  * Responsible for building and applying the "shade node spec": the list (tree) of things that
  * currently populate the notification shade.
  */
-class ShadeViewManager @AssistedInject constructor(
+class ShadeViewManager
+@AssistedInject
+constructor(
     @ShadeDisplayAware context: Context,
     @Assisted listContainer: NotificationListContainer,
     @Assisted private val stackController: NotifStackController,
@@ -45,13 +47,19 @@
     sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
     nodeSpecBuilderLogger: NodeSpecBuilderLogger,
     shadeViewDifferLogger: ShadeViewDifferLogger,
-    private val viewBarn: NotifViewBarn
+    private val viewBarn: NotifViewBarn,
 ) : PipelineDumpable {
     // We pass a shim view here because the listContainer may not actually have a view associated
     // with it and the differ never actually cares about the root node's view.
     private val rootController = RootNodeController(listContainer, View(context))
-    private val specBuilder = NodeSpecBuilder(mediaContainerController, featureManager,
-        sectionHeaderVisibilityProvider, viewBarn, nodeSpecBuilderLogger)
+    private val specBuilder =
+        NodeSpecBuilder(
+            mediaContainerController,
+            featureManager,
+            sectionHeaderVisibilityProvider,
+            viewBarn,
+            nodeSpecBuilderLogger,
+        )
     private val viewDiffer = ShadeViewDiffer(rootController, shadeViewDifferLogger)
 
     /** Method for attaching this manager to the pipeline. */
@@ -59,34 +67,36 @@
         renderStageManager.setViewRenderer(viewRenderer)
     }
 
-    override fun dumpPipeline(d: PipelineDumper) = with(d) {
-        dump("rootController", rootController)
-        dump("specBuilder", specBuilder)
-        dump("viewDiffer", viewDiffer)
-    }
-
-    private val viewRenderer = object : NotifViewRenderer {
-
-        override fun onRenderList(notifList: List<ListEntry>) {
-            traceSection("ShadeViewManager.onRenderList") {
-                viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList))
-            }
+    override fun dumpPipeline(d: PipelineDumper) =
+        with(d) {
+            dump("rootController", rootController)
+            dump("specBuilder", specBuilder)
+            dump("viewDiffer", viewDiffer)
         }
 
-        override fun getStackController(): NotifStackController = stackController
+    private val viewRenderer =
+        object : NotifViewRenderer {
 
-        override fun getGroupController(group: GroupEntry): NotifGroupController =
-            viewBarn.requireGroupController(group.requireSummary)
+            override fun onRenderList(notifList: List<ListEntry>) {
+                traceSection("ShadeViewManager.onRenderList") {
+                    viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList))
+                }
+            }
 
-        override fun getRowController(entry: NotificationEntry): NotifRowController =
-            viewBarn.requireRowController(entry)
-    }
+            override fun getStackController(): NotifStackController = stackController
+
+            override fun getGroupController(group: GroupEntry): NotifGroupController =
+                viewBarn.requireGroupController(group.requireSummary)
+
+            override fun getRowController(entry: NotificationEntry): NotifRowController =
+                viewBarn.requireRowController(entry)
+        }
 }
 
 @AssistedFactory
 interface ShadeViewManagerFactory {
     fun create(
         listContainer: NotificationListContainer,
-        stackController: NotifStackController
+        stackController: NotifStackController,
     ): ShadeViewManager
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt
index 925d4a5..4c25129 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.dagger
 
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsModule
 import com.android.systemui.statusbar.notification.row.NotificationRowModule
 import dagger.Module
 
@@ -23,5 +24,12 @@
  * A module that includes the standard notifications classes that most SysUI variants need. Variants
  * are free to not include this module and instead write a custom notifications module.
  */
-@Module(includes = [NotificationsModule::class, NotificationRowModule::class])
+@Module(
+    includes =
+        [
+            NotificationsModule::class,
+            NotificationRowModule::class,
+            PromotedNotificationsModule::class,
+        ]
+)
 object ReferenceNotificationsModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
index 7d374b0..dbe5833 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
@@ -16,12 +16,12 @@
 
 package com.android.systemui.statusbar.notification.data
 
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.settings.SecureSettingsRepositoryModule
-import com.android.systemui.settings.SystemSettingsRepositoryModule
+import com.android.systemui.settings.UserSettingsRepositoryModule
 import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
 import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
 import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository
@@ -32,9 +32,8 @@
 import dagger.multibindings.IntoMap
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
 
-@Module(includes = [SecureSettingsRepositoryModule::class, SystemSettingsRepositoryModule::class])
+@Module(includes = [UserSettingsRepositoryModule::class])
 object NotificationSettingsRepositoryModule {
     @Provides
     @SysUISingleton
@@ -48,7 +47,8 @@
             backgroundScope,
             backgroundDispatcher,
             secureSettingsRepository,
-            systemSettingsRepository)
+            systemSettingsRepository,
+        )
 
     @Provides
     @IntoMap
@@ -57,7 +57,7 @@
     fun provideCoreStartable(
         @Application applicationScope: CoroutineScope,
         repository: NotificationSettingsRepository,
-        logger: VisualInterruptionDecisionLogger
+        logger: VisualInterruptionDecisionLogger,
     ) = CoreStartable {
         applicationScope.launch {
             repository.isCooldownEnabled.collect { value -> logger.logCooldownSetting(value) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index 697a6ce..cff5bef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -83,6 +83,9 @@
             // TODO(b/364653005): [ongoingCallNotification] should be incorporated into this flow
             // instead of being separate.
             topLevelRepresentativeNotifications
+                .map { notifs -> notifs.filter { it.isPromoted } }
+                .distinctUntilChanged()
+                .flowOn(backgroundDispatcher)
         } else {
             flowOf(emptyList())
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
index 1008451..23da90d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsProvider
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
@@ -50,6 +51,7 @@
 constructor(
     private val repository: ActiveNotificationListRepository,
     private val sectionStyleProvider: SectionStyleProvider,
+    private val promotedNotificationsProvider: PromotedNotificationsProvider,
 ) {
     /**
      * Sets the current list of rendered notification entries as displayed in the notification list.
@@ -57,7 +59,11 @@
     fun setRenderedList(entries: List<ListEntry>) {
         traceSection("RenderNotificationListInteractor.setRenderedList") {
             repository.activeNotifications.update { existingModels ->
-                buildActiveNotificationsStore(existingModels, sectionStyleProvider) {
+                buildActiveNotificationsStore(
+                    existingModels,
+                    sectionStyleProvider,
+                    promotedNotificationsProvider,
+                ) {
                     entries.forEach(::addListEntry)
                     setRankingsMap(entries)
                 }
@@ -69,13 +75,21 @@
 private fun buildActiveNotificationsStore(
     existingModels: ActiveNotificationsStore,
     sectionStyleProvider: SectionStyleProvider,
-    block: ActiveNotificationsStoreBuilder.() -> Unit
+    promotedNotificationsProvider: PromotedNotificationsProvider,
+    block: ActiveNotificationsStoreBuilder.() -> Unit,
 ): ActiveNotificationsStore =
-    ActiveNotificationsStoreBuilder(existingModels, sectionStyleProvider).apply(block).build()
+    ActiveNotificationsStoreBuilder(
+            existingModels,
+            sectionStyleProvider,
+            promotedNotificationsProvider,
+        )
+        .apply(block)
+        .build()
 
 private class ActiveNotificationsStoreBuilder(
     private val existingModels: ActiveNotificationsStore,
     private val sectionStyleProvider: SectionStyleProvider,
+    private val promotedNotificationsProvider: PromotedNotificationsProvider,
 ) {
     private val builder = ActiveNotificationsStore.Builder()
 
@@ -96,7 +110,7 @@
                         existingModels.createOrReuse(
                             key = entry.key,
                             summary = summaryModel,
-                            children = childModels
+                            children = childModels,
                         )
                     )
                 }
@@ -141,6 +155,7 @@
             key = key,
             groupKey = sbn.groupKey,
             whenTime = sbn.notification.`when`,
+            isPromoted = promotedNotificationsProvider.shouldPromote(this),
             isAmbient = sectionStyleProvider.isMinimized(this),
             isRowDismissed = isRowDismissed,
             isSilent = sectionStyleProvider.isSilent(this),
@@ -166,6 +181,7 @@
     key: String,
     groupKey: String?,
     whenTime: Long,
+    isPromoted: Boolean,
     isAmbient: Boolean,
     isRowDismissed: Boolean,
     isSilent: Boolean,
@@ -189,6 +205,7 @@
             key = key,
             groupKey = groupKey,
             whenTime = whenTime,
+            isPromoted = isPromoted,
             isAmbient = isAmbient,
             isRowDismissed = isRowDismissed,
             isSilent = isSilent,
@@ -212,6 +229,7 @@
             key = key,
             groupKey = groupKey,
             whenTime = whenTime,
+            isPromoted = isPromoted,
             isAmbient = isAmbient,
             isRowDismissed = isRowDismissed,
             isSilent = isSilent,
@@ -236,6 +254,7 @@
     key: String,
     groupKey: String?,
     whenTime: Long,
+    isPromoted: Boolean,
     isAmbient: Boolean,
     isRowDismissed: Boolean,
     isSilent: Boolean,
@@ -258,6 +277,7 @@
         key != this.key -> false
         groupKey != this.groupKey -> false
         whenTime != this.whenTime -> false
+        isPromoted != this.isPromoted -> false
         isAmbient != this.isAmbient -> false
         isRowDismissed != this.isRowDismissed -> false
         isSilent != this.isSilent -> false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index 96f47e5..a0515ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -459,8 +459,12 @@
         Resources.Theme theme = mContext.getTheme();
         final @ColorInt int onSurface = Utils.getColorAttrDefaultColor(mContext,
                 com.android.internal.R.attr.materialColorOnSurface);
+        // Same resource, separate drawables to prevent touch effects from showing on the wrong
+        // button.
         final Drawable clearAllBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
-        final Drawable manageBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
+        final Drawable settingsBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
+        final Drawable historyBg = NotifRedesignFooter.isEnabled()
+                ? theme.getDrawable(R.drawable.notif_footer_btn_background) : null;
         final @ColorInt int scHigh;
         if (!notificationFooterBackgroundTintOptimization()) {
             scHigh = Utils.getColorAttrDefaultColor(mContext,
@@ -468,7 +472,10 @@
             if (scHigh != 0) {
                 final ColorFilter bgColorFilter = new PorterDuffColorFilter(scHigh, SRC_ATOP);
                 clearAllBg.setColorFilter(bgColorFilter);
-                manageBg.setColorFilter(bgColorFilter);
+                settingsBg.setColorFilter(bgColorFilter);
+                if (NotifRedesignFooter.isEnabled()) {
+                    historyBg.setColorFilter(bgColorFilter);
+                }
             }
         } else {
             scHigh = 0;
@@ -476,13 +483,13 @@
         mClearAllButton.setBackground(clearAllBg);
         mClearAllButton.setTextColor(onSurface);
         if (NotifRedesignFooter.isEnabled()) {
-            mSettingsButton.setBackground(manageBg);
+            mSettingsButton.setBackground(settingsBg);
             mSettingsButton.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
 
-            mHistoryButton.setBackground(manageBg);
+            mHistoryButton.setBackground(historyBg);
             mHistoryButton.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
         } else {
-            mManageOrHistoryButton.setBackground(manageBg);
+            mManageOrHistoryButton.setBackground(settingsBg);
             mManageOrHistoryButton.setTextColor(onSurface);
         }
         mSeenNotifsFooterTextView.setTextColor(onSurface);
@@ -492,7 +499,7 @@
             colorUpdateLogger.logEvent("Footer.updateColors()",
                     "textColor(onSurface)=" + hexColorString(onSurface)
                             + " backgroundTint(surfaceContainerHigh)=" + hexColorString(scHigh)
-                            + " background=" + DrawableDumpKt.dumpToString(manageBg));
+                            + " background=" + DrawableDumpKt.dumpToString(settingsBg));
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/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/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index 71cddc9..52336be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -75,7 +75,7 @@
     private val bubbles: Optional<Bubbles>,
     @ShadeDisplayAware private val context: Context,
     private val notificationManager: NotificationManager,
-    private val settingsInteractor: NotificationSettingsInteractor
+    private val settingsInteractor: NotificationSettingsInteractor,
 ) : VisualInterruptionDecisionProvider {
 
     init {
@@ -89,7 +89,7 @@
 
     private class DecisionImpl(
         override val shouldInterrupt: Boolean,
-        override val logReason: String
+        override val logReason: String,
     ) : Decision
 
     private data class LoggableDecision
@@ -107,7 +107,7 @@
                 LoggableDecision(
                     DecisionImpl(
                         shouldInterrupt = false,
-                        logReason = "${legacySuppressor.name}.$methodName"
+                        logReason = "${legacySuppressor.name}.$methodName",
                     )
                 )
 
@@ -123,7 +123,7 @@
 
     private class FullScreenIntentDecisionImpl(
         val entry: NotificationEntry,
-        private val fsiDecision: FullScreenIntentDecisionProvider.Decision
+        private val fsiDecision: FullScreenIntentDecisionProvider.Decision,
     ) : FullScreenIntentDecision, Loggable {
         var hasBeenLogged = false
 
@@ -154,7 +154,7 @@
             deviceProvisionedController,
             keyguardStateController,
             powerManager,
-            statusBarStateController
+            statusBarStateController,
         )
 
     private val legacySuppressors = mutableSetOf<NotificationInterruptSuppressor>()
@@ -197,7 +197,7 @@
                     context,
                     notificationManager,
                     logger,
-                    systemSettings
+                    systemSettings,
                 )
             )
             avalancheProvider.register()
@@ -290,7 +290,7 @@
     private fun logDecision(
         type: VisualInterruptionType,
         entry: NotificationEntry,
-        loggableDecision: LoggableDecision
+        loggableDecision: LoggableDecision,
     ) {
         if (!loggableDecision.isSpammy || logger.spew) {
             logger.logDecision(type.name, entry, loggableDecision.decision)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt
new file mode 100644
index 0000000..6324219
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted
+
+import android.app.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the promoted ongoing notifications UI flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object PromotedNotificationUi {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_UI_RICH_ONGOING
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the refactor enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.uiRichOngoing()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is not enabled to ensure that the refactor author catches issues in testing.
+     * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+     */
+    @JvmStatic
+    inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsModule.kt
similarity index 61%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsModule.kt
index 94b2bdf..4be12bd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsModule.kt
@@ -14,9 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.statusbar.notification.promoted
 
-import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+@Module
+abstract class PromotedNotificationsModule {
+    @Binds
+    @SysUISingleton
+    abstract fun bindPromotedNotificationsProvider(
+        impl: PromotedNotificationsProviderImpl
+    ): PromotedNotificationsProvider
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt
new file mode 100644
index 0000000..691dc6f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted
+
+import android.app.Notification.FLAG_PROMOTED_ONGOING
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import javax.inject.Inject
+
+/** A provider for making decisions on which notifications should be promoted. */
+interface PromotedNotificationsProvider {
+    /** Returns true if the given notification should be promoted and false otherwise. */
+    fun shouldPromote(entry: NotificationEntry): Boolean
+}
+
+@SysUISingleton
+open class PromotedNotificationsProviderImpl @Inject constructor() : PromotedNotificationsProvider {
+    override fun shouldPromote(entry: NotificationEntry): Boolean {
+        if (!PromotedNotificationUi.isEnabled) {
+            return false
+        }
+        return (entry.sbn.notification.flags and FLAG_PROMOTED_ONGOING) != 0
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
index e233def..9164145 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
@@ -25,6 +25,7 @@
 import android.util.Log
 import android.util.Size
 import androidx.annotation.MainThread
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.internal.R
 import com.android.internal.widget.NotificationDrawableConsumer
 import com.android.internal.widget.NotificationIconManager
@@ -45,7 +46,6 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.delay
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.withContext
 
 private const val TAG = "BigPicImageLoader"
@@ -67,7 +67,7 @@
     private val statsManager: BigPictureStatsManager,
     @Application private val scope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
-    @Background private val bgDispatcher: CoroutineDispatcher
+    @Background private val bgDispatcher: CoroutineDispatcher,
 ) : NotificationIconManager, Dumpable {
 
     private var lastLoadingJob: Job? = null
@@ -153,7 +153,7 @@
 
     private fun checkPlaceHolderSizeForDrawable(
         displayedState: DrawableState,
-        newDrawable: Drawable
+        newDrawable: Drawable,
     ) {
         if (displayedState is PlaceHolder) {
             val (oldWidth, oldHeight) = displayedState.drawableSize
@@ -163,7 +163,7 @@
                 Log.e(
                     TAG,
                     "Mismatch in dimensions, when replacing PlaceHolder " +
-                        "$oldWidth X $oldHeight with Drawable $newWidth X $newHeight."
+                        "$oldWidth X $oldHeight with Drawable $newWidth X $newHeight.",
                 )
             }
         }
@@ -184,9 +184,8 @@
         displayedState = drawableAndState?.second ?: Empty
     }
 
-    private fun startLoadingJob(icon: Icon): Job = scope.launch {
-        statsManager.measure { loadImage(icon) }
-    }
+    private fun startLoadingJob(icon: Icon): Job =
+        scope.launch { statsManager.measure { loadImage(icon) } }
 
     private suspend fun loadImage(icon: Icon) {
         val drawableAndState = withContext(bgDispatcher) { loadImageSync(icon) }
@@ -254,9 +253,12 @@
 
     private sealed class DrawableState(open val icon: Icon?) {
         data object Initial : DrawableState(null)
+
         data object Empty : DrawableState(null)
+
         data class PlaceHolder(override val icon: Icon, val drawableSize: Size) :
             DrawableState(icon)
+
         data class FullImage(override val icon: Icon, val drawableSize: Size) : DrawableState(icon)
     }
 }
@@ -298,7 +300,7 @@
 }
 
 private val Drawable.intrinsicSize
-    get() = Size(/*width=*/ intrinsicWidth, /*height=*/ intrinsicHeight)
+    get() = Size(/* width= */ intrinsicWidth, /* height= */ intrinsicHeight)
 
 private operator fun Size.component1() = width
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index b166def..2dcb706 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -95,7 +95,7 @@
     private val smartReplyStateInflater: SmartReplyStateInflater,
     private val notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
     private val headsUpStyleProvider: HeadsUpStyleProvider,
-    private val logger: NotificationRowContentBinderLogger
+    private val logger: NotificationRowContentBinderLogger,
 ) : NotificationRowContentBinder {
 
     init {
@@ -110,7 +110,7 @@
         @InflationFlag contentToBind: Int,
         bindParams: BindParams,
         forceInflate: Boolean,
-        callback: InflationCallback?
+        callback: InflationCallback?,
     ) {
         if (row.isRemoved) {
             // We don't want to reinflate anything for removed notifications. Otherwise views might
@@ -147,7 +147,7 @@
                 /* isMediaFlagEnabled = */ smartReplyStateInflater,
                 notifLayoutInflaterFactoryProvider,
                 headsUpStyleProvider,
-                logger
+                logger,
             )
         if (inflateSynchronously) {
             task.onPostExecute(task.doInBackground())
@@ -165,7 +165,7 @@
         @InflationFlag reInflateFlags: Int,
         builder: Notification.Builder,
         packageContext: Context,
-        smartRepliesInflater: SmartReplyStateInflater
+        smartRepliesInflater: SmartReplyStateInflater,
     ): InflationProgress {
         val systemUIContext = row.context
         val result =
@@ -229,7 +229,7 @@
             row,
             remoteInputManager.remoteViewsOnClickHandler,
             /* callback= */ null,
-            logger
+            logger,
         )
         return result
     }
@@ -246,7 +246,7 @@
     override fun unbindContent(
         entry: NotificationEntry,
         row: ExpandableNotificationRow,
-        @InflationFlag contentToUnbind: Int
+        @InflationFlag contentToUnbind: Int,
     ) {
         logger.logUnbinding(entry, contentToUnbind)
         var curFlag = 1
@@ -268,7 +268,7 @@
     private fun freeNotificationView(
         entry: NotificationEntry,
         row: ExpandableNotificationRow,
-        @InflationFlag inflateFlag: Int
+        @InflationFlag inflateFlag: Int,
     ) {
         when (inflateFlag) {
             FLAG_CONTENT_VIEW_CONTRACTED ->
@@ -319,7 +319,7 @@
      */
     private fun cancelContentViewFrees(
         row: ExpandableNotificationRow,
-        @InflationFlag contentViews: Int
+        @InflationFlag contentViews: Int,
     ) {
         if (contentViews and FLAG_CONTENT_VIEW_CONTRACTED != 0) {
             row.privateLayout.removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED)
@@ -372,7 +372,7 @@
         private val smartRepliesInflater: SmartReplyStateInflater,
         private val notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
         private val headsUpStyleProvider: HeadsUpStyleProvider,
-        private val logger: NotificationRowContentBinderLogger
+        private val logger: NotificationRowContentBinderLogger,
     ) : AsyncTask<Void, Void, Result<InflationProgress>>(), InflationCallback, InflationTask {
         private val context: Context
             get() = row.context
@@ -393,7 +393,7 @@
                     context.packageManager.getApplicationInfoAsUser(
                         packageName,
                         PackageManager.MATCH_UNINSTALLED_PACKAGES,
-                        userId
+                        userId,
                     )
             } catch (e: PackageManager.NameNotFoundException) {
                 return
@@ -442,11 +442,11 @@
                     notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider,
                     headsUpStyleProvider = headsUpStyleProvider,
                     conversationProcessor = conversationProcessor,
-                    logger = logger
+                    logger = logger,
                 )
             logger.logAsyncTaskProgress(
                 entry,
-                "getting existing smart reply state (on wrong thread!)"
+                "getting existing smart reply state (on wrong thread!)",
             )
             val previousSmartReplyState: InflatedSmartReplyState? = row.existingSmartReplyState
             logger.logAsyncTaskProgress(entry, "inflating smart reply views")
@@ -469,7 +469,7 @@
                             reInflateFlags,
                             entry,
                             context,
-                            logger
+                            logger,
                         )
                     }
             }
@@ -483,7 +483,7 @@
                             reInflateFlags,
                             entry,
                             context,
-                            logger
+                            logger,
                         )
                     }
             }
@@ -513,7 +513,7 @@
                             row,
                             remoteViewClickHandler,
                             this /* callback */,
-                            logger
+                            logger,
                         )
                 }
                 .onFailure { error -> handleError(error as Exception) }
@@ -530,7 +530,7 @@
             Log.e(TAG, "couldn't inflate view for notification $ident", e)
             callback?.handleInflationException(
                 row.entry,
-                InflationException("Couldn't inflate contentViews$e")
+                InflationException("Couldn't inflate contentViews$e"),
             )
 
             // Cancel any image loading tasks, not useful any more
@@ -618,7 +618,7 @@
             packageContext: Context,
             previousSmartReplyState: InflatedSmartReplyState?,
             inflater: SmartReplyStateInflater,
-            logger: NotificationRowContentBinderLogger
+            logger: NotificationRowContentBinderLogger,
         ) {
             val inflateContracted =
                 (reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0 &&
@@ -641,7 +641,7 @@
                         packageContext,
                         entry,
                         previousSmartReplyState,
-                        result.inflatedSmartReplyState!!
+                        result.inflatedSmartReplyState!!,
                     )
             }
             if (inflateHeadsUp) {
@@ -652,7 +652,7 @@
                         packageContext,
                         entry,
                         previousSmartReplyState,
-                        result.inflatedSmartReplyState!!
+                        result.inflatedSmartReplyState!!,
                     )
             }
         }
@@ -670,7 +670,7 @@
             notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
             headsUpStyleProvider: HeadsUpStyleProvider,
             conversationProcessor: ConversationNotificationProcessor,
-            logger: NotificationRowContentBinderLogger
+            logger: NotificationRowContentBinderLogger,
         ): InflationProgress {
             // process conversations and extract the messaging style
             val messagingStyle =
@@ -713,7 +713,7 @@
                     logger.logAsyncTaskProgress(entry, "inflating public single line view model")
                     SingleLineViewInflater.inflateRedactedSingleLineViewModel(
                         systemUIContext,
-                        entry.ranking.isConversation
+                        entry.ranking.isConversation,
                     )
                 } else null
 
@@ -746,7 +746,7 @@
             row: ExpandableNotificationRow,
             notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
             headsUpStyleProvider: HeadsUpStyleProvider,
-            logger: NotificationRowContentBinderLogger
+            logger: NotificationRowContentBinderLogger,
         ): NewRemoteViews {
             return TraceUtils.trace("NotificationContentInflater.createRemoteViews") {
                 val entryForLogging: NotificationEntry = row.entry
@@ -754,7 +754,7 @@
                     if (reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0) {
                         logger.logAsyncTaskProgress(
                             entryForLogging,
-                            "creating contracted remote view"
+                            "creating contracted remote view",
                         )
                         createContentView(builder, isMinimized, usesIncreasedHeight)
                     } else null
@@ -762,7 +762,7 @@
                     if (reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0) {
                         logger.logAsyncTaskProgress(
                             entryForLogging,
-                            "creating expanded remote view"
+                            "creating expanded remote view",
                         )
                         createExpandedView(builder, isMinimized)
                     } else null
@@ -770,7 +770,7 @@
                     if (reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0) {
                         logger.logAsyncTaskProgress(
                             entryForLogging,
-                            "creating heads up remote view"
+                            "creating heads up remote view",
                         )
                         val isHeadsUpCompact = headsUpStyleProvider.shouldApplyCompactStyle()
                         if (isHeadsUpCompact) {
@@ -791,7 +791,7 @@
                     ) {
                         logger.logAsyncTaskProgress(
                             entryForLogging,
-                            "creating group summary remote view"
+                            "creating group summary remote view",
                         )
                         builder.makeNotificationGroupHeader()
                     } else null
@@ -802,7 +802,7 @@
                     ) {
                         logger.logAsyncTaskProgress(
                             entryForLogging,
-                            "creating low-priority group summary remote view"
+                            "creating low-priority group summary remote view",
                         )
                         builder.makeLowPriorityContentView(true /* useRegularSubtext */)
                     } else null
@@ -812,7 +812,7 @@
                         expanded = expanded,
                         public = public,
                         normalGroupHeader = normalGroupHeader,
-                        minimizedGroupHeader = minimizedGroupHeader
+                        minimizedGroupHeader = minimizedGroupHeader,
                     )
                     .withLayoutInflaterFactory(row, notifLayoutInflaterFactoryProvider)
             }
@@ -820,7 +820,7 @@
 
         private fun NewRemoteViews.withLayoutInflaterFactory(
             row: ExpandableNotificationRow,
-            provider: NotifLayoutInflaterFactory.Provider
+            provider: NotifLayoutInflaterFactory.Provider,
         ): NewRemoteViews {
             contracted?.let {
                 it.layoutInflaterFactory = provider.provide(row, FLAG_CONTENT_VIEW_CONTRACTED)
@@ -848,7 +848,7 @@
             row: ExpandableNotificationRow,
             remoteViewClickHandler: InteractionHandler?,
             callback: InflationCallback?,
-            logger: NotificationRowContentBinderLogger
+            logger: NotificationRowContentBinderLogger,
         ): CancellationSignal {
             Trace.beginAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row))
             val privateLayout = row.privateLayout
@@ -859,7 +859,7 @@
                 val isNewView =
                     !canReapplyRemoteView(
                         newView = result.remoteViews.contracted,
-                        oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)
+                        oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED),
                     )
                 val applyCallback: ApplyCallback =
                     object : ApplyCallback() {
@@ -890,7 +890,7 @@
                     existingWrapper = privateLayout.getVisibleWrapper(VISIBLE_TYPE_CONTRACTED),
                     runningInflations = runningInflations,
                     applyCallback = applyCallback,
-                    logger = logger
+                    logger = logger,
                 )
             }
             flag = FLAG_CONTENT_VIEW_EXPANDED
@@ -898,7 +898,7 @@
                 val isNewView =
                     !canReapplyRemoteView(
                         newView = result.remoteViews.expanded,
-                        oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)
+                        oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED),
                     )
                 val applyCallback: ApplyCallback =
                     object : ApplyCallback() {
@@ -929,7 +929,7 @@
                     existingWrapper = privateLayout.getVisibleWrapper(VISIBLE_TYPE_EXPANDED),
                     runningInflations = runningInflations,
                     applyCallback = applyCallback,
-                    logger = logger
+                    logger = logger,
                 )
             }
             flag = FLAG_CONTENT_VIEW_HEADS_UP
@@ -937,7 +937,7 @@
                 val isNewView =
                     !canReapplyRemoteView(
                         newView = result.remoteViews.headsUp,
-                        oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)
+                        oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP),
                     )
                 val applyCallback: ApplyCallback =
                     object : ApplyCallback() {
@@ -968,7 +968,7 @@
                     existingWrapper = privateLayout.getVisibleWrapper(VISIBLE_TYPE_HEADSUP),
                     runningInflations = runningInflations,
                     applyCallback = applyCallback,
-                    logger = logger
+                    logger = logger,
                 )
             }
             flag = FLAG_CONTENT_VIEW_PUBLIC
@@ -976,7 +976,7 @@
                 val isNewView =
                     !canReapplyRemoteView(
                         newView = result.remoteViews.public,
-                        oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)
+                        oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC),
                     )
                 val applyCallback: ApplyCallback =
                     object : ApplyCallback() {
@@ -1007,7 +1007,7 @@
                     existingWrapper = publicLayout.getVisibleWrapper(VISIBLE_TYPE_CONTRACTED),
                     runningInflations = runningInflations,
                     applyCallback = applyCallback,
-                    logger = logger
+                    logger = logger,
                 )
             }
             if (AsyncGroupHeaderViewInflation.isEnabled) {
@@ -1018,7 +1018,7 @@
                         !canReapplyRemoteView(
                             newView = result.remoteViews.normalGroupHeader,
                             oldView =
-                                remoteViewCache.getCachedView(entry, FLAG_GROUP_SUMMARY_HEADER)
+                                remoteViewCache.getCachedView(entry, FLAG_GROUP_SUMMARY_HEADER),
                         )
                     val applyCallback: ApplyCallback =
                         object : ApplyCallback() {
@@ -1049,7 +1049,7 @@
                         existingWrapper = childrenContainer.notificationHeaderWrapper,
                         runningInflations = runningInflations,
                         applyCallback = applyCallback,
-                        logger = logger
+                        logger = logger,
                     )
                 }
                 if (reInflateFlags and FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER != 0) {
@@ -1059,15 +1059,15 @@
                             oldView =
                                 remoteViewCache.getCachedView(
                                     entry,
-                                    FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER
-                                )
+                                    FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
+                                ),
                         )
                     val applyCallback: ApplyCallback =
                         object : ApplyCallback() {
                             override fun setResultView(v: View) {
                                 logger.logAsyncTaskProgress(
                                     entry,
-                                    "low-priority group header view applied"
+                                    "low-priority group header view applied",
                                 )
                                 result.inflatedMinimizedGroupHeaderView =
                                     v as NotificationHeaderView?
@@ -1095,7 +1095,7 @@
                         existingWrapper = childrenContainer.minimizedGroupHeaderWrapper,
                         runningInflations = runningInflations,
                         applyCallback = applyCallback,
-                        logger = logger
+                        logger = logger,
                     )
                 }
             }
@@ -1110,7 +1110,7 @@
                 callback,
                 entry,
                 row,
-                logger
+                logger,
             )
             val cancellationSignal = CancellationSignal()
             cancellationSignal.setOnCancelListener {
@@ -1142,7 +1142,7 @@
             existingWrapper: NotificationViewWrapper?,
             runningInflations: HashMap<Int, CancellationSignal>,
             applyCallback: ApplyCallback,
-            logger: NotificationRowContentBinderLogger
+            logger: NotificationRowContentBinderLogger,
         ) {
             val newContentView: RemoteViews = applyCallback.remoteView
             if (inflateSynchronously) {
@@ -1152,7 +1152,7 @@
                             newContentView.apply(
                                 result.packageContext,
                                 parentLayout,
-                                remoteViewClickHandler
+                                remoteViewClickHandler,
                             )
                         validateView(v, entry, row.resources)
                         applyCallback.setResultView(v)
@@ -1162,7 +1162,7 @@
                         newContentView.reapply(
                             result.packageContext,
                             existingView,
-                            remoteViewClickHandler
+                            remoteViewClickHandler,
                         )
                         validateView(existingView, entry, row.resources)
                         existingWrapper.onReinflated()
@@ -1174,7 +1174,7 @@
                         row.entry,
                         callback,
                         logger,
-                        "applying view synchronously"
+                        "applying view synchronously",
                     )
                     // Add a running inflation to make sure we don't trigger callbacks.
                     // Safe to do because only happens in tests.
@@ -1199,7 +1199,7 @@
                                 row.entry,
                                 callback,
                                 logger,
-                                "applied invalid view"
+                                "applied invalid view",
                             )
                             runningInflations.remove(inflationId)
                             return
@@ -1219,7 +1219,7 @@
                             callback,
                             entry,
                             row,
-                            logger
+                            logger,
                         )
                     }
 
@@ -1234,20 +1234,20 @@
                                     newContentView.apply(
                                         result.packageContext,
                                         parentLayout,
-                                        remoteViewClickHandler
+                                        remoteViewClickHandler,
                                     )
                                 } else {
                                     newContentView.reapply(
                                         result.packageContext,
                                         existingView,
-                                        remoteViewClickHandler
+                                        remoteViewClickHandler,
                                     )
                                     existingView!!
                                 }
                             Log.wtf(
                                 TAG,
                                 "Async Inflation failed but normal inflation finished normally.",
-                                e
+                                e,
                             )
                             onViewApplied(newView)
                         } catch (anotherException: Exception) {
@@ -1258,7 +1258,7 @@
                                 row.entry,
                                 callback,
                                 logger,
-                                "applying view"
+                                "applying view",
                             )
                         }
                     }
@@ -1270,7 +1270,7 @@
                         parentLayout,
                         inflationExecutor,
                         listener,
-                        remoteViewClickHandler
+                        remoteViewClickHandler,
                     )
                 } else {
                     newContentView.reapplyAsync(
@@ -1278,7 +1278,7 @@
                         existingView,
                         inflationExecutor,
                         listener,
-                        remoteViewClickHandler
+                        remoteViewClickHandler,
                     )
                 }
             runningInflations[inflationId] = cancellationSignal
@@ -1299,7 +1299,7 @@
         private fun satisfiesMinHeightRequirement(
             view: View,
             entry: NotificationEntry,
-            resources: Resources
+            resources: Resources,
         ): Boolean {
             return if (!requiresHeightCheck(entry)) {
                 true
@@ -1353,7 +1353,7 @@
             notification: NotificationEntry,
             callback: InflationCallback?,
             logger: NotificationRowContentBinderLogger,
-            logContext: String
+            logContext: String,
         ) {
             Assert.isMainThread()
             logger.logAsyncTaskException(notification, logContext, e)
@@ -1375,7 +1375,7 @@
             endListener: InflationCallback?,
             entry: NotificationEntry,
             row: ExpandableNotificationRow,
-            logger: NotificationRowContentBinderLogger
+            logger: NotificationRowContentBinderLogger,
         ): Boolean {
             Assert.isMainThread()
             if (runningInflations.isNotEmpty()) {
@@ -1439,19 +1439,19 @@
                 FLAG_CONTENT_VIEW_CONTRACTED,
                 result.remoteViews.contracted,
                 result.inflatedContentView,
-                privateLayout::setContractedChild
+                privateLayout::setContractedChild,
             )
             remoteViewsUpdater.setContentView(
                 FLAG_CONTENT_VIEW_EXPANDED,
                 result.remoteViews.expanded,
                 result.inflatedExpandedView,
-                privateLayout::setExpandedChild
+                privateLayout::setExpandedChild,
             )
             remoteViewsUpdater.setSmartReplies(
                 FLAG_CONTENT_VIEW_EXPANDED,
                 result.remoteViews.expanded,
                 result.expandedInflatedSmartReplies,
-                privateLayout::setExpandedInflatedSmartReplies
+                privateLayout::setExpandedInflatedSmartReplies,
             )
             if (reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0) {
                 row.setExpandable(result.remoteViews.expanded != null)
@@ -1460,19 +1460,19 @@
                 FLAG_CONTENT_VIEW_HEADS_UP,
                 result.remoteViews.headsUp,
                 result.inflatedHeadsUpView,
-                privateLayout::setHeadsUpChild
+                privateLayout::setHeadsUpChild,
             )
             remoteViewsUpdater.setSmartReplies(
                 FLAG_CONTENT_VIEW_HEADS_UP,
                 result.remoteViews.headsUp,
                 result.headsUpInflatedSmartReplies,
-                privateLayout::setHeadsUpInflatedSmartReplies
+                privateLayout::setHeadsUpInflatedSmartReplies,
             )
             remoteViewsUpdater.setContentView(
                 FLAG_CONTENT_VIEW_PUBLIC,
                 result.remoteViews.public,
                 result.inflatedPublicView,
-                publicLayout::setContractedChild
+                publicLayout::setContractedChild,
             )
             if (AsyncGroupHeaderViewInflation.isEnabled) {
                 remoteViewsUpdater.setContentView(
@@ -1540,7 +1540,7 @@
 
         private fun createExpandedView(
             builder: Notification.Builder,
-            isMinimized: Boolean
+            isMinimized: Boolean,
         ): RemoteViews? {
             @Suppress("DEPRECATION")
             val bigContentView: RemoteViews? = builder.createBigContentView()
@@ -1558,7 +1558,7 @@
         private fun createContentView(
             builder: Notification.Builder,
             isMinimized: Boolean,
-            useLarge: Boolean
+            useLarge: Boolean,
         ): RemoteViews {
             return if (isMinimized) {
                 builder.makeLowPriorityContentView(false /* useRegularSubtext */)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt
index 7177a7b..08c1d71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt
@@ -73,8 +73,8 @@
     private val cache = NotifCollectionCache<Boolean>()
 
     override fun shouldShowAppIcon(notification: StatusBarNotification, context: Context): Boolean {
-        val packageContext = notification.getPackageContext(context)
         return cache.getOrFetch(notification.packageName) {
+            val packageContext = notification.getPackageContext(context)
             !belongsToHeadlessSystemApp(packageContext)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
index cf19938..19a92a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -36,6 +36,8 @@
     val groupKey: String?,
     /** When this notification was posted. */
     val whenTime: Long,
+    /** True if this notification should be promoted and false otherwise. */
+    val isPromoted: Boolean,
     /** Is this entry in the ambient / minimized section (lowest priority)? */
     val isAmbient: Boolean,
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index c9a0010..31e4d2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -26,7 +26,14 @@
 import com.android.systemui.statusbar.notification.collection.NotificationClassificationFlag
 import com.android.systemui.statusbar.notification.collection.render.MediaContainerController
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
-import com.android.systemui.statusbar.notification.dagger.*
+import com.android.systemui.statusbar.notification.dagger.AlertingHeader
+import com.android.systemui.statusbar.notification.dagger.IncomingHeader
+import com.android.systemui.statusbar.notification.dagger.NewsHeader
+import com.android.systemui.statusbar.notification.dagger.PeopleHeader
+import com.android.systemui.statusbar.notification.dagger.PromoHeader
+import com.android.systemui.statusbar.notification.dagger.RecsHeader
+import com.android.systemui.statusbar.notification.dagger.SilentHeader
+import com.android.systemui.statusbar.notification.dagger.SocialHeader
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider
@@ -54,7 +61,7 @@
     @NewsHeader private val newsHeaderController: SectionHeaderController,
     @SocialHeader private val socialHeaderController: SectionHeaderController,
     @RecsHeader private val recsHeaderController: SectionHeaderController,
-    @PromoHeader private val promoHeaderController: SectionHeaderController
+    @PromoHeader private val promoHeaderController: SectionHeaderController,
 ) : SectionProvider {
 
     private val configurationListener =
@@ -136,14 +143,16 @@
 
     override fun beginsSection(view: View, previous: View?): Boolean =
         view === silentHeaderView ||
-                view === mediaControlsView ||
-                view === peopleHeaderView ||
-                view === alertingHeaderView ||
-                view === incomingHeaderView ||
-                (NotificationClassificationFlag.isEnabled && (view === newsHeaderView
-                        || view === socialHeaderView || view === recsHeaderView
-                        || view === promoHeaderView)) ||
-                getBucket(view) != getBucket(previous)
+            view === mediaControlsView ||
+            view === peopleHeaderView ||
+            view === alertingHeaderView ||
+            view === incomingHeaderView ||
+            (NotificationClassificationFlag.isEnabled &&
+                (view === newsHeaderView ||
+                    view === socialHeaderView ||
+                    view === recsHeaderView ||
+                    view === promoHeaderView)) ||
+            getBucket(view) != getBucket(previous)
 
     private fun getBucket(view: View?): Int? =
         when {
@@ -165,6 +174,7 @@
         data class Many(val first: ExpandableView, val last: ExpandableView) : SectionBounds()
 
         data class One(val lone: ExpandableView) : SectionBounds()
+
         object None : SectionBounds()
 
         fun addNotif(notif: ExpandableView): SectionBounds =
@@ -183,7 +193,7 @@
 
         private fun NotificationSection.setFirstAndLastVisibleChildren(
             first: ExpandableView?,
-            last: ExpandableView?
+            last: ExpandableView?,
         ): Boolean {
             val firstChanged = setFirstVisibleChild(first)
             val lastChanged = setLastVisibleChild(last)
@@ -198,7 +208,7 @@
      */
     fun updateFirstAndLastViewsForAllSections(
         sections: Array<NotificationSection>,
-        children: List<ExpandableView>
+        children: List<ExpandableView>,
     ): Boolean {
         // Create mapping of bucket to section
         val sectionBounds =
@@ -213,7 +223,7 @@
                 .foldToSparseArray(
                     SectionBounds.None,
                     size = sections.size,
-                    operation = SectionBounds::addNotif
+                    operation = SectionBounds::addNotif,
                 )
 
         // Build a set of the old first/last Views of the sections
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index f02f7f7..57af8ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -797,7 +797,7 @@
                 && !onKeyguard()
                 && mUpcomingStatusBarState != StatusBarState.KEYGUARD
                 // quick settings don't affect notifications when not in full screen
-                && (mQsExpansionFraction != 1 || !mQsFullScreen)
+                && (getQsExpansionFraction() != 1 || !mQsFullScreen)
                 && !mScreenOffAnimationController.shouldHideNotificationsFooter()
                 && !mIsRemoteInputActive;
     }
@@ -1528,7 +1528,7 @@
         float fraction = mAmbientState.getExpansionFraction();
         // If we are on quick settings, we need to quickly hide it to show the bouncer to avoid an
         // overlap. Otherwise, we maintain the normal fraction for smoothness.
-        if (mAmbientState.isBouncerInTransit() && mQsExpansionFraction > 0f) {
+        if (mAmbientState.isBouncerInTransit() && getQsExpansionFraction() > 0f) {
             fraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(fraction);
         }
         final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
@@ -1552,7 +1552,7 @@
             }
             updateInterpolatedStackHeight(endHeight, fraction);
         } else {
-            if (mQsExpansionFraction <= 0 && !shouldSkipHeightUpdate()) {
+            if (getQsExpansionFraction() <= 0 && !shouldSkipHeightUpdate()) {
                 final float endHeight = updateStackEndHeight(
                         getHeight(), getEmptyBottomMarginInternal(), getTopPadding());
                 updateInterpolatedStackHeight(endHeight, fraction);
@@ -1705,7 +1705,7 @@
                         stackHeight = (int) height;
                     } else {
                         stackHeight = (int) NotificationUtils.interpolate(stackStartPosition,
-                                stackEndPosition, mQsExpansionFraction);
+                                stackEndPosition, getQsExpansionFraction());
                     }
                 }
             } else {
@@ -5271,17 +5271,22 @@
         return mQsFullScreen;
     }
 
+    private float getQsExpansionFraction() {
+        SceneContainerFlag.assertInLegacyMode();
+        return mQsExpansionFraction;
+    }
+
     public void setQsExpansionFraction(float qsExpansionFraction) {
         SceneContainerFlag.assertInLegacyMode();
-        boolean footerAffected = mQsExpansionFraction != qsExpansionFraction
-                && (mQsExpansionFraction == 1 || qsExpansionFraction == 1);
+        boolean footerAffected = getQsExpansionFraction() != qsExpansionFraction
+                && (getQsExpansionFraction() == 1 || qsExpansionFraction == 1);
         mQsExpansionFraction = qsExpansionFraction;
         updateUseRoundedRectClipping();
 
         // If notifications are scrolled,
         // clear out scrollY by the time we push notifications offscreen
         if (getOwnScrollY() > 0) {
-            setOwnScrollY((int) MathUtils.lerp(getOwnScrollY(), 0, mQsExpansionFraction));
+            setOwnScrollY((int) MathUtils.lerp(getOwnScrollY(), 0, getQsExpansionFraction()));
         }
         if (!FooterViewRefactor.isEnabled() && footerAffected) {
             updateFooter();
@@ -5514,7 +5519,6 @@
             println(pw, "suppressChildrenMeasureLayout", mSuppressChildrenMeasureAndLayout);
             println(pw, "scrollY", mAmbientState.getScrollY());
             println(pw, "showShelfOnly", mShouldShowShelfOnly);
-            println(pw, "qsExpandFraction", mQsExpansionFraction);
             println(pw, "isCurrentUserSetup", mIsCurrentUserSetup);
             println(pw, "hideAmount", mAmbientState.getHideAmount());
             println(pw, "ambientStateSwipingUp", mAmbientState.isSwipingUp());
@@ -5550,6 +5554,7 @@
                 println(pw, "contentHeight", getContentHeight());
                 println(pw, "topPadding", getTopPadding());
                 println(pw, "maxTopPadding", getMaxTopPadding());
+                println(pw, "qsExpandFraction", getQsExpansionFraction());
             }
         });
         pw.println();
@@ -5622,7 +5627,9 @@
                     pw.println("mIsCurrentUserSetup: " + mIsCurrentUserSetup);
                     pw.println("onKeyguard: " + onKeyguard());
                     pw.println("mUpcomingStatusBarState: " + mUpcomingStatusBarState);
-                    pw.println("mQsExpansionFraction: " + mQsExpansionFraction);
+                    if (!SceneContainerFlag.isEnabled()) {
+                        pw.println("QsExpansionFraction: " + getQsExpansionFraction());
+                    }
                     pw.println("mQsFullScreen: " + mQsFullScreen);
                     pw.println(
                             "mScreenOffAnimationController"
@@ -6246,7 +6253,8 @@
         if (SceneContainerFlag.isEnabled()) return;
         // We don't want to clip notifications when QS is expanded, because incoming heads up on
         // the bottom would be clipped otherwise
-        boolean qsAllowsClipping = mQsExpansionFraction < 0.5f || mShouldUseSplitNotificationShade;
+        boolean qsAllowsClipping =
+                getQsExpansionFraction() < 0.5f || mShouldUseSplitNotificationShade;
         boolean clip = mIsExpanded && qsAllowsClipping;
         if (clip != mShouldUseRoundedRectClipping) {
             mShouldUseRoundedRectClipping = clip;
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 96d0d76..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,
@@ -228,9 +229,14 @@
                     configurationInteractor.onAnyConfigurationChange,
                 ) { isShadeLayoutWide, shadeMode, _ ->
                     with(context.resources) {
-                        // TODO(b/338033836): Define separate horizontal margins for dual shade.
                         val marginHorizontal =
-                            getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
+                            getDimensionPixelSize(
+                                if (shadeMode is Dual) {
+                                    R.dimen.shade_panel_margin_horizontal
+                                } else {
+                                    R.dimen.notification_panel_margin_horizontal
+                                }
+                            )
 
                         val horizontalPosition =
                             when (shadeMode) {
@@ -248,10 +254,7 @@
 
                         ConfigurationBasedDimensions(
                             horizontalPosition = horizontalPosition,
-                            marginStart =
-                                if (horizontalPosition is HorizontalPosition.EdgeToEdge)
-                                    marginHorizontal
-                                else 0,
+                            marginStart = if (shadeMode is Split) 0 else marginHorizontal,
                             marginEnd = marginHorizontal,
                             marginBottom =
                                 getDimensionPixelSize(R.dimen.notification_panel_margin_bottom),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 99efba4..7389086 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -171,12 +171,14 @@
 import com.android.systemui.shade.NotificationShadeWindowViewController;
 import com.android.systemui.shade.QuickSettingsController;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shade.ShadeExpansionListener;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shade.ShadeLogger;
 import com.android.systemui.shade.ShadeSurface;
 import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
 import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.shared.statusbar.phone.BarTransitions;
 import com.android.systemui.statusbar.AutoHideUiElement;
@@ -295,7 +297,7 @@
             };
 
     void onStatusBarWindowStateChanged(@WindowVisibleState int state) {
-        StatusBarSimpleFragment.assertInLegacyMode();
+        StatusBarConnectedDisplays.assertInLegacyMode();
         mStatusBarWindowState = state;
         updateBubblesVisibility();
     }
@@ -366,6 +368,7 @@
 
     private PhoneStatusBarViewController mPhoneStatusBarViewController;
     private PhoneStatusBarTransitions mStatusBarTransitions;
+    private final Provider<StatusBarLongPressGestureDetector> mStatusBarLongPressGestureDetector;
     private final AuthRippleController mAuthRippleController;
     @WindowVisibleState private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
@@ -671,6 +674,7 @@
             ShadeController shadeController,
             WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+            Provider<StatusBarLongPressGestureDetector> statusBarLongPressGestureDetector,
             ViewMediatorCallback viewMediatorCallback,
             InitController initController,
             @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
@@ -778,6 +782,7 @@
         mShadeController = shadeController;
         mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+        mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector;
         mKeyguardViewMediatorCallback = viewMediatorCallback;
         mInitController = initController;
         mPluginDependencyProvider = pluginDependencyProvider;
@@ -1527,6 +1532,11 @@
                 // to touch outside the customizer to close it, such as on the status or nav bar.
                 mShadeController.onStatusBarTouch(event);
             }
+            if (ShadeExpandsOnStatusBarLongPress.isEnabled()
+                    && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+                mStatusBarLongPressGestureDetector.get().handleTouch(event);
+            }
+
             return getNotificationShadeWindowView().onTouchEvent(event);
         };
     }
@@ -1589,8 +1599,6 @@
                 .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
         mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
         mRemoteInputManager.addControllerCallback(mStatusBarKeyguardViewManager);
-
-        mLightBarController.setBiometricUnlockController(mBiometricUnlockController);
         Trace.endSection();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/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/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 98869be..4915b84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -45,6 +45,8 @@
 import com.android.systemui.doze.DozeScreenState;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
@@ -85,6 +87,7 @@
     private final BatteryController mBatteryController;
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private final DozeInteractor mDozeInteractor;
+    private final KeyguardTransitionInteractor mTransitionInteractor;
     private final FoldAodAnimationController mFoldAodAnimationController;
     private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
     private final UserTracker mUserTracker;
@@ -134,6 +137,7 @@
             StatusBarStateController statusBarStateController,
             UserTracker userTracker,
             DozeInteractor dozeInteractor,
+            KeyguardTransitionInteractor transitionInteractor,
             SecureSettings secureSettings) {
         mResources = resources;
         mAmbientDisplayConfiguration = ambientDisplayConfiguration;
@@ -148,6 +152,7 @@
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
         mUserTracker = userTracker;
         mDozeInteractor = dozeInteractor;
+        mTransitionInteractor = transitionInteractor;
         mSecureSettings = secureSettings;
 
         keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
@@ -353,6 +358,9 @@
      * delayed for a few seconds. This might be useful to play animations without reducing FPS.
      */
     public boolean shouldDelayDisplayDozeTransition() {
+        if (mTransitionInteractor.getTransitionState().getValue().getTo() == KeyguardState.AOD) {
+            return true;
+        }
         return willAnimateFromLockScreenToAod()
                 || mScreenOffAnimationController.shouldDelayDisplayDozeTransition();
     }
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/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index be2fb68..2433b78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -50,6 +50,8 @@
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel;
 import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
@@ -82,6 +84,8 @@
 
 import kotlin.Unit;
 
+import kotlinx.coroutines.CoroutineDispatcher;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -108,6 +112,7 @@
             R.id.keyguard_hun_animator_end_tag,
             R.id.keyguard_hun_animator_start_tag);
 
+    private final CoroutineDispatcher mCoroutineDispatcher;
     private final CarrierTextController mCarrierTextController;
     private final ConfigurationController mConfigurationController;
     private final SystemStatusAnimationScheduler mAnimationScheduler;
@@ -133,6 +138,8 @@
     private final Object mLock = new Object();
     private final KeyguardLogger mLogger;
     private final CommunalSceneInteractor mCommunalSceneInteractor;
+    private final GlanceableHubToLockscreenTransitionViewModel mHubToLockscreenTransitionViewModel;
+    private final LockscreenToGlanceableHubTransitionViewModel mLockscreenToHubTransitionViewModel;
 
     private View mSystemIconsContainer;
     private final StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory;
@@ -249,10 +256,21 @@
     private boolean mCommunalShowing;
 
     private final Consumer<Boolean> mCommunalConsumer = (communalShowing) -> {
-        mCommunalShowing = communalShowing;
-        updateViewState();
+        updateCommunalShowing(communalShowing);
     };
 
+    @VisibleForTesting
+    void updateCommunalShowing(boolean communalShowing) {
+        mCommunalShowing = communalShowing;
+
+        // When communal is hidden (either by transition or state change), set alpha to fully
+        // visible.
+        if (!mCommunalShowing) {
+            setAlpha(-1f);
+        }
+        updateViewState();
+    }
+
     private final DisableStateTracker mDisableStateTracker;
 
     private final List<String> mBlockedIcons = new ArrayList<>();
@@ -277,6 +295,15 @@
     private boolean mShowingKeyguardHeadsUp;
     private StatusBarSystemEventDefaultAnimator mSystemEventAnimator;
     private float mSystemEventAnimatorAlpha = 1;
+    private final Consumer<Float> mToGlanceableHubStatusBarAlphaConsumer = (alpha) ->
+            updateCommunalAlphaTransition(alpha);
+
+    private final Consumer<Float> mFromGlanceableHubStatusBarAlphaConsumer = (alpha) ->
+            updateCommunalAlphaTransition(alpha);
+
+    @VisibleForTesting  void updateCommunalAlphaTransition(float alpha) {
+        setAlpha(!mCommunalShowing || alpha == 0 ? -1 : alpha);
+    }
 
     /**
      * The alpha value to be set on the View. If -1, this value is to be ignored.
@@ -285,6 +312,7 @@
 
     @Inject
     public KeyguardStatusBarViewController(
+            @Main CoroutineDispatcher dispatcher,
             KeyguardStatusBarView view,
             CarrierTextController carrierTextController,
             ConfigurationController configurationController,
@@ -310,9 +338,14 @@
             @Background Executor backgroundExecutor,
             KeyguardLogger logger,
             StatusOverlayHoverListenerFactory statusOverlayHoverListenerFactory,
-            CommunalSceneInteractor communalSceneInteractor
+            CommunalSceneInteractor communalSceneInteractor,
+            GlanceableHubToLockscreenTransitionViewModel
+                    glanceableHubToLockscreenTransitionViewModel,
+            LockscreenToGlanceableHubTransitionViewModel
+                    lockscreenToGlanceableHubTransitionViewModel
     ) {
         super(view);
+        mCoroutineDispatcher = dispatcher;
         mCarrierTextController = carrierTextController;
         mConfigurationController = configurationController;
         mAnimationScheduler = animationScheduler;
@@ -337,6 +370,8 @@
         mBackgroundExecutor = backgroundExecutor;
         mLogger = logger;
         mCommunalSceneInteractor = communalSceneInteractor;
+        mHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel;
+        mLockscreenToHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel;
 
         mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
         mKeyguardStateController.addCallback(
@@ -418,7 +453,12 @@
                 UserHandle.USER_ALL);
         updateUserSwitcher();
         onThemeChanged();
-        collectFlow(mView, mCommunalSceneInteractor.isCommunalVisible(), mCommunalConsumer);
+        collectFlow(mView, mCommunalSceneInteractor.isCommunalVisible(), mCommunalConsumer,
+                mCoroutineDispatcher);
+        collectFlow(mView, mLockscreenToHubTransitionViewModel.getStatusBarAlpha(),
+                mToGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher);
+        collectFlow(mView, mHubToLockscreenTransitionViewModel.getStatusBarAlpha(),
+                mFromGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher);
     }
 
     @Override
@@ -573,7 +613,7 @@
                         && !mDozing
                         && !hideForBypass
                         && !mDisableStateTracker.isDisabled()
-                        && !mCommunalShowing
+                        && (!mCommunalShowing || mExplicitAlpha != -1)
                         ? View.VISIBLE : View.INVISIBLE;
 
         updateViewState(newAlpha, newVisibility);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt
new file mode 100644
index 0000000..a6374a6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.view.WindowInsetsController
+import com.android.internal.colorextraction.ColorExtractor
+import com.android.internal.view.AppearanceRegion
+import com.android.systemui.CoreStartable
+
+/** Controls how light status bar flag applies to the icons. */
+interface LightBarController : CoreStartable {
+
+    fun stop()
+
+    fun setNavigationBar(navigationBar: LightBarTransitionsController)
+
+    fun onNavigationBarAppearanceChanged(
+        @WindowInsetsController.Appearance appearance: Int,
+        nbModeChanged: Boolean,
+        navigationBarMode: Int,
+        navbarColorManagedByIme: Boolean,
+    )
+
+    fun onNavigationBarModeChanged(newBarMode: Int)
+
+    fun setQsCustomizing(customizing: Boolean)
+
+    /** Set if Quick Settings is fully expanded, which affects notification scrim visibility. */
+    fun setQsExpanded(expanded: Boolean)
+
+    /** Set if Global Actions dialog is visible, which requires dark mode (light buttons). */
+    fun setGlobalActionsVisible(visible: Boolean)
+
+    /**
+     * Controls the light status bar temporarily for back navigation.
+     *
+     * @param appearance the customized appearance.
+     */
+    fun customizeStatusBarAppearance(appearance: AppearanceRegion)
+
+    /**
+     * Sets whether the direct-reply is in use or not.
+     *
+     * @param directReplying `true` when the direct-reply is in-use.
+     */
+    fun setDirectReplying(directReplying: Boolean)
+
+    fun setScrimState(
+        scrimState: ScrimState,
+        scrimBehindAlpha: Float,
+        scrimInFrontColor: ColorExtractor.GradientColors,
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
similarity index 81%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
index a33996b..ccb9a11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.systemui.statusbar.phone;
@@ -22,9 +22,9 @@
 import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
 import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
 
-import android.content.Context;
 import android.graphics.Rect;
 import android.util.Log;
+import android.view.Display;
 import android.view.InsetsFlags;
 import android.view.ViewDebug;
 import android.view.WindowInsetsController.Appearance;
@@ -34,30 +34,32 @@
 
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.internal.view.AppearanceRegion;
-import com.android.systemui.CoreStartable;
-import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.statusbar.data.model.StatusBarAppearance;
-import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore;
+import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.util.Compile;
-import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.kotlin.JavaAdapterKt;
+
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
+import kotlin.coroutines.CoroutineContext;
+
+import kotlinx.coroutines.CoroutineScope;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
-import javax.inject.Inject;
-
 /**
  * Controls how light status bar flag applies to the icons.
  */
-@SysUISingleton
-public class LightBarController implements
-        BatteryController.BatteryStateChangeCallback, Dumpable, CoreStartable {
+public class LightBarControllerImpl implements
+        BatteryController.BatteryStateChangeCallback, LightBarController {
 
     private static final String TAG = "LightBarController";
     private static final boolean DEBUG_NAVBAR = Compile.IS_DEBUG;
@@ -65,11 +67,14 @@
 
     private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f;
 
-    private final JavaAdapter mJavaAdapter;
+    private final CoroutineScope mCoroutineScope;
     private final SysuiDarkIconDispatcher mStatusBarIconController;
     private final BatteryController mBatteryController;
-    private final StatusBarModeRepositoryStore mStatusBarModeRepository;
-    private BiometricUnlockController mBiometricUnlockController;
+    private final NavigationModeController mNavModeController;
+    private final DumpManager mDumpManager;
+    private final StatusBarModePerDisplayRepository mStatusBarModeRepository;
+    private final CoroutineContext mMainContext;
+    private final BiometricUnlockController mBiometricUnlockController;
 
     private LightBarTransitionsController mNavigationBarController;
     private @Appearance int mAppearance;
@@ -119,47 +124,60 @@
     private String mLastNavigationBarAppearanceChangedLog;
     private StringBuilder mLogStringBuilder = null;
 
-    @Inject
-    public LightBarController(
-            Context ctx,
-            JavaAdapter javaAdapter,
-            DarkIconDispatcher darkIconDispatcher,
+    private final String mDumpableName;
+
+    private final NavigationModeController.ModeChangedListener mNavigationModeListener =
+            (mode) -> mNavigationMode = mode;
+
+    @AssistedInject
+    public LightBarControllerImpl(
+            @Assisted int displayId,
+            @Assisted CoroutineScope coroutineScope,
+            @Assisted DarkIconDispatcher darkIconDispatcher,
             BatteryController batteryController,
             NavigationModeController navModeController,
-            StatusBarModeRepositoryStore statusBarModeRepository,
+            @Assisted StatusBarModePerDisplayRepository statusBarModeRepository,
             DumpManager dumpManager,
-            DisplayTracker displayTracker) {
-        mJavaAdapter = javaAdapter;
+            @Main CoroutineContext mainContext,
+            BiometricUnlockController biometricUnlockController) {
+        mCoroutineScope = coroutineScope;
         mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
         mBatteryController = batteryController;
-        mBatteryController.addCallback(this);
+        mNavModeController = navModeController;
+        mDumpManager = dumpManager;
         mStatusBarModeRepository = statusBarModeRepository;
-        mNavigationMode = navModeController.addListener((mode) -> {
-            mNavigationMode = mode;
-        });
-
-        if (ctx.getDisplayId() == displayTracker.getDefaultDisplayId()) {
-            dumpManager.registerDumpable(getClass().getSimpleName(), this);
-        }
+        mMainContext = mainContext;
+        mBiometricUnlockController = biometricUnlockController;
+        String dumpableNameSuffix =
+                displayId == Display.DEFAULT_DISPLAY ? "" : String.valueOf(displayId);
+        mDumpableName = getClass().getSimpleName() + dumpableNameSuffix;
     }
 
     @Override
     public void start() {
-        mJavaAdapter.alwaysCollectFlow(
-                mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance(),
+        mDumpManager.registerCriticalDumpable(mDumpableName, this);
+        mBatteryController.addCallback(this);
+        mNavigationMode = mNavModeController.addListener(mNavigationModeListener);
+        JavaAdapterKt.collectFlow(
+                mCoroutineScope,
+                mMainContext,
+                mStatusBarModeRepository.getStatusBarAppearance(),
                 this::onStatusBarAppearanceChanged);
     }
 
+    @Override
+    public void stop() {
+        mDumpManager.unregisterDumpable(mDumpableName);
+        mBatteryController.removeCallback(this);
+        mNavModeController.removeListener(mNavigationModeListener);
+    }
+
+    @Override
     public void setNavigationBar(LightBarTransitionsController navigationBar) {
         mNavigationBarController = navigationBar;
         updateNavigation();
     }
 
-    public void setBiometricUnlockController(
-            BiometricUnlockController biometricUnlockController) {
-        mBiometricUnlockController = biometricUnlockController;
-    }
-
     private void onStatusBarAppearanceChanged(@Nullable StatusBarAppearance params) {
         if (params == null) {
             return;
@@ -202,6 +220,7 @@
         mNavbarColorManagedByIme = navbarColorManagedByIme;
     }
 
+    @Override
     public void onNavigationBarAppearanceChanged(@Appearance int appearance, boolean nbModeChanged,
             int navigationBarMode, boolean navbarColorManagedByIme) {
         int diff = appearance ^ mAppearance;
@@ -244,6 +263,7 @@
         mNavbarColorManagedByIme = navbarColorManagedByIme;
     }
 
+    @Override
     public void onNavigationBarModeChanged(int newBarMode) {
         mHasLightNavigationBar = isLight(mAppearance, newBarMode, APPEARANCE_LIGHT_NAVIGATION_BARS);
     }
@@ -258,30 +278,28 @@
                 mNavigationBarMode, mNavbarColorManagedByIme);
     }
 
+    @Override
     public void setQsCustomizing(boolean customizing) {
         if (mQsCustomizing == customizing) return;
         mQsCustomizing = customizing;
         reevaluate();
     }
 
-    /** Set if Quick Settings is fully expanded, which affects notification scrim visibility */
+    @Override
     public void setQsExpanded(boolean expanded) {
         if (mQsExpanded == expanded) return;
         mQsExpanded = expanded;
         reevaluate();
     }
 
-    /** Set if Global Actions dialog is visible, which requires dark mode (light buttons) */
+    @Override
     public void setGlobalActionsVisible(boolean visible) {
         if (mGlobalActionsVisible == visible) return;
         mGlobalActionsVisible = visible;
         reevaluate();
     }
 
-    /**
-     * Controls the light status bar temporarily for back navigation.
-     * @param appearance the custmoized appearance.
-     */
+    @Override
     public void customizeStatusBarAppearance(AppearanceRegion appearance) {
         if (appearance != null) {
             final ArrayList<AppearanceRegion> appearancesList = new ArrayList<>();
@@ -303,16 +321,14 @@
         }
     }
 
-    /**
-     * Sets whether the direct-reply is in use or not.
-     * @param directReplying {@code true} when the direct-reply is in-use.
-     */
+    @Override
     public void setDirectReplying(boolean directReplying) {
         if (mDirectReplying == directReplying) return;
         mDirectReplying = directReplying;
         reevaluate();
     }
 
+    @Override
     public void setScrimState(ScrimState scrimState, float scrimBehindAlpha,
             GradientColors scrimInFrontColor) {
         boolean bouncerVisibleLast = mBouncerVisible;
@@ -368,9 +384,6 @@
     }
 
     private boolean animateChange() {
-        if (mBiometricUnlockController == null) {
-            return false;
-        }
         int unlockMode = mBiometricUnlockController.getMode();
         return unlockMode != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
                 && unlockMode != BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
@@ -387,20 +400,17 @@
             }
         }
 
-        // If no one is light, all icons become white.
         if (lightBarBounds.isEmpty()) {
-            mStatusBarIconController.getTransitionsController().setIconsDark(
-                    false, animateChange());
-        }
-
-        // If all stacks are light, all icons get dark.
-        else if (lightBarBounds.size() == numStacks) {
+            // If no one is light, all icons become white.
+            mStatusBarIconController
+                    .getTransitionsController()
+                    .setIconsDark(false, animateChange());
+        } else if (lightBarBounds.size() == numStacks) {
+            // If all stacks are light, all icons get dark.
             mStatusBarIconController.setIconsDarkArea(null);
             mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
-        }
-
-        // Not the same for every stack, magic!
-        else {
+        } else {
+            // Not the same for every stack, magic!
             mStatusBarIconController.setIconsDarkArea(lightBarBounds);
             mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
         }
@@ -468,47 +478,16 @@
         }
     }
 
-    /**
-     * Injectable factory for creating a {@link LightBarController}.
-     */
-    public static class Factory {
-        private final JavaAdapter mJavaAdapter;
-        private final DarkIconDispatcher mDarkIconDispatcher;
-        private final BatteryController mBatteryController;
-        private final NavigationModeController mNavModeController;
-        private final StatusBarModeRepositoryStore mStatusBarModeRepository;
-        private final DumpManager mDumpManager;
-        private final DisplayTracker mDisplayTracker;
+    /** Injectable factory for creating a {@link LightBarControllerImpl}. */
+    @AssistedFactory
+    @FunctionalInterface
+    public interface Factory {
 
-        @Inject
-        public Factory(
-                JavaAdapter javaAdapter,
+        /** Creates a {@link LightBarControllerImpl}. */
+        LightBarControllerImpl create(
+                int displayId,
+                CoroutineScope coroutineScope,
                 DarkIconDispatcher darkIconDispatcher,
-                BatteryController batteryController,
-                NavigationModeController navModeController,
-                StatusBarModeRepositoryStore statusBarModeRepository,
-                DumpManager dumpManager,
-                DisplayTracker displayTracker) {
-            mJavaAdapter = javaAdapter;
-            mDarkIconDispatcher = darkIconDispatcher;
-            mBatteryController = batteryController;
-            mNavModeController = navModeController;
-            mStatusBarModeRepository = statusBarModeRepository;
-            mDumpManager = dumpManager;
-            mDisplayTracker = displayTracker;
-        }
-
-        /** Create an {@link LightBarController} */
-        public LightBarController create(Context context) {
-            return new LightBarController(
-                    context,
-                    mJavaAdapter,
-                    mDarkIconDispatcher,
-                    mBatteryController,
-                    mNavModeController,
-                    mStatusBarModeRepository,
-                    mDumpManager,
-                    mDisplayTracker);
-        }
+                StatusBarModePerDisplayRepository statusBarModePerDisplayRepository);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 91c43dd..176dd8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -39,8 +39,8 @@
 import com.android.systemui.Flags;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.res.R;
-import com.android.systemui.shade.LongPressGestureDetector;
 import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
 import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
@@ -69,7 +69,7 @@
     private InsetsFetcher mInsetsFetcher;
     private int mDensity;
     private float mFontScale;
-    private LongPressGestureDetector mLongPressGestureDetector;
+    private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
 
     /**
      * Draw this many pixels into the left/right side of the cutout to optimally use the space
@@ -81,9 +81,10 @@
         mStatusBarWindowControllerStore = Dependency.get(StatusBarWindowControllerStore.class);
     }
 
-    void setLongPressGestureDetector(LongPressGestureDetector longPressGestureDetector) {
+    void setLongPressGestureDetector(
+            StatusBarLongPressGestureDetector statusBarLongPressGestureDetector) {
         if (ShadeExpandsOnStatusBarLongPress.isEnabled()) {
-            mLongPressGestureDetector = longPressGestureDetector;
+            mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector;
         }
     }
 
@@ -207,8 +208,9 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        if (ShadeExpandsOnStatusBarLongPress.isEnabled() && mLongPressGestureDetector != null) {
-            mLongPressGestureDetector.handleTouch(event);
+        if (ShadeExpandsOnStatusBarLongPress.isEnabled()
+                && mStatusBarLongPressGestureDetector != null) {
+            mStatusBarLongPressGestureDetector.handleTouch(event);
         }
         if (mTouchEventHandler == null) {
             Log.w(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index c24f432..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,17 +27,18 @@
 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
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.LongPressGestureDetector
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress
 import com.android.systemui.shade.ShadeLogger
 import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.StatusBarLongPressGestureDetector
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
 import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
@@ -68,7 +69,7 @@
     private val shadeController: ShadeController,
     private val shadeViewController: ShadeViewController,
     private val panelExpansionInteractor: PanelExpansionInteractor,
-    private val longPressGestureDetector: Provider<LongPressGestureDetector>,
+    private val statusBarLongPressGestureDetector: Provider<StatusBarLongPressGestureDetector>,
     private val windowRootView: Provider<WindowRootView>,
     private val shadeLogger: ShadeLogger,
     private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
@@ -118,7 +119,7 @@
         addCursorSupportToIconContainers()
 
         if (ShadeExpandsOnStatusBarLongPress.isEnabled) {
-            mView.setLongPressGestureDetector(longPressGestureDetector.get())
+            mView.setLongPressGestureDetector(statusBarLongPressGestureDetector.get())
         }
 
         progressProvider?.setReadyToHandleTransition(true)
@@ -335,13 +336,13 @@
         private val shadeController: ShadeController,
         private val shadeViewController: ShadeViewController,
         private val panelExpansionInteractor: PanelExpansionInteractor,
-        private val longPressGestureDetector: Provider<LongPressGestureDetector>,
+        private val statusBarLongPressGestureDetector: Provider<StatusBarLongPressGestureDetector>,
         private val windowRootView: Provider<WindowRootView>,
         private val shadeLogger: ShadeLogger,
         private val viewUtil: ViewUtil,
         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 {
@@ -360,7 +361,7 @@
                 shadeController,
                 shadeViewController,
                 panelExpansionInteractor,
-                longPressGestureDetector,
+                statusBarLongPressGestureDetector,
                 windowRootView,
                 shadeLogger,
                 statusBarMoveFromCenterAnimationController,
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/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
index e4a75be..ba878ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.statusbar.core.StatusBarOrchestrator
 import com.android.systemui.statusbar.core.StatusBarSimpleFragment
 import com.android.systemui.statusbar.data.repository.PrivacyDotViewControllerStoreModule
+import com.android.systemui.statusbar.data.repository.PrivacyDotWindowControllerStoreModule
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
 import com.android.systemui.statusbar.events.PrivacyDotViewControllerModule
 import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks
@@ -48,7 +49,12 @@
 
 /** Similar in purpose to [StatusBarModule], but scoped only to phones */
 @Module(
-    includes = [PrivacyDotViewControllerModule::class, PrivacyDotViewControllerStoreModule::class]
+    includes =
+        [
+            PrivacyDotViewControllerModule::class,
+            PrivacyDotWindowControllerStoreModule::class,
+            PrivacyDotViewControllerStoreModule::class,
+        ]
 )
 interface StatusBarPhoneModule {
 
@@ -97,8 +103,12 @@
         fun statusBarInitializerImpl(
             implFactory: StatusBarInitializerImpl.Factory,
             statusBarWindowControllerStore: StatusBarWindowControllerStore,
+            statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
         ): StatusBarInitializerImpl {
-            return implFactory.create(statusBarWindowControllerStore.defaultDisplay)
+            return implFactory.create(
+                statusBarWindowControllerStore.defaultDisplay,
+                statusBarModeRepositoryStore.defaultDisplay,
+            )
         }
 
         @Provides
@@ -143,7 +153,7 @@
         fun commandQueueInitializerCoreStartable(
             initializerLazy: Lazy<CommandQueueInitializer>
         ): CoreStartable {
-            return if (StatusBarSimpleFragment.isEnabled) {
+            return if (StatusBarConnectedDisplays.isEnabled) {
                 initializerLazy.get()
             } else {
                 CoreStartable.NOP
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/satellite/data/DeviceBasedSatelliteRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
index 1d08f2b..98eed84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
@@ -37,6 +37,9 @@
 
     /** Clients must observe this property, as device-based satellite is location-dependent */
     val isSatelliteAllowedForCurrentLocation: StateFlow<Boolean>
+
+    /** When enabled, a satellite icon will display when all other connections are OOS */
+    val isOpportunisticSatelliteIconEnabled: Boolean
 }
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt
index 58c30e0..de42b92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt
@@ -97,6 +97,9 @@
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl)
 
+    override val isOpportunisticSatelliteIconEnabled: Boolean
+        get() = activeRepo.value.isOpportunisticSatelliteIconEnabled
+
     override val isSatelliteProvisioned: StateFlow<Boolean> =
         activeRepo
             .flatMapLatest { it.isSatelliteProvisioned }
@@ -118,6 +121,6 @@
             .stateIn(
                 scope,
                 SharingStarted.WhileSubscribed(),
-                realImpl.isSatelliteAllowedForCurrentLocation.value
+                realImpl.isSatelliteAllowedForCurrentLocation.value,
             )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt
index d557bbf..755899f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt
@@ -16,15 +16,18 @@
 
 package com.android.systemui.statusbar.pipeline.satellite.data.demo
 
+import android.content.res.Resources
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
 import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.MutableStateFlow
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** A satellite repository that represents the latest satellite values sent via demo mode. */
 @SysUISingleton
@@ -33,9 +36,13 @@
 constructor(
     private val dataSource: DemoDeviceBasedSatelliteDataSource,
     @Application private val scope: CoroutineScope,
+    @Main resources: Resources,
 ) : DeviceBasedSatelliteRepository {
     private var demoCommandJob: Job? = null
 
+    override val isOpportunisticSatelliteIconEnabled =
+        resources.getBoolean(R.bool.config_showOpportunisticSatelliteIcon)
+
     override val isSatelliteProvisioned = MutableStateFlow(true)
     override val connectionState = MutableStateFlow(SatelliteConnectionState.Unknown)
     override val signalStrength = MutableStateFlow(0)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index 7686338..a36ef56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.satellite.data.prod
 
+import android.content.res.Resources
 import android.os.OutcomeReceiver
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyManager
@@ -27,14 +28,17 @@
 import android.telephony.satellite.SatelliteProvisionStateCallback
 import android.telephony.satellite.SatelliteSupportedStateCallback
 import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.core.MessageInitializer
 import com.android.systemui.log.core.MessagePrinter
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.pipeline.dagger.DeviceBasedSatelliteInputLog
 import com.android.systemui.statusbar.pipeline.dagger.VerboseDeviceBasedSatelliteInputLog
 import com.android.systemui.statusbar.pipeline.satellite.data.RealDeviceBasedSatelliteRepository
@@ -66,7 +70,6 @@
 import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withContext
 
@@ -146,10 +149,14 @@
     @DeviceBasedSatelliteInputLog private val logBuffer: LogBuffer,
     @VerboseDeviceBasedSatelliteInputLog private val verboseLogBuffer: LogBuffer,
     private val systemClock: SystemClock,
+    @Main resources: Resources,
 ) : RealDeviceBasedSatelliteRepository {
 
     private val satelliteManager: SatelliteManager?
 
+    override val isOpportunisticSatelliteIconEnabled: Boolean =
+        resources.getBoolean(R.bool.config_showOpportunisticSatelliteIcon)
+
     // Some calls into satellite manager will throw exceptions if it is not supported.
     // This is never expected to change after boot, but may need to be retried in some cases
     @get:VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
index f1a444f..08a98c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
@@ -53,6 +53,9 @@
     @DeviceBasedSatelliteInputLog private val logBuffer: LogBuffer,
     @DeviceBasedSatelliteTableLog private val tableLog: TableLogBuffer,
 ) {
+    /** Whether or not we should show the satellite icon when all connections are OOS */
+    val isOpportunisticSatelliteIconEnabled = repo.isOpportunisticSatelliteIconEnabled
+
     /** Must be observed by any UI showing Satellite iconography */
     val isSatelliteAllowed =
         if (Flags.oemEnabledSatelliteFlag()) {
@@ -93,12 +96,7 @@
                 flowOf(0)
             }
             .distinctUntilChanged()
-            .logDiffsForTable(
-                tableLog,
-                columnPrefix = "",
-                columnName = COL_LEVEL,
-                initialValue = 0,
-            )
+            .logDiffsForTable(tableLog, columnPrefix = "", columnName = COL_LEVEL, initialValue = 0)
             .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
 
     val isSatelliteProvisioned = repo.isSatelliteProvisioned
@@ -132,10 +130,9 @@
     /** When all connections are considered OOS, satellite connectivity is potentially valid */
     val areAllConnectionsOutOfService =
         if (Flags.oemEnabledSatelliteFlag()) {
-                combine(
-                    allConnectionsOos,
-                    iconsInteractor.isDeviceInEmergencyCallsOnlyMode,
-                ) { connectionsOos, deviceEmergencyOnly ->
+                combine(allConnectionsOos, iconsInteractor.isDeviceInEmergencyCallsOnlyMode) {
+                    connectionsOos,
+                    deviceEmergencyOnly ->
                     logBuffer.log(
                         TAG,
                         LogLevel.INFO,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index 37f2f19..13ac321 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -77,35 +77,38 @@
 
     // This adds a 10 seconds delay before showing the icon
     private val shouldShowIconForOosAfterHysteresis: StateFlow<Boolean> =
-        interactor.areAllConnectionsOutOfService
-            .flatMapLatest { shouldShow ->
-                if (shouldShow) {
-                    logBuffer.log(
-                        TAG,
-                        LogLevel.INFO,
-                        { long1 = DELAY_DURATION.inWholeSeconds },
-                        { "Waiting $long1 seconds before showing the satellite icon" }
+        if (interactor.isOpportunisticSatelliteIconEnabled) {
+                interactor.areAllConnectionsOutOfService
+                    .flatMapLatest { shouldShow ->
+                        if (shouldShow) {
+                            logBuffer.log(
+                                TAG,
+                                LogLevel.INFO,
+                                { long1 = DELAY_DURATION.inWholeSeconds },
+                                { "Waiting $long1 seconds before showing the satellite icon" },
+                            )
+                            delay(DELAY_DURATION)
+                            flowOf(true)
+                        } else {
+                            flowOf(false)
+                        }
+                    }
+                    .distinctUntilChanged()
+                    .logDiffsForTable(
+                        tableLog,
+                        columnPrefix = "vm",
+                        columnName = COL_VISIBLE_FOR_OOS,
+                        initialValue = false,
                     )
-                    delay(DELAY_DURATION)
-                    flowOf(true)
-                } else {
-                    flowOf(false)
-                }
+            } else {
+                flowOf(false)
             }
-            .distinctUntilChanged()
-            .logDiffsForTable(
-                tableLog,
-                columnPrefix = "vm",
-                columnName = COL_VISIBLE_FOR_OOS,
-                initialValue = false,
-            )
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     private val canShowIcon =
-        combine(
-            interactor.isSatelliteAllowed,
-            interactor.isSatelliteProvisioned,
-        ) { allowed, provisioned ->
+        combine(interactor.isSatelliteAllowed, interactor.isSatelliteProvisioned) {
+            allowed,
+            provisioned ->
             allowed && provisioned
         }
 
@@ -141,11 +144,10 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val icon: StateFlow<Icon?> =
-        combine(
-                showIcon,
-                interactor.connectionState,
-                interactor.signalStrength,
-            ) { shouldShow, state, signalStrength ->
+        combine(showIcon, interactor.connectionState, interactor.signalStrength) {
+                shouldShow,
+                state,
+                signalStrength ->
                 if (shouldShow) {
                     SatelliteIconModel.fromConnectionState(state, signalStrength)
                 } else {
@@ -155,10 +157,7 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), null)
 
     override val carrierText: StateFlow<String?> =
-        combine(
-                showIcon,
-                interactor.connectionState,
-            ) { shouldShow, connectionState ->
+        combine(showIcon, interactor.connectionState) { shouldShow, connectionState ->
                 logBuffer.log(
                     TAG,
                     LogLevel.INFO,
@@ -166,7 +165,7 @@
                         bool1 = shouldShow
                         str1 = connectionState.name
                     },
-                    { "Updating carrier text. shouldShow=$bool1 connectionState=$str1" }
+                    { "Updating carrier text. shouldShow=$bool1 connectionState=$str1" },
                 )
                 if (shouldShow) {
                     when (connectionState) {
diff --git a/packages/SystemUI/src/com/android/systemui/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/statusbar/policy/ui/dialog/composable/ModeTile.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
index 76389f3..b033b36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
@@ -46,7 +46,7 @@
 import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModeTileViewModel
 
 @Composable
-fun ModeTile(viewModel: ModeTileViewModel) {
+fun ModeTile(viewModel: ModeTileViewModel, modifier: Modifier = Modifier) {
     val tileColor: Color by
         animateColorAsState(
             if (viewModel.enabled) MaterialTheme.colorScheme.primary
@@ -59,7 +59,7 @@
         )
 
     CompositionLocalProvider(LocalContentColor provides contentColor) {
-        Surface(color = tileColor, shape = RoundedCornerShape(16.dp)) {
+        Surface(color = tileColor, shape = RoundedCornerShape(16.dp), modifier = modifier) {
             Row(
                 modifier =
                     Modifier.combinedClickable(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
index 903c7e1..16f24f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
@@ -17,29 +17,74 @@
 package com.android.systemui.statusbar.policy.ui.dialog.composable
 
 import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.lazy.grid.GridCells
 import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.Flags
+import com.android.systemui.qs.panels.ui.compose.PagerDots
 import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel
 
 @Composable
 fun ModeTileGrid(viewModel: ModesDialogViewModel) {
     val tiles by viewModel.tiles.collectAsStateWithLifecycle(initialValue = emptyList())
 
-    LazyVerticalGrid(
-        columns = GridCells.Fixed(1),
-        modifier = Modifier.fillMaxWidth().heightIn(max = 280.dp),
-        verticalArrangement = Arrangement.spacedBy(8.dp),
-        horizontalArrangement = Arrangement.spacedBy(8.dp),
-    ) {
-        items(tiles.size, key = { index -> tiles[index].id }) { index ->
-            ModeTile(viewModel = tiles[index])
+    if (Flags.modesUiDialogPaging()) {
+        val tilesPerPage = 3
+        val totalPages = { (tiles.size + tilesPerPage - 1) / tilesPerPage }
+        val pagerState = rememberPagerState(initialPage = 0, pageCount = totalPages)
+
+        Column {
+            HorizontalPager(
+                state = pagerState,
+                modifier = Modifier.fillMaxWidth(),
+                pageSpacing = 16.dp,
+                verticalAlignment = Alignment.Top,
+                // Pre-emptively layout and compose the next page, to make sure the height stays
+                // the same even if we have fewer than [tilesPerPage] tiles on the last page.
+                beyondViewportPageCount = 1,
+            ) { page ->
+                Column(
+                    modifier = Modifier.fillMaxWidth().fillMaxHeight(),
+                    verticalArrangement = Arrangement.spacedBy(8.dp, alignment = Alignment.Top),
+                ) {
+                    val startIndex = page * tilesPerPage
+                    val endIndex = minOf((page + 1) * tilesPerPage, tiles.size)
+                    for (index in startIndex until endIndex) {
+                        ModeTile(viewModel = tiles[index], modifier = Modifier.fillMaxWidth())
+                    }
+                }
+            }
+
+            PagerDots(
+                pagerState = pagerState,
+                activeColor = MaterialTheme.colorScheme.primary,
+                nonActiveColor = MaterialTheme.colorScheme.onSurfaceVariant,
+                modifier = Modifier.align(Alignment.CenterHorizontally).padding(top = 8.dp),
+            )
+        }
+    } else {
+        LazyVerticalGrid(
+            columns = GridCells.Fixed(1),
+            modifier = Modifier.fillMaxWidth().heightIn(max = 280.dp),
+            verticalArrangement = Arrangement.spacedBy(8.dp),
+            horizontalArrangement = Arrangement.spacedBy(8.dp),
+        ) {
+            items(tiles.size, key = { index -> tiles[index].id }) { index ->
+                ModeTile(viewModel = tiles[index])
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 9c8ef04..1c3fece 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -242,10 +242,16 @@
         }
     };
 
-    private int getLatestWallpaperType(int userId) {
-        return mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, userId)
-                > mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, userId)
-                ? WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM;
+    private int getDefaultWallpaperColorsSource(int userId) {
+        if (com.android.systemui.shared.Flags.newCustomizationPickerUi()) {
+            // The wallpaper colors source is always the home wallpaper.
+            return WallpaperManager.FLAG_SYSTEM;
+        } else {
+            // The wallpaper colors source is based on the last set wallpaper.
+            return mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, userId)
+                    > mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, userId)
+                    ? WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM;
+        }
     }
 
     private boolean isSeedColorSet(JSONObject jsonObject, WallpaperColors newWallpaperColors) {
@@ -279,9 +285,9 @@
     private void handleWallpaperColors(WallpaperColors wallpaperColors, int flags, int userId) {
         final int currentUser = mUserTracker.getUserId();
         final boolean hadWallpaperColors = mCurrentColors.get(userId) != null;
-        int latestWallpaperType = getLatestWallpaperType(userId);
-        boolean eventForLatestWallpaper = (flags & latestWallpaperType) != 0;
-        if (eventForLatestWallpaper) {
+        int wallpaperColorsSource = getDefaultWallpaperColorsSource(userId);
+        boolean wallpaperColorsNeedUpdate = (flags & wallpaperColorsSource) != 0;
+        if (wallpaperColorsNeedUpdate) {
             mCurrentColors.put(userId, wallpaperColors);
             if (DEBUG) Log.d(TAG, "got new colors: " + wallpaperColors + " where: " + flags);
         }
@@ -328,7 +334,7 @@
             boolean userChoseLockScreenColor = COLOR_SOURCE_LOCK.equals(wallpaperPickerColorSource);
             boolean preserveLockScreenColor = isDestinationHomeOnly && userChoseLockScreenColor;
 
-            if (!userChosePresetColor && !preserveLockScreenColor && eventForLatestWallpaper
+            if (!userChosePresetColor && !preserveLockScreenColor && wallpaperColorsNeedUpdate
                     && !isSeedColorSet(jsonObject, wallpaperColors)) {
                 mSkipSettingChange = true;
                 if (jsonObject.has(OVERLAY_CATEGORY_ACCENT_COLOR) || jsonObject.has(
@@ -494,7 +500,7 @@
         // Upon boot, make sure we have the most up to date colors
         Runnable updateColors = () -> {
             WallpaperColors systemColor = mWallpaperManager.getWallpaperColors(
-                    getLatestWallpaperType(mUserTracker.getUserId()));
+                    getDefaultWallpaperColorsSource(mUserTracker.getUserId()));
             Runnable applyColors = () -> {
                 if (DEBUG) Log.d(TAG, "Boot colors: " + systemColor);
                 mCurrentColors.put(mUserTracker.getUserId(), systemColor);
diff --git a/packages/SystemUI/src/com/android/systemui/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/user/domain/interactor/UserSwitcherInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
index 3662c78..163288b 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
@@ -32,6 +32,7 @@
 import android.os.UserManager
 import android.provider.Settings
 import android.util.Log
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.internal.logging.UiEventLogger
 import com.android.internal.util.UserIcons
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -81,7 +82,6 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.sync.withLock
 import kotlinx.coroutines.withContext
@@ -109,7 +109,7 @@
     private val guestUserInteractor: GuestUserInteractor,
     private val uiEventLogger: UiEventLogger,
     private val userRestrictionChecker: UserRestrictionChecker,
-    private val processWrapper: ProcessWrapper
+    private val processWrapper: ProcessWrapper,
 ) {
     /**
      * Defines interface for classes that can be notified when the state of users on the device is
@@ -137,11 +137,10 @@
     /** List of current on-device users to select from. */
     val users: Flow<List<UserModel>>
         get() =
-            combine(
+            combine(userInfos, repository.selectedUserInfo, repository.userSwitcherSettings) {
                 userInfos,
-                repository.selectedUserInfo,
-                repository.userSwitcherSettings,
-            ) { userInfos, selectedUserInfo, settings ->
+                selectedUserInfo,
+                settings ->
                 toUserModels(
                     userInfos = userInfos,
                     selectedUserId = selectedUserInfo.id,
@@ -157,7 +156,7 @@
                 toUserModel(
                     userInfo = selectedUserInfo,
                     selectedUserId = selectedUserId,
-                    canSwitchUsers = canSwitchUsers(selectedUserId)
+                    canSwitchUsers = canSwitchUsers(selectedUserId),
                 )
             }
 
@@ -211,7 +210,7 @@
                                                 manager,
                                                 repository,
                                                 settings.isUserSwitcherEnabled,
-                                                canAccessUserSwitcher
+                                                canAccessUserSwitcher,
                                             )
 
                                         if (canCreateUsers) {
@@ -238,7 +237,7 @@
                         if (
                             UserActionsUtil.canManageUsers(
                                 repository,
-                                settings.isUserSwitcherEnabled
+                                settings.isUserSwitcherEnabled,
                             )
                         ) {
                             add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
@@ -248,18 +247,14 @@
                 .flowOn(backgroundDispatcher)
 
     val userRecords: StateFlow<ArrayList<UserRecord>> =
-        combine(
+        combine(userInfos, repository.selectedUserInfo, actions, repository.userSwitcherSettings) {
                 userInfos,
-                repository.selectedUserInfo,
-                actions,
-                repository.userSwitcherSettings,
-            ) { userInfos, selectedUserInfo, actionModels, settings ->
+                selectedUserInfo,
+                actionModels,
+                settings ->
                 ArrayList(
                     userInfos.map {
-                        toRecord(
-                            userInfo = it,
-                            selectedUserId = selectedUserInfo.id,
-                        )
+                        toRecord(userInfo = it, selectedUserId = selectedUserInfo.id)
                     } +
                         actionModels.map {
                             toRecord(
@@ -298,7 +293,8 @@
     val isGuestUserResetting: Boolean = guestUserInteractor.isGuestUserResetting
 
     /** Whether to enable the user chip in the status bar */
-    val isStatusBarUserChipEnabled: Boolean = repository.isStatusBarUserChipEnabled
+    val isStatusBarUserChipEnabled: Boolean
+        get() = repository.isStatusBarUserChipEnabled
 
     private val _dialogShowRequests = MutableStateFlow<ShowDialogRequestModel?>(null)
     val dialogShowRequests: Flow<ShowDialogRequestModel?> = _dialogShowRequests.asStateFlow()
@@ -467,10 +463,8 @@
         when (action) {
             UserActionModel.ENTER_GUEST_MODE -> {
                 uiEventLogger.log(MultiUserActionsEvent.CREATE_GUEST_FROM_USER_SWITCHER)
-                guestUserInteractor.createAndSwitchTo(
-                    this::showDialog,
-                    this::dismissDialog,
-                ) { userId ->
+                guestUserInteractor.createAndSwitchTo(this::showDialog, this::dismissDialog) {
+                    userId ->
                     selectUser(userId, dialogShower)
                 }
             }
@@ -481,7 +475,7 @@
                 activityStarter.startActivity(
                     CreateUserActivity.createIntentForStart(
                         applicationContext,
-                        keyguardInteractor.isKeyguardShowing()
+                        keyguardInteractor.isKeyguardShowing(),
                     ),
                     /* dismissShade= */ true,
                     /* animationController */ null,
@@ -523,17 +517,14 @@
         )
     }
 
-    fun removeGuestUser(
-        @UserIdInt guestUserId: Int,
-        @UserIdInt targetUserId: Int,
-    ) {
+    fun removeGuestUser(@UserIdInt guestUserId: Int, @UserIdInt targetUserId: Int) {
         applicationScope.launch {
             guestUserInteractor.remove(
                 guestUserId = guestUserId,
                 targetUserId = targetUserId,
                 ::showDialog,
                 ::dismissDialog,
-                ::switchUser
+                ::switchUser,
             )
         }
     }
@@ -570,10 +561,7 @@
         }
     }
 
-    private suspend fun toRecord(
-        userInfo: UserInfo,
-        selectedUserId: Int,
-    ): UserRecord {
+    private suspend fun toRecord(userInfo: UserInfo, selectedUserId: Int): UserRecord {
         return LegacyUserDataHelper.createRecord(
             context = applicationContext,
             manager = manager,
@@ -595,10 +583,7 @@
             actionType = action,
             isRestricted = isRestricted,
             isSwitchToEnabled =
-                canSwitchUsers(
-                    selectedUserId = selectedUserId,
-                    isAction = true,
-                ) &&
+                canSwitchUsers(selectedUserId = selectedUserId, isAction = true) &&
                     // If the user is auto-created is must not be currently resetting.
                     !(isGuestUserAutoCreated && isGuestUserResetting),
             userRestrictionChecker = userRestrictionChecker,
@@ -623,10 +608,7 @@
         }
     }
 
-    private suspend fun onBroadcastReceived(
-        intent: Intent,
-        previousUserInfo: UserInfo?,
-    ) {
+    private suspend fun onBroadcastReceived(intent: Intent, previousUserInfo: UserInfo?) {
         val shouldRefreshAllUsers =
             when (intent.action) {
                 Intent.ACTION_LOCALE_CHANGED -> true
@@ -645,10 +627,8 @@
                 Intent.ACTION_USER_INFO_CHANGED -> true
                 Intent.ACTION_USER_UNLOCKED -> {
                     // If we unlocked the system user, we should refresh all users.
-                    intent.getIntExtra(
-                        Intent.EXTRA_USER_HANDLE,
-                        UserHandle.USER_NULL,
-                    ) == UserHandle.USER_SYSTEM
+                    intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL) ==
+                        UserHandle.USER_SYSTEM
                 }
                 else -> true
             }
@@ -668,20 +648,14 @@
         // Disconnect from the old secondary user's service
         val secondaryUserId = repository.secondaryUserId
         if (secondaryUserId != UserHandle.USER_NULL) {
-            applicationContext.stopServiceAsUser(
-                intent,
-                UserHandle.of(secondaryUserId),
-            )
+            applicationContext.stopServiceAsUser(intent, UserHandle.of(secondaryUserId))
             repository.secondaryUserId = UserHandle.USER_NULL
         }
 
         // Connect to the new secondary user's service (purely to ensure that a persistent
         // SystemUI application is created for that user)
         if (userId != processWrapper.myUserHandle().identifier && !processWrapper.isSystemUser) {
-            applicationContext.startServiceAsUser(
-                intent,
-                UserHandle.of(userId),
-            )
+            applicationContext.startServiceAsUser(intent, UserHandle.of(userId))
             repository.secondaryUserId = userId
         }
     }
@@ -732,7 +706,7 @@
     private suspend fun toUserModel(
         userInfo: UserInfo,
         selectedUserId: Int,
-        canSwitchUsers: Boolean
+        canSwitchUsers: Boolean,
     ): UserModel {
         val userId = userInfo.id
         val isSelected = userId == selectedUserId
@@ -740,11 +714,7 @@
             UserModel(
                 id = userId,
                 name = Text.Loaded(userInfo.name),
-                image =
-                    getUserImage(
-                        isGuest = true,
-                        userId = userId,
-                    ),
+                image = getUserImage(isGuest = true, userId = userId),
                 isSelected = isSelected,
                 isSelectable = canSwitchUsers,
                 isGuest = true,
@@ -753,11 +723,7 @@
             UserModel(
                 id = userId,
                 name = Text.Loaded(userInfo.name),
-                image =
-                    getUserImage(
-                        isGuest = false,
-                        userId = userId,
-                    ),
+                image = getUserImage(isGuest = false, userId = userId),
                 isSelected = isSelected,
                 isSelectable = canSwitchUsers || isSelected,
                 isGuest = false,
@@ -765,10 +731,7 @@
         }
     }
 
-    private suspend fun canSwitchUsers(
-        selectedUserId: Int,
-        isAction: Boolean = false,
-    ): Boolean {
+    private suspend fun canSwitchUsers(selectedUserId: Int, isAction: Boolean = false): Boolean {
         val isHeadlessSystemUserMode =
             withContext(backgroundDispatcher) { headlessSystemUserMode.isHeadlessSystemUserMode() }
         // Whether menu item should be active. True if item is a user or if any user has
@@ -785,7 +748,7 @@
             .getUsers(
                 /* excludePartial= */ true,
                 /* excludeDying= */ true,
-                /* excludePreCreated= */ true
+                /* excludePreCreated= */ true,
             )
             .any { user ->
                 user.id != UserHandle.USER_SYSTEM &&
@@ -794,10 +757,7 @@
     }
 
     @SuppressLint("UseCompatLoadingForDrawables")
-    private suspend fun getUserImage(
-        isGuest: Boolean,
-        userId: Int,
-    ): Drawable {
+    private suspend fun getUserImage(isGuest: Boolean, userId: Int): Drawable {
         if (isGuest) {
             return checkNotNull(
                 applicationContext.getDrawable(com.android.settingslib.R.drawable.ic_account_circle)
@@ -823,13 +783,13 @@
         return UserIcons.getDefaultUserIcon(
             applicationContext.resources,
             userId,
-            /* light= */ false
+            /* light= */ false,
         )
     }
 
     private fun canCreateGuestUser(
         settings: UserSwitcherSettingsModel,
-        canAccessUserSwitcher: Boolean
+        canAccessUserSwitcher: Boolean,
     ): Boolean {
         return guestUserInteractor.isGuestUserAutoCreated ||
             UserActionsUtil.canCreateGuest(
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
index 2c425b19..53c2d88 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
@@ -30,11 +30,10 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 class StatusBarUserChipViewModel
 @Inject
-constructor(
-    interactor: UserSwitcherInteractor,
-) {
+constructor(private val interactor: UserSwitcherInteractor) {
     /** Whether the status bar chip ui should be available */
-    val chipEnabled: Boolean = interactor.isStatusBarUserChipEnabled
+    val chipEnabled: Boolean
+        get() = interactor.isStatusBarUserChipEnabled
 
     /** Whether or not the chip should be showing, based on the number of users */
     val isChipVisible: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 3159124..63a5b3f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -20,6 +20,7 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.coroutineScope
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -35,7 +36,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.plus
 
 /** A class allowing Java classes to collect on Kotlin flows. */
 @SysUISingleton
@@ -102,6 +103,22 @@
     }
 }
 
+/**
+ * Collect information for the given [flow], calling [consumer] for each emitted event on the
+ * specified [collectContext].
+ *
+ * Collection will continue until the given [scope] is cancelled.
+ */
+@JvmOverloads
+fun <T> collectFlow(
+    scope: CoroutineScope,
+    collectContext: CoroutineContext = scope.coroutineContext,
+    flow: Flow<T>,
+    consumer: Consumer<T>,
+): Job {
+    return scope.plus(collectContext).launch { flow.collect { consumer.accept(it) } }
+}
+
 fun <A, B, R> combineFlows(flow1: Flow<A>, flow2: Flow<B>, bifunction: (A, B) -> R): Flow<R> {
     return combine(flow1, flow2, bifunction)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
index d509b2d..f36c335e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
@@ -16,9 +16,6 @@
 
 package com.android.systemui.util.settings;
 
-import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository;
-import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepositoryImpl;
-
 import dagger.Binds;
 import dagger.Module;
 
@@ -39,9 +36,4 @@
     /** Bind GlobalSettingsImpl to GlobalSettings. */
     @Binds
     GlobalSettings bindsGlobalSettings(GlobalSettingsImpl impl);
-
-    /** Bind UserAwareSecureSettingsRepositoryImpl to UserAwareSecureSettingsRepository. */
-    @Binds
-    UserAwareSecureSettingsRepository bindsUserAwareSecureSettingsRepository(
-            UserAwareSecureSettingsRepositoryImpl impl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt
index d3e5080..71335ec 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt
@@ -19,52 +19,25 @@
 import android.provider.Settings
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxy
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
 import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
 
 /**
  * Repository for observing values of [Settings.Secure] for the currently active user. That means
  * when user is switched and the new user has different value, flow will emit new value.
  */
-interface UserAwareSecureSettingsRepository {
-
-    /**
-     * Emits boolean value of the setting for active user. Also emits starting value when
-     * subscribed.
-     * See: [SettingsProxy.getBool].
-     */
-    fun boolSettingForActiveUser(name: String, defaultValue: Boolean = false): Flow<Boolean>
-}
-
 @SysUISingleton
-@OptIn(ExperimentalCoroutinesApi::class)
-class UserAwareSecureSettingsRepositoryImpl @Inject constructor(
-    private val secureSettings: SecureSettings,
-    private val userRepository: UserRepository,
-    @Background private val backgroundDispatcher: CoroutineDispatcher,
-) : UserAwareSecureSettingsRepository {
-
-    override fun boolSettingForActiveUser(name: String, defaultValue: Boolean): Flow<Boolean> =
-        userRepository.selectedUserInfo
-            .flatMapLatest { userInfo -> settingObserver(name, defaultValue, userInfo.id) }
-            .distinctUntilChanged()
-            .flowOn(backgroundDispatcher)
-
-    private fun settingObserver(name: String, defaultValue: Boolean, userId: Int): Flow<Boolean> {
-        return secureSettings
-            .observerFlow(userId, name)
-            .onStart { emit(Unit) }
-            .map { secureSettings.getBoolForUser(name, defaultValue, userId) }
-    }
-}
\ No newline at end of file
+class UserAwareSecureSettingsRepository
+@Inject
+constructor(
+    secureSettings: SecureSettings,
+    userRepository: UserRepository,
+    @Background backgroundDispatcher: CoroutineDispatcher,
+    @Background bgContext: CoroutineContext,
+) :
+    UserAwareSettingsRepository(secureSettings, userRepository, backgroundDispatcher, bgContext),
+    SecureSettingsRepository
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt
new file mode 100644
index 0000000..a31b8d9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.settings.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import com.android.systemui.util.settings.UserSettingsProxy
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+
+/**
+ * Repository for observing values of a [UserSettingsProxy], for the currently active user. That
+ * means that when user is switched and the new user has a different value, the flow will emit the
+ * new value.
+ */
+@SysUISingleton
+@OptIn(ExperimentalCoroutinesApi::class)
+abstract class UserAwareSettingsRepository(
+    private val userSettings: UserSettingsProxy,
+    private val userRepository: UserRepository,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    @Background private val bgContext: CoroutineContext,
+) {
+
+    fun boolSetting(name: String, defaultValue: Boolean): Flow<Boolean> =
+        userRepository.selectedUserInfo
+            .flatMapLatest { userInfo ->
+                settingObserver(name, userInfo.id) {
+                    userSettings.getBoolForUser(name, defaultValue, userInfo.id)
+                }
+            }
+            .distinctUntilChanged()
+            .flowOn(backgroundDispatcher)
+
+    fun intSetting(name: String, defaultValue: Int): Flow<Int> {
+        return userRepository.selectedUserInfo
+            .flatMapLatest { userInfo ->
+                settingObserver(name, userInfo.id) {
+                    userSettings.getIntForUser(name, defaultValue, userInfo.id)
+                }
+            }
+            .distinctUntilChanged()
+            .flowOn(backgroundDispatcher)
+    }
+
+    private fun <T> settingObserver(name: String, userId: Int, settingsReader: () -> T): Flow<T> {
+        return userSettings
+            .observerFlow(userId, name)
+            .onStart { emit(Unit) }
+            .map { settingsReader.invoke() }
+    }
+
+    suspend fun setInt(name: String, value: Int) {
+        withContext(bgContext) {
+            userSettings.putIntForUser(name, value, userRepository.getSelectedUserInfo().id)
+        }
+    }
+
+    suspend fun getInt(name: String, defaultValue: Int): Int {
+        return withContext(bgContext) {
+            userSettings.getIntForUser(name, defaultValue, userRepository.getSelectedUserInfo().id)
+        }
+    }
+
+    suspend fun getString(name: String): String? {
+        return withContext(bgContext) {
+            userSettings.getStringForUser(name, userRepository.getSelectedUserInfo().id)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt
new file mode 100644
index 0000000..8b1fca5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.settings.repository
+
+import android.provider.Settings
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SystemSettings
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
+
+/**
+ * Repository for observing values of [Settings.Secure] for the currently active user. That means
+ * when user is switched and the new user has different value, flow will emit new value.
+ */
+@SysUISingleton
+class UserAwareSystemSettingsRepository
+@Inject
+constructor(
+    systemSettings: SystemSettings,
+    userRepository: UserRepository,
+    @Background backgroundDispatcher: CoroutineDispatcher,
+    @Background bgContext: CoroutineContext,
+) :
+    UserAwareSettingsRepository(systemSettings, userRepository, backgroundDispatcher, bgContext),
+    SystemSettingsRepository
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index 617aaa7..d5b8597 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -53,7 +53,9 @@
         fun provideAudioManagerIntentsReceiver(
             @Application context: Context,
             @Application coroutineScope: CoroutineScope,
-        ): AudioManagerEventsReceiver = AudioManagerEventsReceiverImpl(context, coroutineScope)
+            @Background coroutineContext: CoroutineContext,
+        ): AudioManagerEventsReceiver =
+            AudioManagerEventsReceiverImpl(context, coroutineScope, coroutineContext)
 
         @Provides
         @SysUISingleton
@@ -82,7 +84,7 @@
             localBluetoothManager: LocalBluetoothManager?,
             @Application coroutineScope: CoroutineScope,
             @Background coroutineContext: CoroutineContext,
-            volumeLogger: VolumeLogger
+            volumeLogger: VolumeLogger,
         ): AudioSharingRepository =
             if (Flags.enableLeAudioSharing() && localBluetoothManager != null) {
                 AudioSharingRepositoryImpl(
@@ -90,7 +92,7 @@
                     localBluetoothManager,
                     coroutineScope,
                     coroutineContext,
-                    volumeLogger
+                    volumeLogger,
                 )
             } else {
                 AudioSharingRepositoryEmptyImpl()
@@ -111,8 +113,7 @@
 
         @Provides
         @SysUISingleton
-        fun provideAudioSystemRepository(
-            @Application context: Context,
-        ): AudioSystemRepository = AudioSystemRepositoryImpl(context)
+        fun provideAudioSystemRepository(@Application context: Context): AudioSystemRepository =
+            AudioSystemRepositoryImpl(context)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
index 097a60f..4fc9a7c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
@@ -19,10 +19,8 @@
 import android.app.Dialog
 import android.content.Context
 import android.os.Bundle
-import android.view.ContextThemeWrapper
 import android.view.MotionEvent
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.res.R
 import com.android.systemui.volume.Events
 import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
 import com.android.systemui.volume.dialog.ui.binder.VolumeDialogViewBinder
@@ -34,7 +32,7 @@
     @Application context: Context,
     private val viewBinder: VolumeDialogViewBinder,
     private val visibilityInteractor: VolumeDialogVisibilityInteractor,
-) : Dialog(ContextThemeWrapper(context, R.style.volume_dialog_theme)) {
+) : Dialog(context) {
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
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/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
index c05a0b2..6816d35 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
@@ -16,15 +16,26 @@
 
 package com.android.systemui.volume.dialog.ringer.ui.binder
 
+import android.view.LayoutInflater
 import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageButton
+import androidx.annotation.LayoutRes
+import androidx.compose.ui.util.fastForEachIndexed
 import com.android.systemui.lifecycle.WindowLifecycleState
 import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.lifecycle.setSnapshotBinding
 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.ringer.ui.viewmodel.RingerButtonViewModel
+import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerDrawerState
+import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerViewModel
+import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerViewModelState
 import com.android.systemui.volume.dialog.ringer.ui.viewmodel.VolumeDialogRingerDrawerViewModel
 import javax.inject.Inject
-import kotlinx.coroutines.awaitCancellation
+import kotlin.math.abs
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
 
 @VolumeDialogScope
 class VolumeDialogRingerViewBinder
@@ -33,16 +44,124 @@
 
     fun bind(view: View) {
         with(view) {
+            val drawerAndRingerContainer =
+                requireViewById<View>(R.id.volume_ringer_and_drawer_container)
+            val drawerContainer = requireViewById<View>(R.id.volume_drawer_container)
+            val selectedButtonView =
+                requireViewById<ImageButton>(R.id.volume_new_ringer_active_button)
+            val volumeDialogView = requireViewById<ViewGroup>(R.id.volume_dialog)
             repeatWhenAttached {
                 viewModel(
                     traceName = "VolumeDialogRingerViewBinder",
                     minWindowLifecycleState = WindowLifecycleState.ATTACHED,
                     factory = { viewModelFactory.create() },
                 ) { viewModel ->
-                    setSnapshotBinding {}
-                    awaitCancellation()
+                    viewModel.ringerViewModel
+                        .onEach { ringerState ->
+                            when (ringerState) {
+                                is RingerViewModelState.Available -> {
+                                    val uiModel = ringerState.uiModel
+
+                                    bindSelectedButton(viewModel, uiModel, selectedButtonView)
+                                    bindDrawerButtons(viewModel, uiModel.availableButtons)
+
+                                    // Set up views background and visibility
+                                    drawerAndRingerContainer.visibility = View.VISIBLE
+                                    when (uiModel.drawerState) {
+                                        is RingerDrawerState.Initial -> {
+                                            drawerContainer.visibility = View.GONE
+                                            selectedButtonView.visibility = View.VISIBLE
+                                            volumeDialogView.setBackgroundResource(
+                                                R.drawable.volume_dialog_background
+                                            )
+                                        }
+
+                                        is RingerDrawerState.Closed -> {
+                                            drawerContainer.visibility = View.GONE
+                                            selectedButtonView.visibility = View.VISIBLE
+                                            volumeDialogView.setBackgroundResource(
+                                                R.drawable.volume_dialog_background
+                                            )
+                                        }
+
+                                        is RingerDrawerState.Open -> {
+                                            drawerContainer.visibility = View.VISIBLE
+                                            selectedButtonView.visibility = View.GONE
+                                            if (
+                                                uiModel.currentButtonIndex !=
+                                                    uiModel.availableButtons.size - 1
+                                            ) {
+                                                volumeDialogView.setBackgroundResource(
+                                                    R.drawable.volume_dialog_background_small_radius
+                                                )
+                                            }
+                                        }
+                                    }
+                                }
+
+                                is RingerViewModelState.Unavailable -> {
+                                    drawerAndRingerContainer.visibility = View.GONE
+                                    volumeDialogView.setBackgroundResource(
+                                        R.drawable.volume_dialog_background
+                                    )
+                                }
+                            }
+                        }
+                        .launchIn(this)
                 }
             }
         }
     }
+
+    private fun View.bindDrawerButtons(
+        viewModel: VolumeDialogRingerDrawerViewModel,
+        availableButtons: List<RingerButtonViewModel?>,
+    ) {
+        val drawerOptions = requireViewById<ViewGroup>(R.id.volume_drawer_options)
+        val count = availableButtons.size
+        drawerOptions.ensureChildCount(R.layout.volume_ringer_button, count)
+
+        availableButtons.fastForEachIndexed { index, ringerButton ->
+            ringerButton?.let {
+                drawerOptions.getChildAt(count - index - 1).bindDrawerButton(it, viewModel)
+            }
+        }
+    }
+
+    private fun View.bindDrawerButton(
+        buttonViewModel: RingerButtonViewModel,
+        viewModel: VolumeDialogRingerDrawerViewModel,
+    ) {
+        with(requireViewById<ImageButton>(R.id.volume_drawer_button)) {
+            setImageResource(buttonViewModel.imageResId)
+            contentDescription = context.getString(buttonViewModel.contentDescriptionResId)
+            setOnClickListener { viewModel.onRingerButtonClicked(buttonViewModel.ringerMode) }
+        }
+    }
+
+    private fun ViewGroup.ensureChildCount(@LayoutRes viewLayoutId: Int, count: Int) {
+        val childCountDelta = childCount - count
+        when {
+            childCountDelta > 0 -> {
+                removeViews(0, childCountDelta)
+            }
+            childCountDelta < 0 -> {
+                val inflater = LayoutInflater.from(context)
+                repeat(abs(childCountDelta)) { inflater.inflate(viewLayoutId, this, true) }
+            }
+        }
+    }
+
+    private fun bindSelectedButton(
+        viewModel: VolumeDialogRingerDrawerViewModel,
+        uiModel: RingerViewModel,
+        selectedButtonView: ImageButton,
+    ) {
+        with(uiModel) {
+            selectedButtonView.setImageResource(selectedButton.imageResId)
+            selectedButtonView.setOnClickListener {
+                viewModel.onRingerButtonClicked(selectedButton.ringerMode)
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt
index a09bfeb..96d4f62 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt
@@ -22,6 +22,8 @@
     val availableButtons: List<RingerButtonViewModel?>,
     /** The index of the currently selected button */
     val currentButtonIndex: Int,
+    /** Currently selected button. */
+    val selectedButton: RingerButtonViewModel,
     /** For open and close animations */
     val drawerState: RingerDrawerState,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
index 5b73107..d4da226 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
@@ -41,8 +41,6 @@
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.stateIn
 
-private const val TAG = "VolumeDialogRingerDrawerViewModel"
-
 class VolumeDialogRingerDrawerViewModel
 @AssistedInject
 constructor(
@@ -111,31 +109,45 @@
         return if (currentIndex == -1 || isSingleVolume) {
             RingerViewModelState.Unavailable
         } else {
-            RingerViewModelState.Available(
-                RingerViewModel(
-                    availableButtons = availableModes.map { mode -> toButtonViewModel(mode) },
-                    currentButtonIndex = currentIndex,
-                    drawerState = drawerState,
+            toButtonViewModel(currentRingerMode, isSelectedButton = true)?.let {
+                RingerViewModelState.Available(
+                    RingerViewModel(
+                        availableButtons = availableModes.map { mode -> toButtonViewModel(mode) },
+                        currentButtonIndex = currentIndex,
+                        selectedButton = it,
+                        drawerState = drawerState,
+                    )
                 )
-            )
+            } ?: RingerViewModelState.Unavailable
         }
     }
 
     private fun VolumeDialogRingerModel.toButtonViewModel(
-        ringerMode: RingerMode
+        ringerMode: RingerMode,
+        isSelectedButton: Boolean = false,
     ): RingerButtonViewModel? {
         return when (ringerMode.value) {
             RINGER_MODE_SILENT ->
                 RingerButtonViewModel(
                     imageResId = R.drawable.ic_speaker_mute,
-                    contentDescriptionResId = R.string.volume_ringer_status_silent,
+                    contentDescriptionResId =
+                        if (isSelectedButton) {
+                            R.string.volume_ringer_status_silent
+                        } else {
+                            R.string.volume_ringer_hint_mute
+                        },
                     hintLabelResId = R.string.volume_ringer_hint_unmute,
                     ringerMode = ringerMode,
                 )
             RINGER_MODE_VIBRATE ->
                 RingerButtonViewModel(
                     imageResId = R.drawable.ic_volume_ringer_vibrate,
-                    contentDescriptionResId = R.string.volume_ringer_status_vibrate,
+                    contentDescriptionResId =
+                        if (isSelectedButton) {
+                            R.string.volume_ringer_status_vibrate
+                        } else {
+                            R.string.volume_ringer_hint_vibrate
+                        },
                     hintLabelResId = R.string.volume_ringer_hint_vibrate,
                     ringerMode = ringerMode,
                 )
@@ -143,8 +155,18 @@
                 when {
                     isMuted && isEnabled ->
                         RingerButtonViewModel(
-                            imageResId = R.drawable.ic_speaker_mute,
-                            contentDescriptionResId = R.string.volume_ringer_status_normal,
+                            imageResId =
+                                if (isSelectedButton) {
+                                    R.drawable.ic_speaker_mute
+                                } else {
+                                    R.drawable.ic_speaker_on
+                                },
+                            contentDescriptionResId =
+                                if (isSelectedButton) {
+                                    R.string.volume_ringer_status_normal
+                                } else {
+                                    R.string.volume_ringer_hint_unmute
+                                },
                             hintLabelResId = R.string.volume_ringer_hint_unmute,
                             ringerMode = ringerMode,
                         )
@@ -152,7 +174,12 @@
                     availableModes.contains(RingerMode(RINGER_MODE_VIBRATE)) ->
                         RingerButtonViewModel(
                             imageResId = R.drawable.ic_speaker_on,
-                            contentDescriptionResId = R.string.volume_ringer_status_normal,
+                            contentDescriptionResId =
+                                if (isSelectedButton) {
+                                    R.string.volume_ringer_status_normal
+                                } else {
+                                    R.string.volume_ringer_hint_unmute
+                                },
                             hintLabelResId = R.string.volume_ringer_hint_vibrate,
                             ringerMode = ringerMode,
                         )
@@ -160,7 +187,12 @@
                     else ->
                         RingerButtonViewModel(
                             imageResId = R.drawable.ic_speaker_on,
-                            contentDescriptionResId = R.string.volume_ringer_status_normal,
+                            contentDescriptionResId =
+                                if (isSelectedButton) {
+                                    R.string.volume_ringer_status_normal
+                                } else {
+                                    R.string.volume_ringer_hint_unmute
+                                },
                             hintLabelResId = R.string.volume_ringer_hint_mute,
                             ringerMode = ringerMode,
                         )
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
new file mode 100644
index 0000000..538ee47
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.dagger
+
+import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
+import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder
+import dagger.BindsInstance
+import dagger.Subcomponent
+
+/**
+ * This component hosts all the stuff, that Volume Dialog sliders need. It's recreated alongside
+ * each slider view.
+ */
+@VolumeDialogSliderScope
+@Subcomponent
+interface VolumeDialogSliderComponent {
+
+    fun sliderViewBinder(): VolumeDialogSliderViewBinder
+
+    @Subcomponent.Factory
+    interface Factory {
+
+        fun create(@BindsInstance sliderType: VolumeDialogSliderType): VolumeDialogSliderComponent
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderScope.kt
similarity index 65%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderScope.kt
index 94b2bdf..9f5e0f6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderScope.kt
@@ -14,9 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.volume.dialog.sliders.dagger
 
-import com.android.systemui.kosmos.Kosmos
+import javax.inject.Scope
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+/**
+ * Volume Panel Slider dependency injection scope. This scope is created for each of the volume
+ * sliders in the dialog.
+ */
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+@Scope
+annotation class VolumeDialogSliderScope
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
index 876bf2c..2967fe8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
@@ -17,22 +17,21 @@
 package com.android.systemui.volume.dialog.sliders.domain.interactor
 
 import com.android.systemui.plugins.VolumeDialogController
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
 import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
 import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.mapNotNull
 
 /** Operates a state of particular slider of the Volume Dialog. */
+@VolumeDialogSliderScope
 class VolumeDialogSliderInteractor
-@AssistedInject
+@Inject
 constructor(
-    @Assisted private val sliderType: VolumeDialogSliderType,
+    private val sliderType: VolumeDialogSliderType,
     volumeDialogStateInteractor: VolumeDialogStateInteractor,
     private val volumeDialogController: VolumeDialogController,
 ) {
@@ -56,11 +55,4 @@
             setActiveStream(sliderType.audioStream)
         }
     }
-
-    @VolumeDialogScope
-    @AssistedFactory
-    interface Factory {
-
-        fun create(sliderType: VolumeDialogSliderType): VolumeDialogSliderInteractor
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
index 3bf8c54..1c231b5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
@@ -24,16 +24,14 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.lifecycle.viewModel
 import com.android.systemui.res.R
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
 import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel
 import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory
 import com.android.systemui.volume.dialog.ui.utils.awaitAnimation
 import com.google.android.material.slider.LabelFormatter
 import com.google.android.material.slider.Slider
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import javax.inject.Inject
 import kotlin.math.roundToInt
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.launchIn
@@ -41,10 +39,11 @@
 
 private const val PROGRESS_CHANGE_ANIMATION_DURATION_MS = 80L
 
+@VolumeDialogSliderScope
 class VolumeDialogSliderViewBinder
-@AssistedInject
+@Inject
 constructor(
-    @Assisted private val viewModelProvider: () -> VolumeDialogSliderViewModel,
+    private val viewModelFactory: VolumeDialogSliderViewModel.Factory,
     private val jankListenerFactory: JankListenerFactory,
 ) {
 
@@ -58,7 +57,7 @@
                 viewModel(
                     traceName = "VolumeDialogSliderViewBinder",
                     minWindowLifecycleState = WindowLifecycleState.ATTACHED,
-                    factory = { viewModelProvider() },
+                    factory = { viewModelFactory.create() },
                 ) { viewModel ->
                     sliderView.addOnChangeListener { _, value, fromUser ->
                         viewModel.setStreamVolume(value.roundToInt(), fromUser)
@@ -85,15 +84,6 @@
             )
         }
     }
-
-    @AssistedFactory
-    @VolumeDialogScope
-    interface Factory {
-
-        fun create(
-            viewModelProvider: () -> VolumeDialogSliderViewModel
-        ): VolumeDialogSliderViewBinder
-    }
 }
 
 private suspend fun Slider.setValueAnimated(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
index 0a4e3f4..a17c1e5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
@@ -28,7 +28,6 @@
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
 import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSlidersViewModel
 import javax.inject.Inject
-import kotlin.math.abs
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 
@@ -50,15 +49,17 @@
                 ) { viewModel ->
                     viewModel.sliders
                         .onEach { uiModel ->
-                            uiModel.sliderViewBinder.bind(volumeDialog)
+                            uiModel.sliderComponent.sliderViewBinder().bind(volumeDialog)
 
-                            val floatingSliderViewBinders = uiModel.floatingSliderViewBinders
+                            val floatingSliderViewBinders = uiModel.floatingSliderComponent
                             floatingSlidersContainer.ensureChildCount(
                                 viewLayoutId = R.layout.volume_dialog_slider_floating,
                                 count = floatingSliderViewBinders.size,
                             )
-                            floatingSliderViewBinders.fastForEachIndexed { index, viewBinder ->
-                                viewBinder.bind(floatingSlidersContainer.getChildAt(index))
+                            floatingSliderViewBinders.fastForEachIndexed { index, sliderComponent ->
+                                sliderComponent
+                                    .sliderViewBinder()
+                                    .bind(floatingSlidersContainer.getChildAt(index))
                             }
                         }
                         .launchIn(this)
@@ -76,7 +77,7 @@
         }
         childCountDelta < 0 -> {
             val inflater = LayoutInflater.from(context)
-            repeat(abs(childCountDelta)) { inflater.inflate(viewLayoutId, this, true) }
+            repeat(-childCountDelta) { inflater.inflate(viewLayoutId, this, true) }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
index ea0b49d..cf04d45 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
@@ -22,7 +22,6 @@
 import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
 import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
-import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.CoroutineScope
@@ -53,7 +52,7 @@
 class VolumeDialogSliderViewModel
 @AssistedInject
 constructor(
-    @Assisted private val interactor: VolumeDialogSliderInteractor,
+    private val interactor: VolumeDialogSliderInteractor,
     private val visibilityInteractor: VolumeDialogVisibilityInteractor,
     @VolumeDialog private val coroutineScope: CoroutineScope,
     private val systemClock: SystemClock,
@@ -90,11 +89,11 @@
 
     private fun getTimestampMillis(): Long = systemClock.uptimeMillis()
 
+    private data class VolumeUpdate(val newVolumeLevel: Int, val timestampMillis: Long)
+
     @AssistedFactory
     interface Factory {
 
-        fun create(interactor: VolumeDialogSliderInteractor): VolumeDialogSliderViewModel
+        fun create(): VolumeDialogSliderViewModel
     }
-
-    private data class VolumeUpdate(val newVolumeLevel: Int, val timestampMillis: Long)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
index b5b292f..d197223 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
@@ -17,16 +17,13 @@
 package com.android.systemui.volume.dialog.sliders.ui.viewmodel
 
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
-import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent
 import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor
-import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
-import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
@@ -36,29 +33,21 @@
 constructor(
     @VolumeDialog coroutineScope: CoroutineScope,
     private val slidersInteractor: VolumeDialogSlidersInteractor,
-    private val sliderInteractorFactory: VolumeDialogSliderInteractor.Factory,
-    private val sliderViewModelFactory: VolumeDialogSliderViewModel.Factory,
-    private val sliderViewBinderFactory: VolumeDialogSliderViewBinder.Factory,
+    private val sliderComponentFactory: VolumeDialogSliderComponent.Factory,
 ) {
 
     val sliders: Flow<VolumeDialogSliderUiModel> =
         slidersInteractor.sliders
-            .distinctUntilChanged()
             .map { slidersModel ->
                 VolumeDialogSliderUiModel(
-                    sliderViewBinder = createSliderViewBinder(slidersModel.slider),
-                    floatingSliderViewBinders =
-                        slidersModel.floatingSliders.map(::createSliderViewBinder),
+                    sliderComponent = sliderComponentFactory.create(slidersModel.slider),
+                    floatingSliderComponent =
+                        slidersModel.floatingSliders.map(sliderComponentFactory::create),
                 )
             }
             .stateIn(coroutineScope, SharingStarted.Eagerly, null)
             .filterNotNull()
 
-    private fun createSliderViewBinder(type: VolumeDialogSliderType): VolumeDialogSliderViewBinder =
-        sliderViewBinderFactory.create {
-            sliderViewModelFactory.create(sliderInteractorFactory.create(type))
-        }
-
     @AssistedFactory
     interface Factory {
 
@@ -68,6 +57,6 @@
 
 /** Models slider ui */
 data class VolumeDialogSliderUiModel(
-    val sliderViewBinder: VolumeDialogSliderViewBinder,
-    val floatingSliderViewBinders: List<VolumeDialogSliderViewBinder>,
+    val sliderComponent: VolumeDialogSliderComponent,
+    val floatingSliderComponent: List<VolumeDialogSliderComponent>,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
index 9be669f..869a6a2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
@@ -18,16 +18,19 @@
 
 import android.content.Context
 import com.android.systemui.res.R
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor
 import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogStateModel
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
 import com.android.systemui.volume.dialog.shared.model.streamLabel
-import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
 import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor
+import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
 
 /** Provides a state for the Volume Dialog. */
@@ -38,18 +41,21 @@
     private val context: Context,
     dialogVisibilityInteractor: VolumeDialogVisibilityInteractor,
     volumeDialogSlidersInteractor: VolumeDialogSlidersInteractor,
-    private val volumeDialogSliderInteractorFactory: VolumeDialogSliderInteractor.Factory,
+    volumeDialogStateInteractor: VolumeDialogStateInteractor,
 ) {
 
     val dialogVisibilityModel: Flow<VolumeDialogVisibilityModel> =
         dialogVisibilityInteractor.dialogVisibility
     val dialogTitle: Flow<String> =
-        volumeDialogSlidersInteractor.sliders.flatMapLatest { slidersModel ->
-            val interactor = volumeDialogSliderInteractorFactory.create(slidersModel.slider)
-            interactor.slider.map { sliderModel ->
-                context.getString(R.string.volume_dialog_title, sliderModel.streamLabel(context))
+        combine(
+                volumeDialogStateInteractor.volumeDialogState,
+                volumeDialogSlidersInteractor.sliders.map { it.slider },
+            ) { state: VolumeDialogStateModel, sliderType: VolumeDialogSliderType ->
+                state.streamModels[sliderType.audioStream]?.let { model ->
+                    context.getString(R.string.volume_dialog_title, model.streamLabel(context))
+                }
             }
-        }
+            .filterNotNull()
 
     @AssistedFactory
     interface Factory {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt
index dacd6c7..b9f47d7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt
@@ -22,15 +22,17 @@
 import android.media.session.PlaybackState
 import android.os.Bundle
 import android.os.Handler
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel
 import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.channels.ProducerScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.flow.flowOn
 
 interface MediaControllerInteractor {
 
@@ -43,14 +45,16 @@
 @Inject
 constructor(
     @Background private val backgroundHandler: Handler,
+    @Background private val backgroundCoroutineContext: CoroutineContext,
 ) : MediaControllerInteractor {
 
     override fun stateChanges(mediaController: MediaController): Flow<MediaControllerChangeModel> {
         return conflatedCallbackFlow {
-            val callback = MediaControllerCallbackProducer(this)
-            mediaController.registerCallback(callback, backgroundHandler)
-            awaitClose { mediaController.unregisterCallback(callback) }
-        }
+                val callback = MediaControllerCallbackProducer(this)
+                mediaController.registerCallback(callback, backgroundHandler)
+                awaitClose { mediaController.unregisterCallback(callback) }
+            }
+            .flowOn(backgroundCoroutineContext)
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
index aa07cfd..b3848a6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
@@ -20,10 +20,12 @@
 import android.media.VolumeProvider
 import android.media.session.MediaController
 import android.util.Log
+import androidx.annotation.WorkerThread
 import com.android.settingslib.media.MediaDevice
 import com.android.settingslib.volume.data.repository.LocalMediaRepository
 import com.android.settingslib.volume.data.repository.MediaControllerRepository
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.concurrency.Execution
 import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
 import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSessions
 import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
@@ -62,6 +64,7 @@
     @Background private val backgroundCoroutineContext: CoroutineContext,
     mediaControllerRepository: MediaControllerRepository,
     private val mediaControllerInteractor: MediaControllerInteractor,
+    private val execution: Execution,
 ) {
 
     private val activeMediaControllers: Flow<MediaControllers> =
@@ -82,9 +85,10 @@
             .map {
                 MediaDeviceSessions(
                     local = it.local?.mediaDeviceSession(),
-                    remote = it.remote?.mediaDeviceSession()
+                    remote = it.remote?.mediaDeviceSession(),
                 )
             }
+            .flowOn(backgroundCoroutineContext)
             .stateIn(coroutineScope, SharingStarted.Eagerly, MediaDeviceSessions(null, null))
 
     /** Returns the default [MediaDeviceSession] from [activeMediaDeviceSessions] */
@@ -115,55 +119,43 @@
     val currentConnectedDevice: Flow<MediaDevice?> =
         localMediaRepository.flatMapLatest { it.currentConnectedDevice }.distinctUntilChanged()
 
-    private suspend fun getApplicationLabel(packageName: String): CharSequence? {
-        return try {
-            withContext(backgroundCoroutineContext) {
-                val appInfo =
-                    packageManager.getApplicationInfo(
-                        packageName,
-                        PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER
-                    )
-                appInfo.loadLabel(packageManager)
-            }
-        } catch (e: PackageManager.NameNotFoundException) {
-            Log.e(TAG, "Unable to find info for package: $packageName")
-            null
-        }
-    }
-
     /** Finds local and remote media controllers. */
-    private fun getMediaControllers(
-        controllers: Collection<MediaController>,
-    ): MediaControllers {
-        var localController: MediaController? = null
-        var remoteController: MediaController? = null
-        val remoteMediaSessions: MutableSet<String> = mutableSetOf()
-        for (controller in controllers) {
-            val playbackInfo: MediaController.PlaybackInfo = controller.playbackInfo ?: continue
-            when (playbackInfo.playbackType) {
-                MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE -> {
-                    // MediaController can't be local if there is a remote one for the same package
-                    if (localController?.packageName.equals(controller.packageName)) {
-                        localController = null
+    private suspend fun getMediaControllers(
+        controllers: Collection<MediaController>
+    ): MediaControllers =
+        withContext(backgroundCoroutineContext) {
+            var localController: MediaController? = null
+            var remoteController: MediaController? = null
+            val remoteMediaSessions: MutableSet<String> = mutableSetOf()
+            for (controller in controllers) {
+                val playbackInfo: MediaController.PlaybackInfo = controller.playbackInfo ?: continue
+                when (playbackInfo.playbackType) {
+                    MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE -> {
+                        // MediaController can't be local if there is a remote one for the same
+                        // package
+                        if (localController?.packageName.equals(controller.packageName)) {
+                            localController = null
+                        }
+                        if (!remoteMediaSessions.contains(controller.packageName)) {
+                            remoteMediaSessions.add(controller.packageName)
+                            remoteController = chooseController(remoteController, controller)
+                        }
                     }
-                    if (!remoteMediaSessions.contains(controller.packageName)) {
-                        remoteMediaSessions.add(controller.packageName)
-                        remoteController = chooseController(remoteController, controller)
+                    MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL -> {
+                        if (controller.packageName in remoteMediaSessions) continue
+                        localController = chooseController(localController, controller)
                     }
                 }
-                MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL -> {
-                    if (controller.packageName in remoteMediaSessions) continue
-                    localController = chooseController(localController, controller)
-                }
             }
+            MediaControllers(local = localController, remote = remoteController)
         }
-        return MediaControllers(local = localController, remote = remoteController)
-    }
 
+    @WorkerThread
     private fun chooseController(
         currentController: MediaController?,
         newController: MediaController,
     ): MediaController {
+        require(!execution.isMainThread())
         if (currentController == null) {
             return newController
         }
@@ -175,12 +167,26 @@
         return currentController
     }
 
-    private suspend fun MediaController.mediaDeviceSession(): MediaDeviceSession? {
+    @WorkerThread
+    private fun MediaController.mediaDeviceSession(): MediaDeviceSession? {
+        require(!execution.isMainThread())
+        val applicationLabel =
+            try {
+                packageManager
+                    .getApplicationInfo(
+                        packageName,
+                        PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER,
+                    )
+                    .loadLabel(packageManager)
+            } catch (e: PackageManager.NameNotFoundException) {
+                Log.e(TAG, "Unable to find info for package: $packageName")
+                null
+            } ?: return null
         return MediaDeviceSession(
             packageName = packageName,
             sessionToken = sessionToken,
             canAdjustVolume = playbackInfo.volumeControl != VolumeProvider.VOLUME_CONTROL_FIXED,
-            appLabel = getApplicationLabel(packageName) ?: return null
+            appLabel = applicationLabel,
         )
     }
 
@@ -195,10 +201,7 @@
             .onStart { emit(this@stateChanges) }
     }
 
-    private data class MediaControllers(
-        val local: MediaController?,
-        val remote: MediaController?,
-    )
+    private data class MediaControllers(val local: MediaController?, val remote: MediaController?)
 
     private companion object {
         const val TAG = "MediaOutputInteractor"
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index 68f7896..cec3d1e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -22,11 +22,13 @@
 import android.media.AudioManager.STREAM_MUSIC
 import android.media.AudioManager.STREAM_NOTIFICATION
 import android.util.Log
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.internal.logging.UiEventLogger
 import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
 import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.settingslib.volume.shared.model.AudioStreamModel
 import com.android.settingslib.volume.shared.model.RingerMode
+import com.android.systemui.Flags
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
 import com.android.systemui.modes.shared.ModesUiIcons
@@ -49,7 +51,6 @@
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** Models a particular slider state. */
 class AudioStreamSliderViewModel
@@ -62,7 +63,7 @@
     private val zenModeInteractor: ZenModeInteractor,
     private val uiEventLogger: UiEventLogger,
     private val volumePanelLogger: VolumePanelLogger,
-    override val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
+    private val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
 ) : SliderViewModel {
 
     private val volumeChanges = MutableStateFlow<Int?>(null)
@@ -171,6 +172,13 @@
         }
     }
 
+    override fun getSliderHapticsViewModelFactory(): SliderHapticsViewModel.Factory? =
+        if (Flags.hapticsForComposeSliders() && slider.value != SliderState.Empty) {
+            hapticsViewModelFactory
+        } else {
+            null
+        }
+
     private fun AudioStreamModel.toState(
         isEnabled: Boolean,
         ringerMode: RingerMode,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
index 8efe915..0d80452 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
@@ -18,6 +18,8 @@
 
 import android.content.Context
 import android.media.session.MediaController.PlaybackInfo
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.Flags
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
 import com.android.systemui.res.R
@@ -32,7 +34,6 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 class CastVolumeSliderViewModel
 @AssistedInject
@@ -41,7 +42,7 @@
     @Assisted private val coroutineScope: CoroutineScope,
     private val context: Context,
     private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
-    override val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
+    private val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
 ) : SliderViewModel {
 
     override val slider: StateFlow<SliderState> =
@@ -62,6 +63,13 @@
         // do nothing because this action isn't supported for Cast sliders.
     }
 
+    override fun getSliderHapticsViewModelFactory(): SliderHapticsViewModel.Factory? =
+        if (Flags.hapticsForComposeSliders() && slider.value != SliderState.Empty) {
+            hapticsViewModelFactory
+        } else {
+            null
+        }
+
     private fun PlaybackInfo.getCurrentState(): State {
         val volumeRange = 0..maxVolume
         return State(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
index 9c1783b..4d9552f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
@@ -24,11 +24,11 @@
 
     val slider: StateFlow<SliderState>
 
-    val hapticsViewModelFactory: SliderHapticsViewModel.Factory
-
     fun onValueChanged(state: SliderState, newValue: Float)
 
     fun onValueChangeFinished()
 
     fun toggleMuted(state: SliderState)
+
+    fun getSliderHapticsViewModelFactory(): SliderHapticsViewModel.Factory?
 }
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withAnimator.json
similarity index 100%
rename from packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json
rename to packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withAnimator.json
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withSpring.json
similarity index 82%
rename from packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching_withSpring.json
rename to packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withSpring.json
index a840d3c..7abff2c 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching_withSpring.json
+++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withSpring.json
@@ -33,118 +33,118 @@
           "bottom": 0
         },
         {
-          "left": 94,
-          "top": 284,
-          "right": 206,
+          "left": 104,
+          "top": 285,
+          "right": 215,
           "bottom": 414
         },
         {
-          "left": 83,
-          "top": 251,
-          "right": 219,
+          "left": 92,
+          "top": 252,
+          "right": 227,
           "bottom": 447
         },
         {
-          "left": 70,
-          "top": 212,
-          "right": 234,
-          "bottom": 485
+          "left": 77,
+          "top": 213,
+          "right": 242,
+          "bottom": 486
         },
         {
-          "left": 57,
-          "top": 173,
-          "right": 250,
-          "bottom": 522
+          "left": 63,
+          "top": 175,
+          "right": 256,
+          "bottom": 524
         },
         {
-          "left": 46,
-          "top": 139,
-          "right": 264,
-          "bottom": 555
+          "left": 50,
+          "top": 141,
+          "right": 269,
+          "bottom": 558
         },
         {
-          "left": 36,
-          "top": 109,
-          "right": 276,
-          "bottom": 584
+          "left": 40,
+          "top": 112,
+          "right": 279,
+          "bottom": 587
         },
         {
-          "left": 28,
-          "top": 84,
-          "right": 285,
-          "bottom": 608
+          "left": 31,
+          "top": 88,
+          "right": 288,
+          "bottom": 611
         },
         {
-          "left": 21,
-          "top": 65,
-          "right": 293,
-          "bottom": 627
+          "left": 23,
+          "top": 68,
+          "right": 296,
+          "bottom": 631
         },
         {
-          "left": 16,
-          "top": 49,
-          "right": 300,
-          "bottom": 642
+          "left": 18,
+          "top": 53,
+          "right": 301,
+          "bottom": 646
         },
         {
-          "left": 12,
-          "top": 36,
-          "right": 305,
-          "bottom": 653
+          "left": 13,
+          "top": 41,
+          "right": 306,
+          "bottom": 658
         },
         {
-          "left": 9,
-          "top": 27,
-          "right": 308,
-          "bottom": 662
+          "left": 10,
+          "top": 31,
+          "right": 309,
+          "bottom": 667
         },
         {
           "left": 7,
-          "top": 20,
+          "top": 24,
           "right": 312,
-          "bottom": 669
+          "bottom": 673
         },
         {
           "left": 5,
-          "top": 14,
+          "top": 18,
           "right": 314,
-          "bottom": 675
-        },
-        {
-          "left": 4,
-          "top": 11,
-          "right": 315,
           "bottom": 678
         },
         {
-          "left": 3,
-          "top": 8,
-          "right": 316,
+          "left": 4,
+          "top": 13,
+          "right": 315,
           "bottom": 681
         },
         {
-          "left": 2,
-          "top": 5,
-          "right": 317,
+          "left": 3,
+          "top": 10,
+          "right": 316,
           "bottom": 684
         },
         {
+          "left": 2,
+          "top": 7,
+          "right": 317,
+          "bottom": 685
+        },
+        {
+          "left": 1,
+          "top": 5,
+          "right": 318,
+          "bottom": 687
+        },
+        {
           "left": 1,
           "top": 4,
           "right": 318,
-          "bottom": 685
-        },
-        {
-          "left": 1,
-          "top": 3,
-          "right": 318,
-          "bottom": 686
+          "bottom": 688
         },
         {
           "left": 0,
-          "top": 2,
+          "top": 3,
           "right": 319,
-          "bottom": 687
+          "bottom": 688
         }
       ]
     },
@@ -371,5 +371,14 @@
         0
       ]
     }
-  ]
+  ],
+  "\/\/metadata": {
+    "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimation_whenLaunching_withSpring.json",
+    "goldenIdentifier": "backgroundAnimation_whenLaunching_withSpring",
+    "testClassName": "TransitionAnimatorTest",
+    "testMethodName": "backgroundAnimation_whenLaunching[true]",
+    "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots",
+    "result": "FAILED",
+    "videoLocation": "TransitionAnimatorTest\/backgroundAnimation_whenLaunching_withSpring.actual.mp4"
+  }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withAnimator.json
similarity index 100%
copy from packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json
copy to packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withAnimator.json
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withSpring.json
similarity index 82%
rename from packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning_withSpring.json
rename to packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withSpring.json
index a840d3c..5619611 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning_withSpring.json
+++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withSpring.json
@@ -33,118 +33,118 @@
           "bottom": 0
         },
         {
-          "left": 94,
-          "top": 284,
-          "right": 206,
+          "left": 104,
+          "top": 285,
+          "right": 215,
           "bottom": 414
         },
         {
-          "left": 83,
-          "top": 251,
-          "right": 219,
+          "left": 92,
+          "top": 252,
+          "right": 227,
           "bottom": 447
         },
         {
-          "left": 70,
-          "top": 212,
-          "right": 234,
-          "bottom": 485
+          "left": 77,
+          "top": 213,
+          "right": 242,
+          "bottom": 486
         },
         {
-          "left": 57,
-          "top": 173,
-          "right": 250,
-          "bottom": 522
+          "left": 63,
+          "top": 175,
+          "right": 256,
+          "bottom": 524
         },
         {
-          "left": 46,
-          "top": 139,
-          "right": 264,
-          "bottom": 555
+          "left": 50,
+          "top": 141,
+          "right": 269,
+          "bottom": 558
         },
         {
-          "left": 36,
-          "top": 109,
-          "right": 276,
-          "bottom": 584
+          "left": 40,
+          "top": 112,
+          "right": 279,
+          "bottom": 587
         },
         {
-          "left": 28,
-          "top": 84,
-          "right": 285,
-          "bottom": 608
+          "left": 31,
+          "top": 88,
+          "right": 288,
+          "bottom": 611
         },
         {
-          "left": 21,
-          "top": 65,
-          "right": 293,
-          "bottom": 627
+          "left": 23,
+          "top": 68,
+          "right": 296,
+          "bottom": 631
         },
         {
-          "left": 16,
-          "top": 49,
-          "right": 300,
-          "bottom": 642
+          "left": 18,
+          "top": 53,
+          "right": 301,
+          "bottom": 646
         },
         {
-          "left": 12,
-          "top": 36,
-          "right": 305,
-          "bottom": 653
+          "left": 13,
+          "top": 41,
+          "right": 306,
+          "bottom": 658
         },
         {
-          "left": 9,
-          "top": 27,
-          "right": 308,
-          "bottom": 662
+          "left": 10,
+          "top": 31,
+          "right": 309,
+          "bottom": 667
         },
         {
           "left": 7,
-          "top": 20,
+          "top": 24,
           "right": 312,
-          "bottom": 669
+          "bottom": 673
         },
         {
           "left": 5,
-          "top": 14,
+          "top": 18,
           "right": 314,
-          "bottom": 675
-        },
-        {
-          "left": 4,
-          "top": 11,
-          "right": 315,
           "bottom": 678
         },
         {
-          "left": 3,
-          "top": 8,
-          "right": 316,
+          "left": 4,
+          "top": 13,
+          "right": 315,
           "bottom": 681
         },
         {
-          "left": 2,
-          "top": 5,
-          "right": 317,
+          "left": 3,
+          "top": 10,
+          "right": 316,
           "bottom": 684
         },
         {
+          "left": 2,
+          "top": 7,
+          "right": 317,
+          "bottom": 685
+        },
+        {
+          "left": 1,
+          "top": 5,
+          "right": 318,
+          "bottom": 687
+        },
+        {
           "left": 1,
           "top": 4,
           "right": 318,
-          "bottom": 685
-        },
-        {
-          "left": 1,
-          "top": 3,
-          "right": 318,
-          "bottom": 686
+          "bottom": 688
         },
         {
           "left": 0,
-          "top": 2,
+          "top": 3,
           "right": 319,
-          "bottom": 687
+          "bottom": 688
         }
       ]
     },
@@ -371,5 +371,14 @@
         0
       ]
     }
-  ]
+  ],
+  "\/\/metadata": {
+    "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimation_whenReturning_withSpring.json",
+    "goldenIdentifier": "backgroundAnimation_whenReturning_withSpring",
+    "testClassName": "TransitionAnimatorTest",
+    "testMethodName": "backgroundAnimation_whenReturning[true]",
+    "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots",
+    "result": "FAILED",
+    "videoLocation": "TransitionAnimatorTest\/backgroundAnimation_whenReturning_withSpring.actual.mp4"
+  }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withAnimator.json
similarity index 100%
rename from packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching.json
rename to packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withAnimator.json
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withSpring.json
similarity index 81%
copy from packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching_withSpring.json
copy to packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withSpring.json
index 18eedd4..825190b 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching_withSpring.json
+++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withSpring.json
@@ -33,118 +33,118 @@
           "bottom": 0
         },
         {
-          "left": 94,
-          "top": 284,
-          "right": 206,
+          "left": 104,
+          "top": 285,
+          "right": 215,
           "bottom": 414
         },
         {
-          "left": 83,
-          "top": 251,
-          "right": 219,
+          "left": 92,
+          "top": 252,
+          "right": 227,
           "bottom": 447
         },
         {
-          "left": 70,
-          "top": 212,
-          "right": 234,
-          "bottom": 485
+          "left": 77,
+          "top": 213,
+          "right": 242,
+          "bottom": 486
         },
         {
-          "left": 57,
-          "top": 173,
-          "right": 250,
-          "bottom": 522
+          "left": 63,
+          "top": 175,
+          "right": 256,
+          "bottom": 524
         },
         {
-          "left": 46,
-          "top": 139,
-          "right": 264,
-          "bottom": 555
+          "left": 50,
+          "top": 141,
+          "right": 269,
+          "bottom": 558
         },
         {
-          "left": 36,
-          "top": 109,
-          "right": 276,
-          "bottom": 584
+          "left": 40,
+          "top": 112,
+          "right": 279,
+          "bottom": 587
         },
         {
-          "left": 28,
-          "top": 84,
-          "right": 285,
-          "bottom": 608
+          "left": 31,
+          "top": 88,
+          "right": 288,
+          "bottom": 611
         },
         {
-          "left": 21,
-          "top": 65,
-          "right": 293,
-          "bottom": 627
+          "left": 23,
+          "top": 68,
+          "right": 296,
+          "bottom": 631
         },
         {
-          "left": 16,
-          "top": 49,
-          "right": 300,
-          "bottom": 642
+          "left": 18,
+          "top": 53,
+          "right": 301,
+          "bottom": 646
         },
         {
-          "left": 12,
-          "top": 36,
-          "right": 305,
-          "bottom": 653
+          "left": 13,
+          "top": 41,
+          "right": 306,
+          "bottom": 658
         },
         {
-          "left": 9,
-          "top": 27,
-          "right": 308,
-          "bottom": 662
+          "left": 10,
+          "top": 31,
+          "right": 309,
+          "bottom": 667
         },
         {
           "left": 7,
-          "top": 20,
+          "top": 24,
           "right": 312,
-          "bottom": 669
+          "bottom": 673
         },
         {
           "left": 5,
-          "top": 14,
+          "top": 18,
           "right": 314,
-          "bottom": 675
-        },
-        {
-          "left": 4,
-          "top": 11,
-          "right": 315,
           "bottom": 678
         },
         {
-          "left": 3,
-          "top": 8,
-          "right": 316,
+          "left": 4,
+          "top": 13,
+          "right": 315,
           "bottom": 681
         },
         {
-          "left": 2,
-          "top": 5,
-          "right": 317,
+          "left": 3,
+          "top": 10,
+          "right": 316,
           "bottom": 684
         },
         {
+          "left": 2,
+          "top": 7,
+          "right": 317,
+          "bottom": 685
+        },
+        {
+          "left": 1,
+          "top": 5,
+          "right": 318,
+          "bottom": 687
+        },
+        {
           "left": 1,
           "top": 4,
           "right": 318,
-          "bottom": 685
-        },
-        {
-          "left": 1,
-          "top": 3,
-          "right": 318,
-          "bottom": 686
+          "bottom": 688
         },
         {
           "left": 0,
-          "top": 2,
+          "top": 3,
           "right": 319,
-          "bottom": 687
+          "bottom": 688
         }
       ]
     },
@@ -371,5 +371,14 @@
         0
       ]
     }
-  ]
+  ],
+  "\/\/metadata": {
+    "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimationWithoutFade_whenLaunching_withSpring.json",
+    "goldenIdentifier": "backgroundAnimationWithoutFade_whenLaunching_withSpring",
+    "testClassName": "TransitionAnimatorTest",
+    "testMethodName": "backgroundAnimationWithoutFade_whenLaunching[true]",
+    "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots",
+    "result": "FAILED",
+    "videoLocation": "TransitionAnimatorTest\/backgroundAnimationWithoutFade_whenLaunching_withSpring.actual.mp4"
+  }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withAnimator.json
similarity index 100%
rename from packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning.json
rename to packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withAnimator.json
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withSpring.json
similarity index 81%
rename from packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching_withSpring.json
rename to packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withSpring.json
index 18eedd4..63c2631 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching_withSpring.json
+++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withSpring.json
@@ -33,118 +33,118 @@
           "bottom": 0
         },
         {
-          "left": 94,
-          "top": 284,
-          "right": 206,
+          "left": 104,
+          "top": 285,
+          "right": 215,
           "bottom": 414
         },
         {
-          "left": 83,
-          "top": 251,
-          "right": 219,
+          "left": 92,
+          "top": 252,
+          "right": 227,
           "bottom": 447
         },
         {
-          "left": 70,
-          "top": 212,
-          "right": 234,
-          "bottom": 485
+          "left": 77,
+          "top": 213,
+          "right": 242,
+          "bottom": 486
         },
         {
-          "left": 57,
-          "top": 173,
-          "right": 250,
-          "bottom": 522
+          "left": 63,
+          "top": 175,
+          "right": 256,
+          "bottom": 524
         },
         {
-          "left": 46,
-          "top": 139,
-          "right": 264,
-          "bottom": 555
+          "left": 50,
+          "top": 141,
+          "right": 269,
+          "bottom": 558
         },
         {
-          "left": 36,
-          "top": 109,
-          "right": 276,
-          "bottom": 584
+          "left": 40,
+          "top": 112,
+          "right": 279,
+          "bottom": 587
         },
         {
-          "left": 28,
-          "top": 84,
-          "right": 285,
-          "bottom": 608
+          "left": 31,
+          "top": 88,
+          "right": 288,
+          "bottom": 611
         },
         {
-          "left": 21,
-          "top": 65,
-          "right": 293,
-          "bottom": 627
+          "left": 23,
+          "top": 68,
+          "right": 296,
+          "bottom": 631
         },
         {
-          "left": 16,
-          "top": 49,
-          "right": 300,
-          "bottom": 642
+          "left": 18,
+          "top": 53,
+          "right": 301,
+          "bottom": 646
         },
         {
-          "left": 12,
-          "top": 36,
-          "right": 305,
-          "bottom": 653
+          "left": 13,
+          "top": 41,
+          "right": 306,
+          "bottom": 658
         },
         {
-          "left": 9,
-          "top": 27,
-          "right": 308,
-          "bottom": 662
+          "left": 10,
+          "top": 31,
+          "right": 309,
+          "bottom": 667
         },
         {
           "left": 7,
-          "top": 20,
+          "top": 24,
           "right": 312,
-          "bottom": 669
+          "bottom": 673
         },
         {
           "left": 5,
-          "top": 14,
+          "top": 18,
           "right": 314,
-          "bottom": 675
-        },
-        {
-          "left": 4,
-          "top": 11,
-          "right": 315,
           "bottom": 678
         },
         {
-          "left": 3,
-          "top": 8,
-          "right": 316,
+          "left": 4,
+          "top": 13,
+          "right": 315,
           "bottom": 681
         },
         {
-          "left": 2,
-          "top": 5,
-          "right": 317,
+          "left": 3,
+          "top": 10,
+          "right": 316,
           "bottom": 684
         },
         {
+          "left": 2,
+          "top": 7,
+          "right": 317,
+          "bottom": 685
+        },
+        {
+          "left": 1,
+          "top": 5,
+          "right": 318,
+          "bottom": 687
+        },
+        {
           "left": 1,
           "top": 4,
           "right": 318,
-          "bottom": 685
-        },
-        {
-          "left": 1,
-          "top": 3,
-          "right": 318,
-          "bottom": 686
+          "bottom": 688
         },
         {
           "left": 0,
-          "top": 2,
+          "top": 3,
           "right": 319,
-          "bottom": 687
+          "bottom": 688
         }
       ]
     },
@@ -371,5 +371,14 @@
         0
       ]
     }
-  ]
+  ],
+  "\/\/metadata": {
+    "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimationWithoutFade_whenReturning_withSpring.json",
+    "goldenIdentifier": "backgroundAnimationWithoutFade_whenReturning_withSpring",
+    "testClassName": "TransitionAnimatorTest",
+    "testMethodName": "backgroundAnimationWithoutFade_whenReturning[true]",
+    "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots",
+    "result": "FAILED",
+    "videoLocation": "TransitionAnimatorTest\/backgroundAnimationWithoutFade_whenReturning_withSpring.actual.mp4"
+  }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning_withSpring.json
deleted file mode 100644
index 18eedd4..0000000
--- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning_withSpring.json
+++ /dev/null
@@ -1,375 +0,0 @@
-{
-  "frame_ids": [
-    0,
-    16,
-    32,
-    48,
-    64,
-    80,
-    96,
-    112,
-    128,
-    144,
-    160,
-    176,
-    192,
-    208,
-    224,
-    240,
-    256,
-    272,
-    288,
-    304
-  ],
-  "features": [
-    {
-      "name": "bounds",
-      "type": "rect",
-      "data_points": [
-        {
-          "left": 0,
-          "top": 0,
-          "right": 0,
-          "bottom": 0
-        },
-        {
-          "left": 94,
-          "top": 284,
-          "right": 206,
-          "bottom": 414
-        },
-        {
-          "left": 83,
-          "top": 251,
-          "right": 219,
-          "bottom": 447
-        },
-        {
-          "left": 70,
-          "top": 212,
-          "right": 234,
-          "bottom": 485
-        },
-        {
-          "left": 57,
-          "top": 173,
-          "right": 250,
-          "bottom": 522
-        },
-        {
-          "left": 46,
-          "top": 139,
-          "right": 264,
-          "bottom": 555
-        },
-        {
-          "left": 36,
-          "top": 109,
-          "right": 276,
-          "bottom": 584
-        },
-        {
-          "left": 28,
-          "top": 84,
-          "right": 285,
-          "bottom": 608
-        },
-        {
-          "left": 21,
-          "top": 65,
-          "right": 293,
-          "bottom": 627
-        },
-        {
-          "left": 16,
-          "top": 49,
-          "right": 300,
-          "bottom": 642
-        },
-        {
-          "left": 12,
-          "top": 36,
-          "right": 305,
-          "bottom": 653
-        },
-        {
-          "left": 9,
-          "top": 27,
-          "right": 308,
-          "bottom": 662
-        },
-        {
-          "left": 7,
-          "top": 20,
-          "right": 312,
-          "bottom": 669
-        },
-        {
-          "left": 5,
-          "top": 14,
-          "right": 314,
-          "bottom": 675
-        },
-        {
-          "left": 4,
-          "top": 11,
-          "right": 315,
-          "bottom": 678
-        },
-        {
-          "left": 3,
-          "top": 8,
-          "right": 316,
-          "bottom": 681
-        },
-        {
-          "left": 2,
-          "top": 5,
-          "right": 317,
-          "bottom": 684
-        },
-        {
-          "left": 1,
-          "top": 4,
-          "right": 318,
-          "bottom": 685
-        },
-        {
-          "left": 1,
-          "top": 3,
-          "right": 318,
-          "bottom": 686
-        },
-        {
-          "left": 0,
-          "top": 2,
-          "right": 319,
-          "bottom": 687
-        }
-      ]
-    },
-    {
-      "name": "corner_radii",
-      "type": "cornerRadii",
-      "data_points": [
-        null,
-        {
-          "top_left_x": 9.492916,
-          "top_left_y": 9.492916,
-          "top_right_x": 9.492916,
-          "top_right_y": 9.492916,
-          "bottom_right_x": 18.985832,
-          "bottom_right_y": 18.985832,
-          "bottom_left_x": 18.985832,
-          "bottom_left_y": 18.985832
-        },
-        {
-          "top_left_x": 8.381761,
-          "top_left_y": 8.381761,
-          "top_right_x": 8.381761,
-          "top_right_y": 8.381761,
-          "bottom_right_x": 16.763521,
-          "bottom_right_y": 16.763521,
-          "bottom_left_x": 16.763521,
-          "bottom_left_y": 16.763521
-        },
-        {
-          "top_left_x": 7.07397,
-          "top_left_y": 7.07397,
-          "top_right_x": 7.07397,
-          "top_right_y": 7.07397,
-          "bottom_right_x": 14.14794,
-          "bottom_right_y": 14.14794,
-          "bottom_left_x": 14.14794,
-          "bottom_left_y": 14.14794
-        },
-        {
-          "top_left_x": 5.7880254,
-          "top_left_y": 5.7880254,
-          "top_right_x": 5.7880254,
-          "top_right_y": 5.7880254,
-          "bottom_right_x": 11.576051,
-          "bottom_right_y": 11.576051,
-          "bottom_left_x": 11.576051,
-          "bottom_left_y": 11.576051
-        },
-        {
-          "top_left_x": 4.6295347,
-          "top_left_y": 4.6295347,
-          "top_right_x": 4.6295347,
-          "top_right_y": 4.6295347,
-          "bottom_right_x": 9.259069,
-          "bottom_right_y": 9.259069,
-          "bottom_left_x": 9.259069,
-          "bottom_left_y": 9.259069
-        },
-        {
-          "top_left_x": 3.638935,
-          "top_left_y": 3.638935,
-          "top_right_x": 3.638935,
-          "top_right_y": 3.638935,
-          "bottom_right_x": 7.27787,
-          "bottom_right_y": 7.27787,
-          "bottom_left_x": 7.27787,
-          "bottom_left_y": 7.27787
-        },
-        {
-          "top_left_x": 2.8209057,
-          "top_left_y": 2.8209057,
-          "top_right_x": 2.8209057,
-          "top_right_y": 2.8209057,
-          "bottom_right_x": 5.6418114,
-          "bottom_right_y": 5.6418114,
-          "bottom_left_x": 5.6418114,
-          "bottom_left_y": 5.6418114
-        },
-        {
-          "top_left_x": 2.1620893,
-          "top_left_y": 2.1620893,
-          "top_right_x": 2.1620893,
-          "top_right_y": 2.1620893,
-          "bottom_right_x": 4.3241787,
-          "bottom_right_y": 4.3241787,
-          "bottom_left_x": 4.3241787,
-          "bottom_left_y": 4.3241787
-        },
-        {
-          "top_left_x": 1.6414614,
-          "top_left_y": 1.6414614,
-          "top_right_x": 1.6414614,
-          "top_right_y": 1.6414614,
-          "bottom_right_x": 3.2829227,
-          "bottom_right_y": 3.2829227,
-          "bottom_left_x": 3.2829227,
-          "bottom_left_y": 3.2829227
-        },
-        {
-          "top_left_x": 1.2361269,
-          "top_left_y": 1.2361269,
-          "top_right_x": 1.2361269,
-          "top_right_y": 1.2361269,
-          "bottom_right_x": 2.4722538,
-          "bottom_right_y": 2.4722538,
-          "bottom_left_x": 2.4722538,
-          "bottom_left_y": 2.4722538
-        },
-        {
-          "top_left_x": 0.92435074,
-          "top_left_y": 0.92435074,
-          "top_right_x": 0.92435074,
-          "top_right_y": 0.92435074,
-          "bottom_right_x": 1.8487015,
-          "bottom_right_y": 1.8487015,
-          "bottom_left_x": 1.8487015,
-          "bottom_left_y": 1.8487015
-        },
-        {
-          "top_left_x": 0.68693924,
-          "top_left_y": 0.68693924,
-          "top_right_x": 0.68693924,
-          "top_right_y": 0.68693924,
-          "bottom_right_x": 1.3738785,
-          "bottom_right_y": 1.3738785,
-          "bottom_left_x": 1.3738785,
-          "bottom_left_y": 1.3738785
-        },
-        {
-          "top_left_x": 0.5076904,
-          "top_left_y": 0.5076904,
-          "top_right_x": 0.5076904,
-          "top_right_y": 0.5076904,
-          "bottom_right_x": 1.0153809,
-          "bottom_right_y": 1.0153809,
-          "bottom_left_x": 1.0153809,
-          "bottom_left_y": 1.0153809
-        },
-        {
-          "top_left_x": 0.3733511,
-          "top_left_y": 0.3733511,
-          "top_right_x": 0.3733511,
-          "top_right_y": 0.3733511,
-          "bottom_right_x": 0.7467022,
-          "bottom_right_y": 0.7467022,
-          "bottom_left_x": 0.7467022,
-          "bottom_left_y": 0.7467022
-        },
-        {
-          "top_left_x": 0.27331638,
-          "top_left_y": 0.27331638,
-          "top_right_x": 0.27331638,
-          "top_right_y": 0.27331638,
-          "bottom_right_x": 0.54663277,
-          "bottom_right_y": 0.54663277,
-          "bottom_left_x": 0.54663277,
-          "bottom_left_y": 0.54663277
-        },
-        {
-          "top_left_x": 0.19925308,
-          "top_left_y": 0.19925308,
-          "top_right_x": 0.19925308,
-          "top_right_y": 0.19925308,
-          "bottom_right_x": 0.39850616,
-          "bottom_right_y": 0.39850616,
-          "bottom_left_x": 0.39850616,
-          "bottom_left_y": 0.39850616
-        },
-        {
-          "top_left_x": 0.14470005,
-          "top_left_y": 0.14470005,
-          "top_right_x": 0.14470005,
-          "top_right_y": 0.14470005,
-          "bottom_right_x": 0.2894001,
-          "bottom_right_y": 0.2894001,
-          "bottom_left_x": 0.2894001,
-          "bottom_left_y": 0.2894001
-        },
-        {
-          "top_left_x": 0.10470486,
-          "top_left_y": 0.10470486,
-          "top_right_x": 0.10470486,
-          "top_right_y": 0.10470486,
-          "bottom_right_x": 0.20940971,
-          "bottom_right_y": 0.20940971,
-          "bottom_left_x": 0.20940971,
-          "bottom_left_y": 0.20940971
-        },
-        {
-          "top_left_x": 0.07550812,
-          "top_left_y": 0.07550812,
-          "top_right_x": 0.07550812,
-          "top_right_y": 0.07550812,
-          "bottom_right_x": 0.15101624,
-          "bottom_right_y": 0.15101624,
-          "bottom_left_x": 0.15101624,
-          "bottom_left_y": 0.15101624
-        }
-      ]
-    },
-    {
-      "name": "alpha",
-      "type": "int",
-      "data_points": [
-        0,
-        255,
-        255,
-        255,
-        255,
-        255,
-        255,
-        255,
-        255,
-        255,
-        249,
-        226,
-        192,
-        153,
-        112,
-        72,
-        34,
-        0,
-        0,
-        0
-      ]
-    }
-  ]
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning.json b/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning.json
deleted file mode 100644
index aa80445..0000000
--- a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning.json
+++ /dev/null
@@ -1,492 +0,0 @@
-{
-  "frame_ids": [
-    0,
-    20,
-    40,
-    60,
-    80,
-    100,
-    120,
-    140,
-    160,
-    180,
-    200,
-    220,
-    240,
-    260,
-    280,
-    300,
-    320,
-    340,
-    360,
-    380,
-    400,
-    420,
-    440,
-    460,
-    480,
-    500
-  ],
-  "features": [
-    {
-      "name": "bounds",
-      "type": "rect",
-      "data_points": [
-        {
-          "left": 100,
-          "top": 300,
-          "right": 200,
-          "bottom": 400
-        },
-        {
-          "left": 99,
-          "top": 296,
-          "right": 202,
-          "bottom": 404
-        },
-        {
-          "left": 95,
-          "top": 283,
-          "right": 207,
-          "bottom": 417
-        },
-        {
-          "left": 86,
-          "top": 256,
-          "right": 219,
-          "bottom": 443
-        },
-        {
-          "left": 68,
-          "top": 198,
-          "right": 243,
-          "bottom": 499
-        },
-        {
-          "left": 39,
-          "top": 110,
-          "right": 278,
-          "bottom": 584
-        },
-        {
-          "left": 26,
-          "top": 74,
-          "right": 292,
-          "bottom": 618
-        },
-        {
-          "left": 19,
-          "top": 55,
-          "right": 299,
-          "bottom": 637
-        },
-        {
-          "left": 15,
-          "top": 42,
-          "right": 304,
-          "bottom": 649
-        },
-        {
-          "left": 12,
-          "top": 33,
-          "right": 307,
-          "bottom": 658
-        },
-        {
-          "left": 9,
-          "top": 27,
-          "right": 310,
-          "bottom": 664
-        },
-        {
-          "left": 7,
-          "top": 21,
-          "right": 312,
-          "bottom": 669
-        },
-        {
-          "left": 6,
-          "top": 17,
-          "right": 314,
-          "bottom": 674
-        },
-        {
-          "left": 5,
-          "top": 13,
-          "right": 315,
-          "bottom": 677
-        },
-        {
-          "left": 4,
-          "top": 10,
-          "right": 316,
-          "bottom": 680
-        },
-        {
-          "left": 3,
-          "top": 8,
-          "right": 317,
-          "bottom": 682
-        },
-        {
-          "left": 2,
-          "top": 6,
-          "right": 318,
-          "bottom": 684
-        },
-        {
-          "left": 2,
-          "top": 5,
-          "right": 318,
-          "bottom": 685
-        },
-        {
-          "left": 1,
-          "top": 4,
-          "right": 319,
-          "bottom": 687
-        },
-        {
-          "left": 1,
-          "top": 2,
-          "right": 319,
-          "bottom": 688
-        },
-        {
-          "left": 1,
-          "top": 2,
-          "right": 319,
-          "bottom": 688
-        },
-        {
-          "left": 0,
-          "top": 1,
-          "right": 320,
-          "bottom": 689
-        },
-        {
-          "left": 0,
-          "top": 1,
-          "right": 320,
-          "bottom": 689
-        },
-        {
-          "left": 0,
-          "top": 0,
-          "right": 320,
-          "bottom": 690
-        },
-        {
-          "left": 0,
-          "top": 0,
-          "right": 320,
-          "bottom": 690
-        },
-        {
-          "left": 0,
-          "top": 0,
-          "right": 320,
-          "bottom": 690
-        }
-      ]
-    },
-    {
-      "name": "corner_radii",
-      "type": "cornerRadii",
-      "data_points": [
-        {
-          "top_left_x": 10,
-          "top_left_y": 10,
-          "top_right_x": 10,
-          "top_right_y": 10,
-          "bottom_right_x": 20,
-          "bottom_right_y": 20,
-          "bottom_left_x": 20,
-          "bottom_left_y": 20
-        },
-        {
-          "top_left_x": 9.865689,
-          "top_left_y": 9.865689,
-          "top_right_x": 9.865689,
-          "top_right_y": 9.865689,
-          "bottom_right_x": 19.731379,
-          "bottom_right_y": 19.731379,
-          "bottom_left_x": 19.731379,
-          "bottom_left_y": 19.731379
-        },
-        {
-          "top_left_x": 9.419104,
-          "top_left_y": 9.419104,
-          "top_right_x": 9.419104,
-          "top_right_y": 9.419104,
-          "bottom_right_x": 18.838207,
-          "bottom_right_y": 18.838207,
-          "bottom_left_x": 18.838207,
-          "bottom_left_y": 18.838207
-        },
-        {
-          "top_left_x": 8.533693,
-          "top_left_y": 8.533693,
-          "top_right_x": 8.533693,
-          "top_right_y": 8.533693,
-          "bottom_right_x": 17.067387,
-          "bottom_right_y": 17.067387,
-          "bottom_left_x": 17.067387,
-          "bottom_left_y": 17.067387
-        },
-        {
-          "top_left_x": 6.5919456,
-          "top_left_y": 6.5919456,
-          "top_right_x": 6.5919456,
-          "top_right_y": 6.5919456,
-          "bottom_right_x": 13.183891,
-          "bottom_right_y": 13.183891,
-          "bottom_left_x": 13.183891,
-          "bottom_left_y": 13.183891
-        },
-        {
-          "top_left_x": 3.6674318,
-          "top_left_y": 3.6674318,
-          "top_right_x": 3.6674318,
-          "top_right_y": 3.6674318,
-          "bottom_right_x": 7.3348637,
-          "bottom_right_y": 7.3348637,
-          "bottom_left_x": 7.3348637,
-          "bottom_left_y": 7.3348637
-        },
-        {
-          "top_left_x": 2.4832253,
-          "top_left_y": 2.4832253,
-          "top_right_x": 2.4832253,
-          "top_right_y": 2.4832253,
-          "bottom_right_x": 4.9664507,
-          "bottom_right_y": 4.9664507,
-          "bottom_left_x": 4.9664507,
-          "bottom_left_y": 4.9664507
-        },
-        {
-          "top_left_x": 1.8252907,
-          "top_left_y": 1.8252907,
-          "top_right_x": 1.8252907,
-          "top_right_y": 1.8252907,
-          "bottom_right_x": 3.6505814,
-          "bottom_right_y": 3.6505814,
-          "bottom_left_x": 3.6505814,
-          "bottom_left_y": 3.6505814
-        },
-        {
-          "top_left_x": 1.4077549,
-          "top_left_y": 1.4077549,
-          "top_right_x": 1.4077549,
-          "top_right_y": 1.4077549,
-          "bottom_right_x": 2.8155098,
-          "bottom_right_y": 2.8155098,
-          "bottom_left_x": 2.8155098,
-          "bottom_left_y": 2.8155098
-        },
-        {
-          "top_left_x": 1.1067667,
-          "top_left_y": 1.1067667,
-          "top_right_x": 1.1067667,
-          "top_right_y": 1.1067667,
-          "bottom_right_x": 2.2135334,
-          "bottom_right_y": 2.2135334,
-          "bottom_left_x": 2.2135334,
-          "bottom_left_y": 2.2135334
-        },
-        {
-          "top_left_x": 0.88593864,
-          "top_left_y": 0.88593864,
-          "top_right_x": 0.88593864,
-          "top_right_y": 0.88593864,
-          "bottom_right_x": 1.7718773,
-          "bottom_right_y": 1.7718773,
-          "bottom_left_x": 1.7718773,
-          "bottom_left_y": 1.7718773
-        },
-        {
-          "top_left_x": 0.7069988,
-          "top_left_y": 0.7069988,
-          "top_right_x": 0.7069988,
-          "top_right_y": 0.7069988,
-          "bottom_right_x": 1.4139977,
-          "bottom_right_y": 1.4139977,
-          "bottom_left_x": 1.4139977,
-          "bottom_left_y": 1.4139977
-        },
-        {
-          "top_left_x": 0.55613136,
-          "top_left_y": 0.55613136,
-          "top_right_x": 0.55613136,
-          "top_right_y": 0.55613136,
-          "bottom_right_x": 1.1122627,
-          "bottom_right_y": 1.1122627,
-          "bottom_left_x": 1.1122627,
-          "bottom_left_y": 1.1122627
-        },
-        {
-          "top_left_x": 0.44889355,
-          "top_left_y": 0.44889355,
-          "top_right_x": 0.44889355,
-          "top_right_y": 0.44889355,
-          "bottom_right_x": 0.8977871,
-          "bottom_right_y": 0.8977871,
-          "bottom_left_x": 0.8977871,
-          "bottom_left_y": 0.8977871
-        },
-        {
-          "top_left_x": 0.34557533,
-          "top_left_y": 0.34557533,
-          "top_right_x": 0.34557533,
-          "top_right_y": 0.34557533,
-          "bottom_right_x": 0.69115067,
-          "bottom_right_y": 0.69115067,
-          "bottom_left_x": 0.69115067,
-          "bottom_left_y": 0.69115067
-        },
-        {
-          "top_left_x": 0.27671337,
-          "top_left_y": 0.27671337,
-          "top_right_x": 0.27671337,
-          "top_right_y": 0.27671337,
-          "bottom_right_x": 0.55342674,
-          "bottom_right_y": 0.55342674,
-          "bottom_left_x": 0.55342674,
-          "bottom_left_y": 0.55342674
-        },
-        {
-          "top_left_x": 0.20785141,
-          "top_left_y": 0.20785141,
-          "top_right_x": 0.20785141,
-          "top_right_y": 0.20785141,
-          "bottom_right_x": 0.41570282,
-          "bottom_right_y": 0.41570282,
-          "bottom_left_x": 0.41570282,
-          "bottom_left_y": 0.41570282
-        },
-        {
-          "top_left_x": 0.1601448,
-          "top_left_y": 0.1601448,
-          "top_right_x": 0.1601448,
-          "top_right_y": 0.1601448,
-          "bottom_right_x": 0.3202896,
-          "bottom_right_y": 0.3202896,
-          "bottom_left_x": 0.3202896,
-          "bottom_left_y": 0.3202896
-        },
-        {
-          "top_left_x": 0.117860794,
-          "top_left_y": 0.117860794,
-          "top_right_x": 0.117860794,
-          "top_right_y": 0.117860794,
-          "bottom_right_x": 0.23572159,
-          "bottom_right_y": 0.23572159,
-          "bottom_left_x": 0.23572159,
-          "bottom_left_y": 0.23572159
-        },
-        {
-          "top_left_x": 0.08036041,
-          "top_left_y": 0.08036041,
-          "top_right_x": 0.08036041,
-          "top_right_y": 0.08036041,
-          "bottom_right_x": 0.16072083,
-          "bottom_right_y": 0.16072083,
-          "bottom_left_x": 0.16072083,
-          "bottom_left_y": 0.16072083
-        },
-        {
-          "top_left_x": 0.05836296,
-          "top_left_y": 0.05836296,
-          "top_right_x": 0.05836296,
-          "top_right_y": 0.05836296,
-          "bottom_right_x": 0.11672592,
-          "bottom_right_y": 0.11672592,
-          "bottom_left_x": 0.11672592,
-          "bottom_left_y": 0.11672592
-        },
-        {
-          "top_left_x": 0.03636551,
-          "top_left_y": 0.03636551,
-          "top_right_x": 0.03636551,
-          "top_right_y": 0.03636551,
-          "bottom_right_x": 0.07273102,
-          "bottom_right_y": 0.07273102,
-          "bottom_left_x": 0.07273102,
-          "bottom_left_y": 0.07273102
-        },
-        {
-          "top_left_x": 0.018137932,
-          "top_left_y": 0.018137932,
-          "top_right_x": 0.018137932,
-          "top_right_y": 0.018137932,
-          "bottom_right_x": 0.036275864,
-          "bottom_right_y": 0.036275864,
-          "bottom_left_x": 0.036275864,
-          "bottom_left_y": 0.036275864
-        },
-        {
-          "top_left_x": 0.0082063675,
-          "top_left_y": 0.0082063675,
-          "top_right_x": 0.0082063675,
-          "top_right_y": 0.0082063675,
-          "bottom_right_x": 0.016412735,
-          "bottom_right_y": 0.016412735,
-          "bottom_left_x": 0.016412735,
-          "bottom_left_y": 0.016412735
-        },
-        {
-          "top_left_x": 0.0031013489,
-          "top_left_y": 0.0031013489,
-          "top_right_x": 0.0031013489,
-          "top_right_y": 0.0031013489,
-          "bottom_right_x": 0.0062026978,
-          "bottom_right_y": 0.0062026978,
-          "bottom_left_x": 0.0062026978,
-          "bottom_left_y": 0.0062026978
-        },
-        {
-          "top_left_x": 0,
-          "top_left_y": 0,
-          "top_right_x": 0,
-          "top_right_y": 0,
-          "bottom_right_x": 0,
-          "bottom_right_y": 0,
-          "bottom_left_x": 0,
-          "bottom_left_y": 0
-        }
-      ]
-    },
-    {
-      "name": "alpha",
-      "type": "int",
-      "data_points": [
-        0,
-        96,
-        153,
-        192,
-        220,
-        238,
-        249,
-        254,
-        233,
-        191,
-        153,
-        117,
-        85,
-        57,
-        33,
-        14,
-        3,
-        0,
-        0,
-        0,
-        0,
-        0,
-        0,
-        0,
-        0,
-        0
-      ]
-    }
-  ]
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
index a95735e..82cfab6 100644
--- a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
+++ b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
@@ -56,7 +56,6 @@
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
 public class AAAPlusPlusVerifySysuiRequiredTestPropertiesTest extends SysuiTestCase {
-
     private static final String TAG = "AAA++VerifyTest";
 
     private static final Class[] BASE_CLS_TO_INCLUDE = {
@@ -149,6 +148,9 @@
      */
     private boolean isTestClass(Class<?> loadedClass) {
         try {
+            if (loadedClass.getAnnotation(SkipSysuiVerification.class) != null) {
+                return false;
+            }
             if (Modifier.isAbstract(loadedClass.getModifiers())) {
                 logDebug(String.format("Skipping abstract class %s: not a test",
                         loadedClass.getName()));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index c6e4e0d..fa88f62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -46,7 +46,6 @@
 import android.view.SurfaceControlViewHost;
 import android.view.View;
 import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
 import android.view.animation.AccelerateInterpolator;
 import android.window.InputTransferToken;
@@ -1030,10 +1029,7 @@
                     callback,
                     sysUiState,
                     secureSettings,
-                    scvhSupplier,
-                    sfVsyncFrameProvider,
-                    WindowManagerGlobal::getWindowSession,
-                    viewCaptureAwareWindowManager);
+                    scvhSupplier);
             mSpyController = Mockito.mock(WindowMagnificationController.class);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
deleted file mode 100644
index 9b09ec2..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
+++ /dev/null
@@ -1,1594 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.accessibility;
-
-import static android.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
-import static android.view.MotionEvent.ACTION_DOWN;
-import static android.view.MotionEvent.ACTION_UP;
-import static android.view.WindowInsets.Type.systemGestures;
-import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.AdditionalAnswers.returnsSecondArg;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import static java.util.Arrays.asList;
-
-import android.animation.ValueAnimator;
-import android.annotation.IdRes;
-import android.annotation.Nullable;
-import android.app.Instrumentation;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Insets;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.provider.Settings;
-import android.testing.TestableLooper;
-import android.testing.TestableResources;
-import android.util.Size;
-import android.view.AttachedSurfaceControl;
-import android.view.Display;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.Surface;
-import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewRootImpl;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.IRemoteMagnificationAnimationCallback;
-import android.widget.FrameLayout;
-import android.window.InputTransferToken;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
-import com.android.systemui.Flags;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.AnimatorTestRule;
-import com.android.systemui.kosmos.KosmosJavaAdapter;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.res.R;
-import com.android.systemui.settings.FakeDisplayTracker;
-import com.android.systemui.util.FakeSharedPreferences;
-import com.android.systemui.util.leak.ReferenceTestUtils;
-import com.android.systemui.util.settings.SecureSettings;
-import com.android.systemui.utils.os.FakeHandler;
-
-import com.google.common.util.concurrent.AtomicDouble;
-
-import org.junit.After;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Supplier;
-
-@LargeTest
-@TestableLooper.RunWithLooper
-@RunWith(AndroidJUnit4.class)
-@EnableFlags(Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER)
-public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiTestCase {
-
-    @Rule
-    // NOTE: pass 'null' to allow this test advances time on the main thread.
-    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(/* test= */ null);
-
-    private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000;
-    @Mock
-    private MirrorWindowControl mMirrorWindowControl;
-    @Mock
-    private WindowMagnifierCallback mWindowMagnifierCallback;
-    @Mock
-    IRemoteMagnificationAnimationCallback mAnimationCallback;
-    @Mock
-    IRemoteMagnificationAnimationCallback mAnimationCallback2;
-
-    private SurfaceControl.Transaction mTransaction;
-    @Mock
-    private SecureSettings mSecureSettings;
-    @Mock
-    private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
-
-    private long mWaitAnimationDuration;
-    private long mWaitBounceEffectDuration;
-
-    private Handler mHandler;
-    private TestableWindowManager mWindowManager;
-    private SysUiState mSysUiState;
-    private Resources mResources;
-    private WindowMagnificationAnimationController mWindowMagnificationAnimationController;
-    private WindowMagnificationController mWindowMagnificationController;
-    private Instrumentation mInstrumentation;
-    private final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 1.0f).setDuration(0);
-    private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
-
-    private View mSpyView;
-    private View.OnTouchListener mTouchListener;
-
-    private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
-
-    // This list contains all SurfaceControlViewHosts created during a given test. If the
-    // magnification window is recreated during a test, the list will contain more than a single
-    // element.
-    private List<SurfaceControlViewHost> mSurfaceControlViewHosts = new ArrayList<>();
-    // The most recently created SurfaceControlViewHost.
-    private SurfaceControlViewHost mSurfaceControlViewHost;
-    private KosmosJavaAdapter mKosmos;
-    private FakeSharedPreferences mSharedPreferences;
-
-    /**
-     *  return whether window magnification is supported for current test context.
-     */
-    private boolean isWindowModeSupported() {
-        return getContext().getPackageManager().hasSystemFeature(FEATURE_WINDOW_MAGNIFICATION);
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mContext = spy(mContext);
-        mKosmos = new KosmosJavaAdapter(this);
-        mContext = Mockito.spy(getContext());
-        mHandler = new FakeHandler(TestableLooper.get(this).getLooper());
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        final WindowManager wm = mContext.getSystemService(WindowManager.class);
-        mWindowManager = spy(new TestableWindowManager(wm));
-
-        mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
-        mSysUiState = new SysUiState(mDisplayTracker, mKosmos.getSceneContainerPlugin());
-        mSysUiState.addCallback(Mockito.mock(SysUiState.SysUiStateCallback.class));
-        when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).then(
-                returnsSecondArg());
-        when(mSecureSettings.getFloatForUser(anyString(), anyFloat(), anyInt())).then(
-                returnsSecondArg());
-
-        mResources = getContext().getOrCreateTestableResources().getResources();
-        // prevent the config orientation from undefined, which may cause config.diff method
-        // neglecting the orientation update.
-        if (mResources.getConfiguration().orientation == ORIENTATION_UNDEFINED) {
-            mResources.getConfiguration().orientation = ORIENTATION_PORTRAIT;
-        }
-
-        // Using the animation duration in WindowMagnificationAnimationController for testing.
-        mWaitAnimationDuration = mResources.getInteger(
-                com.android.internal.R.integer.config_longAnimTime);
-        // Using the bounce effect duration in WindowMagnificationController for testing.
-        mWaitBounceEffectDuration = mResources.getInteger(
-                com.android.internal.R.integer.config_shortAnimTime);
-
-        mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
-                mContext, mValueAnimator);
-        Supplier<SurfaceControlViewHost> scvhSupplier = () -> {
-            mSurfaceControlViewHost = spy(new SurfaceControlViewHost(
-                    mContext, mContext.getDisplay(), new InputTransferToken(),
-                    "WindowMagnification"));
-            ViewRootImpl viewRoot = mock(ViewRootImpl.class);
-            when(mSurfaceControlViewHost.getRootSurfaceControl()).thenReturn(viewRoot);
-            mSurfaceControlViewHosts.add(mSurfaceControlViewHost);
-            return mSurfaceControlViewHost;
-        };
-        mTransaction = spy(new SurfaceControl.Transaction());
-        mSharedPreferences = new FakeSharedPreferences();
-        when(mContext.getSharedPreferences(
-                eq("window_magnification_preferences"), anyInt()))
-                .thenReturn(mSharedPreferences);
-        mWindowMagnificationController =
-                new WindowMagnificationController(
-                        mContext,
-                        mHandler,
-                        mWindowMagnificationAnimationController,
-                        mMirrorWindowControl,
-                        mTransaction,
-                        mWindowMagnifierCallback,
-                        mSysUiState,
-                        mSecureSettings,
-                        scvhSupplier,
-                        /* sfVsyncFrameProvider= */ null,
-                        /* globalWindowSessionSupplier= */ null,
-                        mViewCaptureAwareWindowManager);
-
-        verify(mMirrorWindowControl).setWindowDelegate(
-                any(MirrorWindowControl.MirrorWindowDelegate.class));
-        mSpyView = Mockito.spy(new View(mContext));
-        doAnswer((invocation) -> {
-            mTouchListener = invocation.getArgument(0);
-            return null;
-        }).when(mSpyView).setOnTouchListener(
-                any(View.OnTouchListener.class));
-
-        // skip test if window magnification is not supported to prevent fail results. (b/279820875)
-        Assume.assumeTrue(isWindowModeSupported());
-    }
-
-    @After
-    public void tearDown() {
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.deleteWindowMagnification();
-                });
-        mValueAnimator.cancel();
-    }
-
-    @Test
-    public void initWindowMagnificationController_checkAllowDiagonalScrollingWithSecureSettings() {
-        verify(mSecureSettings).getIntForUser(
-                eq(Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING),
-                /* def */ eq(1), /* userHandle= */ anyInt());
-        assertThat(mWindowMagnificationController.isDiagonalScrollingEnabled()).isTrue();
-    }
-
-    @Test
-    public void enableWindowMagnification_showControlAndNotifyBoundsChanged() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        verify(mMirrorWindowControl).showControl();
-        verify(mWindowMagnifierCallback,
-                timeout(LAYOUT_CHANGE_TIMEOUT_MS).atLeastOnce()).onWindowMagnifierBoundsChanged(
-                eq(mContext.getDisplayId()), any(Rect.class));
-    }
-
-    @Test
-    public void enableWindowMagnification_notifySourceBoundsChanged() {
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
-                        Float.NaN, /* magnificationFrameOffsetRatioX= */ 0,
-                        /* magnificationFrameOffsetRatioY= */ 0, null));
-
-        // Waits for the surface created
-        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS)).onSourceBoundsChanged(
-                (eq(mContext.getDisplayId())), any());
-    }
-
-    @Test
-    public void enableWindowMagnification_disabled_notifySourceBoundsChanged() {
-        enableWindowMagnification_notifySourceBoundsChanged();
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.deleteWindowMagnification(null));
-        Mockito.reset(mWindowMagnifierCallback);
-
-        enableWindowMagnification_notifySourceBoundsChanged();
-    }
-
-    @Test
-    public void enableWindowMagnification_withAnimation_schedulesFrame() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnification(2.0f, 10,
-                    10, /* magnificationFrameOffsetRatioX= */ 0,
-                    /* magnificationFrameOffsetRatioY= */ 0,
-                    Mockito.mock(IRemoteMagnificationAnimationCallback.class));
-        });
-        advanceTimeBy(LAYOUT_CHANGE_TIMEOUT_MS);
-
-        verify(mTransaction, atLeastOnce()).setGeometry(any(), any(), any(),
-                eq(Surface.ROTATION_0));
-    }
-
-    @Test
-    public void moveWindowMagnifier_enabled_notifySourceBoundsChanged() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
-                    Float.NaN, 0, 0, null);
-        });
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.moveWindowMagnifier(10, 10);
-        });
-
-        final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
-        verify(mWindowMagnifierCallback, atLeast(2)).onSourceBoundsChanged(
-                (eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        assertThat(mWindowMagnificationController.getCenterX())
-                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
-        assertThat(mWindowMagnificationController.getCenterY())
-                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
-    }
-
-    @Test
-    public void enableWindowMagnification_systemGestureExclusionRectsIsSet() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-        // Wait for Rects updated.
-        waitForIdleSync();
-
-        List<Rect> rects = mSurfaceControlViewHost.getView().getSystemGestureExclusionRects();
-        assertThat(rects).isNotEmpty();
-    }
-
-    @Ignore("The default window size should be constrained after fixing b/288056772")
-    @Test
-    public void enableWindowMagnification_LargeScreen_windowSizeIsConstrained() {
-        final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10;
-        mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        final int halfScreenSize = screenSize / 2;
-        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
-        // The frame size should be the half of smaller value of window height/width unless it
-        //exceed the max frame size.
-        assertThat(params.width).isLessThan(halfScreenSize);
-        assertThat(params.height).isLessThan(halfScreenSize);
-    }
-
-    @Test
-    public void deleteWindowMagnification_destroyControlAndUnregisterComponentCallback() {
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN,
-                        Float.NaN));
-
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.deleteWindowMagnification());
-
-        verify(mMirrorWindowControl).destroyControl();
-        verify(mContext).unregisterComponentCallbacks(mWindowMagnificationController);
-    }
-
-    @Test
-    public void deleteWindowMagnification_enableAtTheBottom_overlapFlagIsFalse() {
-        final WindowManager wm = mContext.getSystemService(WindowManager.class);
-        final Rect bounds = wm.getCurrentWindowMetrics().getBounds();
-        setSystemGestureInsets();
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    bounds.bottom);
-        });
-        ReferenceTestUtils.waitForCondition(this::hasMagnificationOverlapFlag);
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.deleteWindowMagnification();
-        });
-
-        verify(mMirrorWindowControl).destroyControl();
-        assertThat(hasMagnificationOverlapFlag()).isFalse();
-    }
-
-    @Test
-    public void deleteWindowMagnification_notifySourceBoundsChanged() {
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN,
-                        Float.NaN));
-
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.deleteWindowMagnification());
-
-        // The first time is for notifying magnification enabled and the second time is for
-        // notifying magnification disabled.
-        verify(mWindowMagnifierCallback, times(2)).onSourceBoundsChanged(
-                (eq(mContext.getDisplayId())), any());
-    }
-
-    @Test
-    public void moveMagnifier_schedulesFrame() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        waitForIdleSync();
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.moveWindowMagnifier(100f, 100f));
-
-        verify(mTransaction, atLeastOnce()).setGeometry(any(), any(), any(),
-                eq(Surface.ROTATION_0));
-    }
-
-    @Test
-    public void moveWindowMagnifierToPositionWithAnimation_expectedValuesAndInvokeCallback()
-            throws RemoteException {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
-                    Float.NaN, 0, 0, null);
-        });
-
-        final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
-        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
-                .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        final float targetCenterX = sourceBoundsCaptor.getValue().exactCenterX() + 10;
-        final float targetCenterY = sourceBoundsCaptor.getValue().exactCenterY() + 10;
-
-        reset(mWindowMagnifierCallback);
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.moveWindowMagnifierToPosition(
-                    targetCenterX, targetCenterY, mAnimationCallback);
-        });
-        advanceTimeBy(mWaitAnimationDuration);
-
-        verify(mAnimationCallback, times(1)).onResult(eq(true));
-        verify(mAnimationCallback, never()).onResult(eq(false));
-        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
-                .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        assertThat(mWindowMagnificationController.getCenterX())
-                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
-        assertThat(mWindowMagnificationController.getCenterY())
-                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
-        assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(targetCenterX);
-        assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(targetCenterY);
-    }
-
-    @Test
-    public void moveWindowMagnifierToPositionMultipleTimes_expectedValuesAndInvokeCallback()
-            throws RemoteException {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
-                    Float.NaN, 0, 0, null);
-        });
-
-        final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
-        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
-                .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        final float centerX = sourceBoundsCaptor.getValue().exactCenterX();
-        final float centerY = sourceBoundsCaptor.getValue().exactCenterY();
-
-        reset(mWindowMagnifierCallback);
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.moveWindowMagnifierToPosition(
-                    centerX + 10, centerY + 10, mAnimationCallback);
-            mWindowMagnificationController.moveWindowMagnifierToPosition(
-                    centerX + 20, centerY + 20, mAnimationCallback);
-            mWindowMagnificationController.moveWindowMagnifierToPosition(
-                    centerX + 30, centerY + 30, mAnimationCallback);
-            mWindowMagnificationController.moveWindowMagnifierToPosition(
-                    centerX + 40, centerY + 40, mAnimationCallback2);
-        });
-        advanceTimeBy(mWaitAnimationDuration);
-
-        // only the last one callback will return true
-        verify(mAnimationCallback2).onResult(eq(true));
-        // the others will return false
-        verify(mAnimationCallback, times(3)).onResult(eq(false));
-        verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
-                .onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
-        assertThat(mWindowMagnificationController.getCenterX())
-                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
-        assertThat(mWindowMagnificationController.getCenterY())
-                .isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
-        assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(centerX + 40);
-        assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(centerY + 40);
-    }
-
-    @Test
-    public void setScale_enabled_expectedValueAndUpdateStateDescription() {
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(2.0f,
-                        Float.NaN, Float.NaN));
-
-        mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f));
-
-        assertThat(mWindowMagnificationController.getScale()).isEqualTo(3.0f);
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        assertThat(mirrorView).isNotNull();
-        assertThat(mirrorView.getStateDescription().toString()).contains("300");
-    }
-
-    @Test
-    public void onConfigurationChanged_disabled_withoutException() {
-        Display display = Mockito.spy(mContext.getDisplay());
-        when(display.getRotation()).thenReturn(Surface.ROTATION_90);
-        when(mContext.getDisplay()).thenReturn(display);
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_ORIENTATION);
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE);
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
-        });
-    }
-
-    @Test
-    public void onOrientationChanged_enabled_updateDisplayRotationAndCenterStayAtSamePosition() {
-        final int newRotation = simulateRotateTheDevice();
-        final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
-        final float center = Math.min(windowBounds.exactCenterX(), windowBounds.exactCenterY());
-        final float displayWidth = windowBounds.width();
-        final PointF magnifiedCenter = new PointF(center, center + 5f);
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                    magnifiedCenter.x, magnifiedCenter.y);
-            // Get the center again in case the center we set is out of screen.
-            magnifiedCenter.set(mWindowMagnificationController.getCenterX(),
-                    mWindowMagnificationController.getCenterY());
-        });
-        // Rotate the window clockwise 90 degree.
-        windowBounds.set(windowBounds.top, windowBounds.left, windowBounds.bottom,
-                windowBounds.right);
-        mWindowManager.setWindowBounds(windowBounds);
-
-        mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged(
-                ActivityInfo.CONFIG_ORIENTATION));
-
-        assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
-        final PointF expectedCenter = new PointF(magnifiedCenter.y,
-                displayWidth - magnifiedCenter.x);
-        final PointF actualCenter = new PointF(mWindowMagnificationController.getCenterX(),
-                mWindowMagnificationController.getCenterY());
-        assertThat(actualCenter).isEqualTo(expectedCenter);
-    }
-
-    @Test
-    public void onOrientationChanged_disabled_updateDisplayRotation() {
-        final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
-        // Rotate the window clockwise 90 degree.
-        windowBounds.set(windowBounds.top, windowBounds.left, windowBounds.bottom,
-                windowBounds.right);
-        mWindowManager.setWindowBounds(windowBounds);
-        final int newRotation = simulateRotateTheDevice();
-
-        mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.onConfigurationChanged(
-                ActivityInfo.CONFIG_ORIENTATION));
-
-        assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
-    }
-
-    @Test
-    public void onScreenSizeAndDensityChanged_enabledAtTheCenterOfScreen_keepSameWindowSizeRatio() {
-        // The default position is at the center of the screen.
-        final float expectedRatio = 0.5f;
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        // Screen size and density change
-        mContext.getResources().getConfiguration().smallestScreenWidthDp =
-                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
-        final Rect testWindowBounds = new Rect(
-                mWindowManager.getCurrentWindowMetrics().getBounds());
-        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
-                testWindowBounds.right + 100, testWindowBounds.bottom + 100);
-        mWindowManager.setWindowBounds(testWindowBounds);
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
-        });
-
-        // The ratio of center to window size should be the same.
-        assertThat(mWindowMagnificationController.getCenterX() / testWindowBounds.width())
-                .isEqualTo(expectedRatio);
-        assertThat(mWindowMagnificationController.getCenterY() / testWindowBounds.height())
-                .isEqualTo(expectedRatio);
-    }
-
-    @DisableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
-    @Test
-    public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierWindow() {
-        int newSmallestScreenWidthDp =
-                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
-        int windowFrameSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        Size preferredWindowSize = new Size(windowFrameSize, windowFrameSize);
-        mSharedPreferences
-                .edit()
-                .putString(String.valueOf(newSmallestScreenWidthDp),
-                        preferredWindowSize.toString())
-                .commit();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        // Screen density and size change
-        mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp;
-        final Rect testWindowBounds = new Rect(
-                mWindowManager.getCurrentWindowMetrics().getBounds());
-        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
-                testWindowBounds.right + 100, testWindowBounds.bottom + 100);
-        mWindowManager.setWindowBounds(testWindowBounds);
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
-        });
-
-        // wait for rect update
-        waitForIdleSync();
-        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
-        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
-                R.dimen.magnification_mirror_surface_margin);
-        // The width and height of the view include the magnification frame and the margins.
-        assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
-        assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
-    }
-
-    @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
-    @Test
-    public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierIndexAndWindow() {
-        int newSmallestScreenWidthDp =
-                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
-        int windowFrameSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        Size preferredWindowSize = new Size(windowFrameSize, windowFrameSize);
-        mSharedPreferences
-                .edit()
-                .putString(String.valueOf(newSmallestScreenWidthDp),
-                        WindowMagnificationFrameSpec.serialize(
-                                WindowMagnificationSettings.MagnificationSize.CUSTOM,
-                                preferredWindowSize))
-                .commit();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        // Screen density and size change
-        mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp;
-        final Rect testWindowBounds = new Rect(
-                mWindowManager.getCurrentWindowMetrics().getBounds());
-        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
-                testWindowBounds.right + 100, testWindowBounds.bottom + 100);
-        mWindowManager.setWindowBounds(testWindowBounds);
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
-        });
-
-        // wait for rect update
-        waitForIdleSync();
-        verify(mWindowMagnifierCallback).onWindowMagnifierBoundsRestored(
-                eq(mContext.getDisplayId()),
-                eq(WindowMagnificationSettings.MagnificationSize.CUSTOM));
-        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
-        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
-                R.dimen.magnification_mirror_surface_margin);
-        // The width and height of the view include the magnification frame and the margins.
-        assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
-        assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
-    }
-
-    @Test
-    public void screenSizeIsChangedToLarge_enabled_defaultWindowSize() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-        final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10;
-        // Screen size and density change
-        mContext.getResources().getConfiguration().smallestScreenWidthDp =
-                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
-        mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
-        });
-
-        final int defaultWindowSize =
-                mWindowMagnificationController.getMagnificationWindowSizeFromIndex(
-                        WindowMagnificationSettings.MagnificationSize.MEDIUM);
-        ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
-
-        assertThat(params.width).isEqualTo(defaultWindowSize);
-        assertThat(params.height).isEqualTo(defaultWindowSize);
-    }
-
-    @Test
-    public void onDensityChanged_enabled_updateDimensionsAndResetWindowMagnification() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            Mockito.reset(mWindowManager);
-            Mockito.reset(mMirrorWindowControl);
-        });
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
-        });
-
-        verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt());
-        verify(mSurfaceControlViewHosts.get(0)).release();
-        verify(mMirrorWindowControl).destroyControl();
-        verify(mSurfaceControlViewHosts.get(1)).setView(any(), any());
-        verify(mMirrorWindowControl).showControl();
-    }
-
-    @Test
-    public void onDensityChanged_disabled_updateDimensions() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
-        });
-
-        verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt());
-    }
-
-    @Test
-    public void initializeA11yNode_enabled_expectedValues() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN,
-                    Float.NaN);
-        });
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        assertThat(mirrorView).isNotNull();
-        final AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
-
-        mirrorView.onInitializeAccessibilityNodeInfo(nodeInfo);
-
-        assertThat(nodeInfo.getContentDescription()).isNotNull();
-        assertThat(nodeInfo.getStateDescription().toString()).contains("250");
-        assertThat(nodeInfo.getActionList()).containsExactlyElementsIn(asList(
-                new AccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(),
-                        mContext.getResources().getString(
-                        R.string.magnification_open_settings_click_label)),
-                new AccessibilityAction(R.id.accessibility_action_zoom_in,
-                        mContext.getString(R.string.accessibility_control_zoom_in)),
-                new AccessibilityAction(R.id.accessibility_action_zoom_out,
-                        mContext.getString(R.string.accessibility_control_zoom_out)),
-                new AccessibilityAction(R.id.accessibility_action_move_right,
-                        mContext.getString(R.string.accessibility_control_move_right)),
-                new AccessibilityAction(R.id.accessibility_action_move_left,
-                        mContext.getString(R.string.accessibility_control_move_left)),
-                new AccessibilityAction(R.id.accessibility_action_move_down,
-                        mContext.getString(R.string.accessibility_control_move_down)),
-                new AccessibilityAction(R.id.accessibility_action_move_up,
-                        mContext.getString(R.string.accessibility_control_move_up))));
-    }
-
-    @Test
-    public void performA11yActions_visible_expectedResults() {
-        final int displayId = mContext.getDisplayId();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(1.5f, Float.NaN,
-                    Float.NaN);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null))
-                .isTrue();
-        // Minimum scale is 1.0.
-        verify(mWindowMagnifierCallback).onPerformScaleAction(
-                eq(displayId), /* scale= */ eq(1.0f), /* updatePersistence= */ eq(true));
-
-        assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null))
-                .isTrue();
-        verify(mWindowMagnifierCallback).onPerformScaleAction(
-                eq(displayId), /* scale= */ eq(2.5f), /* updatePersistence= */ eq(true));
-
-        // TODO: Verify the final state when the mirror surface is visible.
-        assertThat(mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null))
-                .isTrue();
-        assertThat(
-                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null))
-                .isTrue();
-        assertThat(
-                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null))
-                .isTrue();
-        assertThat(
-                mirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null))
-                .isTrue();
-        verify(mWindowMagnifierCallback, times(4)).onMove(eq(displayId));
-
-        assertThat(mirrorView.performAccessibilityAction(
-                AccessibilityAction.ACTION_CLICK.getId(), null)).isTrue();
-        verify(mWindowMagnifierCallback).onClickSettingsButton(eq(displayId));
-    }
-
-    @Test
-    public void performA11yActions_visible_notifyAccessibilityActionPerformed() {
-        final int displayId = mContext.getDisplayId();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(2.5f, Float.NaN,
-                    Float.NaN);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        mirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null);
-
-        verify(mWindowMagnifierCallback).onAccessibilityActionPerformed(eq(displayId));
-    }
-
-    @Test
-    public void windowMagnifierEditMode_performA11yClickAction_exitEditMode() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        View closeButton = getInternalView(R.id.close_button);
-        View bottomRightCorner = getInternalView(R.id.bottom_right_corner);
-        View bottomLeftCorner = getInternalView(R.id.bottom_left_corner);
-        View topRightCorner = getInternalView(R.id.top_right_corner);
-        View topLeftCorner = getInternalView(R.id.top_left_corner);
-
-        assertThat(closeButton.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(topRightCorner.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(topLeftCorner.getVisibility()).isEqualTo(View.VISIBLE);
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        mInstrumentation.runOnMainSync(() ->
-                mirrorView.performAccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(),
-                        null));
-
-        assertThat(closeButton.getVisibility()).isEqualTo(View.GONE);
-        assertThat(bottomRightCorner.getVisibility()).isEqualTo(View.GONE);
-        assertThat(bottomLeftCorner.getVisibility()).isEqualTo(View.GONE);
-        assertThat(topRightCorner.getVisibility()).isEqualTo(View.GONE);
-        assertThat(topLeftCorner.getVisibility()).isEqualTo(View.GONE);
-    }
-
-    @Test
-
-    public void windowWidthIsNotMax_performA11yActionIncreaseWidth_windowWidthIncreased() {
-        final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        final int startingWidth = (int) (windowBounds.width() * 0.8);
-        final int startingHeight = (int) (windowBounds.height() * 0.8);
-        final float changeWindowSizeAmount = mContext.getResources().getFraction(
-                R.fraction.magnification_resize_window_size_amount,
-                /* base= */ 1,
-                /* pbase= */ 1);
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mirrorView.performAccessibilityAction(
-                            R.id.accessibility_action_increase_window_width, null);
-                    actualWindowHeight.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().height);
-                    actualWindowWidth.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().width);
-                });
-
-        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
-                R.dimen.magnification_mirror_surface_margin);
-        // Window width includes the magnifier frame and the margin. Increasing the window size
-        // will be increasing the amount of the frame size only.
-        int newWindowWidth =
-                (int) ((startingWidth - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
-                        + 2 * mirrorSurfaceMargin;
-        assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth);
-        assertThat(actualWindowHeight.get()).isEqualTo(startingHeight);
-    }
-
-    @Test
-    public void windowHeightIsNotMax_performA11yActionIncreaseHeight_windowHeightIncreased() {
-        final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        final int startingWidth = (int) (windowBounds.width() * 0.8);
-        final int startingHeight = (int) (windowBounds.height() * 0.8);
-        final float changeWindowSizeAmount = mContext.getResources().getFraction(
-                R.fraction.magnification_resize_window_size_amount,
-                /* base= */ 1,
-                /* pbase= */ 1);
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mirrorView.performAccessibilityAction(
-                            R.id.accessibility_action_increase_window_height, null);
-                    actualWindowHeight.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().height);
-                    actualWindowWidth.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().width);
-                });
-
-        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
-                R.dimen.magnification_mirror_surface_margin);
-        // Window height includes the magnifier frame and the margin. Increasing the window size
-        // will be increasing the amount of the frame size only.
-        int newWindowHeight =
-                (int) ((startingHeight - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
-                        + 2 * mirrorSurfaceMargin;
-        assertThat(actualWindowWidth.get()).isEqualTo(startingWidth);
-        assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight);
-    }
-
-    @Test
-    public void windowWidthIsMax_noIncreaseWindowWidthA11yAction() {
-        final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        final int startingWidth = windowBounds.width();
-        final int startingHeight = windowBounds.height();
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AccessibilityNodeInfo accessibilityNodeInfo =
-                mirrorView.createAccessibilityNodeInfo();
-        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
-                new AccessibilityAction(
-                        R.id.accessibility_action_increase_window_width,
-                        mContext.getString(
-                                R.string.accessibility_control_increase_window_width)));
-    }
-
-    @Test
-    public void windowHeightIsMax_noIncreaseWindowHeightA11yAction() {
-        final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        final int startingWidth = windowBounds.width();
-        final int startingHeight = windowBounds.height();
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AccessibilityNodeInfo accessibilityNodeInfo =
-                mirrorView.createAccessibilityNodeInfo();
-        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
-                new AccessibilityAction(
-                        R.id.accessibility_action_increase_window_height, null));
-    }
-
-    @Test
-    public void windowWidthIsNotMin_performA11yActionDecreaseWidth_windowWidthDecreased() {
-        int mMinWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        final int startingSize = (int) (mMinWindowSize * 1.1);
-        final float changeWindowSizeAmount = mContext.getResources().getFraction(
-                R.fraction.magnification_resize_window_size_amount,
-                /* base= */ 1,
-                /* pbase= */ 1);
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingSize, startingSize);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mirrorView.performAccessibilityAction(
-                            R.id.accessibility_action_decrease_window_width, null);
-                    actualWindowHeight.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().height);
-                    actualWindowWidth.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().width);
-                });
-
-        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
-                R.dimen.magnification_mirror_surface_margin);
-        // Window width includes the magnifier frame and the margin. Decreasing the window size
-        // will be decreasing the amount of the frame size only.
-        int newWindowWidth =
-                (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
-                        + 2 * mirrorSurfaceMargin;
-        assertThat(actualWindowWidth.get()).isEqualTo(newWindowWidth);
-        assertThat(actualWindowHeight.get()).isEqualTo(startingSize);
-    }
-
-    @Test
-    public void windowHeightIsNotMin_performA11yActionDecreaseHeight_windowHeightDecreased() {
-        int mMinWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        final int startingSize = (int) (mMinWindowSize * 1.1);
-        final float changeWindowSizeAmount = mContext.getResources().getFraction(
-                R.fraction.magnification_resize_window_size_amount,
-                /* base= */ 1,
-                /* pbase= */ 1);
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingSize, startingSize);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mirrorView.performAccessibilityAction(
-                            R.id.accessibility_action_decrease_window_height, null);
-                    actualWindowHeight.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().height);
-                    actualWindowWidth.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().width);
-                });
-
-        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
-                R.dimen.magnification_mirror_surface_margin);
-        // Window height includes the magnifier frame and the margin. Decreasing the window size
-        // will be decreasing the amount of the frame size only.
-        int newWindowHeight =
-                (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
-                        + 2 * mirrorSurfaceMargin;
-        assertThat(actualWindowWidth.get()).isEqualTo(startingSize);
-        assertThat(actualWindowHeight.get()).isEqualTo(newWindowHeight);
-    }
-
-    @Test
-    public void windowWidthIsMin_noDecreaseWindowWidthA11yAction() {
-        int mMinWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        final int startingSize = mMinWindowSize;
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingSize, startingSize);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AccessibilityNodeInfo accessibilityNodeInfo =
-                mirrorView.createAccessibilityNodeInfo();
-        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
-                new AccessibilityAction(
-                        R.id.accessibility_action_decrease_window_width, null));
-    }
-
-    @Test
-    public void windowHeightIsMin_noDecreaseWindowHeightA11yAction() {
-        int mMinWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        final int startingSize = mMinWindowSize;
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-            mWindowMagnificationController.setWindowSize(startingSize, startingSize);
-            mWindowMagnificationController.setEditMagnifierSizeMode(true);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        final AccessibilityNodeInfo accessibilityNodeInfo =
-                mirrorView.createAccessibilityNodeInfo();
-        assertThat(accessibilityNodeInfo.getActionList()).doesNotContain(
-                new AccessibilityAction(
-                        R.id.accessibility_action_decrease_window_height, null));
-    }
-
-    @Test
-    public void enableWindowMagnification_hasA11yWindowTitle() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        assertThat(getAccessibilityWindowTitle()).isEqualTo(getContext().getResources().getString(
-                com.android.internal.R.string.android_system_label));
-    }
-
-    @Test
-    public void enableWindowMagnificationWithScaleLessThanOne_enabled_disabled() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(0.9f, Float.NaN,
-                    Float.NaN);
-        });
-
-        assertThat(mWindowMagnificationController.getScale()).isEqualTo(Float.NaN);
-    }
-
-    @Test
-    public void enableWindowMagnification_rotationIsChanged_updateRotationValue() {
-        // the config orientation should not be undefined, since it would cause config.diff
-        // returning 0 and thus the orientation changed would not be detected
-        assertThat(mResources.getConfiguration().orientation).isNotEqualTo(ORIENTATION_UNDEFINED);
-
-        final Configuration config = mResources.getConfiguration();
-        config.orientation = config.orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT
-                : ORIENTATION_LANDSCAPE;
-        final int newRotation = simulateRotateTheDevice();
-
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN, Float.NaN));
-
-        assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
-    }
-
-    @Test
-    public void enableWindowMagnification_registerComponentCallback() {
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN,
-                        Float.NaN));
-
-        verify(mContext).registerComponentCallbacks(mWindowMagnificationController);
-    }
-
-    @Test
-    public void onLocaleChanged_enabled_updateA11yWindowTitle() {
-        final String newA11yWindowTitle = "new a11y window title";
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-        final TestableResources testableResources = getContext().getOrCreateTestableResources();
-        testableResources.addOverride(com.android.internal.R.string.android_system_label,
-                newA11yWindowTitle);
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_LOCALE);
-        });
-
-        assertThat(getAccessibilityWindowTitle()).isEqualTo(newA11yWindowTitle);
-    }
-
-    @Ignore("it's flaky in presubmit but works in abtd, filter for now. b/305654925")
-    @Test
-    public void onSingleTap_enabled_scaleAnimates() {
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.onSingleTap(mSpyView);
-        });
-
-        final View mirrorView = mSurfaceControlViewHost.getView();
-
-        final AtomicDouble maxScaleX = new AtomicDouble();
-        advanceTimeBy(mWaitBounceEffectDuration, /* runnableOnEachRefresh= */ () -> {
-            // For some reason the fancy way doesn't compile...
-            // maxScaleX.getAndAccumulate(mirrorView.getScaleX(), Math::max);
-            final double oldMax = maxScaleX.get();
-            final double newMax = Math.max(mirrorView.getScaleX(), oldMax);
-            assertThat(maxScaleX.compareAndSet(oldMax, newMax)).isTrue();
-        });
-
-        assertThat(maxScaleX.get()).isGreaterThan(1.0);
-    }
-
-    @Test
-    public void moveWindowMagnificationToTheBottom_enabledWithGestureInset_overlapFlagIsTrue() {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        setSystemGestureInsets();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
-                    Float.NaN);
-        });
-
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.moveWindowMagnifier(0, bounds.height());
-        });
-
-        ReferenceTestUtils.waitForCondition(() -> hasMagnificationOverlapFlag());
-    }
-
-    @Test
-    public void moveWindowMagnificationToRightEdge_dragHandleMovesToLeftAndUpdatesTapExcludeRegion()
-            throws RemoteException {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        setSystemGestureInsets();
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.updateWindowMagnificationInternal(
-                            Float.NaN, Float.NaN, Float.NaN);
-                });
-        // Wait for Region updated.
-        waitForIdleSync();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.moveWindowMagnifier(bounds.width(), 0);
-                });
-        // Wait for Region updated.
-        waitForIdleSync();
-
-        AttachedSurfaceControl viewRoot = mSurfaceControlViewHost.getRootSurfaceControl();
-        // Verifying two times in: (1) enable window magnification (2) reposition drag handle
-        verify(viewRoot, times(2)).setTouchableRegion(any());
-
-        View dragButton = getInternalView(R.id.drag_handle);
-        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams();
-        assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.LEFT);
-    }
-
-    @Test
-    public void moveWindowMagnificationToLeftEdge_dragHandleMovesToRightAndUpdatesTapExcludeRegion()
-            throws RemoteException {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        setSystemGestureInsets();
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.updateWindowMagnificationInternal(
-                            Float.NaN, Float.NaN, Float.NaN);
-                });
-        // Wait for Region updated.
-        waitForIdleSync();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.moveWindowMagnifier(-bounds.width(), 0);
-                });
-        // Wait for Region updated.
-        waitForIdleSync();
-
-        AttachedSurfaceControl viewRoot = mSurfaceControlViewHost.getRootSurfaceControl();
-        // Verifying one times in: (1) enable window magnification
-        verify(viewRoot).setTouchableRegion(any());
-
-        View dragButton = getInternalView(R.id.drag_handle);
-        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) dragButton.getLayoutParams();
-        assertThat(params.gravity).isEqualTo(Gravity.BOTTOM | Gravity.RIGHT);
-    }
-
-    @Test
-    public void setMinimumWindowSize_enabled_expectedWindowSize() {
-        final int minimumWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        final int  expectedWindowHeight = minimumWindowSize;
-        final int  expectedWindowWidth = minimumWindowSize;
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN, Float.NaN));
-
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight);
-            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
-            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
-
-        });
-
-        assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
-        assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
-    }
-
-    @Test
-    public void setMinimumWindowSizeThenEnable_expectedWindowSize() {
-        final int minimumWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        final int  expectedWindowHeight = minimumWindowSize;
-        final int  expectedWindowWidth = minimumWindowSize;
-
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.setWindowSize(expectedWindowWidth, expectedWindowHeight);
-            mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                    Float.NaN, Float.NaN);
-            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
-            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
-        });
-
-        assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
-        assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
-    }
-
-    @Test
-    public void setWindowSizeLessThanMin_enabled_minimumWindowSize() {
-        final int minimumWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN, Float.NaN));
-
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.setWindowSize(minimumWindowSize - 10,
-                    minimumWindowSize - 10);
-            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
-            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
-        });
-
-        assertThat(actualWindowHeight.get()).isEqualTo(minimumWindowSize);
-        assertThat(actualWindowWidth.get()).isEqualTo(minimumWindowSize);
-    }
-
-    @Test
-    public void setWindowSizeLargerThanScreenSize_enabled_windowSizeIsScreenSize() {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN, Float.NaN));
-
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.setWindowSize(bounds.width() + 10, bounds.height() + 10);
-            actualWindowHeight.set(mSurfaceControlViewHost.getView().getLayoutParams().height);
-            actualWindowWidth.set(mSurfaceControlViewHost.getView().getLayoutParams().width);
-        });
-
-        assertThat(actualWindowHeight.get()).isEqualTo(bounds.height());
-        assertThat(actualWindowWidth.get()).isEqualTo(bounds.width());
-    }
-
-    @Test
-    public void changeMagnificationSize_expectedWindowSize() {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-
-        final float magnificationScaleLarge = 2.5f;
-        final int initSize = Math.min(bounds.width(), bounds.height()) / 3;
-        final int magnificationSize = (int) (initSize * magnificationScaleLarge)
-                - (int) (initSize * magnificationScaleLarge) % 2;
-
-        final int expectedWindowHeight = magnificationSize;
-        final int expectedWindowWidth = magnificationSize;
-
-        mInstrumentation.runOnMainSync(
-                () ->
-                        mWindowMagnificationController.updateWindowMagnificationInternal(
-                                Float.NaN, Float.NaN, Float.NaN));
-
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.changeMagnificationSize(
-                            WindowMagnificationSettings.MagnificationSize.LARGE);
-                    actualWindowHeight.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().height);
-                    actualWindowWidth.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().width);
-                });
-
-        assertThat(actualWindowHeight.get()).isEqualTo(expectedWindowHeight);
-        assertThat(actualWindowWidth.get()).isEqualTo(expectedWindowWidth);
-    }
-
-    @Test
-    public void editModeOnDragCorner_resizesWindow() {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-
-        final int startingSize = (int) (bounds.width() / 2);
-
-        mInstrumentation.runOnMainSync(
-                () ->
-                        mWindowMagnificationController.updateWindowMagnificationInternal(
-                                Float.NaN, Float.NaN, Float.NaN));
-
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.setWindowSize(startingSize, startingSize);
-                    mWindowMagnificationController.setEditMagnifierSizeMode(true);
-                });
-
-        waitForIdleSync();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController
-                            .onDrag(getInternalView(R.id.bottom_right_corner), 2f, 1f);
-                    actualWindowHeight.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().height);
-                    actualWindowWidth.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().width);
-                });
-
-        assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1);
-        assertThat(actualWindowWidth.get()).isEqualTo(startingSize + 2);
-    }
-
-    @Test
-    public void editModeOnDragEdge_resizesWindowInOnlyOneDirection() {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-
-        final int startingSize = (int) (bounds.width() / 2f);
-
-        mInstrumentation.runOnMainSync(
-                () ->
-                        mWindowMagnificationController.updateWindowMagnificationInternal(
-                                Float.NaN, Float.NaN, Float.NaN));
-
-        final AtomicInteger actualWindowHeight = new AtomicInteger();
-        final AtomicInteger actualWindowWidth = new AtomicInteger();
-
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.setWindowSize(startingSize, startingSize);
-                    mWindowMagnificationController.setEditMagnifierSizeMode(true);
-                    mWindowMagnificationController
-                            .onDrag(getInternalView(R.id.bottom_handle), 2f, 1f);
-                    actualWindowHeight.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().height);
-                    actualWindowWidth.set(
-                            mSurfaceControlViewHost.getView().getLayoutParams().width);
-                });
-        assertThat(actualWindowHeight.get()).isEqualTo(startingSize + 1);
-        assertThat(actualWindowWidth.get()).isEqualTo(startingSize);
-    }
-
-    @Test
-    public void setWindowCenterOutOfScreen_enabled_magnificationCenterIsInsideTheScreen() {
-
-        final int minimumWindowSize = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        mInstrumentation.runOnMainSync(
-                () -> mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
-                        Float.NaN, Float.NaN));
-
-        final AtomicInteger magnificationCenterX = new AtomicInteger();
-        final AtomicInteger magnificationCenterY = new AtomicInteger();
-        mInstrumentation.runOnMainSync(() -> {
-            mWindowMagnificationController.setWindowSizeAndCenter(minimumWindowSize,
-                    minimumWindowSize, bounds.right, bounds.bottom);
-            magnificationCenterX.set((int) mWindowMagnificationController.getCenterX());
-            magnificationCenterY.set((int) mWindowMagnificationController.getCenterY());
-        });
-
-        assertThat(magnificationCenterX.get()).isLessThan(bounds.right);
-        assertThat(magnificationCenterY.get()).isLessThan(bounds.bottom);
-    }
-
-    @Test
-    public void performSingleTap_DragHandle() {
-        final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
-        mInstrumentation.runOnMainSync(
-                () -> {
-                    mWindowMagnificationController.updateWindowMagnificationInternal(
-                            1.5f, bounds.centerX(), bounds.centerY());
-                });
-        View dragButton = getInternalView(R.id.drag_handle);
-
-        // Perform a single-tap
-        final long downTime = SystemClock.uptimeMillis();
-        dragButton.dispatchTouchEvent(
-                obtainMotionEvent(downTime, 0, ACTION_DOWN, 100, 100));
-        dragButton.dispatchTouchEvent(
-                obtainMotionEvent(downTime, downTime, ACTION_UP, 100, 100));
-
-        verify(mSurfaceControlViewHost).setView(any(View.class), any());
-    }
-
-    private <T extends View> T getInternalView(@IdRes int idRes) {
-        View mirrorView = mSurfaceControlViewHost.getView();
-        T view = mirrorView.findViewById(idRes);
-        assertThat(view).isNotNull();
-        return view;
-    }
-
-    private MotionEvent obtainMotionEvent(long downTime, long eventTime, int action, float x,
-            float y) {
-        return mMotionEventHelper.obtainMotionEvent(downTime, eventTime, action, x, y);
-    }
-
-    private String getAccessibilityWindowTitle() {
-        final View mirrorView = mSurfaceControlViewHost.getView();
-        if (mirrorView == null) {
-            return null;
-        }
-        WindowManager.LayoutParams layoutParams =
-                (WindowManager.LayoutParams) mirrorView.getLayoutParams();
-        return layoutParams.accessibilityTitle.toString();
-    }
-
-    private boolean hasMagnificationOverlapFlag() {
-        return (mSysUiState.getFlags() & SYSUI_STATE_MAGNIFICATION_OVERLAP) != 0;
-    }
-
-    private void setSystemGestureInsets() {
-        final WindowInsets testInsets = new WindowInsets.Builder()
-                .setInsets(systemGestures(), Insets.of(0, 0, 0, 10))
-                .build();
-        mWindowManager.setWindowInsets(testInsets);
-    }
-
-    private int updateMirrorSurfaceMarginDimension() {
-        return mContext.getResources().getDimensionPixelSize(
-                R.dimen.magnification_mirror_surface_margin);
-    }
-
-    @Surface.Rotation
-    private int simulateRotateTheDevice() {
-        final Display display = Mockito.spy(mContext.getDisplay());
-        final int currentRotation = display.getRotation();
-        final int newRotation = (currentRotation + 1) % 4;
-        when(display.getRotation()).thenReturn(newRotation);
-        when(mContext.getDisplay()).thenReturn(display);
-        return newRotation;
-    }
-
-    // advance time based on the device frame refresh rate
-    private void advanceTimeBy(long timeDelta) {
-        advanceTimeBy(timeDelta, /* runnableOnEachRefresh= */ null);
-    }
-
-    // advance time based on the device frame refresh rate, and trigger runnable on each refresh
-    private void advanceTimeBy(long timeDelta, @Nullable Runnable runnableOnEachRefresh) {
-        final float frameRate = mContext.getDisplay().getRefreshRate();
-        final int timeSlot = (int) (1000 / frameRate);
-        int round = (int) Math.ceil((double) timeDelta / timeSlot);
-        for (; round >= 0; round--) {
-            mInstrumentation.runOnMainSync(() -> {
-                mAnimatorTestRule.advanceTimeBy(timeSlot);
-                if (runnableOnEachRefresh != null) {
-                    runnableOnEachRefresh.run();
-                }
-            });
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
index 071acfa..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,127 +80,104 @@
             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)
     @get:Rule(order = 2) val animatorTestRule = android.animation.AnimatorTestRule(this)
     @get:Rule(order = 3)
     val motionRule =
-        MotionTestRule(AnimatorTestRuleToolkit(animatorTestRule, kosmos.testScope), pathManager)
+        MotionTestRule(
+            AnimatorTestRuleToolkit(animatorTestRule, kosmos.testScope) { activityRule.scenario },
+            pathManager,
+        )
 
     @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) {
@@ -201,9 +192,13 @@
             sampleIntervalMs = 20L
         }
 
-        var recording: RecordedMotion? = null
-        getInstrumentation().runOnMainSync { recording = record(motionControl, sampleIntervalMs) }
-        return recording!!
+        return motionRule.recordMotion(
+            AnimatorRuleRecordingSpec(backgroundLayer, motionControl, sampleIntervalMs) {
+                feature(DrawableFeatureCaptures.bounds, "bounds")
+                feature(DrawableFeatureCaptures.cornerRadii, "corner_radii")
+                feature(DrawableFeatureCaptures.alpha, "alpha")
+            }
+        )
     }
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index e1b8a1d..91f9cce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -212,6 +212,20 @@
     }
 
     @Test
+    fun testDimissOnLock() {
+        val container = initializeFingerprintContainer(addToView = true)
+        assertThat(container.parent).isNotNull()
+        val root = container.rootView
+
+        // Simulate sleep/lock invocation
+        container.onStartedGoingToSleep()
+        waitForIdleSync()
+
+        assertThat(container.parent).isNull()
+        assertThat(root.isAttachedToWindow).isFalse()
+    }
+
+    @Test
     fun testCredentialPasswordDismissesOnBack() {
         val container = initializeCredentialPasswordContainer(addToView = true)
         assertThat(container.parent).isNotNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
index 9ace8e9..387cc08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
@@ -42,6 +42,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.startable.sceneContainerStartable
 import com.android.systemui.testKosmos
+import kotlin.time.Duration.Companion.seconds
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
@@ -109,7 +110,7 @@
 
     @Test
     fun doubleClick_swapSide() =
-        motionTestRule.runTest {
+        motionTestRule.runTest(timeout = 30.seconds) {
             val motion =
                 recordMotion(
                     content = { BouncerContentUnderTest() },
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
index 088bb02..768f1dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.motion.createSysUiComposeMotionTestRule
 import com.android.systemui.testKosmos
+import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.takeWhile
@@ -71,7 +72,7 @@
 
     @Test
     fun entryAnimation() =
-        motionTestRule.runTest {
+        motionTestRule.runTest(timeout = 30.seconds) {
             val motion =
                 recordMotion(
                     content = { play -> if (play) PatternBouncerUnderTest() },
@@ -89,7 +90,7 @@
 
     @Test
     fun animateFailure() =
-        motionTestRule.runTest {
+        motionTestRule.runTest(timeout = 30.seconds) {
             val failureAnimationMotionControl =
                 MotionControl(
                     delayReadyToPlay = {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index 6061063..5624815 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -20,6 +20,7 @@
 
 import static com.android.systemui.Flags.FLAG_CLIPBOARD_SHARED_TRANSITIONS;
 import static com.android.systemui.Flags.FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE;
+import static com.android.systemui.Flags.FLAG_SHOW_CLIPBOARD_INDICATION;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED;
@@ -121,6 +122,24 @@
 
     private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
 
+    private class FakeClipboardIndicationProvider implements ClipboardIndicationProvider {
+        private ClipboardIndicationCallback mIndicationCallback;
+
+        public void notifyIndicationTextChanged(CharSequence indicationText) {
+            if (mIndicationCallback != null) {
+                mIndicationCallback.onIndicationTextChanged(indicationText);
+            }
+        }
+
+        @Override
+        public void getIndicationText(ClipboardIndicationCallback callback) {
+            mIndicationCallback = callback;
+        }
+    }
+
+    private FakeClipboardIndicationProvider mClipboardIndicationProvider =
+            new FakeClipboardIndicationProvider();
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -156,6 +175,7 @@
                 mExecutor,
                 mClipboardImageLoader,
                 mClipboardTransitionExecutor,
+                mClipboardIndicationProvider,
                 mUiEventLogger);
         verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
         mCallbacks = mOverlayCallbacksCaptor.getValue();
@@ -305,6 +325,17 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SHOW_CLIPBOARD_INDICATION)
+    public void test_onIndicationTextChanged_setIndicationTextCorrectly() {
+        initController();
+        mOverlayController.setClipData(mSampleClipData, "");
+
+        mClipboardIndicationProvider.notifyIndicationTextChanged("copied");
+
+        verify(mClipboardOverlayView).setIndicationText("copied");
+    }
+
+    @Test
     @DisableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
     public void test_viewCallbacks_onShareTapped_sharedTransitionsOff() {
         initController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/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/screenshot/policy/PolicyRequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
index 7709a65..0ab4cd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
@@ -21,7 +21,6 @@
 import android.graphics.Rect
 import android.os.UserHandle
 import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
@@ -76,9 +75,8 @@
 
     /** Tests applying CaptureParameters with 'IsolatedTask' CaptureType */
     @Test
-    @EnableFlags(Flags.FLAG_SCREENSHOT_POLICY_SPLIT_AND_DESKTOP_MODE)
     fun testProcess_newPolicy_isolatedTask() = runTest {
-        val taskImage = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+        val taskImage = Bitmap.createBitmap(200, 100, Bitmap.Config.ARGB_8888)
 
         /* Create a policy request processor with no capture policies */
         val requestProcessor =
@@ -96,9 +94,15 @@
             requestProcessor.modify(
                 screenshotRequest,
                 CaptureParameters(
-                    IsolatedTask(taskId = TASK_ID, taskBounds = null),
-                    ComponentName.unflattenFromString(FILES),
-                    UserHandle.of(WORK),
+                    type = IsolatedTask(taskId = 100, taskBounds = Rect(0, 100, 200, 200)),
+                    contentTask =
+                        TaskReference(
+                            taskId = 1001,
+                            component = ComponentName.unflattenFromString(FILES)!!,
+                            owner = UserHandle.CURRENT,
+                            bounds = Rect(100, 100, 200, 200),
+                        ),
+                    owner = UserHandle.of(WORK),
                 ),
             )
 
@@ -112,14 +116,13 @@
             .that(result.topComponent)
             .isEqualTo(ComponentName.unflattenFromString(FILES))
 
-        assertWithMessage("Task ID").that(result.taskId).isEqualTo(TASK_ID)
+        assertWithMessage("Task ID").that(result.taskId).isEqualTo(1001)
     }
 
     /** Tests applying CaptureParameters with 'FullScreen' CaptureType */
     @Test
-    @EnableFlags(Flags.FLAG_SCREENSHOT_POLICY_SPLIT_AND_DESKTOP_MODE)
     fun testProcess_newPolicy_fullScreen() = runTest {
-        val screenImage = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+        val screenImage = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
 
         /* Create a policy request processor with no capture policies */
         val requestProcessor =
@@ -136,7 +139,17 @@
         val result =
             requestProcessor.modify(
                 screenshotRequest,
-                CaptureParameters(FullScreen(displayId = 0), defaultComponent, defaultOwner),
+                CaptureParameters(
+                    type = FullScreen(displayId = 0),
+                    contentTask =
+                        TaskReference(
+                            taskId = 1234,
+                            component = defaultComponent,
+                            owner = UserHandle.CURRENT,
+                            bounds = Rect(1, 2, 3, 4),
+                        ),
+                    owner = defaultOwner,
+                ),
             )
 
         assertWithMessage("The result bitmap").that(result.bitmap).isSameInstanceAs(screenImage)
@@ -149,7 +162,11 @@
             .that(result.topComponent)
             .isEqualTo(defaultComponent)
 
-        assertWithMessage("Task ID").that(result.taskId).isEqualTo(-1)
+        assertWithMessage("The bounds of the screenshot")
+            .that(result.originalScreenBounds)
+            .isEqualTo(Rect(0, 0, 100, 100))
+
+        assertWithMessage("Task ID").that(result.taskId).isEqualTo(1234)
     }
 
     /** Tests behavior when no policies are applied */
@@ -230,7 +247,7 @@
                 policy = "",
                 reason = "",
                 parameters =
-                    CaptureParameters(
+                    LegacyCaptureParameters(
                         IsolatedTask(taskId = 0, taskBounds = null),
                         null,
                         UserHandle.CURRENT,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index a8929a6..f870200 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.notification.logging;
 
-import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
-
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -59,8 +57,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
 import com.android.systemui.statusbar.notification.logging.nano.Notifications;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
@@ -118,11 +114,6 @@
     private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
     private final PowerInteractor mPowerInteractor =
             PowerInteractorFactory.create().getPowerInteractor();
-    private final ActiveNotificationListRepository mActiveNotificationListRepository =
-            new ActiveNotificationListRepository();
-    private final ActiveNotificationsInteractor mActiveNotificationsInteractor =
-            new ActiveNotificationsInteractor(mActiveNotificationListRepository,
-                    StandardTestDispatcher(null, null));
     private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
     private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
 
@@ -137,7 +128,7 @@
                 mKeyguardRepository,
                 mHeadsUpManager,
                 mPowerInteractor,
-                mActiveNotificationsInteractor,
+                mKosmos.getActiveNotificationsInteractor(),
                 () -> mKosmos.getSceneInteractor());
         mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
deleted file mode 100644
index 625963f..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ /dev/null
@@ -1,584 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification.row;
-
-import static android.app.AppOpsManager.OP_CAMERA;
-import static android.app.AppOpsManager.OP_RECORD_AUDIO;
-import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
-import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
-
-import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
-
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertTrue;
-
-import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.INotificationManager;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.content.Intent;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageManager;
-import android.content.pm.ShortcutManager;
-import android.graphics.Color;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.service.notification.StatusBarNotification;
-import android.testing.TestableLooper;
-import android.util.ArraySet;
-import android.view.View;
-import android.view.accessibility.AccessibilityManager;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.testing.UiEventLoggerFake;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.kosmos.KosmosJavaAdapter;
-import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
-import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository;
-import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
-import com.android.systemui.settings.UserContextProvider;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationPresenter;
-import com.android.systemui.statusbar.notification.AssistantFeedbackController;
-import com.android.systemui.statusbar.notification.NotificationActivityStarter;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
-import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.kotlin.JavaAdapter;
-import com.android.systemui.wmshell.BubblesManager;
-
-import kotlinx.coroutines.test.TestScope;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.Optional;
-
-/**
- * Tests for {@link NotificationGutsManager}.
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@TestableLooper.RunWithLooper
-public class NotificationGutsManagerTest extends SysuiTestCase {
-    private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
-
-    private NotificationChannel mTestNotificationChannel = new NotificationChannel(
-            TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
-
-    private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
-    private final TestScope mTestScope = mKosmos.getTestScope();
-    private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
-    private final FakeExecutor mExecutor = mKosmos.getFakeExecutor();
-    private final Handler mHandler = mKosmos.getFakeExecutorHandler();
-    private NotificationTestHelper mHelper;
-    private NotificationGutsManager mGutsManager;
-
-    @Rule public MockitoRule mockito = MockitoJUnit.rule();
-    @Mock private MetricsLogger mMetricsLogger;
-    @Mock private OnUserInteractionCallback mOnUserInteractionCallback;
-    @Mock private NotificationPresenter mPresenter;
-    @Mock private NotificationActivityStarter mNotificationActivityStarter;
-    @Mock private NotificationListContainer mNotificationListContainer;
-    @Mock private OnSettingsClickListener mOnSettingsClickListener;
-    @Mock private DeviceProvisionedController mDeviceProvisionedController;
-    @Mock private AccessibilityManager mAccessibilityManager;
-    @Mock private HighPriorityProvider mHighPriorityProvider;
-    @Mock private INotificationManager mINotificationManager;
-    @Mock private IStatusBarService mBarService;
-    @Mock private LauncherApps mLauncherApps;
-    @Mock private ShortcutManager mShortcutManager;
-    @Mock private ChannelEditorDialogController mChannelEditorDialogController;
-    @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
-    @Mock private UserContextProvider mContextTracker;
-    @Mock private BubblesManager mBubblesManager;
-    @Mock private ShadeController mShadeController;
-    @Mock private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
-    @Mock private AssistantFeedbackController mAssistantFeedbackController;
-    @Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
-    @Mock private StatusBarStateController mStatusBarStateController;
-    @Mock private HeadsUpManager mHeadsUpManager;
-    @Mock private ActivityStarter mActivityStarter;
-
-    @Mock private UserManager mUserManager;
-
-    private final ActiveNotificationListRepository mActiveNotificationListRepository =
-            new ActiveNotificationListRepository();
-    private final ActiveNotificationsInteractor mActiveNotificationsInteractor =
-            new ActiveNotificationsInteractor(mActiveNotificationListRepository,
-                    StandardTestDispatcher(null, null));
-
-    private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
-
-    @Before
-    public void setUp() {
-        allowTestableLooperAsMainThread();
-        mHelper = new NotificationTestHelper(mContext, mDependency);
-        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
-
-        mWindowRootViewVisibilityInteractor = new WindowRootViewVisibilityInteractor(
-                mTestScope.getBackgroundScope(),
-                new WindowRootViewVisibilityRepository(mBarService, mExecutor),
-                new FakeKeyguardRepository(),
-                mHeadsUpManager,
-                PowerInteractorFactory.create().getPowerInteractor(),
-                mActiveNotificationsInteractor,
-                () -> mKosmos.getSceneInteractor()
-        );
-
-        mGutsManager = new NotificationGutsManager(
-                mContext,
-                mHandler,
-                mHandler,
-                mJavaAdapter,
-                mAccessibilityManager,
-                mHighPriorityProvider,
-                mINotificationManager,
-                mUserManager,
-                mPeopleSpaceWidgetManager,
-                mLauncherApps,
-                mShortcutManager,
-                mChannelEditorDialogController,
-                mContextTracker,
-                mAssistantFeedbackController,
-                Optional.of(mBubblesManager),
-                new UiEventLoggerFake(),
-                mOnUserInteractionCallback,
-                mShadeController,
-                mWindowRootViewVisibilityInteractor,
-                mNotificationLockscreenUserManager,
-                mStatusBarStateController,
-                mBarService,
-                mDeviceProvisionedController,
-                mMetricsLogger,
-                mHeadsUpManager,
-                mActivityStarter);
-        mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
-                mOnSettingsClickListener);
-        mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
-        mGutsManager.start();
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////////////////////
-    // Test methods:
-
-    @Test
-    public void testOpenAndCloseGuts() {
-        NotificationGuts guts = spy(new NotificationGuts(mContext));
-        when(guts.post(any())).thenAnswer(invocation -> {
-            mHandler.post(((Runnable) invocation.getArguments()[0]));
-            return null;
-        });
-
-        // Test doesn't support animation since the guts view is not attached.
-        doNothing().when(guts).openControls(
-                anyInt(),
-                anyInt(),
-                anyBoolean(),
-                any(Runnable.class));
-
-        ExpandableNotificationRow realRow = createTestNotificationRow();
-        NotificationMenuRowPlugin.MenuItem menuItem = createTestMenuItem(realRow);
-
-        ExpandableNotificationRow row = spy(realRow);
-        when(row.getWindowToken()).thenReturn(new Binder());
-        when(row.getGuts()).thenReturn(guts);
-
-        assertTrue(mGutsManager.openGutsInternal(row, 0, 0, menuItem));
-        assertEquals(View.INVISIBLE, guts.getVisibility());
-        mExecutor.runAllReady();
-        verify(guts).openControls(
-                anyInt(),
-                anyInt(),
-                anyBoolean(),
-                any(Runnable.class));
-        verify(mHeadsUpManager).setGutsShown(realRow.getEntry(), true);
-
-        assertEquals(View.VISIBLE, guts.getVisibility());
-        mGutsManager.closeAndSaveGuts(false, false, true, 0, 0, false);
-
-        verify(guts).closeControls(anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean());
-        verify(row, times(1)).setGutsView(any());
-        mExecutor.runAllReady();
-        verify(mHeadsUpManager).setGutsShown(realRow.getEntry(), false);
-    }
-
-    @Test
-    public void testLockscreenShadeVisible_visible_gutsNotClosed() {
-        // First, start out lockscreen or shade as not visible
-        mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false);
-        mTestScope.getTestScheduler().runCurrent();
-
-        NotificationGuts guts = mock(NotificationGuts.class);
-        mGutsManager.setExposedGuts(guts);
-
-        // WHEN the lockscreen or shade becomes visible
-        mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
-        mTestScope.getTestScheduler().runCurrent();
-
-        // THEN the guts are not closed
-        verify(guts, never()).removeCallbacks(any());
-        verify(guts, never()).closeControls(
-                anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean());
-    }
-
-    @Test
-    public void testLockscreenShadeVisible_notVisible_gutsClosed() {
-        // First, start out lockscreen or shade as visible
-        mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
-        mTestScope.getTestScheduler().runCurrent();
-
-        NotificationGuts guts = mock(NotificationGuts.class);
-        mGutsManager.setExposedGuts(guts);
-
-        // WHEN the lockscreen or shade is no longer visible
-        mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false);
-        mTestScope.getTestScheduler().runCurrent();
-
-        // THEN the guts are closed
-        verify(guts).removeCallbacks(any());
-        verify(guts).closeControls(
-                /* leavebehinds= */ eq(true),
-                /* controls= */ eq(true),
-                /* x= */ anyInt(),
-                /* y= */ anyInt(),
-                /* force= */ eq(true));
-    }
-
-    @Test
-    public void testLockscreenShadeVisible_notVisible_listContainerReset() {
-        // First, start out lockscreen or shade as visible
-        mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
-        mTestScope.getTestScheduler().runCurrent();
-
-        // WHEN the lockscreen or shade is no longer visible
-        mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false);
-        mTestScope.getTestScheduler().runCurrent();
-
-        // THEN the list container is reset
-        verify(mNotificationListContainer).resetExposedMenuView(anyBoolean(), anyBoolean());
-    }
-
-    @Test
-    public void testChangeDensityOrFontScale() {
-        NotificationGuts guts = spy(new NotificationGuts(mContext));
-        when(guts.post(any())).thenAnswer(invocation -> {
-            mHandler.post(((Runnable) invocation.getArguments()[0]));
-            return null;
-        });
-
-        // Test doesn't support animation since the guts view is not attached.
-        doNothing().when(guts).openControls(
-                anyInt(),
-                anyInt(),
-                anyBoolean(),
-                any(Runnable.class));
-
-        ExpandableNotificationRow realRow = createTestNotificationRow();
-        NotificationMenuRowPlugin.MenuItem menuItem = createTestMenuItem(realRow);
-
-        ExpandableNotificationRow row = spy(realRow);
-
-        when(row.getWindowToken()).thenReturn(new Binder());
-        when(row.getGuts()).thenReturn(guts);
-        doNothing().when(row).ensureGutsInflated();
-
-        NotificationEntry realEntry = realRow.getEntry();
-        NotificationEntry entry = spy(realEntry);
-
-        when(entry.getRow()).thenReturn(row);
-        when(entry.getGuts()).thenReturn(guts);
-
-        assertTrue(mGutsManager.openGutsInternal(row, 0, 0, menuItem));
-        mExecutor.runAllReady();
-        verify(guts).openControls(
-                anyInt(),
-                anyInt(),
-                anyBoolean(),
-                any(Runnable.class));
-
-        // called once by mGutsManager.bindGuts() in mGutsManager.openGuts()
-        verify(row).setGutsView(any());
-
-        row.onDensityOrFontScaleChanged();
-        mGutsManager.onDensityOrFontScaleChanged(entry);
-
-        mExecutor.runAllReady();
-
-        mGutsManager.closeAndSaveGuts(false, false, false, 0, 0, false);
-
-        verify(guts).closeControls(anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean());
-
-        // called again by mGutsManager.bindGuts(), in mGutsManager.onDensityOrFontScaleChanged()
-        verify(row, times(2)).setGutsView(any());
-    }
-
-    @Test
-    public void testAppOpsSettingsIntent_camera() {
-        ArraySet<Integer> ops = new ArraySet<>();
-        ops.add(OP_CAMERA);
-        mGutsManager.startAppOpsSettingsActivity("", 0, ops, null);
-        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mNotificationActivityStarter, times(1))
-                .startNotificationGutsIntent(captor.capture(), anyInt(), any());
-        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.getValue().getAction());
-    }
-
-    @Test
-    public void testAppOpsSettingsIntent_mic() {
-        ArraySet<Integer> ops = new ArraySet<>();
-        ops.add(OP_RECORD_AUDIO);
-        mGutsManager.startAppOpsSettingsActivity("", 0, ops, null);
-        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mNotificationActivityStarter, times(1))
-                .startNotificationGutsIntent(captor.capture(), anyInt(), any());
-        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.getValue().getAction());
-    }
-
-    @Test
-    public void testAppOpsSettingsIntent_camera_mic() {
-        ArraySet<Integer> ops = new ArraySet<>();
-        ops.add(OP_CAMERA);
-        ops.add(OP_RECORD_AUDIO);
-        mGutsManager.startAppOpsSettingsActivity("", 0, ops, null);
-        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mNotificationActivityStarter, times(1))
-                .startNotificationGutsIntent(captor.capture(), anyInt(), any());
-        assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.getValue().getAction());
-    }
-
-    @Test
-    public void testAppOpsSettingsIntent_overlay() {
-        ArraySet<Integer> ops = new ArraySet<>();
-        ops.add(OP_SYSTEM_ALERT_WINDOW);
-        mGutsManager.startAppOpsSettingsActivity("", 0, ops, null);
-        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mNotificationActivityStarter, times(1))
-                .startNotificationGutsIntent(captor.capture(), anyInt(), any());
-        assertEquals(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION, captor.getValue().getAction());
-    }
-
-    @Test
-    public void testAppOpsSettingsIntent_camera_mic_overlay() {
-        ArraySet<Integer> ops = new ArraySet<>();
-        ops.add(OP_CAMERA);
-        ops.add(OP_RECORD_AUDIO);
-        ops.add(OP_SYSTEM_ALERT_WINDOW);
-        mGutsManager.startAppOpsSettingsActivity("", 0, ops, null);
-        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mNotificationActivityStarter, times(1))
-                .startNotificationGutsIntent(captor.capture(), anyInt(), any());
-        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.getValue().getAction());
-    }
-
-    @Test
-    public void testAppOpsSettingsIntent_camera_overlay() {
-        ArraySet<Integer> ops = new ArraySet<>();
-        ops.add(OP_CAMERA);
-        ops.add(OP_SYSTEM_ALERT_WINDOW);
-        mGutsManager.startAppOpsSettingsActivity("", 0, ops, null);
-        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mNotificationActivityStarter, times(1))
-                .startNotificationGutsIntent(captor.capture(), anyInt(), any());
-        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.getValue().getAction());
-    }
-
-    @Test
-    public void testAppOpsSettingsIntent_mic_overlay() {
-        ArraySet<Integer> ops = new ArraySet<>();
-        ops.add(OP_RECORD_AUDIO);
-        ops.add(OP_SYSTEM_ALERT_WINDOW);
-        mGutsManager.startAppOpsSettingsActivity("", 0, ops, null);
-        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mNotificationActivityStarter, times(1))
-                .startNotificationGutsIntent(captor.capture(), anyInt(), any());
-        assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.getValue().getAction());
-    }
-
-    @Test
-    public void testInitializeNotificationInfoView_highPriority() throws Exception {
-        NotificationInfo notificationInfoView = mock(NotificationInfo.class);
-        ExpandableNotificationRow row = spy(mHelper.createRow());
-        final NotificationEntry entry = row.getEntry();
-        modifyRanking(entry)
-                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
-                .setImportance(IMPORTANCE_HIGH)
-                .build();
-
-        when(row.getIsNonblockable()).thenReturn(false);
-        when(mHighPriorityProvider.isHighPriority(entry)).thenReturn(true);
-        StatusBarNotification statusBarNotification = entry.getSbn();
-        mGutsManager.initializeNotificationInfo(row, notificationInfoView);
-
-        verify(notificationInfoView).bindNotification(
-                any(PackageManager.class),
-                any(INotificationManager.class),
-                eq(mOnUserInteractionCallback),
-                eq(mChannelEditorDialogController),
-                eq(statusBarNotification.getPackageName()),
-                any(NotificationChannel.class),
-                eq(entry),
-                any(NotificationInfo.OnSettingsClickListener.class),
-                any(NotificationInfo.OnAppSettingsClickListener.class),
-                any(UiEventLogger.class),
-                eq(false),
-                eq(false),
-                eq(true), /* wasShownHighPriority */
-                eq(mAssistantFeedbackController),
-                any(MetricsLogger.class));
-    }
-
-    @Test
-    public void testInitializeNotificationInfoView_PassesAlongProvisionedState() throws Exception {
-        NotificationInfo notificationInfoView = mock(NotificationInfo.class);
-        ExpandableNotificationRow row = spy(mHelper.createRow());
-        modifyRanking(row.getEntry())
-                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
-                .build();
-        when(row.getIsNonblockable()).thenReturn(false);
-        StatusBarNotification statusBarNotification = row.getEntry().getSbn();
-        NotificationEntry entry = row.getEntry();
-
-        when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
-
-        mGutsManager.initializeNotificationInfo(row, notificationInfoView);
-
-        verify(notificationInfoView).bindNotification(
-                any(PackageManager.class),
-                any(INotificationManager.class),
-                eq(mOnUserInteractionCallback),
-                eq(mChannelEditorDialogController),
-                eq(statusBarNotification.getPackageName()),
-                any(NotificationChannel.class),
-                eq(entry),
-                any(NotificationInfo.OnSettingsClickListener.class),
-                any(NotificationInfo.OnAppSettingsClickListener.class),
-                any(UiEventLogger.class),
-                eq(true),
-                eq(false),
-                eq(false), /* wasShownHighPriority */
-                eq(mAssistantFeedbackController),
-                any(MetricsLogger.class));
-    }
-
-    @Test
-    public void testInitializeNotificationInfoView_withInitialAction() throws Exception {
-        NotificationInfo notificationInfoView = mock(NotificationInfo.class);
-        ExpandableNotificationRow row = spy(mHelper.createRow());
-        modifyRanking(row.getEntry())
-                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
-                .build();
-        when(row.getIsNonblockable()).thenReturn(false);
-        StatusBarNotification statusBarNotification = row.getEntry().getSbn();
-        NotificationEntry entry = row.getEntry();
-
-        mGutsManager.initializeNotificationInfo(row, notificationInfoView);
-
-        verify(notificationInfoView).bindNotification(
-                any(PackageManager.class),
-                any(INotificationManager.class),
-                eq(mOnUserInteractionCallback),
-                eq(mChannelEditorDialogController),
-                eq(statusBarNotification.getPackageName()),
-                any(NotificationChannel.class),
-                eq(entry),
-                any(NotificationInfo.OnSettingsClickListener.class),
-                any(NotificationInfo.OnAppSettingsClickListener.class),
-                any(UiEventLogger.class),
-                eq(false),
-                eq(false),
-                eq(false), /* wasShownHighPriority */
-                eq(mAssistantFeedbackController),
-                any(MetricsLogger.class));
-    }
-
-    ////////////////////////////////////////////////////////////////////////////////////////////////
-    // Utility methods:
-
-    private ExpandableNotificationRow createTestNotificationRow() {
-        Notification.Builder nb = new Notification.Builder(mContext,
-                mTestNotificationChannel.getId())
-                                        .setContentTitle("foo")
-                                        .setColorized(true).setColor(Color.RED)
-                                        .setFlag(Notification.FLAG_CAN_COLORIZE, true)
-                                        .setSmallIcon(android.R.drawable.sym_def_app_icon);
-
-        try {
-            ExpandableNotificationRow row = mHelper.createRow(nb.build());
-            modifyRanking(row.getEntry())
-                    .setChannel(mTestNotificationChannel)
-                    .build();
-            return row;
-        } catch (Exception e) {
-            fail();
-            return null;
-        }
-    }
-
-    private NotificationMenuRowPlugin.MenuItem createTestMenuItem(ExpandableNotificationRow row) {
-        NotificationMenuRowPlugin menuRow =
-                new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
-        menuRow.createMenu(row, row.getEntry().getSbn());
-
-        NotificationMenuRowPlugin.MenuItem menuItem = menuRow.getLongpressMenuItem(mContext);
-        assertNotNull(menuItem);
-        return menuItem;
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 57fad83..d2350bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -19,6 +19,7 @@
 import static android.view.View.GONE;
 import static android.view.WindowInsets.Type.ime;
 
+import static com.android.systemui.flags.SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.RUBBER_BAND_FACTOR_NORMAL;
@@ -54,6 +55,7 @@
 import android.os.SystemClock;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
 import android.testing.TestableLooper;
 import android.testing.TestableResources;
 import android.util.MathUtils;
@@ -64,13 +66,13 @@
 import android.view.WindowInsetsAnimation;
 import android.widget.TextView;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.ExpandHelper;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.BrokenWithSceneContainer;
 import com.android.systemui.flags.DisableSceneContainer;
 import com.android.systemui.flags.EnableSceneContainer;
 import com.android.systemui.flags.FakeFeatureFlags;
@@ -118,16 +120,25 @@
 import org.mockito.junit.MockitoRule;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.function.Consumer;
 
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
 /**
  * Tests for {@link NotificationStackScrollLayout}.
  */
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
 @TestableLooper.RunWithLooper
 public class NotificationStackScrollLayoutTest extends SysuiTestCase {
 
+    @Parameters(name = "{0}")
+    public static List<FlagsParameterization> getParams() {
+        return parameterizeSceneContainerFlag();
+    }
+
     private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     private NotificationStackScrollLayout mStackScroller;  // Normally test this
     private NotificationStackScrollLayout mStackScrollerInternal;  // See explanation below
@@ -154,6 +165,11 @@
     @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
     @Mock private AvalancheController mAvalancheController;
 
+    public NotificationStackScrollLayoutTest(FlagsParameterization flags) {
+        super();
+        mSetFlagsRule.setFlagsParameterization(flags);
+    }
+
     @Before
     public void setUp() throws Exception {
         allowTestableLooperAsMainThread();
@@ -353,6 +369,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void updateStackEndHeightAndStackHeight_onlyUpdatesStackHeightDuringSwipeUp() {
         final float expansionFraction = 0.5f;
         mAmbientState.setStatusBarState(StatusBarState.KEYGUARD);
@@ -366,6 +383,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void setPanelFlinging_updatesStackEndHeightOnlyOnFinish() {
         final float expansionFraction = 0.5f;
         mAmbientState.setStatusBarState(StatusBarState.KEYGUARD);
@@ -610,6 +628,7 @@
 
     @Test
     @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
+    @DisableSceneContainer
     public void testUpdateFooter_remoteInput() {
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(true);
@@ -643,6 +662,7 @@
 
     @Test
     @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
+    @DisableSceneContainer
     public void testUpdateFooter_oneClearableNotification() {
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(true);
@@ -659,6 +679,7 @@
 
     @Test
     @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
+    @DisableSceneContainer
     public void testUpdateFooter_withoutHistory() {
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(true);
@@ -692,6 +713,7 @@
 
     @Test
     @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
+    @DisableSceneContainer
     public void testUpdateFooter_oneNonClearableNotification() {
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(true);
@@ -725,7 +747,9 @@
     }
 
     @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME, ModesEmptyShadeFix.FLAG_NAME})
+    @DisableFlags({FooterViewRefactor.FLAG_NAME,
+        ModesEmptyShadeFix.FLAG_NAME,
+        NotifRedesignFooter.FLAG_NAME})
     public void testReInflatesFooterViews() {
         when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text);
         clearInvocations(mStackScroller);
@@ -1425,6 +1449,7 @@
 
     @Test
     @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+    @BrokenWithSceneContainer(bugId = 332732878) // because NSSL#mAnimationsEnabled is always true
     public void testGenerateHeadsUpAnimation_isSeenInShade_noAnimation() {
         // GIVEN NSSL is ready for HUN animations
         Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index f472fd1..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
@@ -155,6 +155,7 @@
 import com.android.systemui.shade.ShadeControllerImpl;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shade.ShadeLogger;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
 import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyboardShortcutListSearch;
@@ -174,8 +175,8 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays;
 import com.android.systemui.statusbar.core.StatusBarInitializerImpl;
-import com.android.systemui.statusbar.core.StatusBarOrchestrator;
 import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
+import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
@@ -371,7 +372,7 @@
     @Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
     @Mock private NotificationSettingsInteractor mNotificationSettingsInteractor;
     @Mock private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
-    @Mock private StatusBarOrchestrator mStatusBarOrchestrator;
+    @Mock private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
     private ShadeController mShadeController;
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private final FakeGlobalSettings mFakeGlobalSettings = new FakeGlobalSettings();
@@ -387,6 +388,9 @@
 
     private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor =
             mKosmos.getBrightnessMirrorShowingInteractor();
+
+    private final StatusBarModePerDisplayRepository mStatusBarModePerDisplayRepository =
+            mKosmos.getStatusBarModePerDisplayRepository();
     private ScrimController mScrimController;
 
     @Before
@@ -506,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(
@@ -537,6 +541,7 @@
                 mAutoHideController,
                 new StatusBarInitializerImpl(
                         mStatusBarWindowController,
+                        mStatusBarModePerDisplayRepository,
                         mCollapsedStatusBarFragmentProvider,
                         mock(StatusBarRootFactory.class),
                         mock(HomeStatusBarComponent.Factory.class),
@@ -602,6 +607,7 @@
                 mShadeController,
                 mWindowRootViewVisibilityInteractor,
                 mStatusBarKeyguardViewManager,
+                () -> mStatusBarLongPressGestureDetector,
                 mViewMediatorCallback,
                 mInitController,
                 new Handler(TestableLooper.get(this).getLooper()),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 638f195..69efa87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -40,14 +40,13 @@
 import com.android.systemui.plugins.fakeDarkIconDispatcher
 import com.android.systemui.res.R
 import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.LongPressGestureDetector
 import com.android.systemui.shade.ShadeControllerImpl
 import com.android.systemui.shade.ShadeLogger
 import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.StatusBarLongPressGestureDetector
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.data.repository.fakeStatusBarContentInsetsProviderStore
-import com.android.systemui.statusbar.data.repository.statusBarContentInsetsProviderStore
 import com.android.systemui.statusbar.policy.Clock
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
@@ -98,7 +97,7 @@
     @Mock private lateinit var windowRootView: Provider<WindowRootView>
     @Mock private lateinit var shadeLogger: ShadeLogger
     @Mock private lateinit var viewUtil: ViewUtil
-    @Mock private lateinit var longPressGestureDetector: LongPressGestureDetector
+    @Mock private lateinit var mStatusBarLongPressGestureDetector: StatusBarLongPressGestureDetector
     private lateinit var statusBarWindowStateController: StatusBarWindowStateController
 
     private lateinit var view: PhoneStatusBarView
@@ -395,7 +394,7 @@
                 shadeControllerImpl,
                 shadeViewController,
                 panelExpansionInteractor,
-                { longPressGestureDetector },
+                { mStatusBarLongPressGestureDetector },
                 windowRootView,
                 shadeLogger,
                 viewUtil,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/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/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
index 4d293b9..6326e73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
@@ -102,6 +102,7 @@
                     logBuffer = FakeLogBuffer.Factory.create(),
                     verboseLogBuffer = FakeLogBuffer.Factory.create(),
                     systemClock,
+                    context.resources,
                 )
 
             val connectionState by collectLastValue(underTest.connectionState)
@@ -267,11 +268,7 @@
     fun satelliteProvisioned_notSupported_defaultFalse() =
         testScope.runTest {
             // GIVEN satellite is not supported
-            setUpRepo(
-                uptime = MIN_UPTIME,
-                satMan = satelliteManager,
-                satelliteSupported = false,
-            )
+            setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
 
             assertThat(underTest.isSatelliteProvisioned.value).isFalse()
         }
@@ -280,11 +277,7 @@
     fun satelliteProvisioned_supported_defaultFalse() =
         testScope.runTest {
             // GIVEN satellite is supported
-            setUpRepo(
-                uptime = MIN_UPTIME,
-                satMan = satelliteManager,
-                satelliteSupported = true,
-            )
+            setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = true)
 
             // THEN default provisioned state is false
             assertThat(underTest.isSatelliteProvisioned.value).isFalse()
@@ -323,6 +316,7 @@
                     logBuffer = FakeLogBuffer.Factory.create(),
                     verboseLogBuffer = FakeLogBuffer.Factory.create(),
                     systemClock,
+                    context.resources,
                 )
 
             // WHEN we try to check for provisioned status
@@ -361,6 +355,7 @@
                     logBuffer = FakeLogBuffer.Factory.create(),
                     verboseLogBuffer = FakeLogBuffer.Factory.create(),
                     systemClock,
+                    context.resources,
                 )
 
             // WHEN we try to check for provisioned status
@@ -445,11 +440,7 @@
     fun satelliteProvisioned_supported_tracksCallback_reRegistersOnCrash() =
         testScope.runTest {
             // GIVEN satellite is supported
-            setUpRepo(
-                uptime = MIN_UPTIME,
-                satMan = satelliteManager,
-                satelliteSupported = true,
-            )
+            setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = true)
 
             val provisioned by collectLastValue(underTest.isSatelliteProvisioned)
 
@@ -487,11 +478,7 @@
     fun satelliteNotSupported_listenersAreNotRegistered() =
         testScope.runTest {
             // GIVEN satellite is not supported
-            setUpRepo(
-                uptime = MIN_UPTIME,
-                satMan = satelliteManager,
-                satelliteSupported = false,
-            )
+            setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
 
             // WHEN data is requested from the repo
             val connectionState by collectLastValue(underTest.connectionState)
@@ -517,11 +504,7 @@
     fun satelliteNotSupported_registersCallbackForStateChanges() =
         testScope.runTest {
             // GIVEN satellite is not supported
-            setUpRepo(
-                uptime = MIN_UPTIME,
-                satMan = satelliteManager,
-                satelliteSupported = false,
-            )
+            setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
 
             runCurrent()
             // THEN the repo registers for state changes of satellite support
@@ -577,11 +560,7 @@
     fun satelliteNotSupported_supportShowsUp_registersListeners() =
         testScope.runTest {
             // GIVEN satellite is not supported
-            setUpRepo(
-                uptime = MIN_UPTIME,
-                satMan = satelliteManager,
-                satelliteSupported = false,
-            )
+            setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
             runCurrent()
 
             val callback =
@@ -610,11 +589,7 @@
     fun repoDoesNotCheckForSupportUntilMinUptime() =
         testScope.runTest {
             // GIVEN we init 100ms after sysui starts up
-            setUpRepo(
-                uptime = 100,
-                satMan = satelliteManager,
-                satelliteSupported = true,
-            )
+            setUpRepo(uptime = 100, satMan = satelliteManager, satelliteSupported = true)
 
             // WHEN data is requested
             val connectionState by collectLastValue(underTest.connectionState)
@@ -726,6 +701,7 @@
                 logBuffer = FakeLogBuffer.Factory.create(),
                 verboseLogBuffer = FakeLogBuffer.Factory.create(),
                 systemClock,
+                context.resources,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index c4c2aa7..48106de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -2533,6 +2533,42 @@
         verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_DISMISSED_DRAG_BAR);
     }
 
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
+    public void testEventLogging_bubbleBar_expandAndCollapse() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        mEntryListener.onEntryAdded(mRow);
+        mBubbleController.expandStackAndSelectBubbleFromLauncher(mRow.getKey(), 0);
+
+        verify(mBubbleLogger).log(eqBubbleWithKey(mRow.getKey()),
+                eq(BubbleLogger.Event.BUBBLE_BAR_EXPANDED));
+
+        mBubbleController.collapseStack();
+
+        verify(mBubbleLogger).log(eqBubbleWithKey(mRow.getKey()),
+                eq(BubbleLogger.Event.BUBBLE_BAR_COLLAPSED));
+    }
+
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
+    public void testEventLogging_bubbleBar_autoExpandingBubble() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        setMetadataFlags(mRow,
+                Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */);
+        mEntryListener.onEntryAdded(mRow);
+
+        verify(mBubbleLogger).log(eqBubbleWithKey(mRow.getKey()),
+                eq(BubbleLogger.Event.BUBBLE_BAR_EXPANDED));
+    }
+
     /** Creates a bubble using the userId and package. */
     private Bubble createBubble(int userId, String pkg) {
         final UserHandle userHandle = new UserHandle(userId);
diff --git a/packages/SystemUI/tests/utils/src/android/view/FakeWindowManager.kt b/packages/SystemUI/tests/utils/src/android/view/FakeWindowManager.kt
new file mode 100644
index 0000000..c28449f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/view/FakeWindowManager.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view
+
+import android.content.Context
+import android.graphics.Region
+import android.view.WindowManager.LayoutParams
+
+class FakeWindowManager(private val context: Context) : WindowManager {
+
+    val addedViews = mutableMapOf<View, LayoutParams>()
+
+    override fun addView(view: View, params: ViewGroup.LayoutParams) {
+        addedViews[view] = params as LayoutParams
+    }
+
+    override fun removeView(view: View) {
+        addedViews.remove(view)
+    }
+
+    override fun updateViewLayout(view: View, params: ViewGroup.LayoutParams) {
+        addedViews[view] = params as LayoutParams
+    }
+
+    override fun getApplicationLaunchKeyboardShortcuts(deviceId: Int): KeyboardShortcutGroup {
+        return KeyboardShortcutGroup("Fake group")
+    }
+
+    override fun getCurrentImeTouchRegion(): Region {
+        return Region.obtain()
+    }
+
+    override fun getDefaultDisplay(): Display {
+        return context.display
+    }
+
+    override fun removeViewImmediate(view: View) {
+        addedViews.remove(view)
+    }
+
+    override fun requestAppKeyboardShortcuts(
+        receiver: WindowManager.KeyboardShortcutsReceiver,
+        deviceId: Int,
+    ) {
+        receiver.onKeyboardShortcutsReceived(emptyList())
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
index d5451ee..025f556 100644
--- a/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
@@ -16,9 +16,12 @@
 
 package android.view
 
+import android.content.applicationContext
 import com.android.systemui.kosmos.Kosmos
 import org.mockito.Mockito.mock
 
+val Kosmos.fakeWindowManager by Kosmos.Fixture { FakeWindowManager(applicationContext) }
+
 val Kosmos.mockWindowManager: WindowManager by Kosmos.Fixture { mock(WindowManager::class.java) }
 
 var Kosmos.windowManager: WindowManager by Kosmos.Fixture { mockWindowManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt
index e1c6699..021c7bb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt
@@ -16,11 +16,21 @@
 
 package com.android.app.viewcapture
 
+import android.view.fakeWindowManager
 import com.android.systemui.kosmos.Kosmos
 import org.mockito.kotlin.mock
 
 val Kosmos.mockViewCaptureAwareWindowManager by
     Kosmos.Fixture { mock<ViewCaptureAwareWindowManager>() }
 
+val Kosmos.realCaptureAwareWindowManager by
+    Kosmos.Fixture {
+        ViewCaptureAwareWindowManager(
+            fakeWindowManager,
+            lazyViewCapture = lazy { mock<ViewCapture>() },
+            isViewCaptureEnabled = false,
+        )
+    }
+
 var Kosmos.viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager by
     Kosmos.Fixture { mockViewCaptureAwareWindowManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt
new file mode 100644
index 0000000..778614b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+import org.junit.runners.model.MultipleFailureException
+
+/**
+ * Rule that allows teardown steps to be added right next to the places where it becomes clear they
+ * are needed. This can avoid the need for complicated or conditional logic in a single teardown
+ * method. Examples:
+ * ```
+ * @get:Rule teardownRule = OnTeardownRule()
+ *
+ * // setup and teardown right next to each other
+ * @Before
+ * fun setUp() {
+ *   val oldTimeout = getGlobalTimeout()
+ *   teardownRule.onTeardown { setGlobalTimeout(oldTimeout) }
+ *   overrideGlobalTimeout(5000)
+ * }
+ *
+ * // add teardown logic for fixtures that aren't used in every test
+ * fun addCustomer() {
+ *   val id = globalDatabase.addCustomer(TEST_NAME, TEST_ADDRESS, ...)
+ *   teardownRule.onTeardown { globalDatabase.deleteCustomer(id) }
+ * }
+ * ```
+ */
+class OnTeardownRule : TestWatcher() {
+    private var canAdd = true
+    private val teardowns = mutableListOf<() -> Unit>()
+
+    fun onTeardown(teardownRunnable: () -> Unit) {
+        if (!canAdd) {
+            throw IllegalStateException("Cannot add new teardown routines after test complete.")
+        }
+        teardowns.add(teardownRunnable)
+    }
+
+    fun onTeardown(teardownRunnable: Runnable) {
+        if (!canAdd) {
+            throw IllegalStateException("Cannot add new teardown routines after test complete.")
+        }
+        teardowns.add { teardownRunnable.run() }
+    }
+
+    override fun finished(description: Description?) {
+        canAdd = false
+        val errors = mutableListOf<Throwable>()
+        teardowns.reversed().forEach {
+            try {
+                it()
+            } catch (e: Throwable) {
+                errors.add(e)
+            }
+        }
+        MultipleFailureException.assertEmpty(errors)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 27a2cab..153a8be 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -56,6 +56,8 @@
 
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Future;
@@ -69,6 +71,17 @@
 // background on Ravenwood is available at go/ravenwood-docs
 @DisabledOnRavenwood
 public abstract class SysuiTestCase {
+    /**
+     * Especially when self-testing test utilities, we may have classes that look like test
+     * classes, but we don't expect to ever actually run as a top-level test.
+     * For example, {@link com.android.systemui.TryToDoABadThing}.
+     * Verifying properties on these as a part of structural tests like
+     * AAAPlusPlusVerifySysuiRequiredTestPropertiesTest is a waste of our time, and makes things
+     * look more confusing, so this lets us skip when appropriate.
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface SkipSysuiVerification {
+    }
 
     private static final String TAG = "SysuiTestCase";
 
@@ -172,6 +185,15 @@
     public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
             new DexmakerShareClassLoaderRule();
 
+    @Rule public final OnTeardownRule mTearDownRule = new OnTeardownRule();
+
+    /**
+     * Schedule a cleanup routine to happen when the test state is torn down.
+     */
+    protected void onTeardown(Runnable tearDownRunnable) {
+        mTearDownRule.onTeardown(tearDownRunnable);
+    }
+
     // set the highest order so it's the innermost rule
     @Rule(order = Integer.MAX_VALUE)
     public TestWithLooperRule mlooperRule = new TestWithLooperRule();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
index 3041240..b8be6aa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
@@ -29,6 +29,8 @@
 import android.util.Log;
 import android.view.Display;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.systemui.res.R;
 
@@ -43,6 +45,9 @@
     private final Map<UserHandle, Context> mContextForUser = new HashMap<>();
     private final Map<String, Context> mContextForPackage = new HashMap<>();
 
+    @Nullable
+    private Display mCustomDisplay;
+
     public SysuiTestableContext(Context base) {
         super(base);
         setTheme(R.style.Theme_SystemUI);
@@ -64,6 +69,18 @@
         return context;
     }
 
+    public void setDisplay(Display display) {
+        mCustomDisplay = display;
+    }
+
+    @Override
+    public Display getDisplay() {
+        if (mCustomDisplay != null) {
+            return mCustomDisplay;
+        }
+        return super.getDisplay();
+    }
+
     public SysuiTestableContext createDefaultDisplayContext() {
         Display display = getBaseContext().getSystemService(DisplayManager.class).getDisplays()[0];
         return (SysuiTestableContext) createDisplayContext(display);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorKosmos.kt
index 7e0e5f3..f876003 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorKosmos.kt
@@ -20,4 +20,4 @@
 import com.android.systemui.kosmos.Kosmos
 
 var Kosmos.configurationInteractor: ConfigurationInteractor by
-    Kosmos.Fixture { ConfigurationInteractor(configurationRepository) }
+    Kosmos.Fixture { ConfigurationInteractorImpl(configurationRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index 62d221d..3b175853de7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -40,8 +40,9 @@
     ) {
         coroutineScope.launch {
             val id = nextWidgetId++
-            val providerInfo = AppWidgetProviderInfo().apply { this.provider = provider }
-            val configured = configurator?.configureWidget(id) ?: true
+            val providerInfo = createAppWidgetProviderInfo(provider, user.identifier)
+
+            val configured = configurator?.configureWidget(id) != false
             if (configured) {
                 onConfigured(id, providerInfo, rank ?: -1)
             }
@@ -61,20 +62,15 @@
                 appWidgetId = appWidgetId,
                 rank = rank,
                 providerInfo =
-                    AppWidgetProviderInfo().apply {
-                        provider = ComponentName.unflattenFromString(componentName)!!
-                        widgetCategory = category
-                        providerInfo =
-                            ActivityInfo().apply {
-                                applicationInfo =
-                                    ApplicationInfo().apply {
-                                        uid = userId * UserHandle.PER_USER_RANGE
-                                    }
-                            }
-                    },
+                    createAppWidgetProviderInfo(
+                        ComponentName.unflattenFromString(componentName)!!,
+                        userId,
+                        category,
+                    ),
                 spanY = spanY,
             )
         updateListFromDatabase()
+        nextWidgetId = appWidgetId + 1
     }
 
     fun addPendingWidget(
@@ -151,4 +147,20 @@
             )
         updateListFromDatabase()
     }
+
+    private fun createAppWidgetProviderInfo(
+        componentName: ComponentName,
+        userId: Int,
+        category: Int = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
+    ): AppWidgetProviderInfo {
+        return AppWidgetProviderInfo().apply {
+            provider = componentName
+            widgetCategory = category
+            providerInfo =
+                ActivityInfo().apply {
+                    applicationInfo =
+                        ApplicationInfo().apply { uid = userId * UserHandle.PER_USER_RANGE }
+                }
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 629fda6..1f68195 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -54,7 +54,6 @@
         keyguardInteractor = keyguardInteractor,
         keyguardTransitionInteractor = keyguardTransitionInteractor,
         communalSettingsInteractor = communalSettingsInteractor,
-        appWidgetHost = mock(),
         editWidgetsActivityStarter = editWidgetsActivityStarter,
         userTracker = userTracker,
         activityStarter = activityStarter,
@@ -62,7 +61,7 @@
         sceneInteractor = sceneInteractor,
         logBuffer = logcatLogBuffer("CommunalInteractor"),
         tableLogBuffer = mock(),
-        managedProfileController = fakeManagedProfileController
+        managedProfileController = fakeManagedProfileController,
     )
 }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/model/FakeGlanceableHubMultiUserHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/model/FakeGlanceableHubMultiUserHelper.kt
new file mode 100644
index 0000000..de44399
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/model/FakeGlanceableHubMultiUserHelper.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.communal.shared.model
+
+class FakeGlanceableHubMultiUserHelper(
+    override val glanceableHubHsumFlagEnabled: Boolean = true,
+    private var isHeadlessSystemUserMode: Boolean = false,
+    private var isInHeadlessSystemUser: Boolean = false,
+) : GlanceableHubMultiUserHelper {
+
+    override fun isHeadlessSystemUserMode(): Boolean {
+        return isHeadlessSystemUserMode
+    }
+
+    fun setIsHeadlessSystemUserMode(isHeadlessSystemUserMode: Boolean) {
+        this.isHeadlessSystemUserMode = isHeadlessSystemUserMode
+    }
+
+    override fun isInHeadlessSystemUser(): Boolean {
+        return isInHeadlessSystemUser
+    }
+
+    fun setIsInHeadlessSystemUser(isInHeadlessSystemUser: Boolean) {
+        this.isInHeadlessSystemUser = isInHeadlessSystemUser
+    }
+
+    override fun assertInHeadlessSystemUser() {
+        check(isInHeadlessSystemUser())
+    }
+
+    override fun assertNotInHeadlessSystemUser() {
+        check(!isInHeadlessSystemUser())
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/model/GlanceableHubMultiUserHelperKosmos.kt
similarity index 69%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/model/GlanceableHubMultiUserHelperKosmos.kt
index 94b2bdf..adee440 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/model/GlanceableHubMultiUserHelperKosmos.kt
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.communal.shared.model
 
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+val Kosmos.fakeGlanceableHubMultiUserHelper by Kosmos.Fixture { FakeGlanceableHubMultiUserHelper() }
+
+val Kosmos.glanceableHubMultiUserHelper by
+    Kosmos.Fixture<GlanceableHubMultiUserHelper> { fakeGlanceableHubMultiUserHelper }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerKosmos.kt
similarity index 78%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerKosmos.kt
index 94b2bdf..583ae44 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerKosmos.kt
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.communal.widgets
 
 import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+val Kosmos.mockGlanceableHubWidgetManager by Kosmos.Fixture<GlanceableHubWidgetManager> { mock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryKosmos.kt
similarity index 73%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryKosmos.kt
index 94b2bdf..4bcff55 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryKosmos.kt
@@ -14,9 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.decor
 
+import android.content.testableContext
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+var Kosmos.privacyDotDecorProviderFactory by
+    Kosmos.Fixture {
+        PrivacyDotDecorProviderFactory(testableContext.orCreateTestableResources.resources)
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index 8c4ec4c..4a6e273 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -19,7 +19,7 @@
 
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -77,7 +77,7 @@
                 repository = repository,
                 powerInteractor = powerInteractor,
                 bouncerRepository = bouncerRepository,
-                configurationInteractor = ConfigurationInteractor(configurationRepository),
+                configurationInteractor = ConfigurationInteractorImpl(configurationRepository),
                 shadeRepository = shadeRepository,
                 keyguardTransitionInteractor = keyguardTransitionInteractor,
                 sceneInteractorProvider = { sceneInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index ddae581..72cb1df 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -1,8 +1,10 @@
 package com.android.systemui.kosmos
 
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos.Fixture
 import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -45,3 +47,5 @@
     testScope.runTest { this@runTest.testBody() }
 
 fun Kosmos.runCurrent() = testScope.runCurrent()
+
+fun <T> Kosmos.collectLastValue(flow: Flow<T>) = testScope.collectLastValue(flow)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 5eaa198..63e6eb6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -51,6 +51,8 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.pulseExpansionInteractor
+import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
 import com.android.systemui.model.sceneContainerPlugin
 import com.android.systemui.plugins.statusbar.statusBarStateController
 import com.android.systemui.power.data.repository.fakePowerRepository
@@ -68,6 +70,8 @@
 import com.android.systemui.shade.shadeController
 import com.android.systemui.shade.ui.viewmodel.notificationShadeWindowModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
@@ -106,6 +110,7 @@
     val bouncerRepository by lazy { kosmos.bouncerRepository }
     val communalRepository by lazy { kosmos.fakeCommunalSceneRepository }
     val communalTransitionViewModel by lazy { kosmos.communalTransitionViewModel }
+    val activeNotificationsInteractor by lazy { kosmos.activeNotificationsInteractor }
     val headsUpNotificationInteractor by lazy { kosmos.headsUpNotificationInteractor }
     val seenNotificationsInteractor by lazy { kosmos.seenNotificationsInteractor }
     val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
@@ -119,6 +124,7 @@
     val mobileConnectionsRepository by lazy { kosmos.fakeMobileConnectionsRepository }
     val simBouncerInteractor by lazy { kosmos.simBouncerInteractor }
     val statusBarStateController by lazy { kosmos.statusBarStateController }
+    val statusBarModePerDisplayRepository by lazy { kosmos.fakeStatusBarModePerDisplayRepository }
     val interactionJankMonitor by lazy { kosmos.interactionJankMonitor }
     val fakeSceneContainerConfig by lazy { kosmos.sceneContainerConfig }
     val sceneInteractor by lazy { kosmos.sceneInteractor }
@@ -164,4 +170,11 @@
     val msdlPlayer by lazy { kosmos.fakeMSDLPlayer }
     val shadeModeInteractor by lazy { kosmos.shadeModeInteractor }
     val bouncerHapticHelper by lazy { kosmos.bouncerHapticPlayer }
+
+    val glanceableHubToLockscreenTransitionViewModel by lazy {
+        kosmos.glanceableHubToLockscreenTransitionViewModel
+    }
+    val lockscreenToGlanceableHubTransitionViewModel by lazy {
+        kosmos.lockscreenToGlanceableHubTransitionViewModel
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt
new file mode 100644
index 0000000..f49e377
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline
+
+import android.content.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
+
+/**
+ * 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/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
index cb7750f5..af6a0c5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
@@ -34,6 +34,7 @@
             fakeMediaControllerFactory,
             mediaFlags,
             imageLoader,
-            statusBarManager
+            statusBarManager,
+            media3ActionFactory,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
index 7f8348e..b833750 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
@@ -18,21 +18,32 @@
 
 import android.content.Context
 import android.media.session.MediaController
-import android.media.session.MediaSession
 import android.media.session.MediaSession.Token
+import android.os.Looper
+import androidx.media3.session.MediaController as Media3Controller
+import androidx.media3.session.SessionToken
 
 class FakeMediaControllerFactory(context: Context) : MediaControllerFactory(context) {
 
     private val mediaControllersForToken = mutableMapOf<Token, MediaController>()
+    private var media3Controller: Media3Controller? = null
 
-    override fun create(token: MediaSession.Token): android.media.session.MediaController {
+    override fun create(token: Token): MediaController {
         if (token !in mediaControllersForToken) {
             super.create(token)
         }
         return mediaControllersForToken[token]!!
     }
 
+    override suspend fun create(token: SessionToken, looper: Looper): Media3Controller {
+        return media3Controller ?: super.create(token, looper)
+    }
+
     fun setControllerForToken(token: Token, mediaController: MediaController) {
         mediaControllersForToken[token] = mediaController
     }
+
+    fun setMedia3Controller(mediaController: Media3Controller) {
+        media3Controller = mediaController
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeSessionTokenFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeSessionTokenFactory.kt
new file mode 100644
index 0000000..94e0bca
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeSessionTokenFactory.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaSession.Token
+import androidx.media3.session.SessionToken
+
+class FakeSessionTokenFactory(context: Context) : SessionTokenFactory(context) {
+    private var sessionToken: SessionToken? = null
+
+    override suspend fun createTokenFromLegacy(token: Token): SessionToken {
+        return sessionToken ?: super.createTokenFromLegacy(token)
+    }
+
+    fun setMedia3SessionToken(token: SessionToken) {
+        sessionToken = token
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/SessionTokenFactoryKosmos.kt
similarity index 77%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/SessionTokenFactoryKosmos.kt
index 94b2bdf..8e473042 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/SessionTokenFactoryKosmos.kt
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.media.controls.util
 
+import android.content.applicationContext
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+val Kosmos.fakeSessionTokenFactory by Kosmos.Fixture { FakeSessionTokenFactory(applicationContext) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/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/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
index f66125a..6787b8e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
@@ -52,7 +52,7 @@
                     )
                 object : QSTileViewModel {
                     override val state: StateFlow<QSTileState?> =
-                        MutableStateFlow(QSTileState.build({ null }, tileSpec.spec) {})
+                        MutableStateFlow(QSTileState.build(null, tileSpec.spec) {})
                     override val config: QSTileConfig = config
                     override val isAvailable: StateFlow<Boolean> = MutableStateFlow(true)
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
index 5b6fd8c..ab1c181 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
@@ -44,7 +44,7 @@
             check("other").that(other).isNotNull()
             other ?: return
         }
-        check("icon").that(actual.icon()).isEqualTo(other.icon())
+        check("icon").that(actual.icon).isEqualTo(other.icon)
         check("iconRes").that(actual.iconRes).isEqualTo(other.iconRes)
         check("label").that(actual.label).isEqualTo(other.label)
         check("activationState").that(actual.activationState).isEqualTo(other.activationState)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/notes/NotesTileKosmos.kt
similarity index 71%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/notes/NotesTileKosmos.kt
index 94b2bdf..75c98cb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/notes/NotesTileKosmos.kt
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.qs.tiles.impl.notes
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.notetask.NoteTaskModule
+import com.android.systemui.qs.qsEventLogger
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+val Kosmos.qsNotesTileConfig by
+    Kosmos.Fixture { NoteTaskModule.provideNotesTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index 4f414d9..3300c96 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -17,6 +17,7 @@
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
 import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector
 import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
 import kotlinx.coroutines.flow.MutableStateFlow
 import org.mockito.kotlin.mock
 
@@ -87,6 +88,7 @@
                 falsingInteractor = falsingInteractor,
                 powerInteractor = powerInteractor,
                 shadeInteractor = shadeInteractor,
+                remoteInputInteractor = remoteInputInteractor,
                 splitEdgeDetector = splitEdgeDetector,
                 logger = sceneLogger,
                 hapticsViewModelFactory = sceneContainerHapticsViewModelFactory,
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/FakeStatusBarInitializerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt
index 8c218be..50a19a9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt
@@ -16,11 +16,13 @@
 
 package com.android.systemui.statusbar.core
 
+import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository
 import com.android.systemui.statusbar.window.StatusBarWindowController
 
 class FakeStatusBarInitializerFactory() : StatusBarInitializer.Factory {
 
     override fun create(
-        statusBarWindowController: StatusBarWindowController
+        statusBarWindowController: StatusBarWindowController,
+        statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository,
     ): StatusBarInitializer = FakeStatusBarInitializer()
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
index 303529b..6e99027 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.display.data.repository.displayRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
 import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore
 
 val Kosmos.fakeStatusBarInitializer by Kosmos.Fixture { FakeStatusBarInitializer() }
@@ -37,6 +38,7 @@
             displayRepository,
             fakeStatusBarInitializerFactory,
             fakeStatusBarWindowControllerStore,
+            fakeStatusBarModeRepository,
         )
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
index 87f7142..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,8 @@
 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
 import com.android.systemui.statusbar.phone.mockAutoHideController
@@ -77,5 +79,7 @@
             statusBarInitializerStore,
             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/FakePrivacyDotViewControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotViewControllerStore.kt
new file mode 100644
index 0000000..27845aa
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotViewControllerStore.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import android.view.Display
+import com.android.systemui.statusbar.events.PrivacyDotViewController
+import org.mockito.kotlin.mock
+
+class FakePrivacyDotViewControllerStore : PrivacyDotViewControllerStore {
+    private val perDisplayMockControllers = mutableMapOf<Int, PrivacyDotViewController>()
+
+    override val defaultDisplay: PrivacyDotViewController
+        get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+    override fun forDisplay(displayId: Int): PrivacyDotViewController {
+        return perDisplayMockControllers.computeIfAbsent(displayId) { mock() }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotWindowControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotWindowControllerStore.kt
new file mode 100644
index 0000000..f0aacc0d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotWindowControllerStore.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.events.PrivacyDotWindowController
+import org.mockito.kotlin.mock
+
+class FakePrivacyDotWindowControllerStore : PrivacyDotWindowControllerStore {
+
+    private val perDisplayMockControllers = mutableMapOf<Int, PrivacyDotWindowController>()
+
+    override val defaultDisplay: PrivacyDotWindowController
+        get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+    override fun forDisplay(displayId: Int): PrivacyDotWindowController {
+        return perDisplayMockControllers.computeIfAbsent(displayId) { mock() }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
index 91602c2..375bede 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
@@ -23,6 +23,11 @@
 class FakeRemoteInputRepository : RemoteInputRepository {
     override val isRemoteInputActive = MutableStateFlow(false)
     override val remoteInputRowBottomBound: Flow<Float?> = flowOf(null)
+    var areRemoteInputsClosed: Boolean = false
 
     override fun setRemoteInputRowBottomBound(bottom: Float?) {}
+
+    override fun closeRemoteInputs() {
+        areRemoteInputsClosed = true
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
index 285cebb..8712b02 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
@@ -20,8 +20,10 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.data.model.StatusBarAppearance
 import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
 import dagger.Binds
 import dagger.Module
+import java.io.PrintWriter
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 
@@ -53,6 +55,14 @@
     override fun clearTransient() {
         isTransientShown.value = false
     }
+
+    override fun start() {}
+
+    override fun stop() {}
+
+    override fun onStatusBarViewInitialized(component: HomeStatusBarComponent) {}
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {}
 }
 
 @Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/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/FakeSystemEventChipAnimationControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeSystemEventChipAnimationControllerStore.kt
new file mode 100644
index 0000000..fa9f1bf
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeSystemEventChipAnimationControllerStore.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.events.SystemEventChipAnimationController
+import org.mockito.kotlin.mock
+
+class FakeSystemEventChipAnimationControllerStore : SystemEventChipAnimationControllerStore {
+
+    private val perDisplayMocks = mutableMapOf<Int, SystemEventChipAnimationController>()
+
+    override val defaultDisplay: SystemEventChipAnimationController
+        get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+    override fun forDisplay(displayId: Int): SystemEventChipAnimationController {
+        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
new file mode 100644
index 0000000..13fa3fe
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreKosmos.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.statusbar.data.repository
+
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.displayScopeRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import org.mockito.kotlin.mock
+
+val Kosmos.lightBarControllerStoreImpl by
+    Kosmos.Fixture {
+        LightBarControllerStoreImpl(
+            backgroundApplicationScope = applicationCoroutineScope,
+            displayRepository = displayRepository,
+            factory = { _, _, _, _ -> mock() },
+            displayScopeRepository = displayScopeRepository,
+            statusBarModeRepositoryStore = statusBarModeRepository,
+            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/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStoreKosmos.kt
similarity index 68%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStoreKosmos.kt
index 94b2bdf..3d428a1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStoreKosmos.kt
@@ -14,9 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.statusbar.data.repository
 
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+val Kosmos.fakePrivacyDotViewControllerStore by
+    Kosmos.Fixture { FakePrivacyDotViewControllerStore() }
+
+var Kosmos.privacyDotViewControllerStore: PrivacyDotViewControllerStore by
+    Kosmos.Fixture { fakePrivacyDotViewControllerStore }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt
new file mode 100644
index 0000000..aae32cfa
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt
@@ -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.
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import android.view.WindowManager
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+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.fakePrivacyDotWindowControllerStore by
+    Kosmos.Fixture { FakePrivacyDotWindowControllerStore() }
+
+val Kosmos.privacyDotWindowControllerStoreImpl by
+    Kosmos.Fixture {
+        PrivacyDotWindowControllerStoreImpl(
+            backgroundApplicationScope = applicationCoroutineScope,
+            displayRepository = displayRepository,
+            windowControllerFactory = { _, _, _, _ -> mock() },
+            displayWindowPropertiesRepository = displayWindowPropertiesRepository,
+            privacyDotViewControllerStore = privacyDotViewControllerStore,
+            viewCaptureAwareWindowManagerFactory =
+                object : ViewCaptureAwareWindowManager.Factory {
+                    override fun create(
+                        windowManager: WindowManager
+                    ): ViewCaptureAwareWindowManager {
+                        return mock()
+                    }
+                },
+        )
+    }
+
+var Kosmos.privacyDotWindowControllerStore: PrivacyDotWindowControllerStore by
+    Kosmos.Fixture { fakePrivacyDotWindowControllerStore }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt
index 12db2f741..a585602 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt
@@ -16,7 +16,10 @@
 
 package com.android.systemui.statusbar.data.repository
 
+import com.android.systemui.display.data.repository.displayRepository
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import org.mockito.kotlin.mock
 
 val Kosmos.fakeStatusBarModePerDisplayRepository by
     Kosmos.Fixture { FakeStatusBarModePerDisplayRepository() }
@@ -24,3 +27,21 @@
 val Kosmos.statusBarModeRepository: StatusBarModeRepositoryStore by
     Kosmos.Fixture { fakeStatusBarModeRepository }
 val Kosmos.fakeStatusBarModeRepository by Kosmos.Fixture { FakeStatusBarModeRepository() }
+val Kosmos.fakeStatusBarModePerDisplayRepositoryFactory by
+    Kosmos.Fixture { FakeStatusBarModePerDisplayRepositoryFactory() }
+
+val Kosmos.multiDisplayStatusBarModeRepositoryStore by
+    Kosmos.Fixture {
+        MultiDisplayStatusBarModeRepositoryStore(
+            applicationCoroutineScope,
+            fakeStatusBarModePerDisplayRepositoryFactory,
+            displayRepository,
+        )
+    }
+
+class FakeStatusBarModePerDisplayRepositoryFactory : StatusBarModePerDisplayRepositoryFactory {
+
+    override fun create(displayId: Int): StatusBarModePerDisplayRepositoryImpl {
+        return mock<StatusBarModePerDisplayRepositoryImpl>()
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreKosmos.kt
new file mode 100644
index 0000000..f0c8f4b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * 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 com.android.systemui.statusbar.window.statusBarWindowControllerStore
+import org.mockito.kotlin.mock
+
+val Kosmos.fakeSystemEventChipAnimationControllerStore by
+    Kosmos.Fixture { FakeSystemEventChipAnimationControllerStore() }
+
+val Kosmos.systemEventChipAnimationControllerStoreImpl by
+    Kosmos.Fixture {
+        SystemEventChipAnimationControllerStoreImpl(
+            backgroundApplicationScope = applicationCoroutineScope,
+            displayRepository = displayRepository,
+            factory = { _, _, _ -> mock() },
+            displayWindowPropertiesRepository = displayWindowPropertiesRepository,
+            statusBarWindowControllerStore = statusBarWindowControllerStore,
+            statusBarContentInsetsProviderStore = statusBarContentInsetsProviderStore,
+        )
+    }
+
+var Kosmos.systemEventChipAnimationControllerStore by
+    Kosmos.Fixture { fakeSystemEventChipAnimationControllerStore }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/FakePrivacyDotViewController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/FakePrivacyDotViewController.kt
new file mode 100644
index 0000000..53c39a6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/FakePrivacyDotViewController.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.events
+
+import android.view.View
+
+class FakePrivacyDotViewController : PrivacyDotViewController {
+
+    var topLeft: View? = null
+        private set
+
+    var topRight: View? = null
+        private set
+
+    var bottomLeft: View? = null
+        private set
+
+    var bottomRight: View? = null
+        private set
+
+    var isInitialized = false
+        private set
+
+    override fun stop() {}
+
+    override var currentViewState: ViewState = ViewState()
+
+    override var showingListener: PrivacyDotViewController.ShowingListener? = null
+
+    override fun setNewRotation(rot: Int) {}
+
+    override fun hideDotView(dot: View, animate: Boolean) {}
+
+    override fun showDotView(dot: View, animate: Boolean) {}
+
+    override fun updateRotations(rotation: Int, paddingTop: Int) {}
+
+    override fun setCornerSizes(state: ViewState) {}
+
+    override fun initialize(topLeft: View, topRight: View, bottomLeft: View, bottomRight: View) {
+        this.topLeft = topLeft
+        this.topRight = topRight
+        this.bottomLeft = bottomLeft
+        this.bottomRight = bottomRight
+        isInitialized = true
+    }
+
+    override fun updateDotView(state: ViewState) {}
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerKosmos.kt
similarity index 64%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerKosmos.kt
index 94b2bdf..9cbc975 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerKosmos.kt
@@ -14,9 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.statusbar.events
 
 import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+val Kosmos.mockPrivacyDotViewController by Kosmos.Fixture { mock<PrivacyDotViewController>() }
+
+val Kosmos.fakePrivacyDotViewController by Kosmos.Fixture { FakePrivacyDotViewController() }
+
+var Kosmos.privacyDotViewController by Kosmos.Fixture { fakePrivacyDotViewController }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt
new file mode 100644
index 0000000..c738387
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt
@@ -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 com.android.systemui.statusbar.events
+
+import android.content.testableContext
+import android.view.layoutInflater
+import com.android.app.viewcapture.realCaptureAwareWindowManager
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.decor.privacyDotDecorProviderFactory
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.privacyDotWindowController by
+    Kosmos.Fixture {
+        PrivacyDotWindowController(
+            testableContext.displayId,
+            privacyDotViewController,
+            realCaptureAwareWindowManager,
+            layoutInflater,
+            fakeExecutor,
+            privacyDotDecorProviderFactory,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerKosmos.kt
new file mode 100644
index 0000000..186b045
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.events
+
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.data.repository.systemEventChipAnimationControllerStore
+
+val Kosmos.multiDisplaySystemEventChipAnimationController by
+    Kosmos.Fixture {
+        MultiDisplaySystemEventChipAnimationController(
+            displayRepository,
+            systemEventChipAnimationControllerStore,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
index 76bdc0d..32c582f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
@@ -28,6 +28,7 @@
     key: String,
     groupKey: String? = null,
     whenTime: Long = 0L,
+    isPromoted: Boolean = false,
     isAmbient: Boolean = false,
     isRowDismissed: Boolean = false,
     isSilent: Boolean = false,
@@ -50,6 +51,7 @@
         key = key,
         groupKey = groupKey,
         whenTime = whenTime,
+        isPromoted = isPromoted,
         isAmbient = isAmbient,
         isRowDismissed = isRowDismissed,
         isSilent = isSilent,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
index f7acae9..067193f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
@@ -19,8 +19,13 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.statusbar.notification.collection.provider.sectionStyleProvider
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.promoted.promotedNotificationsProvider
 
 val Kosmos.renderNotificationListInteractor by
     Kosmos.Fixture {
-        RenderNotificationListInteractor(activeNotificationListRepository, sectionStyleProvider)
+        RenderNotificationListInteractor(
+            activeNotificationListRepository,
+            sectionStyleProvider,
+            promotedNotificationsProvider,
+        )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderKosmos.kt
similarity index 80%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderKosmos.kt
index 94b2bdf..a7aa0b4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderKosmos.kt
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.statusbar.notification.promoted
 
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+val Kosmos.promotedNotificationsProvider by Kosmos.Fixture { PromotedNotificationsProviderImpl() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/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/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/viewmodel/StatusBarUserChipViewModelKosmos.kt
similarity index 65%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/viewmodel/StatusBarUserChipViewModelKosmos.kt
index 94b2bdf..01175a5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/viewmodel/StatusBarUserChipViewModelKosmos.kt
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.statusbar.ui.viewmodel
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.user.domain.interactor.userSwitcherInteractor
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+val Kosmos.statusBarUserChipViewModel: StatusBarUserChipViewModel by
+    Kosmos.Fixture { StatusBarUserChipViewModel(userSwitcherInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/ExecutionKosmos.kt
similarity index 73%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/ExecutionKosmos.kt
index 94b2bdf..bf66cb6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/ExecutionKosmos.kt
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.systemui.util.concurrency
 
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+val Kosmos.fakeExecution: FakeExecution by
+    Kosmos.Fixture { FakeExecution().apply { simulateMainThread = false } }
+var Kosmos.execution: Execution by Kosmos.Fixture { fakeExecution }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt
deleted file mode 100644
index 5054e29..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.util.settings
-
-import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.map
-
-class FakeUserAwareSecureSettingsRepository : UserAwareSecureSettingsRepository {
-
-    private val settings = MutableStateFlow<Map<String, Boolean>>(mutableMapOf())
-
-    override fun boolSettingForActiveUser(name: String, defaultValue: Boolean): Flow<Boolean> {
-        return settings.map { it.getOrDefault(name, defaultValue) }
-    }
-
-    fun setBoolSettingForActiveUser(name: String, value: Boolean) {
-        settings.value = settings.value.toMutableMap().apply { this[name] = value }
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSecureSettingsRepositoryKosmos.kt
new file mode 100644
index 0000000..dc10ca9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSecureSettingsRepositoryKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.settings.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.user.data.repository.userRepository
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
+
+val Kosmos.userAwareSecureSettingsRepository by
+    Kosmos.Fixture {
+        UserAwareSecureSettingsRepository(
+            fakeSettings,
+            userRepository,
+            testDispatcher,
+            backgroundCoroutineContext,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSystemSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSystemSettingsRepositoryKosmos.kt
new file mode 100644
index 0000000..ff77908
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSystemSettingsRepositoryKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.settings.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.user.data.repository.userRepository
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.util.settings.repository.UserAwareSystemSettingsRepository
+
+val Kosmos.userAwareSystemSettingsRepository by
+    Kosmos.Fixture {
+        UserAwareSystemSettingsRepository(
+            fakeSettings,
+            userRepository,
+            testDispatcher,
+            backgroundCoroutineContext,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
index 1b58582..ed5322e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.mediaOutputDialogManager
+import com.android.systemui.util.concurrency.execution
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -53,6 +54,7 @@
             testScope.testScheduler,
             mediaControllerRepository,
             mediaControllerInteractor,
+            execution,
         )
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt
index 652b3ea..fdeb8ce 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt
@@ -19,6 +19,7 @@
 import android.os.Handler
 import android.os.looper
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
 
 var Kosmos.mediaControllerInteractor: MediaControllerInteractor by
-    Kosmos.Fixture { MediaControllerInteractorImpl(Handler(looper)) }
+    Kosmos.Fixture { MediaControllerInteractorImpl(Handler(looper), testScope.testScheduler) }
diff --git a/packages/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/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/Vcn/service-b/src/com/android/server/vcn/Placeholder.java
similarity index 75%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
copy to packages/Vcn/service-b/src/com/android/server/vcn/Placeholder.java
index 94b2bdf..e799145 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/Placeholder.java
@@ -14,9 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.settings
+package com.android.server.vcn;
 
-import com.android.systemui.kosmos.Kosmos
-
-val Kosmos.userAwareSecureSettingsRepository by
-    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+/**
+ * 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/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index 8663593..44ea9a2 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -945,10 +945,11 @@
                 String tag = parser.getName();
                 if (!sectionTag.equals(tag)) continue;
                 for (Pair<Integer, String> pair : List.of(
-                        new Pair<>(WallpaperManager.PORTRAIT, "Portrait"),
-                        new Pair<>(WallpaperManager.LANDSCAPE, "Landscape"),
-                        new Pair<>(WallpaperManager.SQUARE_PORTRAIT, "SquarePortrait"),
-                        new Pair<>(WallpaperManager.SQUARE_LANDSCAPE, "SquareLandscape"))) {
+                        new Pair<>(WallpaperManager.ORIENTATION_PORTRAIT, "Portrait"),
+                        new Pair<>(WallpaperManager.ORIENTATION_LANDSCAPE, "Landscape"),
+                        new Pair<>(WallpaperManager.ORIENTATION_SQUARE_PORTRAIT, "SquarePortrait"),
+                        new Pair<>(WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE,
+                                "SquareLandscape"))) {
                     Rect cropHint = new Rect(
                             getAttributeInt(parser, "cropLeft" + pair.second, 0),
                             getAttributeInt(parser, "cropTop" + pair.second, 0),
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index 3ecdf3f..f5fb644 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -827,16 +827,17 @@
 
     @Test
     public void testOnRestore_singleCropHint() throws Exception {
-        Map<Integer, Rect> testMap = Map.of(WallpaperManager.PORTRAIT, new Rect(1, 2, 3, 4));
+        Map<Integer, Rect> testMap = Map.of(
+                WallpaperManager.ORIENTATION_PORTRAIT, new Rect(1, 2, 3, 4));
         testParseCropHints(testMap);
     }
 
     @Test
     public void testOnRestore_multipleCropHints() throws Exception {
         Map<Integer, Rect> testMap = Map.of(
-                WallpaperManager.PORTRAIT, new Rect(1, 2, 3, 4),
-                WallpaperManager.SQUARE_PORTRAIT, new Rect(5, 6, 7, 8),
-                WallpaperManager.SQUARE_LANDSCAPE, new Rect(9, 10, 11, 12));
+                WallpaperManager.ORIENTATION_PORTRAIT, new Rect(1, 2, 3, 4),
+                WallpaperManager.ORIENTATION_SQUARE_PORTRAIT, new Rect(5, 6, 7, 8),
+                WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE, new Rect(9, 10, 11, 12));
         testParseCropHints(testMap);
     }
 
@@ -936,10 +937,10 @@
         out.startTag(null, "wp");
         for (Map.Entry<Integer, Rect> entry: crops.entrySet()) {
             String orientation = switch (entry.getKey()) {
-                case WallpaperManager.PORTRAIT -> "Portrait";
-                case WallpaperManager.LANDSCAPE -> "Landscape";
-                case WallpaperManager.SQUARE_PORTRAIT -> "SquarePortrait";
-                case WallpaperManager.SQUARE_LANDSCAPE -> "SquareLandscape";
+                case WallpaperManager.ORIENTATION_PORTRAIT -> "Portrait";
+                case WallpaperManager.ORIENTATION_LANDSCAPE -> "Landscape";
+                case WallpaperManager.ORIENTATION_SQUARE_PORTRAIT -> "SquarePortrait";
+                case WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE -> "SquareLandscape";
                 default -> throw new IllegalArgumentException("Invalid orientation");
             };
             Rect rect = entry.getValue();
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 65ea9fe..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/.
@@ -360,6 +368,239 @@
     ],
 }
 
+filegroup {
+    name: "ravenwood-data",
+    device_common_srcs: [
+        ":system-build.prop",
+        ":framework-res",
+        ":ravenwood-empty-res",
+        ":framework-platform-compat-config",
+        ":services-platform-compat-config",
+    ],
+    device_first_srcs: [
+        ":apex_icu.dat",
+    ],
+    visibility: ["//visibility:private"],
+}
+
+// Keep in sync with build/make/target/product/generic/Android.bp
+filegroup {
+    name: "ravenwood-fonts",
+    device_common_srcs: [
+        ":AndroidClock.ttf",
+        ":CarroisGothicSC-Regular.ttf",
+        ":ComingSoon.ttf",
+        ":CutiveMono.ttf",
+        ":DancingScript-Regular.ttf",
+        ":DroidSansMono.ttf",
+        ":NotoColorEmoji.ttf",
+        ":NotoColorEmojiFlags.ttf",
+        ":NotoNaskhArabic-Bold.ttf",
+        ":NotoNaskhArabic-Regular.ttf",
+        ":NotoNaskhArabicUI-Bold.ttf",
+        ":NotoNaskhArabicUI-Regular.ttf",
+        ":NotoSansAdlam-VF.ttf",
+        ":NotoSansAhom-Regular.otf",
+        ":NotoSansAnatolianHieroglyphs-Regular.otf",
+        ":NotoSansArmenian-VF.ttf",
+        ":NotoSansAvestan-Regular.ttf",
+        ":NotoSansBalinese-Regular.ttf",
+        ":NotoSansBamum-Regular.ttf",
+        ":NotoSansBassaVah-Regular.otf",
+        ":NotoSansBatak-Regular.ttf",
+        ":NotoSansBengali-VF.ttf",
+        ":NotoSansBengaliUI-VF.ttf",
+        ":NotoSansBhaiksuki-Regular.otf",
+        ":NotoSansBrahmi-Regular.ttf",
+        ":NotoSansBuginese-Regular.ttf",
+        ":NotoSansBuhid-Regular.ttf",
+        ":NotoSansCJK-Regular.ttc",
+        ":NotoSansCanadianAboriginal-Regular.ttf",
+        ":NotoSansCarian-Regular.ttf",
+        ":NotoSansChakma-Regular.otf",
+        ":NotoSansCham-Bold.ttf",
+        ":NotoSansCham-Regular.ttf",
+        ":NotoSansCherokee-Regular.ttf",
+        ":NotoSansCoptic-Regular.ttf",
+        ":NotoSansCuneiform-Regular.ttf",
+        ":NotoSansCypriot-Regular.ttf",
+        ":NotoSansDeseret-Regular.ttf",
+        ":NotoSansDevanagari-VF.ttf",
+        ":NotoSansDevanagariUI-VF.ttf",
+        ":NotoSansEgyptianHieroglyphs-Regular.ttf",
+        ":NotoSansElbasan-Regular.otf",
+        ":NotoSansEthiopic-VF.ttf",
+        ":NotoSansGeorgian-VF.ttf",
+        ":NotoSansGlagolitic-Regular.ttf",
+        ":NotoSansGothic-Regular.ttf",
+        ":NotoSansGrantha-Regular.ttf",
+        ":NotoSansGujarati-Bold.ttf",
+        ":NotoSansGujarati-Regular.ttf",
+        ":NotoSansGujaratiUI-Bold.ttf",
+        ":NotoSansGujaratiUI-Regular.ttf",
+        ":NotoSansGunjalaGondi-Regular.otf",
+        ":NotoSansGurmukhi-VF.ttf",
+        ":NotoSansGurmukhiUI-VF.ttf",
+        ":NotoSansHanifiRohingya-Regular.otf",
+        ":NotoSansHanunoo-Regular.ttf",
+        ":NotoSansHatran-Regular.otf",
+        ":NotoSansHebrew-Bold.ttf",
+        ":NotoSansHebrew-Regular.ttf",
+        ":NotoSansImperialAramaic-Regular.ttf",
+        ":NotoSansInscriptionalPahlavi-Regular.ttf",
+        ":NotoSansInscriptionalParthian-Regular.ttf",
+        ":NotoSansJavanese-Regular.otf",
+        ":NotoSansKaithi-Regular.ttf",
+        ":NotoSansKannada-VF.ttf",
+        ":NotoSansKannadaUI-VF.ttf",
+        ":NotoSansKayahLi-Regular.ttf",
+        ":NotoSansKharoshthi-Regular.ttf",
+        ":NotoSansKhmer-VF.ttf",
+        ":NotoSansKhmerUI-Bold.ttf",
+        ":NotoSansKhmerUI-Regular.ttf",
+        ":NotoSansKhojki-Regular.otf",
+        ":NotoSansLao-Bold.ttf",
+        ":NotoSansLao-Regular.ttf",
+        ":NotoSansLaoUI-Bold.ttf",
+        ":NotoSansLaoUI-Regular.ttf",
+        ":NotoSansLepcha-Regular.ttf",
+        ":NotoSansLimbu-Regular.ttf",
+        ":NotoSansLinearA-Regular.otf",
+        ":NotoSansLinearB-Regular.ttf",
+        ":NotoSansLisu-Regular.ttf",
+        ":NotoSansLycian-Regular.ttf",
+        ":NotoSansLydian-Regular.ttf",
+        ":NotoSansMalayalam-VF.ttf",
+        ":NotoSansMalayalamUI-VF.ttf",
+        ":NotoSansMandaic-Regular.ttf",
+        ":NotoSansManichaean-Regular.otf",
+        ":NotoSansMarchen-Regular.otf",
+        ":NotoSansMasaramGondi-Regular.otf",
+        ":NotoSansMedefaidrin-VF.ttf",
+        ":NotoSansMeeteiMayek-Regular.ttf",
+        ":NotoSansMeroitic-Regular.otf",
+        ":NotoSansMiao-Regular.otf",
+        ":NotoSansModi-Regular.ttf",
+        ":NotoSansMongolian-Regular.ttf",
+        ":NotoSansMro-Regular.otf",
+        ":NotoSansMultani-Regular.otf",
+        ":NotoSansMyanmar-Bold.otf",
+        ":NotoSansMyanmar-Medium.otf",
+        ":NotoSansMyanmar-Regular.otf",
+        ":NotoSansMyanmarUI-Bold.otf",
+        ":NotoSansMyanmarUI-Medium.otf",
+        ":NotoSansMyanmarUI-Regular.otf",
+        ":NotoSansNKo-Regular.ttf",
+        ":NotoSansNabataean-Regular.otf",
+        ":NotoSansNewTaiLue-Regular.ttf",
+        ":NotoSansNewa-Regular.otf",
+        ":NotoSansOgham-Regular.ttf",
+        ":NotoSansOlChiki-Regular.ttf",
+        ":NotoSansOldItalic-Regular.ttf",
+        ":NotoSansOldNorthArabian-Regular.otf",
+        ":NotoSansOldPermic-Regular.otf",
+        ":NotoSansOldPersian-Regular.ttf",
+        ":NotoSansOldSouthArabian-Regular.ttf",
+        ":NotoSansOldTurkic-Regular.ttf",
+        ":NotoSansOriya-Bold.ttf",
+        ":NotoSansOriya-Regular.ttf",
+        ":NotoSansOriyaUI-Bold.ttf",
+        ":NotoSansOriyaUI-Regular.ttf",
+        ":NotoSansOsage-Regular.ttf",
+        ":NotoSansOsmanya-Regular.ttf",
+        ":NotoSansPahawhHmong-Regular.otf",
+        ":NotoSansPalmyrene-Regular.otf",
+        ":NotoSansPauCinHau-Regular.otf",
+        ":NotoSansPhagsPa-Regular.ttf",
+        ":NotoSansPhoenician-Regular.ttf",
+        ":NotoSansRejang-Regular.ttf",
+        ":NotoSansRunic-Regular.ttf",
+        ":NotoSansSamaritan-Regular.ttf",
+        ":NotoSansSaurashtra-Regular.ttf",
+        ":NotoSansSharada-Regular.otf",
+        ":NotoSansShavian-Regular.ttf",
+        ":NotoSansSinhala-VF.ttf",
+        ":NotoSansSinhalaUI-VF.ttf",
+        ":NotoSansSoraSompeng-Regular.otf",
+        ":NotoSansSoyombo-VF.ttf",
+        ":NotoSansSundanese-Regular.ttf",
+        ":NotoSansSylotiNagri-Regular.ttf",
+        ":NotoSansSymbols-Regular-Subsetted.ttf",
+        ":NotoSansSymbols-Regular-Subsetted2.ttf",
+        ":NotoSansSyriacEastern-Regular.ttf",
+        ":NotoSansSyriacEstrangela-Regular.ttf",
+        ":NotoSansSyriacWestern-Regular.ttf",
+        ":NotoSansTagalog-Regular.ttf",
+        ":NotoSansTagbanwa-Regular.ttf",
+        ":NotoSansTaiLe-Regular.ttf",
+        ":NotoSansTaiTham-Regular.ttf",
+        ":NotoSansTaiViet-Regular.ttf",
+        ":NotoSansTakri-VF.ttf",
+        ":NotoSansTamil-VF.ttf",
+        ":NotoSansTamilUI-VF.ttf",
+        ":NotoSansTelugu-VF.ttf",
+        ":NotoSansTeluguUI-VF.ttf",
+        ":NotoSansThaana-Bold.ttf",
+        ":NotoSansThaana-Regular.ttf",
+        ":NotoSansThai-Bold.ttf",
+        ":NotoSansThai-Regular.ttf",
+        ":NotoSansThaiUI-Bold.ttf",
+        ":NotoSansThaiUI-Regular.ttf",
+        ":NotoSansTifinagh-Regular.otf",
+        ":NotoSansUgaritic-Regular.ttf",
+        ":NotoSansVai-Regular.ttf",
+        ":NotoSansWancho-Regular.otf",
+        ":NotoSansWarangCiti-Regular.otf",
+        ":NotoSansYi-Regular.ttf",
+        ":NotoSerif-Bold.ttf",
+        ":NotoSerif-BoldItalic.ttf",
+        ":NotoSerif-Italic.ttf",
+        ":NotoSerif-Regular.ttf",
+        ":NotoSerifArmenian-VF.ttf",
+        ":NotoSerifBengali-VF.ttf",
+        ":NotoSerifCJK-Regular.ttc",
+        ":NotoSerifDevanagari-VF.ttf",
+        ":NotoSerifDogra-Regular.ttf",
+        ":NotoSerifEthiopic-VF.ttf",
+        ":NotoSerifGeorgian-VF.ttf",
+        ":NotoSerifGujarati-VF.ttf",
+        ":NotoSerifGurmukhi-VF.ttf",
+        ":NotoSerifHebrew-Bold.ttf",
+        ":NotoSerifHebrew-Regular.ttf",
+        ":NotoSerifHentaigana.ttf",
+        ":NotoSerifKannada-VF.ttf",
+        ":NotoSerifKhmer-Bold.otf",
+        ":NotoSerifKhmer-Regular.otf",
+        ":NotoSerifLao-Bold.ttf",
+        ":NotoSerifLao-Regular.ttf",
+        ":NotoSerifMalayalam-VF.ttf",
+        ":NotoSerifMyanmar-Bold.otf",
+        ":NotoSerifMyanmar-Regular.otf",
+        ":NotoSerifNyiakengPuachueHmong-VF.ttf",
+        ":NotoSerifSinhala-VF.ttf",
+        ":NotoSerifTamil-VF.ttf",
+        ":NotoSerifTelugu-VF.ttf",
+        ":NotoSerifThai-Bold.ttf",
+        ":NotoSerifThai-Regular.ttf",
+        ":NotoSerifTibetan-VF.ttf",
+        ":NotoSerifYezidi-VF.ttf",
+        ":Roboto-Regular.ttf",
+        ":RobotoFlex-Regular.ttf",
+        ":RobotoStatic-Regular.ttf",
+        ":SourceSansPro-Bold.ttf",
+        ":SourceSansPro-BoldItalic.ttf",
+        ":SourceSansPro-Italic.ttf",
+        ":SourceSansPro-Regular.ttf",
+        ":SourceSansPro-SemiBold.ttf",
+        ":SourceSansPro-SemiBoldItalic.ttf",
+    ],
+    device_first_srcs: [
+        ":font_fallback.xml",
+        ":fonts.xml",
+    ],
+    visibility: ["//visibility:private"],
+}
+
 // JARs in "ravenwood-runtime" are set to the classpath, sorted alphabetically.
 // Rename some of the dependencies to make sure they're included in the intended order.
 
@@ -386,13 +627,8 @@
 
 android_ravenwood_libgroup {
     name: "ravenwood-runtime",
-    data: [
-        ":system-build.prop",
-        ":framework-res",
-        ":ravenwood-empty-res",
-        ":framework-platform-compat-config",
-        ":services-platform-compat-config",
-    ],
+    data: [":ravenwood-data"],
+    fonts: [":ravenwood-fonts"],
     libs: [
         "100-framework-minus-apex.ravenwood",
         "200-kxml2-android",
@@ -431,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/RavenwoodAwareTestRunner.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index 66a6890..869d854 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -26,12 +26,10 @@
 import android.os.Bundle;
 import android.platform.test.annotations.RavenwoodTestRunnerInitializing;
 import android.platform.test.annotations.internal.InnerRunner;
-import android.platform.test.ravenwood.RavenwoodTestStats.Result;
 import android.util.Log;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import org.junit.AssumptionViolatedException;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runner.Runner;
@@ -171,10 +169,11 @@
         final var notifier = new RavenwoodRunNotifier(realNotifier);
         final var description = getDescription();
 
+        RavenwoodTestStats.getInstance().attachToRunNotifier(notifier);
+
         if (mRealRunner instanceof ClassSkippingTestRunner) {
-            mRealRunner.run(notifier);
             Log.i(TAG, "onClassSkipped: description=" + description);
-            RavenwoodTestStats.getInstance().onClassSkipped(description);
+            mRealRunner.run(notifier);
             return;
         }
 
@@ -205,7 +204,6 @@
 
             if (!skipRunnerHook) {
                 try {
-                    RavenwoodTestStats.getInstance().onClassFinished(description);
                     mState.exitTestClass();
                 } catch (Throwable th) {
                     notifier.reportAfterTestFailure(th);
@@ -295,8 +293,6 @@
         // method-level annotations here.
         if (scope == Scope.Instance && order == Order.Outer) {
             if (!RavenwoodEnablementChecker.shouldEnableOnRavenwood(description, true)) {
-                RavenwoodTestStats.getInstance().onTestFinished(
-                        classDescription, description, Result.Skipped);
                 return false;
             }
         }
@@ -317,16 +313,6 @@
             // End of a test method.
             mState.exitTestMethod();
 
-            final Result result;
-            if (th == null) {
-                result = Result.Passed;
-            } else if (th instanceof AssumptionViolatedException) {
-                result = Result.Skipped;
-            } else {
-                result = Result.Failed;
-            }
-
-            RavenwoodTestStats.getInstance().onTestFinished(classDescription, description, result);
         }
 
         // If RUN_DISABLED_TESTS is set, and the method did _not_ throw, make it an error.
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java
index d29b93c..a208d6d 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodNativeLoader.java
@@ -40,7 +40,7 @@
      * See frameworks/base/core/jni/platform/host/HostRuntime.cpp
      */
     private static final Class<?>[] sLibandroidClasses = {
-            android.util.Log.class,
+//            android.util.Log.class, // Not using native log: b/377377826
             android.os.Parcel.class,
             android.os.Binder.class,
             android.os.SystemProperties.class,
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index 9002e40..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,16 +31,21 @@
 
 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;
 import android.os.Bundle;
 import android.os.HandlerThread;
 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;
@@ -50,12 +55,15 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.hoststubgen.hosthelper.HostTestUtils;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.RuntimeInit;
 import com.android.ravenwood.RavenwoodRuntimeNative;
+import com.android.ravenwood.RavenwoodRuntimeState;
 import com.android.ravenwood.common.RavenwoodCommonUtils;
 import com.android.ravenwood.common.RavenwoodRuntimeException;
 import com.android.ravenwood.common.SneakyThrow;
 import com.android.server.LocalServices;
+import com.android.server.compat.PlatformCompat;
 
 import org.junit.runner.Description;
 
@@ -84,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 =
@@ -137,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);
@@ -165,6 +212,17 @@
         RavenwoodSystemProperties.initialize(RAVENWOOD_BUILD_PROP);
         setSystemProperties(null);
 
+        // Do this after loading RAVENWOOD_NATIVE_RUNTIME_NAME (which backs Os.setenv()),
+        // before loadFrameworkNativeCode() (which uses $ANDROID_LOG_TAGS).
+        if (RAVENWOOD_VERBOSE_LOGGING) {
+            RavenwoodCommonUtils.log(TAG, "Force enabling verbose logging");
+            try {
+                Os.setenv("ANDROID_LOG_TAGS", "*:v", true);
+            } catch (ErrnoException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
         // Make sure libandroid_runtime is loaded.
         RavenwoodNativeLoader.loadFrameworkNativeCode();
 
@@ -175,15 +233,6 @@
         Objects.requireNonNull(Build.TYPE);
         Objects.requireNonNull(Build.VERSION.SDK);
 
-        if (RAVENWOOD_VERBOSE_LOGGING) {
-            RavenwoodCommonUtils.log(TAG, "Force enabling verbose logging");
-            try {
-                Os.setenv("ANDROID_LOG_TAGS", "*:v", true);
-            } catch (ErrnoException e) {
-                // Shouldn't happen.
-            }
-        }
-
         System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1");
         // This will let AndroidJUnit4 use the original runner.
         System.setProperty("android.junit.runner",
@@ -197,7 +246,7 @@
      */
     public static void init(RavenwoodAwareTestRunner runner) {
         if (RAVENWOOD_VERBOSE_LOGGING) {
-            Log.i(TAG, "init() called here: " + runner, new RuntimeException("STACKTRACE"));
+            Log.v(TAG, "init() called here: " + runner, new RuntimeException("STACKTRACE"));
         }
         if (sRunner == runner) {
             return;
@@ -221,7 +270,9 @@
             Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
         }
 
-        android.os.Process.init$ravenwood(config.mUid, config.mPid);
+        RavenwoodRuntimeState.sUid = config.mUid;
+        RavenwoodRuntimeState.sPid = config.mPid;
+        RavenwoodRuntimeState.sTargetSdkLevel = config.mTargetSdkLevel;
         sOriginalIdentityToken = Binder.clearCallingIdentity();
         reinit();
         setSystemProperties(config.mSystemProperties);
@@ -288,6 +339,8 @@
 
         RavenwoodSystemServer.init(config);
 
+        initializeCompatIds(config);
+
         if (ENABLE_TIMEOUT_STACKS) {
             sPendingTimeout = sTimeoutExecutor.schedule(
                     RavenwoodRuntimeEnvironmentController::dumpStacks,
@@ -303,12 +356,37 @@
         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.
      */
     public static void reset() {
         if (RAVENWOOD_VERBOSE_LOGGING) {
-            Log.i(TAG, "reset() called here", new RuntimeException("STACKTRACE"));
+            Log.v(TAG, "reset() called here", new RuntimeException("STACKTRACE"));
         }
         if (sRunner == null) {
             throw new RavenwoodRuntimeException("Internal error: reset() already called");
@@ -348,8 +426,8 @@
         if (sOriginalIdentityToken != -1) {
             Binder.restoreCallingIdentity(sOriginalIdentityToken);
         }
-        android.os.Process.reset$ravenwood();
-
+        RavenwoodRuntimeState.reset();
+        Process_ravenwood.reset();
         DeviceConfig_host.reset();
 
         try {
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/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/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
index 016de8e..7870585 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
@@ -18,6 +18,9 @@
 import android.util.Log;
 
 import org.junit.runner.Description;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+import org.junit.runner.notification.RunNotifier;
 
 import java.io.File;
 import java.io.IOException;
@@ -27,7 +30,7 @@
 import java.nio.file.Paths;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.Map;
 
 /**
@@ -39,7 +42,7 @@
  */
 public class RavenwoodTestStats {
     private static final String TAG = "RavenwoodTestStats";
-    private static final String HEADER = "Module,Class,ClassDesc,Passed,Failed,Skipped";
+    private static final String HEADER = "Module,Class,OuterClass,Passed,Failed,Skipped";
 
     private static RavenwoodTestStats sInstance;
 
@@ -66,7 +69,7 @@
     private final PrintWriter mOutputWriter;
     private final String mTestModuleName;
 
-    public final Map<Description, Map<Description, Result>> mStats = new HashMap<>();
+    public final Map<String, Map<String, Result>> mStats = new LinkedHashMap<>();
 
     /** Ctor */
     public RavenwoodTestStats() {
@@ -115,75 +118,129 @@
         return cwd.getName();
     }
 
-    private void addResult(Description classDescription, Description methodDescription,
+    private void addResult(String className, String methodName,
             Result result) {
-        mStats.compute(classDescription, (classDesc, value) -> {
+        mStats.compute(className, (className_, value) -> {
             if (value == null) {
-                value = new HashMap<>();
+                value = new LinkedHashMap<>();
             }
-            value.put(methodDescription, result);
+            // If the result is already set, don't overwrite it.
+            if (!value.containsKey(methodName)) {
+                value.put(methodName, result);
+            }
             return value;
         });
     }
 
     /**
-     * Call it when a test class is skipped.
-     */
-    public void onClassSkipped(Description classDescription) {
-        addResult(classDescription, Description.EMPTY, Result.Skipped);
-        onClassFinished(classDescription);
-    }
-
-    /**
      * Call it when a test method is finished.
      */
-    public void onTestFinished(Description classDescription, Description testDescription,
-            Result result) {
-        addResult(classDescription, testDescription, result);
+    private void onTestFinished(String className, String testName, Result result) {
+        addResult(className, testName, result);
     }
 
     /**
-     * Call it when a test class is finished.
+     * Dump all the results and clear it.
      */
-    public void onClassFinished(Description classDescription) {
-        int passed = 0;
-        int skipped = 0;
-        int failed = 0;
-        var stats = mStats.get(classDescription);
-        if (stats == null) {
-            return;
-        }
-        for (var e : stats.values()) {
-            switch (e) {
-                case Passed: passed++; break;
-                case Skipped: skipped++; break;
-                case Failed: failed++; break;
+    private void dumpAllAndClear() {
+        for (var entry : mStats.entrySet()) {
+            int passed = 0;
+            int skipped = 0;
+            int failed = 0;
+            var className = entry.getKey();
+
+            for (var e : entry.getValue().values()) {
+                switch (e) {
+                    case Passed:
+                        passed++;
+                        break;
+                    case Skipped:
+                        skipped++;
+                        break;
+                    case Failed:
+                        failed++;
+                        break;
+                }
             }
+
+            mOutputWriter.printf("%s,%s,%s,%d,%d,%d\n",
+                    mTestModuleName, className, getOuterClassName(className),
+                    passed, failed, skipped);
         }
-
-        var testClass = extractTestClass(classDescription);
-
-        mOutputWriter.printf("%s,%s,%s,%d,%d,%d\n",
-                mTestModuleName, (testClass == null ? "?" : testClass.getCanonicalName()),
-                classDescription, passed, failed, skipped);
         mOutputWriter.flush();
+        mStats.clear();
     }
 
-    /**
-     * Try to extract the class from a description, which is needed because
-     * ParameterizedAndroidJunit4's description doesn't contain a class.
-     */
-    private Class<?> extractTestClass(Description desc) {
-        if (desc.getTestClass() != null) {
-            return desc.getTestClass();
+    private static String getOuterClassName(String className) {
+        // Just delete the '$', because I'm not sure if the className we get here is actaully a
+        // valid class name that does exist. (it might have a parameter name, etc?)
+        int p = className.indexOf('$');
+        if (p < 0) {
+            return className;
         }
-        // Look into the children.
-        for (var child : desc.getChildren()) {
-            var fromChild = extractTestClass(child);
-            if (fromChild != null) {
-                return fromChild;
-            }
-        }
-        return null;
+        return className.substring(0, p);
     }
+
+    public void attachToRunNotifier(RunNotifier notifier) {
+        notifier.addListener(mRunListener);
+    }
+
+    private final RunListener mRunListener = new RunListener() {
+        @Override
+        public void testSuiteStarted(Description description) {
+            Log.d(TAG, "testSuiteStarted: " + description);
+        }
+
+        @Override
+        public void testSuiteFinished(Description description) {
+            Log.d(TAG, "testSuiteFinished: " + description);
+        }
+
+        @Override
+        public void testRunStarted(Description description) {
+            Log.d(TAG, "testRunStarted: " + description);
+        }
+
+        @Override
+        public void testRunFinished(org.junit.runner.Result result) {
+            Log.d(TAG, "testRunFinished: " + result);
+
+            dumpAllAndClear();
+        }
+
+        @Override
+        public void testStarted(Description description) {
+            Log.d(TAG, "  testStarted: " + description);
+        }
+
+        @Override
+        public void testFinished(Description description) {
+            Log.d(TAG, "  testFinished: " + description);
+
+            // Send "Passed", but if there's already another result sent for this, this won't
+            // override it.
+            onTestFinished(description.getClassName(), description.getMethodName(), Result.Passed);
+        }
+
+        @Override
+        public void testFailure(Failure failure) {
+            Log.d(TAG, "    testFailure: " + failure);
+
+            var description = failure.getDescription();
+            onTestFinished(description.getClassName(), description.getMethodName(), Result.Failed);
+        }
+
+        @Override
+        public void testAssumptionFailure(Failure failure) {
+            Log.d(TAG, "    testAssumptionFailure: " + failure);
+            var description = failure.getDescription();
+            onTestFinished(description.getClassName(), description.getMethodName(), Result.Skipped);
+        }
+
+        @Override
+        public void testIgnored(Description description) {
+            Log.d(TAG, "    testIgnored: " + description);
+            onTestFinished(description.getClassName(), description.getMethodName(), Result.Skipped);
+        }
+    };
 }
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
index 37b0abc..d8f2b70 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.app.Instrumentation;
 import android.content.Context;
+import android.os.Build;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -67,7 +68,7 @@
     String mTargetPackageName;
 
     int mMinSdkLevel;
-    int mTargetSdkLevel;
+    int mTargetSdkLevel = Build.VERSION_CODES.CUR_DEVELOPMENT;
 
     boolean mProvideMainThread = false;
 
diff --git a/ravenwood/runtime-helper-src/framework/android/os/Process_ravenwood.java b/ravenwood/runtime-helper-src/framework/android/os/Process_ravenwood.java
new file mode 100644
index 0000000..3c6a4d7
--- /dev/null
+++ b/ravenwood/runtime-helper-src/framework/android/os/Process_ravenwood.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+import android.util.Pair;
+
+public class Process_ravenwood {
+
+    private static volatile ThreadLocal<Pair<Integer, Boolean>> sThreadPriority;
+
+    static {
+        reset();
+    }
+
+    public static void reset() {
+        // Reset the thread local variable
+        sThreadPriority = ThreadLocal.withInitial(
+                () -> Pair.create(Process.THREAD_PRIORITY_DEFAULT, true));
+    }
+
+    /**
+     * Called by {@link Process#setThreadPriority(int, int)}
+     */
+    public static void setThreadPriority(int tid, int priority) {
+        if (Process.myTid() == tid) {
+            boolean backgroundOk = sThreadPriority.get().second;
+            if (priority >= Process.THREAD_PRIORITY_BACKGROUND && !backgroundOk) {
+                throw new IllegalArgumentException(
+                        "Priority " + priority + " blocked by setCanSelfBackground()");
+            }
+            sThreadPriority.set(Pair.create(priority, backgroundOk));
+        } else {
+            throw new UnsupportedOperationException(
+                    "Cross-thread priority management not yet available in Ravenwood");
+        }
+    }
+
+    /**
+     * Called by {@link Process#setCanSelfBackground(boolean)}
+     */
+    public static void setCanSelfBackground(boolean backgroundOk) {
+        int priority = sThreadPriority.get().first;
+        sThreadPriority.set(Pair.create(priority, backgroundOk));
+    }
+
+    /**
+     * Called by {@link Process#getThreadPriority(int)}
+     */
+    public static int getThreadPriority(int tid) {
+        if (Process.myTid() == tid) {
+            return sThreadPriority.get().first;
+        } else {
+            throw new UnsupportedOperationException(
+                    "Cross-thread priority management not yet available in Ravenwood");
+        }
+    }
+}
diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayContainer_host.java b/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayContainer_host.java
deleted file mode 100644
index c18c307..0000000
--- a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayContainer_host.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.internal.os;
-
-import java.util.Arrays;
-import java.util.HashMap;
-
-public class LongArrayContainer_host {
-    private static final HashMap<Long, long[]> sInstances = new HashMap<>();
-    private static long sNextId = 1;
-
-    public static long native_init(int arrayLength) {
-        long[] array = new long[arrayLength];
-        long instanceId = sNextId++;
-        sInstances.put(instanceId, array);
-        return instanceId;
-    }
-
-    static long[] getInstance(long instanceId) {
-        return sInstances.get(instanceId);
-    }
-
-    public static void native_setValues(long instanceId, long[] values) {
-        System.arraycopy(values, 0, getInstance(instanceId), 0, values.length);
-    }
-
-    public static void native_getValues(long instanceId, long[] values) {
-        System.arraycopy(getInstance(instanceId), 0, values, 0, values.length);
-    }
-
-    public static boolean native_combineValues(long instanceId, long[] array, int[] indexMap) {
-        long[] values = getInstance(instanceId);
-
-        boolean nonZero = false;
-        Arrays.fill(array, 0);
-
-        for (int i = 0; i < values.length; i++) {
-            int index = indexMap[i];
-            if (index < 0 || index >= array.length) {
-                throw new IndexOutOfBoundsException("Index " + index + " is out of bounds: [0, "
-                        + (array.length - 1) + "]");
-            }
-            if (values[i] != 0) {
-                array[index] += values[i];
-                nonZero = true;
-            }
-        }
-        return nonZero;
-    }
-}
diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java b/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java
index 9ce8ea8..90608f6 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java
@@ -286,15 +286,12 @@
         return getInstance(instanceId).mArrayLength;
     }
 
-    public static void native_setValues(long instanceId, int state, long containerInstanceId) {
-        getInstance(instanceId).setValue(state,
-                LongArrayContainer_host.getInstance(containerInstanceId));
+    public static void native_setValues(long instanceId, int state, long[] values) {
+        getInstance(instanceId).setValue(state, values);
     }
 
-    public static void native_updateValues(long instanceId, long containerInstanceId,
-            long timestampMs) {
-        getInstance(instanceId).updateValue(
-                LongArrayContainer_host.getInstance(containerInstanceId), timestampMs);
+    public static void native_updateValues(long instanceId, long[] values, long timestampMs) {
+        getInstance(instanceId).updateValue(values, timestampMs);
     }
 
     public static void native_setState(long instanceId, int state, long timestampMs) {
@@ -305,19 +302,16 @@
         getInstance(targetInstanceId).copyStatesFrom(getInstance(sourceInstanceId));
     }
 
-    public static void native_incrementValues(long instanceId, long containerInstanceId,
-            long timestampMs) {
-        getInstance(instanceId).incrementValues(
-                LongArrayContainer_host.getInstance(containerInstanceId), timestampMs);
+    public static void native_incrementValues(long instanceId, long[] delta, long timestampMs) {
+        getInstance(instanceId).incrementValues(delta, timestampMs);
     }
 
-    public static void native_addCounts(long instanceId, long containerInstanceId) {
-        getInstance(instanceId).addCounts(LongArrayContainer_host.getInstance(containerInstanceId));
+    public static void native_addCounts(long instanceId, long[] counts) {
+        getInstance(instanceId).addCounts(counts);
     }
 
-    public static void native_getCounts(long instanceId, long containerInstanceId, int state) {
-        getInstance(instanceId).getValues(LongArrayContainer_host.getInstance(containerInstanceId),
-                state);
+    public static void native_getCounts(long instanceId, long[] counts, int state) {
+        getInstance(instanceId).getValues(counts, state);
     }
 
     public static void native_reset(long instanceId) {
diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java b/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java
index e12ff24..b65668b 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java
@@ -23,14 +23,6 @@
     }
 
     /**
-     * Called from {@link RavenwoodEnvironment#ensureRavenwoodInitialized()}.
-     */
-    public static void ensureRavenwoodInitialized() {
-        // Initialization is now done by RavenwoodAwareTestRunner.
-        // Should we remove it?
-    }
-
-    /**
      * Called from {@link RavenwoodEnvironment#getRavenwoodRuntimePath()}.
      */
     public static String getRavenwoodRuntimePath(RavenwoodEnvironment env) {
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
index c94ef31..0298171 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
@@ -16,6 +16,7 @@
 package android.system;
 
 import com.android.ravenwood.RavenwoodRuntimeNative;
+import com.android.ravenwood.RavenwoodRuntimeState;
 import com.android.ravenwood.common.JvmWorkaround;
 
 import java.io.FileDescriptor;
@@ -97,4 +98,16 @@
     public static void setenv(String name, String value, boolean overwrite) throws ErrnoException {
         RavenwoodRuntimeNative.setenv(name, value, overwrite);
     }
+
+    public static int getpid() {
+        return RavenwoodRuntimeState.sPid;
+    }
+
+    public static int getuid() {
+        return RavenwoodRuntimeState.sUid;
+    }
+
+    public static int gettid() {
+        return RavenwoodRuntimeNative.gettid();
+    }
 }
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
index f13189f..7b940b4 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
@@ -58,6 +58,8 @@
 
     public static native void clearSystemProperties();
 
+    public static native int gettid();
+
     public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException {
         return nLseek(JvmWorkaround.getInstance().getFdInt(fd), offset, whence);
     }
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeState.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeState.java
new file mode 100644
index 0000000..175e020
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeState.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwood;
+
+public class RavenwoodRuntimeState {
+    // This must match VMRuntime.SDK_VERSION_CUR_DEVELOPMENT.
+    public static final int CUR_DEVELOPMENT = 10000;
+
+    public static volatile int sUid;
+    public static volatile int sPid;
+    public static volatile int sTargetSdkLevel;
+
+    static {
+        reset();
+    }
+
+    public static void reset() {
+        sUid = -1;
+        sPid = -1;
+        sTargetSdkLevel = CUR_DEVELOPMENT;
+    }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
index ba89f71..eaadac6 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
@@ -19,6 +19,7 @@
 // The original is here:
 // $ANDROID_BUILD_TOP/libcore/libart/src/main/java/dalvik/system/VMRuntime.java
 
+import com.android.ravenwood.RavenwoodRuntimeState;
 import com.android.ravenwood.common.JvmWorkaround;
 
 import java.lang.reflect.Array;
@@ -52,4 +53,8 @@
     public long addressOf(Object obj) {
         return JvmWorkaround.getInstance().addressOf(obj);
     }
+
+    public int getTargetSdkVersion() {
+        return RavenwoodRuntimeState.sTargetSdkLevel;
+    }
 }
diff --git a/ravenwood/runtime-jni/ravenwood_initializer.cpp b/ravenwood/runtime-jni/ravenwood_initializer.cpp
new file mode 100644
index 0000000..89fb7c3
--- /dev/null
+++ b/ravenwood/runtime-jni/ravenwood_initializer.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ /*
+  * This file is compiled into a single SO file, which we load at the very first.
+  * We can do process-wide initialization here.
+  */
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "jni_helper.h"
+
+static void maybeRedirectLog() {
+    auto ravenwoodLogOut = getenv("RAVENWOOD_LOG_OUT");
+    if (ravenwoodLogOut == NULL) {
+        return;
+    }
+    ALOGI("RAVENWOOD_LOG_OUT set. Redirecting output to %s", ravenwoodLogOut);
+
+    // Redirect stdin / stdout to /dev/tty.
+    int ttyFd = open(ravenwoodLogOut, O_WRONLY | O_APPEND);
+    if (ttyFd == -1) {
+        ALOGW("$RAVENWOOD_LOG_OUT is set to %s, but failed to open: %s ", ravenwoodLogOut,
+                strerror(errno));
+        return;
+    }
+    dup2(ttyFd, 1);
+    dup2(ttyFd, 2);
+}
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
+    ALOGI("%s: JNI_OnLoad", __FILE__);
+
+    maybeRedirectLog();
+    return JNI_VERSION_1_4;
+}
diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp
index 3ff0848..c1993f6 100644
--- a/ravenwood/runtime-jni/ravenwood_runtime.cpp
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -17,6 +17,7 @@
 #include <fcntl.h>
 #include <string.h>
 #include <sys/stat.h>
+#include <sys/syscall.h>
 #include <unistd.h>
 #include <utils/misc.h>
 
@@ -173,6 +174,12 @@
     throwIfMinusOne(env, "setenv", setenv(name.c_str(), value.c_str(), overwrite ? 1 : 0));
 }
 
+
+static jint Linux_gettid(JNIEnv* env, jobject) {
+    // gettid(2() was added in glibc 2.30 but Android uses an older version in prebuilt.
+    return syscall(__NR_gettid);
+}
+
 // ---- Registration ----
 
 extern void register_android_system_OsConstants(JNIEnv* env);
@@ -189,6 +196,7 @@
     { "stat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_stat },
     { "nOpen", "(Ljava/lang/String;II)I", (void*)Linux_open },
     { "setenv", "(Ljava/lang/String;Ljava/lang/String;Z)V", (void*)Linux_setenv },
+    { "gettid", "()I", (void*)Linux_gettid },
 };
 
 extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
diff --git a/ravenwood/scripts/run-ravenwood-tests.sh b/ravenwood/scripts/run-ravenwood-tests.sh
index 5d623e0..1910100 100755
--- a/ravenwood/scripts/run-ravenwood-tests.sh
+++ b/ravenwood/scripts/run-ravenwood-tests.sh
@@ -18,12 +18,38 @@
 # Options:
 #
 #   -s: "Smoke" test -- skip slow tests (SysUI, ICU)
+#
+#   -x PCRE: Specify exclusion filter in PCRE
+#            Example: -x '^(Cts|hoststub)' # Exclude CTS and hoststubgen tests.
+#
+#   -f PCRE: Specify inclusion filter in PCRE
+
+
+# Regex to identify slow tests, in PCRE
+SLOW_TEST_RE='^(SystemUiRavenTests|CtsIcuTestCasesRavenwood|CarSystemUIRavenTests)$'
 
 smoke=0
-while getopts "s" opt; do
+include_re=""
+exclude_re=""
+smoke_exclude_re=""
+dry_run=""
+while getopts "sx:f:d" opt; do
 case "$opt" in
     s)
-        smoke=1
+        # Remove slow tests.
+        smoke_exclude_re="$SLOW_TEST_RE"
+        ;;
+    x)
+        # Take a PCRE from the arg, and use it as an exclusion filter.
+        exclude_re="$OPTARG"
+        ;;
+    f)
+        # Take a PCRE from the arg, and use it as an inclusion filter.
+        include_re="$OPTARG"
+        ;;
+    d)
+        # Dry run
+        dry_run="echo"
         ;;
     '?')
         exit 1
@@ -35,21 +61,46 @@
 all_tests=(hoststubgentest tiny-framework-dump-test hoststubgen-invoke-test ravenwood-stats-checker)
 all_tests+=( $(${0%/*}/list-ravenwood-tests.sh) )
 
-# Regex to identify slow tests, in PCRE
-slow_tests_re='^(SystemUiRavenTests|CtsIcuTestCasesRavenwood)$'
-
-if (( $smoke )) ; then
-    # Remove the slow tests.
-    all_tests=( $(
-        for t in "${all_tests[@]}"; do
-            echo $t | grep -vP "$slow_tests_re"
-        done
-    ) )
-fi
-
-run() {
-    echo "Running: $*"
-    "${@}"
+filter() {
+    local re="$1"
+    local grep_arg="$2"
+    if [[ "$re" == "" ]] ; then
+        cat # No filtering
+    else
+        grep $grep_arg -iP "$re"
+    fi
 }
 
-run ${ATEST:-atest} "${all_tests[@]}"
+filter_in() {
+    filter "$1"
+}
+
+filter_out() {
+    filter "$1" -v
+}
+
+
+# Remove the slow tests.
+targets=( $(
+    for t in "${all_tests[@]}"; do
+        echo $t | filter_in "$include_re" | filter_out "$smoke_exclude_re" | filter_out "$exclude_re"
+    done
+) )
+
+# Show the target tests
+
+echo "Target tests:"
+for t in "${targets[@]}"; do
+    echo "  $t"
+done
+
+# Calculate the removed tests.
+
+diff="$(diff  <(echo "${all_tests[@]}" | tr ' ' '\n') <(echo "${targets[@]}" | tr ' ' '\n') )"
+
+if [[ "$diff" != "" ]]; then
+    echo "Excluded tests:"
+    echo "$diff"
+fi
+
+$dry_run ${ATEST:-atest} "${targets[@]}"
diff --git a/ravenwood/tests/bivalenttest/Android.bp b/ravenwood/tests/bivalenttest/Android.bp
index ce0033d..40e6672 100644
--- a/ravenwood/tests/bivalenttest/Android.bp
+++ b/ravenwood/tests/bivalenttest/Android.bp
@@ -43,9 +43,12 @@
 
         // To make sure it won't cause VerifyError (b/324063814)
         "platformprotosnano",
+
+        "com.android.internal.os.flags-aconfig-java",
     ],
     srcs: [
         "test/**/*.java",
+        "test/**/*.kt",
     ],
     jni_libs: [
         "libravenwoodbivalenttest_jni",
@@ -55,13 +58,18 @@
 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",
+        "test/**/ravenizer/*.kt",
     ],
     static_libs: [
         "junit",
         "truth",
+        "flag-junit",
         "ravenwood-junit",
     ],
     test_suites: [
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt
new file mode 100644
index 0000000..fd6d6fb
--- /dev/null
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.aconfig
+
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.internal.os.Flags
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+
+@RunWith(AndroidJUnit4::class)
+class RavenwoodAconfigSimpleReadTests {
+    @Test
+    fun testFalseFlags() {
+        assertFalse(Flags.ravenwoodFlagRo1())
+        assertFalse(Flags.ravenwoodFlagRw1())
+    }
+
+    @Test
+    @Ignore // TODO: Enable this test after rolling out the "2" flags.
+    fun testTrueFlags() {
+        assertTrue(Flags.ravenwoodFlagRo2())
+        assertTrue(Flags.ravenwoodFlagRw2())
+    }
+}
+
+@RunWith(AndroidJUnit4::class)
+class RavenwoodAconfigCheckFlagsRuleTests {
+    @Rule
+    @JvmField
+    val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_RAVENWOOD_FLAG_RO_1)
+    fun testRequireFlagsEnabledRo() {
+        fail("This test shouldn't be executed")
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_RAVENWOOD_FLAG_RW_1)
+    fun testRequireFlagsEnabledRw() {
+        fail("This test shouldn't be executed")
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_RAVENWOOD_FLAG_RO_2)
+    @Ignore // TODO: Enable this test after rolling out the "2" flags.
+    fun testRequireFlagsDisabledRo() {
+        fail("This test shouldn't be executed")
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_RAVENWOOD_FLAG_RW_2)
+    @Ignore // TODO: Enable this test after rolling out the "2" flags.
+    fun testRequireFlagsDisabledRw() {
+        fail("This test shouldn't be executed")
+    }
+}
+
+@RunWith(AndroidJUnit4::class)
+class RavenwoodAconfigSetFlagsRuleWithDefaultTests {
+    @Rule
+    @JvmField
+    val setFlagsRule = SetFlagsRule()
+
+    @Test
+    @EnableFlags(Flags.FLAG_RAVENWOOD_FLAG_RO_1)
+    fun testSetRoFlag() {
+        assertTrue(Flags.ravenwoodFlagRo1())
+        assertFalse(Flags.ravenwoodFlagRw1())
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RAVENWOOD_FLAG_RW_1)
+    fun testSetRwFlag() {
+        assertFalse(Flags.ravenwoodFlagRo1())
+        assertTrue(Flags.ravenwoodFlagRw1())
+    }
+}
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/runtime-test/Android.bp b/ravenwood/tests/runtime-test/Android.bp
index 4102920..0c0df1f 100644
--- a/ravenwood/tests/runtime-test/Android.bp
+++ b/ravenwood/tests/runtime-test/Android.bp
@@ -10,6 +10,9 @@
 android_ravenwood_test {
     name: "RavenwoodRuntimeTest",
 
+    libs: [
+        "ravenwood-helper-runtime",
+    ],
     static_libs: [
         "androidx.annotation_annotation",
         "androidx.test.ext.junit",
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
new file mode 100644
index 0000000..8e04b69
--- /dev/null
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.runtimetest;
+
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+import static android.os.Process.FIRST_APPLICATION_UID;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Binder;
+import android.os.Build;
+import android.os.Process;
+import android.platform.test.ravenwood.RavenwoodConfig;
+import android.system.Os;
+
+import com.android.ravenwood.RavenwoodRuntimeState;
+
+import dalvik.system.VMRuntime;
+
+import org.junit.Test;
+
+public class IdentityTest {
+
+    @RavenwoodConfig.Config
+    public static final RavenwoodConfig sConfig =
+            new RavenwoodConfig.Builder()
+                    .setTargetSdkLevel(UPSIDE_DOWN_CAKE)
+                    .setProcessApp()
+                    .build();
+
+    @Test
+    public void testUid() {
+        assertEquals(FIRST_APPLICATION_UID, RavenwoodRuntimeState.sUid);
+        assertEquals(FIRST_APPLICATION_UID, Os.getuid());
+        assertEquals(FIRST_APPLICATION_UID, Process.myUid());
+        assertEquals(FIRST_APPLICATION_UID, Binder.getCallingUid());
+    }
+
+    @Test
+    public void testPid() {
+        int pid = RavenwoodRuntimeState.sPid;
+        assertEquals(pid, Os.getpid());
+        assertEquals(pid, Process.myPid());
+        assertEquals(pid, Binder.getCallingPid());
+    }
+
+    @Test
+    public void testTargetSdkLevel() {
+        assertEquals(Build.VERSION_CODES.CUR_DEVELOPMENT, RavenwoodRuntimeState.CUR_DEVELOPMENT);
+        assertEquals(UPSIDE_DOWN_CAKE, RavenwoodRuntimeState.sTargetSdkLevel);
+        assertEquals(UPSIDE_DOWN_CAKE, VMRuntime.getRuntime().getTargetSdkVersion());
+    }
+}
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java
index c2230c7..c55506a 100644
--- a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java
@@ -24,6 +24,8 @@
 import static android.system.OsConstants.S_ISSOCK;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 
 import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
 
@@ -51,10 +53,12 @@
 import java.nio.file.attribute.PosixFilePermission;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
 public class OsTest {
+
     public interface ConsumerWithThrow<T> {
         void accept(T var1) throws Exception;
     }
@@ -165,6 +169,35 @@
         });
     }
 
+    private static class TestThread extends Thread {
+
+        final CountDownLatch mLatch = new CountDownLatch(1);
+        int mTid;
+
+        TestThread() {
+            setDaemon(true);
+        }
+
+        @Override
+        public void run() {
+            mTid = Os.gettid();
+            mLatch.countDown();
+        }
+    }
+
+    @Test
+    public void testGetTid() throws InterruptedException {
+        var t1 = new TestThread();
+        var t2 = new TestThread();
+        t1.start();
+        t2.start();
+        // Wait for thread execution
+        assertTrue(t1.mLatch.await(1, TimeUnit.SECONDS));
+        assertTrue(t2.mLatch.await(1, TimeUnit.SECONDS));
+        // Make sure the tid is unique per-thread
+        assertNotEquals(t1.mTid, t2.mTid);
+    }
+
     // Verify StructStat values from libcore against native JVM PosixFileAttributes
     private static void assertAttributesEqual(PosixFileAttributes attr, StructStat stat) {
         assertEquals(attr.lastModifiedTime(), convertTimespecToFileTime(stat.st_mtim));
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/ProcessTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/ProcessTest.java
new file mode 100644
index 0000000..d25b5c1
--- /dev/null
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/ProcessTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.runtimetest;
+
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+import static android.os.Process.THREAD_PRIORITY_DEFAULT;
+import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.os.Process;
+import android.system.Os;
+
+import org.junit.Test;
+
+public class ProcessTest {
+
+    @Test
+    public void testGetUidPidTid() {
+        assertEquals(Os.getuid(), Process.myUid());
+        assertEquals(Os.getpid(), Process.myPid());
+        assertEquals(Os.gettid(), Process.myTid());
+    }
+
+    @Test
+    public void testThreadPriority() {
+        assertThrows(UnsupportedOperationException.class,
+                () -> Process.getThreadPriority(Process.myTid() + 1));
+        assertThrows(UnsupportedOperationException.class,
+                () -> Process.setThreadPriority(Process.myTid() + 1, THREAD_PRIORITY_DEFAULT));
+        assertEquals(THREAD_PRIORITY_DEFAULT, Process.getThreadPriority(Process.myTid()));
+        Process.setThreadPriority(THREAD_PRIORITY_FOREGROUND);
+        assertEquals(THREAD_PRIORITY_FOREGROUND, Process.getThreadPriority(Process.myTid()));
+        Process.setCanSelfBackground(false);
+        Process.setThreadPriority(THREAD_PRIORITY_DEFAULT);
+        assertEquals(THREAD_PRIORITY_DEFAULT, Process.getThreadPriority(Process.myTid()));
+        assertThrows(IllegalArgumentException.class,
+                () -> Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND));
+    }
+}
diff --git a/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/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index a02082d..f47aaba 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -327,6 +327,10 @@
     return (this.access and Opcodes.ACC_SYNTHETIC) != 0
 }
 
+fun ClassNode.isAbstract(): Boolean {
+    return (this.access and Opcodes.ACC_ABSTRACT) != 0
+}
+
 fun MethodNode.isSynthetic(): Boolean {
     return (this.access and Opcodes.ACC_SYNTHETIC) != 0
 }
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
index 32dcbe5..a0e5599 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
@@ -46,7 +46,7 @@
     var enableValidation: SetOnce<Boolean> = SetOnce(true),
 
     /** Whether the validation failure is fatal or not. */
-    var fatalValidation: SetOnce<Boolean> = SetOnce(false),
+    var fatalValidation: SetOnce<Boolean> = SetOnce(true),
 
     /** Whether to remove mockito and dexmaker classes. */
     var stripMockito: SetOnce<Boolean> = SetOnce(false),
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt
index 27092d2..8ec0932 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt
@@ -16,10 +16,12 @@
 package com.android.platform.test.ravenwood.ravenizer
 
 import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.isAbstract
 import com.android.hoststubgen.asm.startsWithAny
 import com.android.hoststubgen.asm.toHumanReadableClassName
 import com.android.hoststubgen.log
 import org.objectweb.asm.tree.ClassNode
+import java.util.regex.Pattern
 
 fun validateClasses(classes: ClassNodes): Boolean {
     var allOk = true
@@ -41,25 +43,35 @@
     }
     var allOk = true
 
+    log.i("Checking ${cn.name.toHumanReadableClassName()}")
+
     // See if there's any class that extends a legacy base class.
     // But ignore the base classes in android.test.
-    if (!cn.name.startsWithAny("android/test/")) {
-        allOk = checkSuperClass(cn, cn, classes) && allOk
+    if (!cn.isAbstract() && !cn.name.startsWith("android/test/")
+        && !isAllowListedLegacyTest(cn)
+        ) {
+        allOk = checkSuperClassForJunit3(cn, cn, classes) && allOk
     }
     return allOk
 }
 
-fun checkSuperClass(targetClass: ClassNode, currentClass: ClassNode, classes: ClassNodes): Boolean {
+fun checkSuperClassForJunit3(
+    targetClass: ClassNode,
+    currentClass: ClassNode,
+    classes: ClassNodes,
+): Boolean {
     if (currentClass.superName == null || currentClass.superName == "java/lang/Object") {
         return true // No parent class
     }
+    // Make sure the class doesn't extend a junit3 TestCase class.
     if (currentClass.superName.isLegacyTestBaseClass()) {
         log.e("Error: Class ${targetClass.name.toHumanReadableClassName()} extends"
-                + " a legacy test class ${currentClass.superName.toHumanReadableClassName()}.")
+                + " a legacy test class ${currentClass.superName.toHumanReadableClassName()}"
+                + ", which is not supported on Ravenwood. Please migrate to Junit4 syntax.")
         return false
     }
     classes.findClass(currentClass.superName)?.let {
-        return checkSuperClass(targetClass, it, classes)
+        return checkSuperClassForJunit3(targetClass, it, classes)
     }
     // Super class not found.
     // log.w("Class ${currentClass.superName} not found.")
@@ -73,9 +85,64 @@
     return this.startsWithAny(
         "junit/framework/TestCase",
 
-        // In case the test doesn't statically include JUnit, we need
+        // In case the test doesn't statically include JUnit, we need the following.
         "android/test/AndroidTestCase",
         "android/test/InstrumentationTestCase",
         "android/test/InstrumentationTestSuite",
     )
 }
+
+private val allowListedLegacyTests = setOf(
+// List of existing test classes that use the JUnit3 syntax. We exempt them for now, but
+// will reject any more of them.
+//
+// Note, we want internal class names, but for convenience, we use '.'s and '%'s here
+// and replace them later. (a '$' would be parsed as a string template.)
+    *"""
+android.util.proto.cts.DebuggingTest
+android.util.proto.cts.EncodedBufferTest
+android.util.proto.cts.ProtoOutputStreamBoolTest
+android.util.proto.cts.ProtoOutputStreamBytesTest
+android.util.proto.cts.ProtoOutputStreamDoubleTest
+android.util.proto.cts.ProtoOutputStreamEnumTest
+android.util.proto.cts.ProtoOutputStreamFixed32Test
+android.util.proto.cts.ProtoOutputStreamFixed64Test
+android.util.proto.cts.ProtoOutputStreamFloatTest
+android.util.proto.cts.ProtoOutputStreamInt32Test
+android.util.proto.cts.ProtoOutputStreamInt64Test
+android.util.proto.cts.ProtoOutputStreamObjectTest
+android.util.proto.cts.ProtoOutputStreamSFixed32Test
+android.util.proto.cts.ProtoOutputStreamSFixed64Test
+android.util.proto.cts.ProtoOutputStreamSInt32Test
+android.util.proto.cts.ProtoOutputStreamSInt64Test
+android.util.proto.cts.ProtoOutputStreamStringTest
+android.util.proto.cts.ProtoOutputStreamSwitchedWriteTest
+android.util.proto.cts.ProtoOutputStreamTagTest
+android.util.proto.cts.ProtoOutputStreamUInt32Test
+android.util.proto.cts.ProtoOutputStreamUInt64Test
+
+android.os.cts.BadParcelableExceptionTest
+android.os.cts.DeadObjectExceptionTest
+android.os.cts.ParcelFormatExceptionTest
+android.os.cts.PatternMatcherTest
+android.os.cts.RemoteExceptionTest
+
+android.os.storage.StorageManagerBaseTest
+android.os.storage.StorageManagerIntegrationTest
+android.util.LogTest%PerformanceTest
+
+com.android.server.power.stats.BatteryStatsCounterTest
+com.android.server.power.stats.BatteryStatsDualTimerTest
+com.android.server.power.stats.BatteryStatsDurationTimerTest
+com.android.server.power.stats.BatteryStatsSamplingTimerTest
+com.android.server.power.stats.BatteryStatsStopwatchTimerTest
+com.android.server.power.stats.BatteryStatsTimeBaseTest
+com.android.server.power.stats.BatteryStatsTimerTest
+
+    """.trim().replace('%', '$').replace('.', '/')
+        .split(Pattern.compile("""\s+""")).toTypedArray()
+)
+
+private fun isAllowListedLegacyTest(targetClass: ClassNode): Boolean {
+    return allowListedLegacyTests.contains(targetClass.name)
+}
\ 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/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index 89f14b0..268e564 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -86,7 +86,6 @@
     private final Context mContext;
     private final Map<String, Object> mLocks = new WeakHashMap<>();
 
-
     public AppFunctionManagerServiceImpl(@NonNull Context context) {
         this(
                 context,
@@ -201,7 +200,7 @@
         if (mCallerValidator.isUserOrganizationManaged(targetUser)) {
             safeExecuteAppFunctionCallback.onResult(
                     ExecuteAppFunctionResponse.newFailure(
-                            ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+                            ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
                             "Cannot run on a device with a device owner or from the managed"
                                     + " profile.",
                             /* extras= */ null));
@@ -256,7 +255,7 @@
                             if (serviceIntent == null) {
                                 safeExecuteAppFunctionCallback.onResult(
                                         ExecuteAppFunctionResponse.newFailure(
-                                                ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+                                                ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
                                                 "Cannot find the target service.",
                                                 /* extras= */ null));
                                 return;
@@ -449,7 +448,7 @@
             Slog.e(TAG, "Failed to bind to the AppFunctionService");
             safeExecuteAppFunctionCallback.onResult(
                     ExecuteAppFunctionResponse.newFailure(
-                            ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+                            ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
                             "Failed to bind the AppFunctionService.",
                             /* extras= */ null));
         }
@@ -464,7 +463,7 @@
         if (e instanceof CompletionException) {
             e = e.getCause();
         }
-        int resultCode = ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR;
+        int resultCode = ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR;
         if (e instanceof AppSearchException appSearchException) {
             resultCode =
                     mapAppSearchResultFailureCodeToExecuteAppFunctionResponse(
@@ -486,13 +485,13 @@
 
         switch (resultCode) {
             case AppSearchResult.RESULT_NOT_FOUND:
-                return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT;
+                return ExecuteAppFunctionResponse.RESULT_FUNCTION_NOT_FOUND;
             case AppSearchResult.RESULT_INVALID_ARGUMENT:
             case AppSearchResult.RESULT_INTERNAL_ERROR:
             case AppSearchResult.RESULT_SECURITY_ERROR:
                 // fall-through
         }
-        return ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR;
+        return ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR;
     }
 
     private void registerAppSearchObserver(@NonNull TargetUser user) {
@@ -543,12 +542,13 @@
                                     });
         }
     }
+
     /**
      * Retrieves the lock object associated with the given package name.
      *
-     * This method returns the lock object from the {@code mLocks} map if it exists.
-     * If no lock is found for the given package name, a new lock object is created,
-     * stored in the map, and returned.
+     * <p>This method returns the lock object from the {@code mLocks} map if it exists. If no lock
+     * is found for the given package name, a new lock object is created, stored in the map, and
+     * returned.
      */
     @VisibleForTesting
     @NonNull
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index f9abd85..8567ccb 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -17,6 +17,7 @@
 package com.android.server.appwidget;
 
 import static android.appwidget.flags.Flags.remoteAdapterConversion;
+import static android.appwidget.flags.Flags.remoteViewsProto;
 import static android.appwidget.flags.Flags.removeAppWidgetServiceIoFromCriticalPath;
 import static android.appwidget.flags.Flags.securityPolicyInteractAcrossUsers;
 import static android.appwidget.flags.Flags.supportResumeRestoreAfterReboot;
@@ -31,6 +32,7 @@
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.PermissionName;
@@ -104,6 +106,7 @@
 import android.os.UserManager;
 import android.provider.DeviceConfig;
 import android.service.appwidget.AppWidgetServiceDumpProto;
+import android.service.appwidget.GeneratedPreviewsProto;
 import android.service.appwidget.WidgetProto;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -122,7 +125,9 @@
 import android.util.SparseLongArray;
 import android.util.TypedValue;
 import android.util.Xml;
+import android.util.proto.ProtoInputStream;
 import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
 import android.view.Display;
 import android.view.View;
 import android.widget.RemoteViews;
@@ -134,6 +139,7 @@
 import com.android.internal.appwidget.IAppWidgetHost;
 import com.android.internal.appwidget.IAppWidgetService;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.android.internal.infra.AndroidFuture;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.ArrayUtils;
@@ -221,6 +227,10 @@
     // XML attribute for widget ids that are pending deletion.
     // See {@link Provider#pendingDeletedWidgetIds}.
     private static final String PENDING_DELETED_IDS_ATTR = "pending_deleted_ids";
+    // Name of service directory in /data/system_ce/<user>/
+    private static final String APPWIDGET_CE_DATA_DIRNAME = "appwidget";
+    // Name of previews directory in /data/system_ce/<user>/appwidget/
+    private static final String WIDGET_PREVIEWS_DIRNAME = "previews";
 
     // Hard limit of number of hosts an app can create, note that the app that hosts the widgets
     // can have multiple instances of {@link AppWidgetHost}, typically in respect to different
@@ -320,6 +330,9 @@
 
     // Handler to the background thread that saves states to disk.
     private Handler mSaveStateHandler;
+    // Handler to the background thread that saves generated previews to disk. All operations that
+    // modify saved previews must be run on this Handler.
+    private Handler mSavePreviewsHandler;
     // Handler to the foreground thread that handles broadcasts related to user
     // and package events, as well as various internal events within
     // AppWidgetService.
@@ -363,6 +376,7 @@
         } else {
             mSaveStateHandler = BackgroundThread.getHandler();
         }
+        mSavePreviewsHandler = new Handler(BackgroundThread.get().getLooper());
         final ServiceThread serviceThread = new ServiceThread(TAG,
                 android.os.Process.THREAD_PRIORITY_FOREGROUND, false /* allowIo */);
         serviceThread.start();
@@ -382,7 +396,9 @@
                 SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_MAX_PROVIDERS,
                 DEFAULT_GENERATED_PREVIEW_MAX_PROVIDERS);
         mGeneratedPreviewsApiCounter = new ApiCounter(generatedPreviewResetInterval,
-                generatedPreviewMaxCallsPerInterval, generatedPreviewsMaxProviders);
+                generatedPreviewMaxCallsPerInterval,
+                // Set a limit on the number of providers if storing them in memory.
+                remoteViewsProto() ? Integer.MAX_VALUE : generatedPreviewsMaxProviders);
         DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI,
                 new HandlerExecutor(mCallbackHandler), this::handleSystemUiDeviceConfigChange);
 
@@ -648,7 +664,14 @@
         for (int i = 0; i < providerCount; i++) {
             Provider provider = mProviders.get(i);
             if (provider.id.uid == clearedUid) {
-                changed |= provider.clearGeneratedPreviewsLocked();
+                if (remoteViewsProto()) {
+                    changed |= clearGeneratedPreviewsAsync(provider);
+                } else {
+                    changed |= provider.clearGeneratedPreviewsLocked();
+                }
+                if (DEBUG) {
+                    Slog.e(TAG, "clearPreviewsForUidLocked " + provider + " changed " + changed);
+                }
             }
         }
         return changed;
@@ -898,18 +921,30 @@
             for (int j = 0; j < widgetCount; j++) {
                 Widget widget = provider.widgets.get(j);
                 if (targetWidget != null && targetWidget != widget) continue;
+                // Identify the user in the host process since the intent will be invoked by
+                // the host app.
+                final Host host = widget.host;
+                final UserHandle hostUser;
+                if (host != null && host.id != null) {
+                    hostUser = UserHandle.getUserHandleForUid(host.id.uid);
+                } else {
+                    // Fallback to the parent profile if the host is null.
+                    Slog.w(TAG, "Host is null when masking widget: " + widget.appWidgetId);
+                    hostUser = mUserManager.getProfileParent(appUserId).getUserHandle();
+                }
                 if (provider.maskedByStoppedPackage) {
                     Intent intent = createUpdateIntentLocked(provider,
                             new int[] { widget.appWidgetId });
                     views.setOnClickPendingIntent(android.R.id.background,
-                            PendingIntent.getBroadcast(mContext, widget.appWidgetId,
+                            PendingIntent.getBroadcastAsUser(mContext, widget.appWidgetId,
                                     intent, PendingIntent.FLAG_UPDATE_CURRENT
-                                            | PendingIntent.FLAG_IMMUTABLE));
+                                            | PendingIntent.FLAG_IMMUTABLE, hostUser));
                 } else if (onClickIntent != null) {
                     views.setOnClickPendingIntent(android.R.id.background,
-                            PendingIntent.getActivity(mContext, widget.appWidgetId, onClickIntent,
-                                    PendingIntent.FLAG_UPDATE_CURRENT
-                                       | PendingIntent.FLAG_IMMUTABLE));
+                            PendingIntent.getActivityAsUser(mContext, widget.appWidgetId,
+                            onClickIntent, PendingIntent.FLAG_UPDATE_CURRENT
+                                    | PendingIntent.FLAG_IMMUTABLE, null /* options */,
+                            hostUser));
                 }
                 if (widget.replaceWithMaskedViewsLocked(views)) {
                     scheduleNotifyUpdateAppWidgetLocked(widget, widget.getEffectiveViewsLocked());
@@ -1469,9 +1504,7 @@
         mSecurityPolicy.enforceCallFromPackage(callingPackage);
 
         // Check that if a cross-profile binding is attempted, it is allowed.
-        // Cross-profile binding is also allowed if the caller has interact across users permission.
-        if (!mSecurityPolicy.isEnabledGroupProfile(providerProfileId)
-                && !mSecurityPolicy.hasCallerInteractAcrossUsersPermission()) {
+        if (!mSecurityPolicy.isEnabledGroupProfile(providerProfileId)) {
             return false;
         }
 
@@ -2440,10 +2473,8 @@
             Slog.i(TAG, "getInstalledProvidersForProfiles() " + userId);
         }
 
-        // Ensure the profile is in the group and enabled, or that the caller has permission to
-        // interact across users.
-        if (!mSecurityPolicy.isEnabledGroupProfile(profileId)
-                && !mSecurityPolicy.hasCallerInteractAcrossUsersPermission()) {
+        // Ensure the profile is in the group and enabled.
+        if (!mSecurityPolicy.isEnabledGroupProfile(profileId)) {
             return null;
         }
 
@@ -3250,6 +3281,9 @@
         deleteWidgetsLocked(provider, UserHandle.USER_ALL);
         mProviders.remove(provider);
         mGeneratedPreviewsApiCounter.remove(provider.id);
+        if (remoteViewsProto()) {
+            clearGeneratedPreviewsAsync(provider);
+        }
 
         // no need to send the DISABLE broadcast, since the receiver is gone anyway
         cancelBroadcastsLocked(provider);
@@ -3828,6 +3862,14 @@
             } catch (IOException e) {
                 Slog.w(TAG, "Failed to read state: " + e);
             }
+
+            if (remoteViewsProto()) {
+                try {
+                    loadGeneratedPreviewCategoriesLocked(profileId);
+                } catch (IOException e) {
+                    Slog.w(TAG, "Failed to read preview categories: " + e);
+                }
+            }
         }
 
         if (version >= 0) {
@@ -4597,6 +4639,12 @@
                         keep.add(providerId);
                         // Use the new AppWidgetProviderInfo.
                         provider.setPartialInfoLocked(info);
+                        // Clear old previews
+                        if (remoteViewsProto()) {
+                            clearGeneratedPreviewsAsync(provider);
+                        } else {
+                            provider.clearGeneratedPreviewsLocked();
+                        }
                         // If it's enabled
                         final int M = provider.widgets.size();
                         if (M > 0) {
@@ -4888,6 +4936,7 @@
         mSecurityPolicy.enforceCallFromPackage(callingPackage);
         ensureWidgetCategoryCombinationIsValid(widgetCategory);
 
+        AndroidFuture<RemoteViews> result = null;
         synchronized (mLock) {
             ensureGroupStateLoadedLocked(profileId);
             final int providerCount = mProviders.size();
@@ -4921,10 +4970,23 @@
                                 callingPackage);
                 if (providerIsInCallerProfile && !shouldFilterAppAccess
                         && (providerIsInCallerPackage || hasBindAppWidgetPermission)) {
-                    return provider.getGeneratedPreviewLocked(widgetCategory);
+                    if (remoteViewsProto()) {
+                        result = getGeneratedPreviewsAsync(provider, widgetCategory);
+                    } else {
+                        return provider.getGeneratedPreviewLocked(widgetCategory);
+                    }
                 }
             }
         }
+
+        if (result != null) {
+            try {
+                return result.get();
+            } catch (Exception e) {
+                Slog.e(TAG, "Failed to get generated previews Future result", e);
+                return null;
+            }
+        }
         // Either the provider does not exist or the caller does not have permission to access its
         // previews.
         return null;
@@ -4954,8 +5016,12 @@
                         providerComponent + " is not a valid AppWidget provider");
             }
             if (mGeneratedPreviewsApiCounter.tryApiCall(providerId)) {
-                provider.setGeneratedPreviewLocked(widgetCategories, preview);
-                scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+                if (remoteViewsProto()) {
+                    setGeneratedPreviewsAsync(provider, widgetCategories, preview);
+                } else {
+                    provider.setGeneratedPreviewLocked(widgetCategories, preview);
+                    scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+                }
                 return true;
             }
             return false;
@@ -4983,11 +5049,361 @@
                 throw new IllegalArgumentException(
                         providerComponent + " is not a valid AppWidget provider");
             }
-            final boolean changed = provider.removeGeneratedPreviewLocked(widgetCategories);
-            if (changed) scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+
+            if (remoteViewsProto()) {
+                removeGeneratedPreviewsAsync(provider, widgetCategories);
+            } else {
+                final boolean changed = provider.removeGeneratedPreviewLocked(widgetCategories);
+                if (changed) scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+            }
         }
     }
 
+    /**
+     * Return previews for the specified provider from a background thread. The result of the future
+     * is nullable.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+    @NonNull
+    private AndroidFuture<RemoteViews> getGeneratedPreviewsAsync(
+            @NonNull Provider provider, @AppWidgetProviderInfo.CategoryFlags int widgetCategory) {
+        AndroidFuture<RemoteViews> result = new AndroidFuture<>();
+        mSavePreviewsHandler.post(() -> {
+            SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider);
+            for (int i = 0; i < previews.size(); i++) {
+                if ((widgetCategory & previews.keyAt(i)) != 0) {
+                    result.complete(previews.valueAt(i));
+                    return;
+                }
+            }
+            result.complete(null);
+        });
+        return result;
+    }
+
+    /**
+     * Set previews for the specified provider on a background thread.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+    private void setGeneratedPreviewsAsync(@NonNull Provider provider, int widgetCategories,
+            @NonNull RemoteViews preview) {
+        mSavePreviewsHandler.post(() -> {
+            SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider);
+            for (int flag : Provider.WIDGET_CATEGORY_FLAGS) {
+                if ((widgetCategories & flag) != 0) {
+                    previews.put(flag, preview);
+                }
+            }
+            saveGeneratedPreviews(provider, previews, /* notify= */ true);
+        });
+    }
+
+    /**
+     * Remove previews for the specified provider on a background thread.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+    private void removeGeneratedPreviewsAsync(@NonNull Provider provider, int widgetCategories) {
+        mSavePreviewsHandler.post(() -> {
+            SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider);
+            boolean changed = false;
+            for (int flag : Provider.WIDGET_CATEGORY_FLAGS) {
+                if ((widgetCategories & flag) != 0) {
+                    changed |= previews.removeReturnOld(flag) != null;
+                }
+            }
+            if (changed) {
+                saveGeneratedPreviews(provider, previews, /* notify= */ true);
+            }
+        });
+    }
+
+    /**
+     * Clear previews for the specified provider on a background thread. Returns true if changed
+     * (i.e. there are previews to clear). If returns true, the caller should schedule a providers
+     * changed notification.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+    private boolean clearGeneratedPreviewsAsync(@NonNull Provider provider) {
+        mSavePreviewsHandler.post(() -> {
+            saveGeneratedPreviews(provider, /* previews= */ null, /* notify= */ false);
+        });
+        return provider.info.generatedPreviewCategories != 0;
+    }
+
+    private void checkSavePreviewsThread() {
+        if (DEBUG && !mSavePreviewsHandler.getLooper().isCurrentThread()) {
+            throw new IllegalStateException("Only modify previews on the background thread");
+        }
+    }
+
+    /**
+     * Load previews from file for the given provider. If there are no previews, returns an empty
+     * SparseArray. Else, returns a SparseArray of the previews mapped by widget category.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+    @NonNull
+    private SparseArray<RemoteViews> loadGeneratedPreviews(@NonNull Provider provider) {
+        checkSavePreviewsThread();
+        try {
+            AtomicFile previewsFile = getWidgetPreviewsFile(provider);
+            if (!previewsFile.exists()) {
+                return new SparseArray<>();
+            }
+            ProtoInputStream input = new ProtoInputStream(previewsFile.readFully());
+            SparseArray<RemoteViews> entries = readGeneratedPreviewsFromProto(input);
+            SparseArray<RemoteViews> singleCategoryKeyedEntries = new SparseArray<>();
+            for (int i = 0; i < entries.size(); i++) {
+                int widgetCategories = entries.keyAt(i);
+                RemoteViews preview = entries.valueAt(i);
+                for (int flag : Provider.WIDGET_CATEGORY_FLAGS) {
+                    if ((widgetCategories & flag) != 0) {
+                        singleCategoryKeyedEntries.put(flag, preview);
+                    }
+                }
+            }
+            return singleCategoryKeyedEntries;
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to load generated previews for " + provider, e);
+            return new SparseArray<>();
+        }
+    }
+
+    /**
+     * This is called when loading profile/group state to populate
+     * AppWidgetProviderInfo.generatedPreviewCategories based on what previews are saved.
+     *
+     * This is the only time previews are read while not on mSavePreviewsHandler. It happens once
+     * per profile during initialization, before any calls to get/set/removeWidgetPreviewAsync
+     * happen for that profile.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+    @GuardedBy("mLock")
+    private void loadGeneratedPreviewCategoriesLocked(int profileId) throws IOException {
+        for (Provider provider : mProviders) {
+            if (provider.id.getProfile().getIdentifier() != profileId) {
+                continue;
+            }
+            AtomicFile previewsFile = getWidgetPreviewsFile(provider);
+            if (!previewsFile.exists()) {
+                continue;
+            }
+            ProtoInputStream input = new ProtoInputStream(previewsFile.readFully());
+            provider.info.generatedPreviewCategories = readGeneratedPreviewCategoriesFromProto(
+                    input);
+            if (DEBUG) {
+                Slog.i(TAG, TextUtils.formatSimple(
+                        "loadGeneratedPreviewCategoriesLocked %d %s categories %d", profileId,
+                        provider, provider.info.generatedPreviewCategories));
+            }
+        }
+    }
+
+    /**
+     * Save the given previews into storage.
+     *
+     * @param provider Provider for which to save previews
+     * @param previews Previews to save. If null or empty, clears any saved previews for this
+     *                 provider.
+     * @param notify If true, then this function will notify hosts of updated provider info.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+    private void saveGeneratedPreviews(@NonNull Provider provider,
+            @Nullable SparseArray<RemoteViews> previews, boolean notify) {
+        checkSavePreviewsThread();
+        AtomicFile file = null;
+        FileOutputStream stream = null;
+        try {
+            file = getWidgetPreviewsFile(provider);
+            if (previews == null || previews.size() == 0) {
+                if (file.exists()) {
+                    if (DEBUG) {
+                        Slog.i(TAG, "Deleting widget preview file " + file);
+                    }
+                    file.delete();
+                }
+            } else {
+                if (DEBUG) {
+                    Slog.i(TAG, "Writing widget preview file " + file);
+                }
+                ProtoOutputStream out = new ProtoOutputStream();
+                writePreviewsToProto(out, previews);
+                stream = file.startWrite();
+                stream.write(out.getBytes());
+                file.finishWrite(stream);
+            }
+
+            synchronized (mLock) {
+                provider.updateGeneratedPreviewCategoriesLocked(previews);
+                if (notify) {
+                    scheduleNotifyGroupHostsForProvidersChangedLocked(provider.getUserId());
+                }
+            }
+        } catch (IOException e) {
+            if (file != null && stream != null) {
+                file.failWrite(stream);
+            }
+            Slog.w(TAG, "Failed to save widget previews for provider " + provider.id.componentName);
+        }
+    }
+
+
+    /**
+     * Write the given previews as a GeneratedPreviewsProto to the output stream.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+    private void writePreviewsToProto(@NonNull ProtoOutputStream out,
+            @NonNull SparseArray<RemoteViews> generatedPreviews) {
+        // Collect RemoteViews mapped by hashCode in order to avoid writing duplicates.
+        SparseArray<Pair<Integer, RemoteViews>> previewsToWrite = new SparseArray<>();
+        for (int i = 0; i < generatedPreviews.size(); i++) {
+            int widgetCategory = generatedPreviews.keyAt(i);
+            RemoteViews views = generatedPreviews.valueAt(i);
+            if (!previewsToWrite.contains(views.hashCode())) {
+                previewsToWrite.put(views.hashCode(), new Pair<>(widgetCategory, views));
+            } else {
+                Pair<Integer, RemoteViews> entry = previewsToWrite.get(views.hashCode());
+                previewsToWrite.put(views.hashCode(),
+                        Pair.create(entry.first | widgetCategory, views));
+            }
+        }
+
+        for (int i = 0; i < previewsToWrite.size(); i++) {
+            final long token = out.start(GeneratedPreviewsProto.PREVIEWS);
+            Pair<Integer, RemoteViews> entry = previewsToWrite.valueAt(i);
+            out.write(GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES, entry.first);
+            final long viewsToken = out.start(GeneratedPreviewsProto.Preview.VIEWS);
+            entry.second.writePreviewToProto(mContext, out);
+            out.end(viewsToken);
+            out.end(token);
+        }
+    }
+
+    /**
+     * Read a GeneratedPreviewsProto message from the input stream.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+    @NonNull
+    private SparseArray<RemoteViews> readGeneratedPreviewsFromProto(@NonNull ProtoInputStream input)
+            throws IOException {
+        SparseArray<RemoteViews> entries = new SparseArray<>();
+        while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (input.getFieldNumber()) {
+                case (int) GeneratedPreviewsProto.PREVIEWS:
+                    final long token = input.start(GeneratedPreviewsProto.PREVIEWS);
+                    Pair<Integer, RemoteViews> entry = readSinglePreviewFromProto(input,
+                            /* skipViews= */ false);
+                    entries.put(entry.first, entry.second);
+                    input.end(token);
+                    break;
+                default:
+                    Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! "
+                            + ProtoUtils.currentFieldToString(input));
+            }
+        }
+        return entries;
+    }
+
+    /**
+     * Read the widget categories from GeneratedPreviewsProto and return an int representing the
+     * combined widget categories of all the previews.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+    @AppWidgetProviderInfo.CategoryFlags
+    private int readGeneratedPreviewCategoriesFromProto(@NonNull ProtoInputStream input)
+            throws IOException {
+        int widgetCategories = 0;
+        while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (input.getFieldNumber()) {
+                case (int) GeneratedPreviewsProto.PREVIEWS:
+                    final long token = input.start(GeneratedPreviewsProto.PREVIEWS);
+                    Pair<Integer, RemoteViews> entry = readSinglePreviewFromProto(input,
+                            /* skipViews= */ true);
+                    widgetCategories |= entry.first;
+                    input.end(token);
+                    break;
+                default:
+                    Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! "
+                            + ProtoUtils.currentFieldToString(input));
+            }
+        }
+        return widgetCategories;
+    }
+
+    /**
+     * Read a single GeneratedPreviewsProto.Preview message from the input stream, and returns a
+     * pair of widget category and corresponding RemoteViews. If skipViews is true, this function
+     * will only read widget categories and the returned RemoteViews will be null.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+    @NonNull
+    private Pair<Integer, RemoteViews> readSinglePreviewFromProto(@NonNull ProtoInputStream input,
+            boolean skipViews) throws IOException {
+        int widgetCategories = 0;
+        RemoteViews views = null;
+        while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (input.getFieldNumber()) {
+                case (int) GeneratedPreviewsProto.Preview.VIEWS:
+                    if (skipViews)  {
+                        // ProtoInputStream will skip over the nested message when nextField() is
+                        // called.
+                        continue;
+                    }
+                    final long token = input.start(GeneratedPreviewsProto.Preview.VIEWS);
+                    try {
+                        views = RemoteViews.createPreviewFromProto(mContext, input);
+                    } catch (Exception e) {
+                        Slog.e(TAG, "Unable to deserialize RemoteViews", e);
+                    }
+                    input.end(token);
+                    break;
+                case (int) GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES:
+                    widgetCategories = input.readInt(
+                            GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES);
+                    break;
+                default:
+                    Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! "
+                            + ProtoUtils.currentFieldToString(input));
+            }
+        }
+        return Pair.create(widgetCategories, views);
+    }
+
+    /**
+     * Returns the file in which all generated previews for this provider are stored. This will be
+     * a path of the form:
+     *  {@literal /data/system_ce/<userId>/appwidget/previews/<package>-<class>-<uid>.binpb}
+     *
+     * This function will not create the file if it does not already exist.
+     */
+    @NonNull
+    private static AtomicFile getWidgetPreviewsFile(@NonNull Provider provider) throws IOException {
+        int userId = provider.getUserId();
+        File previewsDirectory = getWidgetPreviewsDirectory(userId);
+        File providerPreviews = Environment.buildPath(previewsDirectory,
+                TextUtils.formatSimple("%s-%s-%d.binpb", provider.id.componentName.getPackageName(),
+                        provider.id.componentName.getClassName(), provider.id.uid));
+        return new AtomicFile(providerPreviews);
+    }
+
+    /**
+     * Returns the widget previews directory for the given user, creating it if it does not exist.
+     * This will be a path of the form:
+     *  {@literal /data/system_ce/<userId>/appwidget/previews}
+     */
+    @NonNull
+    private static File getWidgetPreviewsDirectory(int userId) throws IOException {
+        File dataSystemCeDirectory = Environment.getDataSystemCeDirectory(userId);
+        File previewsDirectory = Environment.buildPath(dataSystemCeDirectory,
+                APPWIDGET_CE_DATA_DIRNAME, WIDGET_PREVIEWS_DIRNAME);
+        if (!previewsDirectory.exists()) {
+            if (!previewsDirectory.mkdirs()) {
+                throw new IOException("Unable to create widget preview directory "
+                        + previewsDirectory.getPath());
+            }
+        }
+        return previewsDirectory;
+    }
+
     private static void ensureWidgetCategoryCombinationIsValid(int widgetCategories) {
         int validCategories = AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN
                 | AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD
@@ -5235,11 +5651,14 @@
                 return true;
             }
             final int userId = UserHandle.getUserId(uid);
-            if ((widget.host.getUserId() == userId || (widget.provider != null
-                    && widget.provider.getUserId() == userId))
+            if ((widget.host.getUserId() == userId
+                    || (widget.provider != null && widget.provider.getUserId() == userId)
+                    || hasCallerInteractAcrossUsersPermission())
                     && callerHasPermission(android.Manifest.permission.BIND_APPWIDGET)) {
-                // Apps that run in the same user as either the host or the provider and
-                // have the bind widget permission have access to the widget.
+                // Access to the widget requires the app to:
+                // - Run in the same user as the host or provider, or have permission to interact
+                //   across users
+                // - Have bind widget permission
                 return true;
             }
             if (DEBUG) {
@@ -5260,16 +5679,12 @@
          * The provider is accessible by the caller if any of the following is true:
          * - The provider belongs to the caller
          * - The provider belongs to a profile of the caller and is allowlisted
-         * - The caller has permission to interact across users
          */
         public boolean canAccessProvider(String packageName, int profileId) {
             final int callerId = UserHandle.getCallingUserId();
             if (profileId == callerId) {
                 return true;
             }
-            if (hasCallerInteractAcrossUsersPermission()) {
-                return true;
-            }
             final int parentId = getProfileParent(profileId);
             if (parentId != callerId) {
                 return false;
@@ -5419,11 +5834,11 @@
                                 AppWidgetManager.META_DATA_APPWIDGET_PROVIDER);
                     }
                     if (newInfo != null) {
+                        newInfo.generatedPreviewCategories = info.generatedPreviewCategories;
                         info = newInfo;
                         if (DEBUG) {
                             Objects.requireNonNull(info);
                         }
-                        updateGeneratedPreviewCategoriesLocked();
                     }
                 }
                 mInfoParsed = true;
@@ -5480,7 +5895,7 @@
                     generatedPreviews.put(flag, preview);
                 }
             }
-            updateGeneratedPreviewCategoriesLocked();
+            updateGeneratedPreviewCategoriesLocked(generatedPreviews);
         }
 
         @GuardedBy("this.mLock")
@@ -5492,7 +5907,7 @@
                 }
             }
             if (changed) {
-                updateGeneratedPreviewCategoriesLocked();
+                updateGeneratedPreviewCategoriesLocked(generatedPreviews);
             }
             return changed;
         }
@@ -5501,17 +5916,19 @@
         public boolean clearGeneratedPreviewsLocked() {
             if (generatedPreviews.size() > 0) {
                 generatedPreviews.clear();
-                updateGeneratedPreviewCategoriesLocked();
+                updateGeneratedPreviewCategoriesLocked(generatedPreviews);
                 return true;
             }
             return false;
         }
-
         @GuardedBy("this.mLock")
-        private void updateGeneratedPreviewCategoriesLocked() {
+        private void updateGeneratedPreviewCategoriesLocked(
+                @Nullable SparseArray<RemoteViews> previews) {
             info.generatedPreviewCategories = 0;
-            for (int i = 0; i < generatedPreviews.size(); i++) {
-                info.generatedPreviewCategories |= generatedPreviews.keyAt(i);
+            if (previews != null) {
+                for (int i = 0; i < previews.size(); i++) {
+                    info.generatedPreviewCategories |= previews.keyAt(i);
+                }
             }
         }
 
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 51034d2..7cba9e0 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -31,16 +31,13 @@
 
 import static com.android.internal.util.CollectionUtils.any;
 import static com.android.internal.util.Preconditions.checkState;
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
-import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
 import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
 import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
 import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOr;
 import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
 
 import static java.util.Objects.requireNonNull;
-import static java.util.concurrent.TimeUnit.MINUTES;
 
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
@@ -69,31 +66,22 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.net.MacAddress;
-import android.net.NetworkPolicyManager;
 import android.os.Binder;
-import android.os.Environment;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.PowerExemptionManager;
 import android.os.PowerManagerInternal;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.permission.flags.Flags;
-import android.util.ArraySet;
 import android.util.ExceptionUtils;
 import android.util.Slog;
 
-import com.android.internal.app.IAppOpsService;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.notification.NotificationAccessConfirmationActivityContract;
-import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.server.FgThread;
@@ -114,35 +102,25 @@
 import com.android.server.companion.devicepresence.ObservableUuid;
 import com.android.server.companion.devicepresence.ObservableUuidStore;
 import com.android.server.companion.transport.CompanionTransportManager;
-import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
-import java.io.File;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Collection;
 import java.util.List;
-import java.util.Set;
 
 @SuppressLint("LongLogTag")
 public class CompanionDeviceManagerService extends SystemService {
     private static final String TAG = "CDM_CompanionDeviceManagerService";
 
     private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min
-
-    private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
-    private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done";
     private static final int MAX_CN_LENGTH = 500;
 
-    private final ActivityTaskManagerInternal mAtmInternal;
-    private final ActivityManagerInternal mAmInternal;
-    private final IAppOpsService mAppOpsManager;
-    private final PowerExemptionManager mPowerExemptionManager;
-    private final PackageManagerInternal mPackageManagerInternal;
-
     private final AssociationStore mAssociationStore;
     private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
     private final ObservableUuidStore mObservableUuidStore;
+
+    private final CompanionExemptionProcessor mCompanionExemptionProcessor;
     private final AssociationRequestsProcessor mAssociationRequestsProcessor;
     private final SystemDataTransferProcessor mSystemDataTransferProcessor;
     private final BackupRestoreProcessor mBackupRestoreProcessor;
@@ -156,12 +134,15 @@
         super(context);
 
         final ActivityManager activityManager = context.getSystemService(ActivityManager.class);
-        mPowerExemptionManager = context.getSystemService(PowerExemptionManager.class);
-        mAppOpsManager = IAppOpsService.Stub.asInterface(
-                ServiceManager.getService(Context.APP_OPS_SERVICE));
-        mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
-        mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
-        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+        final PowerExemptionManager powerExemptionManager = context.getSystemService(
+                PowerExemptionManager.class);
+        final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+        final ActivityTaskManagerInternal atmInternal = LocalServices.getService(
+                ActivityTaskManagerInternal.class);
+        final ActivityManagerInternal amInternal = LocalServices.getService(
+                ActivityManagerInternal.class);
+        final PackageManagerInternal packageManagerInternal = LocalServices.getService(
+                PackageManagerInternal.class);
         final UserManager userManager = context.getSystemService(UserManager.class);
         final PowerManagerInternal powerManagerInternal = LocalServices.getService(
                 PowerManagerInternal.class);
@@ -173,25 +154,29 @@
 
         // Init processors
         mAssociationRequestsProcessor = new AssociationRequestsProcessor(context,
-                mPackageManagerInternal, mAssociationStore);
-        mBackupRestoreProcessor = new BackupRestoreProcessor(context, mPackageManagerInternal,
+                packageManagerInternal, mAssociationStore);
+        mBackupRestoreProcessor = new BackupRestoreProcessor(context, packageManagerInternal,
                 mAssociationStore, associationDiskStore, mSystemDataTransferRequestStore,
                 mAssociationRequestsProcessor);
 
         mCompanionAppBinder = new CompanionAppBinder(context);
 
+        mCompanionExemptionProcessor = new CompanionExemptionProcessor(context,
+                powerExemptionManager, appOpsManager, packageManagerInternal, atmInternal,
+                amInternal, mAssociationStore);
+
         mDevicePresenceProcessor = new DevicePresenceProcessor(context,
                 mCompanionAppBinder, userManager, mAssociationStore, mObservableUuidStore,
-                powerManagerInternal);
+                powerManagerInternal, mCompanionExemptionProcessor);
 
         mTransportManager = new CompanionTransportManager(context, mAssociationStore);
 
         mDisassociationProcessor = new DisassociationProcessor(context, activityManager,
-                mAssociationStore, mPackageManagerInternal, mDevicePresenceProcessor,
+                mAssociationStore, packageManagerInternal, mDevicePresenceProcessor,
                 mCompanionAppBinder, mSystemDataTransferRequestStore, mTransportManager);
 
         mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
-                mPackageManagerInternal, mAssociationStore,
+                packageManagerInternal, mAssociationStore,
                 mSystemDataTransferRequestStore, mTransportManager);
 
         // TODO(b/279663946): move context sync to a dedicated system service
@@ -202,7 +187,6 @@
     public void onStart() {
         // Init association stores
         mAssociationStore.refreshCache();
-        mAssociationStore.registerLocalListener(mAssociationStoreChangeListener);
 
         // Init UUID store
         mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId());
@@ -240,11 +224,8 @@
 
         if (associations.isEmpty()) return;
 
-        updateAtm(userId, associations);
-
-        BackgroundThread.getHandler().sendMessageDelayed(
-                obtainMessage(CompanionDeviceManagerService::maybeGrantAutoRevokeExemptions, this),
-                MINUTES.toMillis(10));
+        mCompanionExemptionProcessor.updateAtm(userId, associations);
+        mCompanionExemptionProcessor.updateAutoRevokeExemptions();
     }
 
     @Override
@@ -262,9 +243,12 @@
         if (!associationsForPackage.isEmpty()) {
             Slog.i(TAG, "Package removed or data cleared for user=[" + userId + "], package=["
                     + packageName + "]. Cleaning up CDM data...");
-        }
-        for (AssociationInfo association : associationsForPackage) {
-            mDisassociationProcessor.disassociate(association.getId());
+
+            for (AssociationInfo association : associationsForPackage) {
+                mDisassociationProcessor.disassociate(association.getId());
+            }
+
+            mCompanionAppBinder.onPackageChanged(userId);
         }
 
         // Clear observable UUIDs for the package.
@@ -273,19 +257,16 @@
         for (ObservableUuid uuid : uuidsTobeObserved) {
             mObservableUuidStore.removeObservableUuid(userId, uuid.getUuid(), packageName);
         }
-
-        mCompanionAppBinder.onPackagesChanged(userId);
     }
 
     private void onPackageModifiedInternal(@UserIdInt int userId, @NonNull String packageName) {
-        final List<AssociationInfo> associationsForPackage =
+        final List<AssociationInfo> associations =
                 mAssociationStore.getAssociationsByPackage(userId, packageName);
-        for (AssociationInfo association : associationsForPackage) {
-            updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
-                    association.getPackageName());
-        }
+        if (!associations.isEmpty()) {
+            mCompanionExemptionProcessor.exemptPackage(userId, packageName, false);
 
-        mCompanionAppBinder.onPackagesChanged(userId);
+            mCompanionAppBinder.onPackageChanged(userId);
+        }
     }
 
     private void onPackageAddedInternal(@UserIdInt int userId, @NonNull String packageName) {
@@ -765,130 +746,6 @@
         }
     }
 
-    /**
-     * Update special access for the association's package
-     */
-    public void updateSpecialAccessPermissionForAssociatedPackage(int userId, String packageName) {
-        final PackageInfo packageInfo =
-                getPackageInfo(getContext(), userId, packageName);
-
-        Binder.withCleanCallingIdentity(() -> updateSpecialAccessPermissionAsSystem(packageInfo));
-    }
-
-    private void updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo) {
-        if (packageInfo == null) {
-            return;
-        }
-
-        if (containsEither(packageInfo.requestedPermissions,
-                android.Manifest.permission.RUN_IN_BACKGROUND,
-                android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) {
-            mPowerExemptionManager.addToPermanentAllowList(packageInfo.packageName);
-        } else {
-            try {
-                mPowerExemptionManager.removeFromPermanentAllowList(packageInfo.packageName);
-            } catch (UnsupportedOperationException e) {
-                Slog.w(TAG, packageInfo.packageName + " can't be removed from power save"
-                        + " whitelist. It might due to the package is whitelisted by the system.");
-            }
-        }
-
-        NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(getContext());
-        try {
-            if (containsEither(packageInfo.requestedPermissions,
-                    android.Manifest.permission.USE_DATA_IN_BACKGROUND,
-                    android.Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND)) {
-                networkPolicyManager.addUidPolicy(
-                        packageInfo.applicationInfo.uid,
-                        NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
-            } else {
-                networkPolicyManager.removeUidPolicy(
-                        packageInfo.applicationInfo.uid,
-                        NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
-            }
-        } catch (IllegalArgumentException e) {
-            Slog.e(TAG, e.getMessage());
-        }
-
-        exemptFromAutoRevoke(packageInfo.packageName, packageInfo.applicationInfo.uid);
-    }
-
-    private void exemptFromAutoRevoke(String packageName, int uid) {
-        try {
-            mAppOpsManager.setMode(
-                    AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
-                    uid,
-                    packageName,
-                    AppOpsManager.MODE_IGNORED);
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Error while granting auto revoke exemption for " + packageName, e);
-        }
-    }
-
-    private void updateAtm(int userId, List<AssociationInfo> associations) {
-        final Set<Integer> companionAppUids = new ArraySet<>();
-        for (AssociationInfo association : associations) {
-            final int uid = mPackageManagerInternal.getPackageUid(association.getPackageName(),
-                    0, userId);
-            if (uid >= 0) {
-                companionAppUids.add(uid);
-            }
-        }
-        if (mAtmInternal != null) {
-            mAtmInternal.setCompanionAppUids(userId, companionAppUids);
-        }
-        if (mAmInternal != null) {
-            // Make a copy of the set and send it to ActivityManager.
-            mAmInternal.setCompanionAppUids(userId, new ArraySet<>(companionAppUids));
-        }
-    }
-
-    private void maybeGrantAutoRevokeExemptions() {
-        Slog.d(TAG, "maybeGrantAutoRevokeExemptions()");
-
-        PackageManager pm = getContext().getPackageManager();
-        for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) {
-            SharedPreferences pref = getContext().getSharedPreferences(
-                    new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME),
-                    Context.MODE_PRIVATE);
-            if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) {
-                continue;
-            }
-
-            try {
-                final List<AssociationInfo> associations =
-                        mAssociationStore.getActiveAssociationsByUser(userId);
-                for (AssociationInfo a : associations) {
-                    try {
-                        int uid = pm.getPackageUidAsUser(a.getPackageName(), userId);
-                        exemptFromAutoRevoke(a.getPackageName(), uid);
-                    } catch (PackageManager.NameNotFoundException e) {
-                        Slog.w(TAG, "Unknown companion package: " + a.getPackageName(), e);
-                    }
-                }
-            } finally {
-                pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply();
-            }
-        }
-    }
-
-    private final AssociationStore.OnChangeListener mAssociationStoreChangeListener =
-            new AssociationStore.OnChangeListener() {
-                @Override
-                public void onAssociationChanged(int changeType, AssociationInfo association) {
-                    Slog.d(TAG, "onAssociationChanged changeType=[" + changeType
-                            + "], association=[" + association);
-
-                    final int userId = association.getUserId();
-                    final List<AssociationInfo> updatedAssociations =
-                            mAssociationStore.getActiveAssociationsByUser(userId);
-
-                    updateAtm(userId, updatedAssociations);
-                    updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
-                            association.getPackageName());
-                }
-            };
-
     private final PackageMonitor mPackageMonitor = new PackageMonitor() {
         @Override
         public void onPackageRemoved(String packageName, int uid) {
@@ -911,10 +768,6 @@
         }
     };
 
-    private static <T> boolean containsEither(T[] array, T a, T b) {
-        return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b);
-    }
-
     private class LocalService implements CompanionDeviceManagerServiceInternal {
 
         @Override
diff --git a/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java b/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java
new file mode 100644
index 0000000..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/CameraAccessController.java b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
index ef39846..8a4b1fa 100644
--- a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
+++ b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
@@ -59,14 +59,14 @@
     private int mObserverCount = 0;
 
     @GuardedBy("mLock")
-    private ArrayMap<String, InjectionSessionData> mPackageToSessionData = new ArrayMap<>();
+    private final ArrayMap<String, InjectionSessionData> mPackageToSessionData = new ArrayMap<>();
 
     /**
      * Mapping from camera ID to open camera app associations. Key is the camera id, value is the
      * information of the app's uid and package name.
      */
     @GuardedBy("mLock")
-    private ArrayMap<String, OpenCameraInfo> mAppsToBlockOnVirtualDevice = new ArrayMap<>();
+    private final ArrayMap<String, OpenCameraInfo> mAppsToBlockOnVirtualDevice = new ArrayMap<>();
 
     static class InjectionSessionData {
         public int appUid;
@@ -179,6 +179,15 @@
                 Slog.w(TAG, "Unexpected close with observers remaining: " + mObserverCount);
             }
         }
+        // Clean up camera injection sessions (if any).
+        synchronized (mLock) {
+            for (InjectionSessionData sessionData : mPackageToSessionData.values()) {
+                for (CameraInjectionSession session : sessionData.cameraIdToSession.values()) {
+                    session.close();
+                }
+            }
+            mPackageToSessionData.clear();
+        }
         mCameraManager.unregisterAvailabilityCallback(this);
     }
 
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 281a2ce..8b5b93e 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -1465,28 +1465,28 @@
     public int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
             @NonNull IVirtualDisplayCallback callback) {
         checkCallerIsDeviceOwner();
+
+        int displayId;
+        boolean showPointer;
+        boolean isTrustedDisplay;
         GenericWindowPolicyController gwpc;
         synchronized (mVirtualDeviceLock) {
             gwpc = createWindowPolicyControllerLocked(virtualDisplayConfig.getDisplayCategories());
-        }
-        int displayId;
-        displayId = mDisplayManagerInternal.createVirtualDisplay(virtualDisplayConfig, callback,
-                this, gwpc, mOwnerPackageName);
-        boolean isMirrorDisplay =
-                mDisplayManagerInternal.getDisplayIdToMirror(displayId) != Display.INVALID_DISPLAY;
-        gwpc.setDisplayId(displayId, isMirrorDisplay);
-        boolean isTrustedDisplay =
-                (mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED)
-                        == Display.FLAG_TRUSTED;
-        if (!isTrustedDisplay) {
-            if (getDevicePolicy(POLICY_TYPE_CLIPBOARD) != DEVICE_POLICY_DEFAULT) {
-                throw new SecurityException("All displays must be trusted for devices with custom"
-                        + "clipboard policy.");
+            displayId = mDisplayManagerInternal.createVirtualDisplay(virtualDisplayConfig,
+                    callback, this, gwpc, mOwnerPackageName);
+            boolean isMirrorDisplay =
+                    mDisplayManagerInternal.getDisplayIdToMirror(displayId)
+                            != Display.INVALID_DISPLAY;
+            gwpc.setDisplayId(displayId, isMirrorDisplay);
+            isTrustedDisplay =
+                    (mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED)
+                            == Display.FLAG_TRUSTED;
+            if (!isTrustedDisplay
+                    && getDevicePolicy(POLICY_TYPE_CLIPBOARD) != DEVICE_POLICY_DEFAULT) {
+                throw new SecurityException("All displays must be trusted for devices with "
+                        + "custom clipboard policy.");
             }
-        }
 
-        boolean showPointer;
-        synchronized (mVirtualDeviceLock) {
             if (mVirtualDisplays.contains(displayId)) {
                 gwpc.unregisterRunningAppsChangedListener(this);
                 throw new IllegalStateException(
@@ -1523,6 +1523,9 @@
     }
 
     private PowerManager.WakeLock createAndAcquireWakeLockForDisplay(int displayId) {
+        if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()) {
+            return null;
+        }
         final long token = Binder.clearCallingIdentity();
         try {
             PowerManager powerManager = mContext.getSystemService(PowerManager.class);
@@ -1681,6 +1684,14 @@
         return mOwnerUid;
     }
 
+    long getDimDurationMillis() {
+        return mParams.getDimDuration().toMillis();
+    }
+
+    long getScreenOffTimeoutMillis() {
+        return mParams.getScreenOffTimeout().toMillis();
+    }
+
     @Override  // Binder call
     public int[] getDisplayIds() {
         synchronized (mVirtualDeviceLock) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index f87e3c3..6729231d 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.companion.virtual;
 
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS;
 import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
 
 import static com.android.server.wm.ActivityInterceptorCallback.VIRTUAL_DEVICE_SERVICE_ORDERED_ID;
@@ -27,6 +28,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.app.ActivityOptions;
+import android.app.compat.CompatChanges;
 import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
 import android.companion.CompanionDeviceManager;
@@ -41,11 +43,14 @@
 import android.companion.virtual.flags.Flags;
 import android.companion.virtual.sensor.VirtualSensor;
 import android.companion.virtualnative.IVirtualDeviceManagerNative;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.AttributionSource;
 import android.content.Context;
 import android.content.Intent;
 import android.hardware.display.DisplayManagerInternal;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.LocaleList;
@@ -88,7 +93,6 @@
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
-
 @SuppressLint("LongLogTag")
 public class VirtualDeviceManagerService extends SystemService {
 
@@ -101,6 +105,11 @@
             AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
             AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING);
 
+    /** Enable default device camera access for apps running on virtual devices. */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    public static final long ENABLE_DEFAULT_DEVICE_CAMERA_ACCESS = 371173368L;
+
     /**
      * A virtual device association id corresponding to no CDM association.
      */
@@ -110,7 +119,7 @@
     private final VirtualDeviceManagerImpl mImpl;
     private final VirtualDeviceManagerNativeImpl mNativeImpl;
     private final VirtualDeviceManagerInternal mLocalService;
-    private VirtualDeviceLog mVirtualDeviceLog = new VirtualDeviceLog(getContext());
+    private final VirtualDeviceLog mVirtualDeviceLog = new VirtualDeviceLog(getContext());
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler);
 
@@ -236,7 +245,7 @@
         }
     }
 
-    void onCameraAccessBlocked(int appUid) {
+    private void onCameraAccessBlocked(int appUid) {
         ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
         for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
             VirtualDeviceImpl virtualDevice = virtualDevicesSnapshot.get(i);
@@ -248,8 +257,13 @@
         }
     }
 
-    CameraAccessController getCameraAccessController(UserHandle userHandle) {
-        if (Flags.streamCamera()) {
+    private CameraAccessController getCameraAccessController(UserHandle userHandle,
+            VirtualDeviceParams params, String callingPackage) {
+        if (CompatChanges.isChangeEnabled(ENABLE_DEFAULT_DEVICE_CAMERA_ACCESS, callingPackage,
+                userHandle)
+                && android.companion.virtualdevice.flags.Flags.defaultDeviceCameraAccessPolicy()
+                && (params.getDevicePolicy(POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS)
+                    == DEVICE_POLICY_DEFAULT)) {
             return null;
         }
         int userId = userHandle.getIdentifier();
@@ -496,7 +510,8 @@
 
             final UserHandle userHandle = getCallingUserHandle();
             final CameraAccessController cameraAccessController =
-                    getCameraAccessController(userHandle);
+                    getCameraAccessController(userHandle, params,
+                            attributionSource.getPackageName());
             final int deviceId = sNextUniqueIndex.getAndIncrement();
             final Consumer<ArraySet<Integer>> runningAppsChangedCallback =
                     runningUids -> notifyRunningAppsChanged(deviceId, runningUids);
@@ -576,7 +591,6 @@
             }
         }
 
-
         @Override // Binder call
         public int getDeviceIdForDisplayId(int displayId) {
             if (displayId == Display.INVALID_DISPLAY || displayId == Display.DEFAULT_DISPLAY) {
@@ -911,6 +925,22 @@
         }
 
         @Override
+        public long getDimDurationMillisForDeviceId(int deviceId) {
+            synchronized (mVirtualDeviceManagerLock) {
+                VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId);
+                return virtualDevice == null ? -1 : virtualDevice.getDimDurationMillis();
+            }
+        }
+
+        @Override
+        public long getScreenOffTimeoutMillisForDeviceId(int deviceId) {
+            synchronized (mVirtualDeviceManagerLock) {
+                VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId);
+                return virtualDevice == null ? -1 : virtualDevice.getScreenOffTimeoutMillis();
+            }
+        }
+
+        @Override
         public boolean isValidVirtualDeviceId(int deviceId) {
             return mImpl.isValidVirtualDeviceId(deviceId);
         }
diff --git a/services/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/SerialService.java b/services/core/java/com/android/server/SerialService.java
index dbf144f..95ae11e 100644
--- a/services/core/java/com/android/server/SerialService.java
+++ b/services/core/java/com/android/server/SerialService.java
@@ -18,22 +18,30 @@
 
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.ISerialManager;
 import android.hardware.SerialManagerInternal;
 import android.os.ParcelFileDescriptor;
 import android.os.PermissionEnforcer;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.function.Supplier;
 
 @android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class SerialService extends ISerialManager.Stub {
+    private static final String TAG = "SerialService";
+
     private final Context mContext;
 
     @GuardedBy("mSerialPorts")
@@ -50,7 +58,7 @@
             final String[] serialPorts = getSerialPorts(context);
             for (String serialPort : serialPorts) {
                 mSerialPorts.put(serialPort, () -> {
-                    return native_open(serialPort);
+                    return tryOpen(serialPort);
                 });
             }
         }
@@ -130,5 +138,14 @@
         }
     };
 
-    private native ParcelFileDescriptor native_open(String path);
+    private static @Nullable ParcelFileDescriptor tryOpen(String path) {
+        try {
+            FileDescriptor fd = Os.open(path, OsConstants.O_RDWR | OsConstants.O_NOCTTY, 0);
+            return new ParcelFileDescriptor(fd);
+        } catch (ErrnoException e) {
+            Slog.e(TAG, "Could not open: " + path, e);
+            // We return null to preserve API semantics from earlier implementation variants.
+            return null;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/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/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 363807d..72a9a2d 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -102,6 +102,7 @@
 import com.android.internal.telephony.ICarrierPrivilegesCallback;
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
 import com.android.internal.telephony.IPhoneStateListener;
+import com.android.internal.telephony.ISatelliteStateChangeListener;
 import com.android.internal.telephony.ITelephonyRegistry;
 import com.android.internal.telephony.TelephonyPermissions;
 import com.android.internal.telephony.flags.Flags;
@@ -126,6 +127,7 @@
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 
 /**
@@ -164,6 +166,7 @@
         IOnSubscriptionsChangedListener onOpportunisticSubscriptionsChangedListenerCallback;
         ICarrierPrivilegesCallback carrierPrivilegesCallback;
         ICarrierConfigChangeListener carrierConfigChangeListener;
+        ISatelliteStateChangeListener satelliteStateChangeListener;
 
         int callerUid;
         int callerPid;
@@ -196,6 +199,10 @@
             return carrierConfigChangeListener != null;
         }
 
+        boolean matchSatelliteStateChangeListener() {
+            return satelliteStateChangeListener != null;
+        }
+
         boolean canReadCallLog() {
             try {
                 return TelephonyPermissions.checkReadCallLog(
@@ -215,6 +222,7 @@
                     + onOpportunisticSubscriptionsChangedListenerCallback
                     + " carrierPrivilegesCallback=" + carrierPrivilegesCallback
                     + " carrierConfigChangeListener=" + carrierConfigChangeListener
+                    + " satelliteStateChangeListener=" + satelliteStateChangeListener
                     + " subId=" + subId + " phoneId=" + phoneId + " events=" + eventList + "}";
         }
     }
@@ -433,6 +441,10 @@
 
     private List<IntArray> mCarrierRoamingNtnAvailableServices;
 
+    // Local cache to check if Satellite Modem is enabled
+    private AtomicBoolean mIsSatelliteEnabled;
+    private AtomicBoolean mWasSatelliteEnabledNotified;
+
     /**
      * Per-phone map of precise data connection state. The key of the map is the pair of transport
      * type and APN setting. This is the cache to prevent redundant callbacks to the listeners.
@@ -871,6 +883,9 @@
         mCarrierRoamingNtnMode = new boolean[numPhones];
         mCarrierRoamingNtnEligible = new boolean[numPhones];
         mCarrierRoamingNtnAvailableServices = new ArrayList<>();
+        mIsSatelliteEnabled = new AtomicBoolean();
+        mWasSatelliteEnabledNotified = new AtomicBoolean();
+
 
         for (int i = 0; i < numPhones; i++) {
             mCallState[i] =  TelephonyManager.CALL_STATE_IDLE;
@@ -3425,6 +3440,94 @@
     }
 
     @Override
+    public void addSatelliteStateChangeListener(@NonNull ISatelliteStateChangeListener listener,
+            @NonNull String pkg, @Nullable String featureId) {
+        final int callerUserId = UserHandle.getCallingUserId();
+        mAppOps.checkPackage(Binder.getCallingUid(), pkg);
+        enforceCallingOrSelfAtLeastReadBasicPhoneStatePermission(pkg, featureId,
+                "addSatelliteStateChangeListener");
+        if (VDBG) {
+            log("addSatelliteStateChangeListener pkg=" + pii(pkg)
+                    + " uid=" + Binder.getCallingUid()
+                    + " myUserId=" + UserHandle.myUserId() + " callerUerId" + callerUserId
+                    + " listener=" + listener + " listener.asBinder=" + listener.asBinder());
+        }
+
+        synchronized (mRecords) {
+            final IBinder b = listener.asBinder();
+            boolean doesLimitApply = doesLimitApplyForListeners(Binder.getCallingUid(),
+                    Process.myUid());
+            Record r = add(b, Binder.getCallingUid(), Binder.getCallingPid(), doesLimitApply);
+
+            if (r == null) {
+                loge("addSatelliteStateChangeListener: can not create Record instance!");
+                return;
+            }
+
+            r.context = mContext;
+            r.satelliteStateChangeListener = listener;
+            r.callingPackage = pkg;
+            r.callingFeatureId = featureId;
+            r.callerUid = Binder.getCallingUid();
+            r.callerPid = Binder.getCallingPid();
+            r.eventList = new ArraySet<>();
+            if (DBG) {
+                log("addSatelliteStateChangeListener:  Register r=" + r);
+            }
+
+            // Always notify registrants on registration if it has been notified before
+            if (mWasSatelliteEnabledNotified.get() && r.matchSatelliteStateChangeListener()) {
+                try {
+                    r.satelliteStateChangeListener.onSatelliteEnabledStateChanged(
+                            mIsSatelliteEnabled.get());
+                } catch (RemoteException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void removeSatelliteStateChangeListener(@NonNull ISatelliteStateChangeListener listener,
+            @NonNull String pkg) {
+        if (DBG) log("removeSatelliteStateChangeListener listener=" + listener + ", pkg=" + pkg);
+        mAppOps.checkPackage(Binder.getCallingUid(), pkg);
+        enforceCallingOrSelfAtLeastReadBasicPhoneStatePermission(pkg, null,
+                "removeSatelliteStateChangeListener");
+        remove(listener.asBinder());
+    }
+
+    @Override
+    public void notifySatelliteStateChanged(boolean isEnabled) {
+        if (!checkNotifyPermission("notifySatelliteStateChanged")) {
+            loge("notifySatelliteStateChanged: Caller has no notify permission!");
+            return;
+        }
+        if (VDBG) {
+            log("notifySatelliteStateChanged: isEnabled=" + isEnabled);
+        }
+
+        mWasSatelliteEnabledNotified.set(true);
+        mIsSatelliteEnabled.set(isEnabled);
+
+        synchronized (mRecords) {
+            mRemoveList.clear();
+            for (Record r : mRecords) {
+                // Listeners are "global", neither per-slot nor per-sub, so no idMatch check here
+                if (!r.matchSatelliteStateChangeListener()) {
+                    continue;
+                }
+                try {
+                    r.satelliteStateChangeListener.onSatelliteEnabledStateChanged(isEnabled);
+                } catch (RemoteException re) {
+                    mRemoveList.add(r.binder);
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
+    @Override
     public void notifyMediaQualityStatusChanged(int phoneId, int subId, MediaQualityStatus status) {
         if (!checkNotifyPermission("notifyMediaQualityStatusChanged()")) {
             return;
@@ -4622,4 +4725,32 @@
         if (packageNames.isEmpty() || Build.IS_DEBUGGABLE) return packageNames.toString();
         return "[***, size=" + packageNames.size() + "]";
     }
+
+    /**
+     * The method enforces the calling package at least has READ_BASIC_PHONE_STATE permission.
+     * That is, calling package either has READ_PRIVILEGED_PHONE_STATE, READ_PHONE_STATE or Carrier
+     * Privileges on ANY active subscription, or has READ_BASIC_PHONE_STATE permission.
+     */
+    private void enforceCallingOrSelfAtLeastReadBasicPhoneStatePermission(String pkgName,
+            String featureId, String message) {
+        // Check if calling app has READ_PHONE_STATE on ANY active subscription
+        boolean hasReadPhoneState = false;
+        SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class);
+        if (sm != null) {
+            for (int subId : sm.getActiveSubscriptionIdList()) {
+                if (TelephonyPermissions.checkCallingOrSelfReadPhoneStateNoThrow(mContext, subId,
+                        pkgName, featureId, message)) {
+                    hasReadPhoneState = true;
+                    break;
+                }
+            }
+        }
+
+        // If yes, pass. If not, then enforce READ_BASIC_PHONE_STATE permission
+        if (!hasReadPhoneState) {
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.READ_BASIC_PHONE_STATE,
+                    message);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/TradeInModeService.java b/services/core/java/com/android/server/TradeInModeService.java
index 9ad550b..70a0330 100644
--- a/services/core/java/com/android/server/TradeInModeService.java
+++ b/services/core/java/com/android/server/TradeInModeService.java
@@ -110,6 +110,8 @@
                     stopTradeInMode();
                 } else {
                     watchForSetupCompletion();
+                    watchForNetworkChange();
+                    watchForAccountsCreated();
                 }
             }
         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1c3569d..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);
+            }
         }
     };
 
@@ -5155,6 +5168,11 @@
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
+                final String action = intent.getAction();
+                if (action == null) {
+                    return;
+                }
+
                 String[] pkgs = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
                 if (pkgs != null) {
                     for (String pkg : pkgs) {
@@ -11332,7 +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);
@@ -11340,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/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index 61079fc..c1d5597 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -49,6 +49,7 @@
 
 # Broadcasts
 per-file Broadcast* = file:/BROADCASTS_OWNERS
+per-file broadcasts_flags.aconfig = file:/BROADCASTS_OWNERS
 
 # Permissions & Packages
 per-file *Permission* = patb@google.com
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index de3e2c9..08632fe 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -114,6 +114,7 @@
 import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ;
 import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
 import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ;
 import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
 import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT;
 import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW;
@@ -696,7 +697,7 @@
             // In case the app goes from non-cached to cached but it doesn't have other reachable
             // processes, its adj could be still unknown as of now, assign one.
             processes.add(app);
-            assignCachedAdjIfNecessary(processes);
+            applyLruAdjust(processes);
             applyOomAdjLSP(app, false, mInjector.getUptimeMillis(),
                     mInjector.getElapsedRealtimeMillis(), oomAdjReason);
         }
@@ -1086,7 +1087,7 @@
         }
         mProcessesInCycle.clear();
 
-        assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+        applyLruAdjust(mProcessList.getLruProcessesLOSP());
 
         postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime, true);
 
@@ -1148,8 +1149,9 @@
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    protected void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) {
+    protected void applyLruAdjust(ArrayList<ProcessRecord> lruList) {
         final int numLru = lruList.size();
+        int nextPreviousAppAdj = PREVIOUS_APP_ADJ;
         if (mConstants.USE_TIERED_CACHED_ADJ) {
             final long now = mInjector.getUptimeMillis();
             int uiTargetAdj = 10;
@@ -1159,9 +1161,12 @@
                 ProcessRecord app = lruList.get(i);
                 final ProcessStateRecord state = app.mState;
                 final ProcessCachedOptimizerRecord opt = app.mOptRecord;
-                if (!app.isKilledByAm() && app.getThread() != null
-                        && (state.getCurAdj() >= UNKNOWN_ADJ
-                            || (state.hasShownUi() && state.getCurAdj() >= CACHED_APP_MIN_ADJ))) {
+                final int curAdj = state.getCurAdj();
+                if (PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ) {
+                    state.setCurAdj(nextPreviousAppAdj);
+                    nextPreviousAppAdj = Math.min(nextPreviousAppAdj + 1, PREVIOUS_APP_MAX_ADJ);
+                } else if (!app.isKilledByAm() && app.getThread() != null && (curAdj >= UNKNOWN_ADJ
+                            || (state.hasShownUi() && curAdj >= CACHED_APP_MIN_ADJ))) {
                     final ProcessServiceRecord psr = app.mServices;
                     int targetAdj = CACHED_APP_MIN_ADJ;
 
@@ -1228,10 +1233,13 @@
             for (int i = numLru - 1; i >= 0; i--) {
                 ProcessRecord app = lruList.get(i);
                 final ProcessStateRecord state = app.mState;
-                // If we haven't yet assigned the final cached adj
-                // to the process, do that now.
-                if (!app.isKilledByAm() && app.getThread() != null && state.getCurAdj()
-                    >= UNKNOWN_ADJ) {
+                final int curAdj = state.getCurAdj();
+                if (PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ) {
+                    state.setCurAdj(nextPreviousAppAdj);
+                    nextPreviousAppAdj = Math.min(nextPreviousAppAdj + 1, PREVIOUS_APP_MAX_ADJ);
+                } else if (!app.isKilledByAm() && app.getThread() != null
+                               && curAdj >= UNKNOWN_ADJ) {
+                    // If we haven't yet assigned the final cached adj to the process, do that now.
                     final ProcessServiceRecord psr = app.mServices;
                     switch (state.getCurProcState()) {
                         case PROCESS_STATE_LAST_ACTIVITY:
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index e452c45..8b66055 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -54,6 +54,7 @@
 import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ;
 import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
 import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ;
 import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
 import static com.android.server.am.ProcessList.SERVICE_ADJ;
 import static com.android.server.am.ProcessList.SERVICE_B_ADJ;
@@ -968,7 +969,7 @@
         mTmpOomAdjusterArgs.update(topApp, now, UNKNOWN_ADJ, oomAdjReason, null, true);
         computeConnectionsLSP();
 
-        assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+        applyLruAdjust(mProcessList.getLruProcessesLOSP());
         postUpdateOomAdjInnerLSP(oomAdjReason, mActiveUids, now, nowElapsed, oldTime, true);
     }
 
@@ -1049,20 +1050,24 @@
         // Now traverse and compute the connections of processes with changed importance.
         computeConnectionsLSP();
 
-        boolean unassignedAdj = false;
+        boolean needLruAdjust = false;
         for (int i = 0, size = reachables.size(); i < size; i++) {
             final ProcessStateRecord state = reachables.get(i).mState;
             state.setReachable(false);
             state.setCompletedAdjSeq(mAdjSeq);
-            if (state.getCurAdj() >= UNKNOWN_ADJ) {
-                unassignedAdj = true;
+            final int curAdj = state.getCurAdj();
+            // Processes assigned the PREV oomscore will have a laddered oomscore with respect to
+            // their positions in the LRU list. i.e. prev+0, prev+1, prev+2, etc.
+            final boolean isPrevApp = PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ;
+            if (curAdj >= UNKNOWN_ADJ || (Flags.oomadjusterPrevLaddering() && isPrevApp)) {
+                needLruAdjust = true;
             }
         }
 
         // If all processes have an assigned adj, no need to calculate and assign cached adjs.
-        if (unassignedAdj) {
+        if (needLruAdjust) {
             // TODO: b/319163103 - optimize cache adj assignment to not require the whole lru list.
-            assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+            applyLruAdjust(mProcessList.getLruProcessesLOSP());
         }
 
         // Repopulate any uid record that may have changed.
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index cdb0188..f86474f 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -225,6 +225,7 @@
     // UI flow such as clicking on a URI in the e-mail app to view in the browser,
     // and then pressing back to return to e-mail.
     public static final int PREVIOUS_APP_ADJ = 700;
+    public static final int PREVIOUS_APP_MAX_ADJ = Flags.oomadjusterPrevLaddering() ? 799 : 700;
 
     // This is a process holding the home application -- we want to try
     // avoiding killing it, even if it would normally be in the background,
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index bcde1ff..8dc7c73 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -41,6 +41,7 @@
 import android.aconfigd.Aconfigd.StorageReturnMessages;
 import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
 import static com.android.aconfig_new_storage.Flags.supportImmediateLocalOverrides;
+import static com.android.aconfig_new_storage.Flags.supportClearLocalOverridesImmediately;
 
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
@@ -526,14 +527,21 @@
      * @param proto
      * @param packageName the package of the flag
      * @param flagName the name of the flag
+     * @param immediate if true, clear immediately; otherwise, clear on next reboot
+     *
+     * @hide
      */
-    static void writeFlagOverrideRemovalRequest(
-        ProtoOutputStream proto, String packageName, String flagName) {
+    public static void writeFlagOverrideRemovalRequest(
+        ProtoOutputStream proto, String packageName, String flagName, boolean immediate) {
       long msgsToken = proto.start(StorageRequestMessages.MSGS);
       long msgToken = proto.start(StorageRequestMessage.REMOVE_LOCAL_OVERRIDE_MESSAGE);
       proto.write(StorageRequestMessage.RemoveLocalOverrideMessage.PACKAGE_NAME, packageName);
       proto.write(StorageRequestMessage.RemoveLocalOverrideMessage.FLAG_NAME, flagName);
       proto.write(StorageRequestMessage.RemoveLocalOverrideMessage.REMOVE_ALL, false);
+      proto.write(StorageRequestMessage.RemoveLocalOverrideMessage.REMOVE_OVERRIDE_TYPE,
+          immediate
+              ? StorageRequestMessage.REMOVE_LOCAL_IMMEDIATE
+              : StorageRequestMessage.REMOVE_LOCAL_ON_REBOOT);
       proto.end(msgToken);
       proto.end(msgsToken);
     }
@@ -610,7 +618,11 @@
             String realFlagName = fullFlagName.substring(idx+1);
 
             if (Flags.syncLocalOverridesRemovalNewStorage() && flagValue == null) {
-              writeFlagOverrideRemovalRequest(requests, packageName, realFlagName);
+              if (supportClearLocalOverridesImmediately()) {
+                writeFlagOverrideRemovalRequest(requests, packageName, realFlagName, true);
+              } else {
+                writeFlagOverrideRemovalRequest(requests, packageName, realFlagName, false);
+              }
             } else {
               writeFlagOverrideRequest(requests, packageName, realFlagName, flagValue, true);
             }
diff --git a/services/core/java/com/android/server/am/broadcasts_flags.aconfig b/services/core/java/com/android/server/am/broadcasts_flags.aconfig
new file mode 100644
index 0000000..b1185d5
--- /dev/null
+++ b/services/core/java/com/android/server/am/broadcasts_flags.aconfig
@@ -0,0 +1,10 @@
+package: "com.android.server.am"
+container: "system"
+
+flag {
+    name: "restrict_priority_values"
+    namespace: "backstage_power"
+    description: "Restrict priority values defined by non-system apps"
+    is_fixed_read_only: true
+    bug: "369487976"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 8834820..7b4d6c7 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -235,14 +235,6 @@
 }
 
 flag {
-    name: "restrict_priority_values"
-    namespace: "backstage_power"
-    description: "Restrict priority values defined by non-system apps"
-    is_fixed_read_only: true
-    bug: "369487976"
-}
-
-flag {
     name: "unfreeze_bind_policy_fix"
     namespace: "backstage_power"
     description: "Make sure shouldNotFreeze state change correctly triggers updates."
@@ -250,4 +242,12 @@
     metadata {
         purpose: PURPOSE_BUGFIX
     }
+}
+
+flag {
+    name: "oomadjuster_prev_laddering"
+    namespace: "system_performance"
+    is_fixed_read_only: true
+    description: "Add +X to the prev scores according to their positions in the process LRU list"
+    bug: "359912586"
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/appbinding/AppBindingService.java b/services/core/java/com/android/server/appbinding/AppBindingService.java
index 5db6dc7..6ccb3ee 100644
--- a/services/core/java/com/android/server/appbinding/AppBindingService.java
+++ b/services/core/java/com/android/server/appbinding/AppBindingService.java
@@ -235,6 +235,9 @@
             }
 
             final String action = intent.getAction();
+            if (action == null) {
+                return;
+            }
 
             if (Intent.ACTION_USER_REMOVED.equals(action)) {
                 onUserRemoved(userId);
diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
index ae93991..0855815b 100644
--- a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
@@ -65,27 +65,31 @@
 
     @Override
     public boolean setGlobalRestriction(Object clientToken, int code, boolean restricted) {
+        boolean changed;
         if (restricted) {
             if (!mGlobalRestrictions.containsKey(clientToken)) {
                 mGlobalRestrictions.put(clientToken, new SparseBooleanArray());
             }
             SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken);
             Objects.requireNonNull(restrictedCodes);
-            boolean changed = !restrictedCodes.get(code);
+            changed = !restrictedCodes.get(code);
             restrictedCodes.put(code, true);
-            return changed;
         } else {
             SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken);
             if (restrictedCodes == null) {
                 return false;
             }
-            boolean changed = restrictedCodes.get(code);
+            changed = restrictedCodes.get(code);
             restrictedCodes.delete(code);
             if (restrictedCodes.size() == 0) {
                 mGlobalRestrictions.remove(clientToken);
             }
-            return changed;
         }
+
+        if (changed) {
+            AppOpsManager.invalidateAppOpModeCache();
+        }
+        return changed;
     }
 
     @Override
@@ -104,7 +108,11 @@
 
     @Override
     public boolean clearGlobalRestrictions(Object clientToken) {
-        return mGlobalRestrictions.remove(clientToken) != null;
+        boolean changed = mGlobalRestrictions.remove(clientToken) != null;
+        if (changed) {
+            AppOpsManager.invalidateAppOpModeCache();
+        }
+        return changed;
     }
 
     @RequiresPermission(anyOf = {
@@ -122,6 +130,9 @@
             changed |= putUserRestrictionExclusions(clientToken, userIds[i],
                     excludedPackageTags);
         }
+        if (changed) {
+            AppOpsManager.invalidateAppOpModeCache();
+        }
         return changed;
     }
 
@@ -191,6 +202,9 @@
         changed |= mUserRestrictions.remove(clientToken) != null;
         changed |= mUserRestrictionExcludedPackageTags.remove(clientToken) != null;
         notifyAllUserRestrictions(allUserRestrictedCodes);
+        if (changed) {
+            AppOpsManager.invalidateAppOpModeCache();
+        }
         return changed;
     }
 
@@ -244,6 +258,9 @@
             }
         }
 
+        if (changed) {
+            AppOpsManager.invalidateAppOpModeCache();
+        }
         return changed;
     }
 
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 702ad95..5e74d67 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -998,6 +998,7 @@
                     @Override
                     public void onUidModeChanged(int uid, int code, int mode,
                             String persistentDeviceId) {
+                        AppOpsManager.invalidateAppOpModeCache();
                         mHandler.sendMessage(PooledLambda.obtainMessage(
                                 AppOpsService::notifyOpChangedForAllPkgsInUid, AppOpsService.this,
                                 code, uid, false, persistentDeviceId));
@@ -1006,6 +1007,7 @@
                     @Override
                     public void onPackageModeChanged(String packageName, int userId, int code,
                             int mode) {
+                        AppOpsManager.invalidateAppOpModeCache();
                         mHandler.sendMessage(PooledLambda.obtainMessage(
                                 AppOpsService::notifyOpChangedForPkg, AppOpsService.this,
                                 packageName, code, mode, userId));
@@ -1032,6 +1034,11 @@
         // To migrate storageFile to recentAccessesFile, these reads must be called in this order.
         readRecentAccesses();
         mAppOpsCheckingService.readState();
+        // The system property used by the cache is created the first time it is written, that only
+        // happens inside invalidateCache().  Until the service calls invalidateCache() the property
+        // will not exist and the nonce will be UNSET.
+        AppOpsManager.invalidateAppOpModeCache();
+        AppOpsManager.disableAppOpModeCache();
     }
 
     public void publish() {
@@ -2830,6 +2837,13 @@
     @Override
     public int checkOperationRaw(int code, int uid, String packageName,
             @Nullable String attributionTag) {
+        if (Binder.getCallingPid() != Process.myPid()
+                && Flags.appopAccessTrackingLoggingEnabled()) {
+            FrameworkStatsLog.write(
+                    APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, uid, code,
+                    APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION,
+                    false);
+        }
         return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, attributionTag,
                 Context.DEVICE_ID_DEFAULT, true /*raw*/);
     }
@@ -2837,6 +2851,13 @@
     @Override
     public int checkOperationRawForDevice(int code, int uid, @Nullable String packageName,
             @Nullable String attributionTag, int virtualDeviceId) {
+        if (Binder.getCallingPid() != Process.myPid()
+                && Flags.appopAccessTrackingLoggingEnabled()) {
+            FrameworkStatsLog.write(
+                    APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, uid, code,
+                    APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION,
+                    false);
+        }
         return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, attributionTag,
                 virtualDeviceId, true /*raw*/);
     }
@@ -2894,8 +2915,14 @@
                 return AppOpsManager.MODE_IGNORED;
             }
         }
-        return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
-                virtualDeviceId, raw);
+
+        if (Flags.appopModeCachingEnabled()) {
+            return getAppOpMode(code, uid, resolvedPackageName, attributionTag, virtualDeviceId,
+                    raw, true);
+        } else {
+            return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
+                    virtualDeviceId, raw);
+        }
     }
 
     /**
@@ -2961,6 +2988,54 @@
         }
     }
 
+    /**
+     * This method unifies mode checking logic between checkOperationUnchecked and
+     * noteOperationUnchecked. It can replace those two methods once the flag is fully rolled out.
+     *
+     * @param isCheckOp This param is only used in user's op restriction. When checking if a package
+     *                  can bypass user's restriction we should account for attributionTag as well.
+     *                  But existing checkOp APIs don't accept attributionTag so we added a hack to
+     *                  skip attributionTag check for checkOp. After we add an overload of checkOp
+     *                  that accepts attributionTag we should remove this param.
+     */
+    private @Mode int getAppOpMode(int code, int uid, @NonNull String packageName,
+            @Nullable String attributionTag, int virtualDeviceId, boolean raw, boolean isCheckOp) {
+        PackageVerificationResult pvr;
+        try {
+            pvr = verifyAndGetBypass(uid, packageName, attributionTag);
+        } catch (SecurityException e) {
+            logVerifyAndGetBypassFailure(uid, e, "getAppOpMode");
+            return MODE_IGNORED;
+        }
+
+        if (isOpRestrictedDueToSuspend(code, packageName, uid)) {
+            return MODE_IGNORED;
+        }
+
+        synchronized (this) {
+            if (isOpRestrictedLocked(uid, code, packageName, attributionTag, virtualDeviceId,
+                    pvr.bypass, isCheckOp)) {
+                return MODE_IGNORED;
+            }
+            if (isOpAllowedForUid(uid)) {
+                return MODE_ALLOWED;
+            }
+
+            int switchCode = AppOpsManager.opToSwitch(code);
+            int rawUidMode = mAppOpsCheckingService.getUidMode(uid,
+                    getPersistentId(virtualDeviceId), switchCode);
+
+            if (rawUidMode != AppOpsManager.opToDefaultMode(switchCode)) {
+                return raw ? rawUidMode : evaluateForegroundMode(uid, switchCode, rawUidMode);
+            }
+
+            int rawPackageMode = mAppOpsCheckingService.getPackageMode(packageName, switchCode,
+                    UserHandle.getUserId(uid));
+            return raw ? rawPackageMode : evaluateForegroundMode(uid, switchCode, rawPackageMode);
+        }
+    }
+
+
     @Override
     public int checkAudioOperation(int code, int usage, int uid, String packageName) {
         return mCheckOpsDelegateDispatcher.checkAudioOperation(code, usage, uid, packageName);
@@ -3213,7 +3288,6 @@
         PackageVerificationResult pvr;
         try {
             pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
-            boolean wasNull = attributionTag == null;
             if (!pvr.isAttributionTagValid) {
                 attributionTag = null;
             }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 985155d..0cf55bb 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -493,7 +493,7 @@
     private static final int MSG_INIT_ADI_DEVICE_STATES = 103;
 
     private static final int MSG_INIT_INPUT_GAINS = 104;
-    private static final int MSG_SET_INPUT_GAIN_INDEX = 105;
+    private static final int MSG_APPLY_INPUT_GAIN_INDEX = 105;
     private static final int MSG_PERSIST_INPUT_GAIN_INDEX = 106;
 
     // end of messages handled under wakelock
@@ -1626,7 +1626,6 @@
                 new InputDeviceVolumeHelper(
                         mSettings,
                         mContentResolver,
-                        mSettingsLock,
                         System.INPUT_GAIN_INDEX_SETTINGS);
     }
 
@@ -5804,7 +5803,7 @@
             // to persist).
             sendMsg(
                     mAudioHandler,
-                    MSG_SET_INPUT_GAIN_INDEX,
+                    MSG_APPLY_INPUT_GAIN_INDEX,
                     SENDMSG_QUEUE,
                     /*arg1*/ index,
                     /*arg2*/ 0,
@@ -5813,22 +5812,22 @@
         }
     }
 
-    private void setInputGainIndexInt(@NonNull AudioDeviceAttributes ada, int index) {
+    private void onApplyInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) {
         // TODO(b/364923030): call AudioSystem to apply input gain in native layer.
 
         // Post a persist input gain msg.
         sendMsg(
                 mAudioHandler,
                 MSG_PERSIST_INPUT_GAIN_INDEX,
-                SENDMSG_QUEUE,
-                /*arg1*/ index,
+                SENDMSG_REPLACE,
+                /*arg1*/ 0,
                 /*arg2*/ 0,
                 /*obj*/ ada,
                 PERSIST_DELAY);
     }
 
-    private void persistInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) {
-        mInputDeviceVolumeHelper.persistInputGainIndex(ada, index);
+    private void onPersistInputGainIndex(@NonNull AudioDeviceAttributes ada) {
+        mInputDeviceVolumeHelper.persistInputGainIndex(ada);
     }
 
     /**
@@ -10213,12 +10212,12 @@
                     vgs.persistVolumeGroup(msg.arg1);
                     break;
 
-                case MSG_SET_INPUT_GAIN_INDEX:
-                    setInputGainIndexInt((AudioDeviceAttributes) msg.obj, msg.arg1);
+                case MSG_APPLY_INPUT_GAIN_INDEX:
+                    onApplyInputGainIndex((AudioDeviceAttributes) msg.obj, msg.arg1);
                     break;
 
                 case MSG_PERSIST_INPUT_GAIN_INDEX:
-                    persistInputGainIndex((AudioDeviceAttributes) msg.obj, msg.arg1);
+                    onPersistInputGainIndex((AudioDeviceAttributes) msg.obj);
                     break;
 
                 case MSG_PERSIST_RINGER_MODE:
diff --git a/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java b/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java
index d83dca6..d7d1ac9 100644
--- a/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java
+++ b/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,10 +23,10 @@
 import android.content.ContentResolver;
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
-import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.os.UserHandle;
 import android.util.IntArray;
+import android.util.Log;
 import android.util.SparseIntArray;
 
 import java.util.HashSet;
@@ -43,73 +43,59 @@
 
     private final SettingsAdapter mSettings;
     private final ContentResolver mContentResolver;
-    private final Object mSettingsLock;
     private final String mInputGainIndexSettingsName;
 
     // A map between device internal type (e.g. AudioSystem.DEVICE_IN_BUILTIN_MIC) to its input gain
     // index.
     private final SparseIntArray mInputGainIndexMap;
-    private final Set<Integer> mSupportedDeviceTypes;
+    private final Set<Integer> mSupportedDeviceTypes = new HashSet<>();
 
     InputDeviceVolumeHelper(
             SettingsAdapter settings,
             ContentResolver contentResolver,
-            Object settingsLock,
             String settingsName) {
         mSettings = settings;
         mContentResolver = contentResolver;
-        mSettingsLock = settingsLock;
         mInputGainIndexSettingsName = settingsName;
 
         IntArray internalDeviceTypes = new IntArray();
         int status = AudioSystem.getSupportedDeviceTypes(GET_DEVICES_INPUTS, internalDeviceTypes);
-        mInputGainIndexMap =
-                new SparseIntArray(
-                        status == AudioManager.SUCCESS
-                                ? internalDeviceTypes.size()
-                                : AudioSystem.DEVICE_IN_ALL_SET.size());
+        if (status != AudioSystem.SUCCESS) {
+            Log.e(TAG, "AudioSystem.getSupportedDeviceTypes(GET_DEVICES_INPUTS) failed. status:"
+                    + status);
+        }
 
-        if (status == AudioManager.SUCCESS) {
-            Set<Integer> supportedDeviceTypes = new HashSet<>();
-            for (int i = 0; i < internalDeviceTypes.size(); i++) {
-                supportedDeviceTypes.add(internalDeviceTypes.get(i));
-            }
-            mSupportedDeviceTypes = supportedDeviceTypes;
-        } else {
-            mSupportedDeviceTypes = AudioSystem.DEVICE_IN_ALL_SET;
+        // Note that in a rare case, if AudioSystem.getSupportedDeviceTypes call fails, both
+        // mInputGainIndexMap and mSupportedDeviceTypes will be empty.
+        mInputGainIndexMap = new SparseIntArray(internalDeviceTypes.size());
+        for (int i = 0; i < internalDeviceTypes.size(); i++) {
+            mSupportedDeviceTypes.add(internalDeviceTypes.get(i));
         }
 
         readSettings();
     }
 
-    public void readSettings() {
+    private void readSettings() {
         synchronized (InputDeviceVolumeHelper.class) {
             for (int inputDeviceType : mSupportedDeviceTypes) {
                 // Retrieve current input gain for device. If no input gain stored for current
                 // device, use default input gain.
-                int index;
-                if (!hasValidSettingsName()) {
-                    index = INDEX_DEFAULT;
-                } else {
-                    String name = getSettingNameForDevice(inputDeviceType);
-                    index =
-                            mSettings.getSystemIntForUser(
-                                    mContentResolver, name, INDEX_DEFAULT, UserHandle.USER_CURRENT);
-                }
+                String name = getSettingNameForDevice(inputDeviceType);
+                int index = name == null
+                        ? INDEX_DEFAULT
+                        : mSettings.getSystemIntForUser(
+                                mContentResolver, name, INDEX_DEFAULT, UserHandle.USER_CURRENT);
 
                 mInputGainIndexMap.put(inputDeviceType, getValidIndex(index));
             }
         }
     }
 
-    public boolean hasValidSettingsName() {
-        return mInputGainIndexSettingsName != null && !mInputGainIndexSettingsName.isEmpty();
-    }
-
-    public @Nullable String getSettingNameForDevice(int inputDeviceType) {
-        if (!hasValidSettingsName()) {
+    private @Nullable String getSettingNameForDevice(int inputDeviceType) {
+        if (mInputGainIndexSettingsName == null || mInputGainIndexSettingsName.isEmpty()) {
             return null;
         }
+
         final String suffix = AudioSystem.getInputDeviceName(inputDeviceType);
         if (suffix.isEmpty()) {
             return mInputGainIndexSettingsName;
@@ -158,29 +144,27 @@
         ensureValidInputDeviceType(inputDeviceType);
 
         int oldIndex;
-        synchronized (mSettingsLock) {
-            synchronized (InputDeviceVolumeHelper.class) {
-                oldIndex = getInputGainIndex(ada);
-                index = getValidIndex(index);
+        synchronized (InputDeviceVolumeHelper.class) {
+            oldIndex = getInputGainIndex(ada);
+            index = getValidIndex(index);
 
-                if (oldIndex == index) {
-                    return false;
-                }
-
-                mInputGainIndexMap.put(inputDeviceType, index);
-                return true;
+            if (oldIndex == index) {
+                return false;
             }
+
+            mInputGainIndexMap.put(inputDeviceType, index);
+            return true;
         }
     }
 
-    public void persistInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) {
+    public void persistInputGainIndex(@NonNull AudioDeviceAttributes ada) {
         int inputDeviceType = AudioDeviceInfo.convertDeviceTypeToInternalInputDevice(ada.getType());
-        ensureValidInputDeviceType(inputDeviceType);
-
-        if (hasValidSettingsName()) {
+        String name = getSettingNameForDevice(inputDeviceType);
+        if (name != null) {
+            int index = getInputGainIndex(ada);
             mSettings.putSystemIntForUser(
                     mContentResolver,
-                    getSettingNameForDevice(inputDeviceType),
+                    name,
                     index,
                     UserHandle.USER_CURRENT);
         }
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 1da62d7..1604e94 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -1068,6 +1068,7 @@
         switch (attr.getUsage()) {
             case AudioAttributes.USAGE_MEDIA:
             case AudioAttributes.USAGE_GAME:
+            case AudioAttributes.USAGE_SPEAKER_CLEANUP:
                 return 1000;
             case AudioAttributes.USAGE_ALARM:
             case AudioAttributes.USAGE_NOTIFICATION_RINGTONE:
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index a9ada29..e8fa417 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -146,7 +146,7 @@
         final List<Pair<BiometricSensor, Integer>> ineligibleSensors = new ArrayList<>();
 
         final int effectiveUserId;
-        if (Flags.privateSpaceBp()) {
+        if (Flags.effectiveUserBp()) {
             effectiveUserId = userManager.getCredentialOwnerProfile(userId);
         } else {
             effectiveUserId = userId;
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/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index f49608b..a9bdcce 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -438,3 +438,11 @@
     bug: "361433796"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "enable_get_supported_refresh_rates"
+    namespace: "core_graphics"
+    description: "Flag to use the surfaceflinger rates for getSupportedRefreshRates"
+    bug: "365163968"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/display/state/DisplayStateController.java b/services/core/java/com/android/server/display/state/DisplayStateController.java
index dba6874..0b46e0f 100644
--- a/services/core/java/com/android/server/display/state/DisplayStateController.java
+++ b/services/core/java/com/android/server/display/state/DisplayStateController.java
@@ -61,7 +61,7 @@
         // We might override this below based on other factors.
         // Initialise brightness as invalid.
         int state;
-        int reason = Display.STATE_REASON_DEFAULT_POLICY;
+        int reason = displayPowerRequest.policyReason;
         switch (displayPowerRequest.policy) {
             case DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF:
                 state = Display.STATE_OFF;
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 19305de..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)");
@@ -605,7 +649,7 @@
     private ComponentName chooseDreamForUser(boolean doze, int userId) {
         if (doze) {
             ComponentName dozeComponent = getDozeComponent(userId);
-            return validateDream(dozeComponent) ? dozeComponent : null;
+            return validateDream(dozeComponent, userId) ? dozeComponent : null;
         }
 
         if (mSystemDreamComponent != null) {
@@ -616,11 +660,11 @@
         return dreams != null && dreams.length != 0 ? dreams[0] : null;
     }
 
-    private boolean validateDream(ComponentName component) {
+    private boolean validateDream(ComponentName component, int userId) {
         if (component == null) return false;
-        final ServiceInfo serviceInfo = getServiceInfo(component);
+        final ServiceInfo serviceInfo = getServiceInfo(component, userId);
         if (serviceInfo == null) {
-            Slog.w(TAG, "Dream " + component + " does not exist");
+            Slog.w(TAG, "Dream " + component + " does not exist on user " + userId);
             return false;
         } else if (serviceInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP
                 && !BIND_DREAM_SERVICE.equals(serviceInfo.permission)) {
@@ -647,7 +691,7 @@
         List<ComponentName> validComponents = new ArrayList<>();
         if (components != null) {
             for (ComponentName component : components) {
-                if (validateDream(component)) {
+                if (validateDream(component, userId)) {
                     validComponents.add(component);
                 }
             }
@@ -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,
@@ -718,9 +778,10 @@
         return userId == mainUserId;
     }
 
-    private ServiceInfo getServiceInfo(ComponentName name) {
+    private ServiceInfo getServiceInfo(ComponentName name, int userId) {
+        final Context userContext = mContext.createContextAsUser(UserHandle.of(userId), 0);
         try {
-            return name != null ? mContext.getPackageManager().getServiceInfo(name,
+            return name != null ? userContext.getPackageManager().getServiceInfo(name,
                     PackageManager.MATCH_DEBUG_TRIAGED_MISSING) : null;
         } catch (NameNotFoundException e) {
             return null;
@@ -813,7 +874,7 @@
 
     private void writePulseGestureEnabled() {
         ComponentName name = getDozeComponent();
-        boolean dozeEnabled = validateDream(name);
+        boolean dozeEnabled = validateDream(name, ActivityManager.getCurrentUser());
         LocalServices.getService(InputManagerInternal.class).setPulseGestureEnabled(dozeEnabled);
     }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index b696c54..1b527da 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -273,13 +273,8 @@
     private class DelayedStandbyOnActiveSourceLostRunnable implements Runnable {
         @Override
         public void run() {
-            if (mService.getPowerManagerInternal().wasDeviceIdleFor(
-                    STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS)) {
+            if (!isActiveSource()) {
                 mService.standby();
-            } else {
-                mService.setAndBroadcastActiveSource(mService.getPhysicalAddress(),
-                        getDeviceInfo().getDeviceType(), Constants.ADDR_TV,
-                        "DelayedActiveSourceLostStandbyRunnable");
             }
         }
     }
diff --git a/services/core/java/com/android/server/input/AppLaunchShortcutManager.java b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java
new file mode 100644
index 0000000..aef207f
--- /dev/null
+++ b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.XmlResourceParser;
+import android.hardware.input.AppLaunchData;
+import android.hardware.input.InputGestureData;
+import android.hardware.input.KeyGestureEvent;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.SparseArray;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+
+import com.android.internal.R;
+import com.android.internal.policy.IShortcutService;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Manages quick launch app shortcuts by parsing {@code bookmarks.xml} and intercepting the
+ * correct key combinations for the app shortcuts defined.
+ *
+ * Currently there are 2 ways of defining shortcuts:
+ * - Adding shortcuts to {@code bookmarks.xml}
+ * - Calling into {@code registerShortcutKey()}.
+ */
+final class AppLaunchShortcutManager {
+    private static final String TAG = "AppShortcutManager";
+
+    private static final String TAG_BOOKMARKS = "bookmarks";
+    private static final String TAG_BOOKMARK = "bookmark";
+
+    private static final String ATTRIBUTE_PACKAGE = "package";
+    private static final String ATTRIBUTE_CLASS = "class";
+    private static final String ATTRIBUTE_SHORTCUT = "shortcut";
+    private static final String ATTRIBUTE_CATEGORY = "category";
+    private static final String ATTRIBUTE_SHIFT = "shift";
+    private static final String ATTRIBUTE_ROLE = "role";
+
+    private static final int SHORTCUT_CODE_META_MASK =
+            KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON
+                    | KeyEvent.META_META_ON;
+
+    private LongSparseArray<IShortcutService> mShortcutKeyServices = new LongSparseArray<>();
+
+    /* Table of Application Launch keys.  Maps from key codes to intent categories.
+     *
+     * These are special keys that are used to launch particular kinds of applications,
+     * such as a web browser.  HID defines nearly a hundred of them in the Consumer (0x0C)
+     * usage page.  We don't support quite that many yet...
+     */
+    private static final SparseArray<String> sApplicationLaunchKeyCategories;
+    private static final SparseArray<String> sApplicationLaunchKeyRoles;
+    static {
+        sApplicationLaunchKeyRoles = new SparseArray<>();
+        sApplicationLaunchKeyCategories = new SparseArray<>();
+        sApplicationLaunchKeyRoles.append(
+                KeyEvent.KEYCODE_EXPLORER, RoleManager.ROLE_BROWSER);
+        sApplicationLaunchKeyCategories.append(
+                KeyEvent.KEYCODE_ENVELOPE, Intent.CATEGORY_APP_EMAIL);
+        sApplicationLaunchKeyCategories.append(
+                KeyEvent.KEYCODE_CONTACTS, Intent.CATEGORY_APP_CONTACTS);
+        sApplicationLaunchKeyCategories.append(
+                KeyEvent.KEYCODE_CALENDAR, Intent.CATEGORY_APP_CALENDAR);
+        sApplicationLaunchKeyCategories.append(
+                KeyEvent.KEYCODE_MUSIC, Intent.CATEGORY_APP_MUSIC);
+        sApplicationLaunchKeyCategories.append(
+                KeyEvent.KEYCODE_CALCULATOR, Intent.CATEGORY_APP_CALCULATOR);
+    }
+
+    private final Context mContext;
+    private boolean mSearchKeyShortcutPending = false;
+    private boolean mConsumeSearchKeyUp = true;
+    private final Map<InputGestureData.Trigger, InputGestureData> mBookmarks = new HashMap<>();
+
+    @SuppressLint("MissingPermission")
+    AppLaunchShortcutManager(Context context) {
+        mContext = context;
+    }
+
+    public void systemRunning() {
+        loadShortcuts();
+    }
+
+    private void loadShortcuts() {
+        try {
+            XmlResourceParser parser = mContext.getResources().getXml(R.xml.bookmarks);
+            XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
+            KeyCharacterMap virtualKcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+
+            while (true) {
+                XmlUtils.nextElement(parser);
+
+                if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
+                    break;
+                }
+
+                if (!TAG_BOOKMARK.equals(parser.getName())) {
+                    Log.w(TAG, "TAG_BOOKMARK not found");
+                    break;
+                }
+
+                String packageName = parser.getAttributeValue(null, ATTRIBUTE_PACKAGE);
+                String className = parser.getAttributeValue(null, ATTRIBUTE_CLASS);
+                String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY);
+                String shiftName = parser.getAttributeValue(null, ATTRIBUTE_SHIFT);
+                String roleName = parser.getAttributeValue(null, ATTRIBUTE_ROLE);
+
+                // TODO(b/358569822): Shift bookmarks to use keycode instead of shortcutChar
+                int keycode = KeyEvent.KEYCODE_UNKNOWN;
+                String shortcut = parser.getAttributeValue(null, ATTRIBUTE_SHORTCUT);
+                if (!TextUtils.isEmpty(shortcut)) {
+                    KeyEvent[] events = virtualKcm.getEvents(new char[]{shortcut.toLowerCase(
+                            Locale.ROOT).charAt(0)});
+                    // Single key press can generate the character
+                    if (events != null && events.length == 2) {
+                        keycode = events[0].getKeyCode();
+                    }
+                }
+                if (keycode == KeyEvent.KEYCODE_UNKNOWN) {
+                    Log.w(TAG, "Keycode required for bookmark with category=" + categoryName
+                            + " packageName=" + packageName + " className=" + className
+                            + " role=" + roleName + " shiftName=" + shiftName
+                            + " shortcut=" + shortcut);
+                    continue;
+                }
+
+                final boolean isShiftShortcut = (shiftName != null && shiftName.toLowerCase(
+                        Locale.ROOT).equals("true"));
+                AppLaunchData launchData = null;
+                if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
+                    launchData = AppLaunchData.createLaunchDataForComponent(packageName, className);
+                } else if (!TextUtils.isEmpty(categoryName)) {
+                    launchData = AppLaunchData.createLaunchDataForCategory(categoryName);
+                } else if (!TextUtils.isEmpty(roleName)) {
+                    launchData = AppLaunchData.createLaunchDataForRole(roleName);
+                }
+                if (launchData != null) {
+                    Log.d(TAG, "adding shortcut " + launchData + "shift="
+                            + isShiftShortcut + " keycode=" + keycode);
+                    // All bookmarks are based on Action key
+                    int modifierState =
+                            KeyEvent.META_META_ON | (isShiftShortcut ? KeyEvent.META_SHIFT_ON : 0);
+                    InputGestureData bookmark = new InputGestureData.Builder()
+                            .setTrigger(InputGestureData.createKeyTrigger(keycode, modifierState))
+                            .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
+                            .setAppLaunchData(launchData)
+                            .build();
+                    mBookmarks.put(bookmark.getTrigger(), bookmark);
+                }
+            }
+        } catch (XmlPullParserException | IOException e) {
+            Log.e(TAG, "Got exception parsing bookmarks.", e);
+        }
+    }
+
+    public void registerShortcutKey(long shortcutCode, IShortcutService shortcutService)
+            throws RemoteException {
+        IShortcutService service = mShortcutKeyServices.get(shortcutCode);
+        if (service != null && service.asBinder().pingBinder()) {
+            throw new RemoteException("Key: " + shortcutCode + ", already exists.");
+        }
+
+        mShortcutKeyServices.put(shortcutCode, shortcutService);
+    }
+
+    /**
+     * Handle the shortcut to {@link IShortcutService}
+     * @param keyCode The key code of the event.
+     * @param metaState The meta key modifier state.
+     * @return True if invoked the shortcut, otherwise false.
+     */
+    private boolean handleShortcutService(int keyCode, int metaState) {
+        final long shortcutCodeMeta = metaState & SHORTCUT_CODE_META_MASK;
+        if (shortcutCodeMeta == 0) {
+            return false;
+        }
+        long shortcutCode = keyCode | (shortcutCodeMeta << Integer.SIZE);
+        IShortcutService shortcutService = mShortcutKeyServices.get(shortcutCode);
+        if (shortcutService != null) {
+            try {
+                shortcutService.notifyShortcutKeyPressed(shortcutCode);
+            } catch (RemoteException e) {
+                Log.w(TAG,
+                        "Shortcut key service not found, deleting shortcut code: " + shortcutCode);
+                mShortcutKeyServices.delete(shortcutCode);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Handle the shortcut to Launch application.
+     *
+     * @param keyEvent The key event.
+     */
+    @SuppressLint("MissingPermission")
+    @Nullable
+    private AppLaunchData interceptShortcut(KeyEvent keyEvent) {
+        final int keyCode = keyEvent.getKeyCode();
+        final int modifierState = keyEvent.getMetaState() & SHORTCUT_CODE_META_MASK;
+        // Shortcuts are invoked through Search+key, so intercept those here
+        // Any printing key that is chorded with Search should be consumed
+        // even if no shortcut was invoked.  This prevents text from being
+        // inadvertently inserted when using a keyboard that has built-in macro
+        // shortcut keys (that emit Search+x) and some of them are not registered.
+        if (mSearchKeyShortcutPending) {
+            KeyCharacterMap kcm = keyEvent.getKeyCharacterMap();
+            if (kcm != null && kcm.isPrintingKey(keyCode)) {
+                mConsumeSearchKeyUp = true;
+                mSearchKeyShortcutPending = false;
+            } else {
+                return null;
+            }
+        } else if (modifierState == 0) {
+            AppLaunchData appLaunchData = null;
+            // Handle application launch keys.
+            String role = sApplicationLaunchKeyRoles.get(keyCode);
+            String category = sApplicationLaunchKeyCategories.get(keyCode);
+            if (!TextUtils.isEmpty(role)) {
+                appLaunchData = AppLaunchData.createLaunchDataForRole(role);
+            } else if (!TextUtils.isEmpty(category)) {
+                appLaunchData = AppLaunchData.createLaunchDataForCategory(category);
+            }
+
+            return appLaunchData;
+        }
+
+        if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
+            return null;
+        }
+        InputGestureData gesture = mBookmarks.get(
+                InputGestureData.createKeyTrigger(keyCode, modifierState));
+        if (gesture == null) {
+            return null;
+        }
+        return gesture.getAction().appLaunchData();
+    }
+
+    /**
+     * Handle the shortcut from {@link KeyEvent}
+     *
+     * @param event Description of the key event.
+     */
+    public InterceptKeyResult interceptKey(KeyEvent event) {
+        if (event.getRepeatCount() != 0) {
+            return InterceptKeyResult.DO_NOTHING;
+        }
+
+        final int metaState = event.getModifiers();
+        final int keyCode = event.getKeyCode();
+        if (keyCode == KeyEvent.KEYCODE_SEARCH) {
+            if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                mSearchKeyShortcutPending = true;
+                mConsumeSearchKeyUp = false;
+            } else {
+                mSearchKeyShortcutPending = false;
+                if (mConsumeSearchKeyUp) {
+                    mConsumeSearchKeyUp = false;
+                    return InterceptKeyResult.CONSUME_KEY;
+                }
+            }
+            return InterceptKeyResult.DO_NOTHING;
+        }
+
+        if (event.getAction() != KeyEvent.ACTION_DOWN) {
+            return InterceptKeyResult.DO_NOTHING;
+        }
+
+        // Intercept shortcuts defined in bookmarks or through application launch keycodes
+        AppLaunchData appLaunchData = interceptShortcut(event);
+
+        // TODO(b/358569822): Ideally shortcut service custom shortcuts should be either
+        //  migrated to bookmarks or customizable shortcut APIs.
+        if (appLaunchData == null && handleShortcutService(keyCode, metaState)) {
+            return InterceptKeyResult.CONSUME_KEY;
+        }
+
+        return new InterceptKeyResult(/* consumed =*/ false, appLaunchData);
+    }
+
+    /**
+     * @return a list of {@link InputGestureData} containing the application launch shortcuts parsed
+     * at boot time from {@code bookmarks.xml}.
+     */
+    public List<InputGestureData> getBookmarks() {
+        return new ArrayList<>(mBookmarks.values());
+    }
+
+    public void dump(IndentingPrintWriter ipw) {
+        ipw.println("AppLaunchShortcutManager:");
+        ipw.increaseIndent();
+        for (InputGestureData data : mBookmarks.values()) {
+            ipw.println(data);
+        }
+        ipw.decreaseIndent();
+    }
+
+    public record InterceptKeyResult(boolean consumed, @Nullable AppLaunchData appLaunchData) {
+        private static final InterceptKeyResult DO_NOTHING = new InterceptKeyResult(false, null);
+        private static final InterceptKeyResult CONSUME_KEY = new InterceptKeyResult(true, null);
+    }
+}
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index ae31b33..8cb51ce 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -16,37 +16,287 @@
 
 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;
 
 import com.android.internal.annotations.GuardedBy;
 
 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 {
     private static final String TAG = "InputGestureManager";
 
-    @GuardedBy("mCustomInputGestures")
+    private static final int KEY_GESTURE_META_MASK =
+            KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON
+                    | KeyEvent.META_META_ON;
+
+    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<>());
             }
@@ -62,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;
             }
@@ -81,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();
             }
@@ -96,10 +346,77 @@
         }
     }
 
+    @Nullable
+    public InputGestureData getCustomGestureForKeyEvent(@UserIdInt int userId, KeyEvent event) {
+        final int keyCode = event.getKeyCode();
+        if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
+            return null;
+        }
+        synchronized (mGestureLock) {
+            Map<InputGestureData.Trigger, InputGestureData> customGestures =
+                    mCustomInputGestures.get(userId);
+            if (customGestures == null) {
+                return null;
+            }
+            int modifierState = event.getMetaState() & KEY_GESTURE_META_MASK;
+            return customGestures.get(InputGestureData.createKeyTrigger(keyCode, modifierState));
+        }
+    }
+
+    @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 d43ce71..e0f3a9b 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -130,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,6 +2314,13 @@
 
     // Native callback.
     @SuppressWarnings("unused")
+    private void notifyTouchpadThreeFingerTap() {
+        mKeyGestureController.handleTouchpadGesture(
+                InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP);
+    }
+
+    // Native callback.
+    @SuppressWarnings("unused")
     private void notifySwitch(long whenNanos, int switchValues, int switchMask) {
         if (DEBUG) {
             Slog.d(TAG, "notifySwitch: values=" + Integer.toHexString(switchValues)
@@ -2989,39 +2997,45 @@
 
     @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) {
         mCurrentUserId = userId;
+        mKeyGestureController.setCurrentUserId(userId);
     }
 
     /**
@@ -3563,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/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index d1a6d3b..420db90 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -143,6 +143,10 @@
             observer.accept("just booted");
         }
 
+        // TODO(b/365063048): add an entry to mObservers that calls this instead, once we have a
+        //   setting that can be observed.
+        updateTouchpadThreeFingerTapShortcutEnabled();
+
         configureUserActivityPokeInterval();
     }
 
@@ -205,6 +209,11 @@
         mNative.setTouchpadRightClickZoneEnabled(InputSettings.useTouchpadRightClickZone(mContext));
     }
 
+    private void updateTouchpadThreeFingerTapShortcutEnabled() {
+        mNative.setTouchpadThreeFingerTapShortcutEnabled(
+                InputSettings.useTouchpadThreeFingerTapShortcut(mContext));
+    }
+
     private void updateShowTouches() {
         mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES, false));
     }
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index e665f96..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;
@@ -40,6 +36,7 @@
 import android.database.ContentObserver;
 import android.hardware.input.AidlInputGestureData;
 import android.hardware.input.AidlKeyGestureEvent;
+import android.hardware.input.AppLaunchData;
 import android.hardware.input.IKeyGestureEventListener;
 import android.hardware.input.IKeyGestureHandler;
 import android.hardware.input.InputGestureData;
@@ -68,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;
@@ -91,6 +89,9 @@
 
     // Maximum key gesture events that are tracked and will be available in input dump.
     private static final int MAX_TRACKED_EVENTS = 10;
+    private static final int SHORTCUT_META_MASK =
+            KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON
+                    | KeyEvent.META_SHIFT_ON;
 
     private static final int MSG_NOTIFY_KEY_GESTURE_EVENT = 1;
 
@@ -115,7 +116,12 @@
     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")
+    private int mCurrentUserId = UserHandle.USER_SYSTEM;
 
     // Pending actions
     private boolean mPendingMetaAction;
@@ -123,7 +129,6 @@
     private boolean mPendingHideRecentSwitcher;
 
     // Platform behaviors
-    private boolean mEnableBugReportKeyboardShortcut;
     private boolean mHasFeatureWatch;
     private boolean mHasFeatureLeanback;
 
@@ -170,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);
@@ -229,7 +234,7 @@
 
                         @Override
                         public void execute() {
-                            handleKeyGesture(
+                            handleMultiKeyGesture(
                                     new int[]{KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_POWER},
                                     KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD,
                                     KeyGestureEvent.ACTION_GESTURE_START, 0);
@@ -237,7 +242,7 @@
 
                         @Override
                         public void cancel() {
-                            handleKeyGesture(
+                            handleMultiKeyGesture(
                                     new int[]{KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_POWER},
                                     KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD,
                                     KeyGestureEvent.ACTION_GESTURE_COMPLETE,
@@ -257,14 +262,14 @@
 
                             @Override
                             public void execute() {
-                                handleKeyGesture(new int[]{KeyEvent.KEYCODE_POWER,
+                                handleMultiKeyGesture(new int[]{KeyEvent.KEYCODE_POWER,
                                                 KeyEvent.KEYCODE_STEM_PRIMARY},
                                         KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD,
                                         KeyGestureEvent.ACTION_GESTURE_START, 0);
                             }
                             @Override
                             public void cancel() {
-                                handleKeyGesture(new int[]{KeyEvent.KEYCODE_POWER,
+                                handleMultiKeyGesture(new int[]{KeyEvent.KEYCODE_POWER,
                                                 KeyEvent.KEYCODE_STEM_PRIMARY},
                                         KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD,
                                         KeyGestureEvent.ACTION_GESTURE_COMPLETE,
@@ -285,7 +290,7 @@
 
                     @Override
                     public void execute() {
-                        handleKeyGesture(
+                        handleMultiKeyGesture(
                                 new int[]{KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP},
                                 KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD,
                                 KeyGestureEvent.ACTION_GESTURE_START, 0);
@@ -293,7 +298,7 @@
 
                     @Override
                     public void cancel() {
-                        handleKeyGesture(
+                        handleMultiKeyGesture(
                                 new int[]{KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP},
                                 KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE,
@@ -325,7 +330,7 @@
                         if (gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) {
                             return;
                         }
-                        handleKeyGesture(
+                        handleMultiKeyGesture(
                                 new int[]{KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_POWER},
                                 gestureType, KeyGestureEvent.ACTION_GESTURE_START, 0);
                     }
@@ -335,7 +340,7 @@
                         if (gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) {
                             return;
                         }
-                        handleKeyGesture(
+                        handleMultiKeyGesture(
                                 new int[]{KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_POWER},
                                 gestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE,
                                 KeyGestureEvent.FLAG_CANCELLED);
@@ -369,7 +374,7 @@
 
                         @Override
                         public void execute() {
-                            handleKeyGesture(
+                            handleMultiKeyGesture(
                                     new int[]{KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN},
                                     KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD,
                                     KeyGestureEvent.ACTION_GESTURE_START, 0);
@@ -377,7 +382,7 @@
 
                         @Override
                         public void cancel() {
-                            handleKeyGesture(
+                            handleMultiKeyGesture(
                                     new int[]{KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN},
                                     KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD,
                                     KeyGestureEvent.ACTION_GESTURE_COMPLETE,
@@ -406,14 +411,14 @@
 
                         @Override
                         public void execute() {
-                            handleKeyGesture(
+                            handleMultiKeyGesture(
                                     new int[]{KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_CENTER},
                                     KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT,
                                     KeyGestureEvent.ACTION_GESTURE_START, 0);
                         }
                         @Override
                         public void cancel() {
-                            handleKeyGesture(
+                            handleMultiKeyGesture(
                                     new int[]{KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_CENTER},
                                     KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT,
                                     KeyGestureEvent.ACTION_GESTURE_COMPLETE,
@@ -429,6 +434,8 @@
 
     public void systemRunning() {
         mSettingsObserver.observe();
+        mAppLaunchShortcutManager.systemRunning();
+        mInputGestureManager.systemRunning();
     }
 
     public boolean interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
@@ -488,7 +495,7 @@
     private boolean interceptSystemKeysAndShortcuts(IBinder focusedToken, KeyEvent event) {
         final int keyCode = event.getKeyCode();
         final int repeatCount = event.getRepeatCount();
-        final int metaState = event.getMetaState();
+        final int metaState = event.getMetaState() & SHORTCUT_META_MASK;
         final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
         final boolean canceled = event.isCanceled();
         final int displayId = event.getDisplayId();
@@ -505,21 +512,40 @@
             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);
-                }
-                break;
             case KeyEvent.KEYCODE_RECENT_APPS:
                 if (firstDown) {
                     handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 return true;
             case KeyEvent.KEYCODE_APP_SWITCH:
@@ -527,270 +553,15 @@
                     handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
                             KeyGestureEvent.ACTION_GESTURE_START, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 } else if (!down) {
                     handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, canceled ? KeyGestureEvent.FLAG_CANCELLED : 0);
+                            focusedToken, canceled ? KeyGestureEvent.FLAG_CANCELLED : 0,
+                            /* 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);
-                }
-                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);
-                }
-                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);
-                }
-                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);
-                    } 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);
-                    }
-                }
-                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);
-                }
-                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);
-                    }
-                }
-                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);
-                    }
-                }
-                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);
-                    }
-                }
-                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);
-                    }
-                }
-                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);
-                    }
-                }
-                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);
-                    }
-                }
-                // 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);
-                }
-                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);
-                }
-                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);
-                }
-                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);
-                    } 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);
-                    } else {
-                        return handleKeyGesture(deviceId, new int[]{keyCode},
-                                KeyEvent.META_META_ON,
-                                KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
-                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
-                    }
-                }
-                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);
-                    } 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);
-                    }
-                }
-                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);
-                    }
-                }
-                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);
-                    }
-                }
-                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);
-                    }
-                }
-                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);
-                    }
-                }
-                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);
-                    }
-                }
-                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);
-                }
-                break;
             case KeyEvent.KEYCODE_BRIGHTNESS_UP:
             case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
                 if (down) {
@@ -799,7 +570,7 @@
                                     ? KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP
                                     : KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN:
@@ -807,7 +578,7 @@
                     handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP:
@@ -815,7 +586,7 @@
                     handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE:
@@ -824,7 +595,7 @@
                     handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 return true;
             case KeyEvent.KEYCODE_ALL_APPS:
@@ -832,7 +603,7 @@
                     handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 return true;
             case KeyEvent.KEYCODE_NOTIFICATION:
@@ -840,7 +611,7 @@
                     handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 return true;
             case KeyEvent.KEYCODE_SEARCH:
@@ -848,7 +619,7 @@
                     return handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
 
                 }
                 break;
@@ -859,13 +630,13 @@
                                 new int[]{keyCode}, /* modifierState = */0,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     } else if (mSettingsKeyBehavior == SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL) {
                         handleKeyGesture(deviceId,
                                 new int[]{keyCode}, /* modifierState = */0,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     }
                 }
                 return true;
@@ -875,7 +646,7 @@
                             event.isShiftPressed() ? KeyEvent.META_SHIFT_ON : 0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 return true;
             case KeyEvent.KEYCODE_CAPS_LOCK:
@@ -885,7 +656,8 @@
                     AidlKeyGestureEvent eventToNotify = createKeyGestureEvent(deviceId,
                             new int[]{keyCode}, metaState,
                             KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
-                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, /* flags = */0);
+                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, /* flags = */0,
+                            /* appLaunchData = */null);
                     Message msg = Message.obtain(mHandler, MSG_NOTIFY_KEY_GESTURE_EVENT,
                             eventToNotify);
                     mHandler.sendMessage(msg);
@@ -896,7 +668,7 @@
                     handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 return true;
             case KeyEvent.KEYCODE_META_LEFT:
@@ -917,7 +689,7 @@
                                         KeyEvent.KEYCODE_ALT_LEFT}, /* modifierState = */0,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
 
                     } else if (mPendingMetaAction) {
                         mPendingMetaAction = false;
@@ -926,19 +698,14 @@
                                     /* modifierState = */0,
                                     KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
                                     KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                    focusedToken, /* flags = */0);
+                                    focusedToken, /* flags = */0, /* appLaunchData = */null);
                         }
                     }
                 }
                 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);
-                    } else if (!mPendingHideRecentSwitcher) {
+                    if (!mPendingHideRecentSwitcher) {
                         final int shiftlessModifiers =
                                 event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
                         if (KeyEvent.metaStateHasModifiers(
@@ -948,7 +715,7 @@
                                     KeyEvent.META_ALT_ON,
                                     KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER,
                                     KeyGestureEvent.ACTION_GESTURE_START, displayId,
-                                    focusedToken, /* flags = */0);
+                                    focusedToken, /* flags = */0, /* appLaunchData = */null);
                         }
                     }
                 }
@@ -969,7 +736,7 @@
                                 KeyEvent.META_ALT_ON,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     }
 
                     // Toggle Caps Lock on META-ALT.
@@ -979,7 +746,7 @@
                                         KeyEvent.KEYCODE_ALT_LEFT}, /* modifierState = */0,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     }
                 }
                 break;
@@ -998,6 +765,23 @@
                         + " interceptKeyBeforeQueueing");
                 return true;
         }
+
+        // Handle custom shortcuts
+        if (firstDown) {
+            InputGestureData customGesture;
+            synchronized (mUserLock) {
+                customGesture = mInputGestureManager.getCustomGestureForKeyEvent(mCurrentUserId,
+                        event);
+            }
+            if (customGesture == null) {
+                return false;
+            }
+            return handleKeyGesture(deviceId, new int[]{keyCode}, metaState,
+                    customGesture.getAction().keyGestureType(),
+                    KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+                    displayId, focusedToken, /* flags = */0,
+                    customGesture.getAction().appLaunchData());
+        }
         return false;
     }
 
@@ -1020,7 +804,7 @@
                                         ? KeyEvent.META_SHIFT_ON : 0),
                                 KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     }
                 }
                 break;
@@ -1032,7 +816,7 @@
                             KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON,
                             KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 break;
             case KeyEvent.KEYCODE_SYSRQ:
@@ -1040,7 +824,7 @@
                     return handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 break;
             case KeyEvent.KEYCODE_ESCAPE:
@@ -1048,7 +832,7 @@
                     return handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 break;
         }
@@ -1056,18 +840,26 @@
         return false;
     }
 
-    private boolean handleKeyGesture(int[] keycodes,
+    private void handleMultiKeyGesture(int[] keycodes,
             @KeyGestureEvent.KeyGestureType int gestureType, int action, int flags) {
-        return handleKeyGesture(KeyCharacterMap.VIRTUAL_KEYBOARD, keycodes, /* modifierState= */0,
-                gestureType, action, Display.DEFAULT_DISPLAY, /* focusedToken = */null, flags);
+        handleKeyGesture(KeyCharacterMap.VIRTUAL_KEYBOARD, keycodes, /* modifierState= */0,
+                gestureType, action, Display.DEFAULT_DISPLAY, /* focusedToken = */null, flags,
+                /* 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,
-            @Nullable IBinder focusedToken, int flags) {
+            @Nullable IBinder focusedToken, int flags, @Nullable AppLaunchData appLaunchData) {
         return handleKeyGesture(createKeyGestureEvent(deviceId, keycodes,
-                modifierState, gestureType, action, displayId, flags), focusedToken);
+                modifierState, gestureType, action, displayId, flags, appLaunchData), focusedToken);
     }
 
     private boolean handleKeyGesture(AidlKeyGestureEvent event, @Nullable IBinder focusedToken) {
@@ -1099,17 +891,40 @@
         // TODO(b/358569822): Once we move the gesture detection logic to IMS, we ideally
         //  should not rely on PWM to tell us about the gesture start and end.
         AidlKeyGestureEvent event = createKeyGestureEvent(deviceId, keycodes, modifierState,
-                gestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE, Display.DEFAULT_DISPLAY, 0);
+                gestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE, Display.DEFAULT_DISPLAY,
+                /* flags = */0, /* appLaunchData = */null);
         mHandler.obtainMessage(MSG_NOTIFY_KEY_GESTURE_EVENT, event).sendToTarget();
     }
 
     public void handleKeyGesture(int deviceId, int[] keycodes, int modifierState,
             @KeyGestureEvent.KeyGestureType int gestureType) {
         AidlKeyGestureEvent event = createKeyGestureEvent(deviceId, keycodes, modifierState,
-                gestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE, Display.DEFAULT_DISPLAY, 0);
+                gestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE, Display.DEFAULT_DISPLAY,
+                /* flags = */0, /* appLaunchData = */null);
         handleKeyGesture(event, null /*focusedToken*/);
     }
 
+    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
+    public void setCurrentUserId(@UserIdInt int userId) {
+        synchronized (mUserLock) {
+            mCurrentUserId = userId;
+        }
+    }
+
     @MainThread
     private void notifyKeyGestureEvent(AidlKeyGestureEvent event) {
         InputDevice device = getInputDevice(event.deviceId);
@@ -1220,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);
@@ -1291,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);
@@ -1367,7 +1201,7 @@
 
     private AidlKeyGestureEvent createKeyGestureEvent(int deviceId, int[] keycodes,
             int modifierState, @KeyGestureEvent.KeyGestureType int gestureType, int action,
-            int displayId, int flags) {
+            int displayId, int flags, @Nullable AppLaunchData appLaunchData) {
         AidlKeyGestureEvent event = new AidlKeyGestureEvent();
         event.deviceId = deviceId;
         event.keycodes = keycodes;
@@ -1376,12 +1210,25 @@
         event.action = action;
         event.displayId = displayId;
         event.flags = flags;
+        if (appLaunchData != null) {
+            if (appLaunchData instanceof AppLaunchData.CategoryData categoryData) {
+                event.appLaunchCategory = categoryData.getCategory();
+            } else if (appLaunchData instanceof AppLaunchData.RoleData roleData) {
+                event.appLaunchRole = roleData.getRole();
+            } else if (appLaunchData instanceof AppLaunchData.ComponentData componentData) {
+                event.appLaunchPackageName = componentData.getPackageName();
+                event.appLaunchClassName = componentData.getClassName();
+            } else {
+                throw new IllegalArgumentException("AppLaunchData type is invalid!");
+            }
+        }
         return event;
     }
 
     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);
@@ -1421,6 +1268,7 @@
         }
         ipw.decreaseIndent();
         mKeyCombinationManager.dump("", ipw);
+        mAppLaunchShortcutManager.dump(ipw);
         mInputGestureManager.dump(ipw);
     }
 }
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 283fdea..8903c27 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -143,6 +143,8 @@
 
     void setTouchpadRightClickZoneEnabled(boolean enabled);
 
+    void setTouchpadThreeFingerTapShortcutEnabled(boolean enabled);
+
     void setShowTouches(boolean enabled);
 
     void setNonInteractiveDisplays(int[] displayIds);
@@ -427,6 +429,9 @@
         public native void setTouchpadRightClickZoneEnabled(boolean enabled);
 
         @Override
+        public native void setTouchpadThreeFingerTapShortcutEnabled(boolean enabled);
+
+        @Override
         public native void setShowTouches(boolean enabled);
 
         @Override
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
index 146ce17..46be1ca 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
@@ -27,6 +27,7 @@
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.Xml;
+import android.view.inputmethod.Flags;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
@@ -59,8 +60,14 @@
     private static final String NODE_SUBTYPE = "subtype";
     private static final String NODE_IMI = "imi";
     private static final String ATTR_ID = "id";
+    /** The resource ID of the subtype name. */
     private static final String ATTR_LABEL = "label";
+    /** The untranslatable name of the subtype. */
     private static final String ATTR_NAME_OVERRIDE = "nameOverride";
+    /** The layout label string resource identifier. */
+    private static final String ATTR_LAYOUT_LABEL = "layoutLabel";
+    /** The non-localized layout label. */
+    private static final String ATTR_LAYOUT_LABEL_NON_LOCALIZED = "layoutLabelNonLocalized";
     private static final String ATTR_NAME_PK_LANGUAGE_TAG = "pkLanguageTag";
     private static final String ATTR_NAME_PK_LAYOUT_TYPE = "pkLayoutType";
     private static final String ATTR_ICON = "icon";
@@ -173,6 +180,11 @@
                     out.attributeInt(null, ATTR_ICON, subtype.getIconResId());
                     out.attributeInt(null, ATTR_LABEL, subtype.getNameResId());
                     out.attribute(null, ATTR_NAME_OVERRIDE, subtype.getNameOverride().toString());
+                    if (Flags.imeSwitcherRevampApi()) {
+                        out.attributeInt(null, ATTR_LAYOUT_LABEL, subtype.getLayoutLabelResource());
+                        out.attribute(null, ATTR_LAYOUT_LABEL_NON_LOCALIZED,
+                                subtype.getLayoutLabelNonLocalized().toString());
+                    }
                     ULocale pkLanguageTag = subtype.getPhysicalKeyboardHintLanguageTag();
                     if (pkLanguageTag != null) {
                         out.attribute(null, ATTR_NAME_PK_LANGUAGE_TAG,
@@ -264,6 +276,16 @@
                     final int label = parser.getAttributeInt(null, ATTR_LABEL);
                     final String untranslatableName = parser.getAttributeValue(null,
                             ATTR_NAME_OVERRIDE);
+                    final int layoutLabelResource;
+                    final String layoutLabelNonLocalized;
+                    if (Flags.imeSwitcherRevampApi()) {
+                        layoutLabelResource = parser.getAttributeInt(null, ATTR_LAYOUT_LABEL);
+                        layoutLabelNonLocalized = parser.getAttributeValue(null,
+                                ATTR_LAYOUT_LABEL_NON_LOCALIZED);
+                    } else {
+                        layoutLabelResource = 0;
+                        layoutLabelNonLocalized = null;
+                    }
                     final String pkLanguageTag = parser.getAttributeValue(null,
                             ATTR_NAME_PK_LANGUAGE_TAG);
                     final String pkLayoutType = parser.getAttributeValue(null,
@@ -283,6 +305,7 @@
                     final InputMethodSubtype.InputMethodSubtypeBuilder
                             builder = new InputMethodSubtype.InputMethodSubtypeBuilder()
                             .setSubtypeNameResId(label)
+                            .setLayoutLabelResource(layoutLabelResource)
                             .setPhysicalKeyboardHint(
                                     pkLanguageTag == null ? null : new ULocale(pkLanguageTag),
                                     pkLayoutType == null ? "" : pkLayoutType)
@@ -301,6 +324,9 @@
                     if (untranslatableName != null) {
                         builder.setSubtypeNameOverride(untranslatableName);
                     }
+                    if (layoutLabelNonLocalized != null) {
+                        builder.setLayoutLabelNonLocalized(layoutLabelNonLocalized);
+                    }
                     tempSubtypesArray.add(builder.build());
                 }
             }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 0104373..d8483f7 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4969,7 +4969,7 @@
                     final var userData = getUserData(userId);
                     if (Flags.refactorInsetsController()) {
                         setImeVisibilityOnFocusedWindowClient(false, userData,
-                                null /* TODO(b329229469) check statsToken */);
+                                null /* TODO(b/353463205) check statsToken */);
                     } else {
 
                         hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
index 6abd5aa..9f94905 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
@@ -238,8 +238,8 @@
                 prevImeId = imeId;
             }
 
-            menuItems.add(new SubtypeItem(item.mImeName, item.mSubtypeName, item.mImi,
-                    item.mSubtypeIndex));
+            menuItems.add(new SubtypeItem(item.mImeName, item.mSubtypeName, item.mLayoutName,
+                    item.mImi, item.mSubtypeIndex));
         }
 
         return menuItems;
@@ -348,6 +348,13 @@
         @Nullable
         final CharSequence mSubtypeName;
 
+        /**
+         * The name of the subtype's layout, or {@code null} if this item doesn't have a subtype,
+         * or doesn't specify a layout.
+         */
+        @Nullable
+        private final CharSequence mLayoutName;
+
         /** The info of the input method. */
         @NonNull
         final InputMethodInfo mImi;
@@ -360,10 +367,11 @@
         final int mSubtypeIndex;
 
         SubtypeItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName,
-                @NonNull InputMethodInfo imi,
+                @Nullable CharSequence layoutName, @NonNull InputMethodInfo imi,
                 @IntRange(from = NOT_A_SUBTYPE_INDEX) int subtypeIndex) {
             mImeName = imeName;
             mSubtypeName = subtypeName;
+            mLayoutName = layoutName;
             mImi = imi;
             mSubtypeIndex = subtypeIndex;
         }
@@ -521,6 +529,9 @@
             /** The name of the item. */
             @NonNull
             private final TextView mName;
+            /** The layout name. */
+            @NonNull
+            private final TextView mLayout;
             /** Indicator for the selected status of the item. */
             @NonNull
             private final ImageView mCheckmark;
@@ -536,6 +547,7 @@
 
                 mContainer = itemView;
                 mName = itemView.requireViewById(com.android.internal.R.id.text);
+                mLayout = itemView.requireViewById(com.android.internal.R.id.text2);
                 mCheckmark = itemView.requireViewById(com.android.internal.R.id.image);
 
                 mContainer.setOnClickListener((v) -> {
@@ -563,6 +575,9 @@
                 // Trigger the ellipsize marquee behaviour by selecting the name.
                 mName.setSelected(isSelected);
                 mName.setText(name);
+                mLayout.setText(item.mLayoutName);
+                mLayout.setVisibility(
+                        !TextUtils.isEmpty(item.mLayoutName) ? View.VISIBLE : View.GONE);
                 mCheckmark.setVisibility(isSelected ? View.VISIBLE : View.GONE);
             }
         }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index 96b3e08..51b85e9 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -85,6 +85,12 @@
         public final CharSequence mImeName;
         @Nullable
         public final CharSequence mSubtypeName;
+        /**
+         * The subtype's layout name, or {@code null} if this item doesn't have a subtype,
+         * or doesn't specify a layout.
+         */
+        @Nullable
+        public final CharSequence mLayoutName;
         @NonNull
         public final InputMethodInfo mImi;
         /**
@@ -96,10 +102,11 @@
         public final boolean mIsSystemLanguage;
 
         ImeSubtypeListItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName,
-                @NonNull InputMethodInfo imi, int subtypeIndex, @Nullable String subtypeLocale,
-                @NonNull String systemLocale) {
+                @Nullable CharSequence layoutName, @NonNull InputMethodInfo imi, int subtypeIndex,
+                @Nullable String subtypeLocale, @NonNull String systemLocale) {
             mImeName = imeName;
             mSubtypeName = subtypeName;
+            mLayoutName = layoutName;
             mImi = imi;
             mSubtypeIndex = subtypeIndex;
             if (TextUtils.isEmpty(subtypeLocale)) {
@@ -252,8 +259,11 @@
                                 subtype.overridesImplicitlyEnabledSubtype() ? null : subtype
                                         .getDisplayName(userAwareContext, imi.getPackageName(),
                                                 imi.getServiceInfo().applicationInfo);
-                        imList.add(new ImeSubtypeListItem(imeLabel,
-                                subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr));
+                        final var layoutName = subtype.overridesImplicitlyEnabledSubtype() ? null
+                                : subtype.getLayoutDisplayName(userAwareContext,
+                                        imi.getServiceInfo().applicationInfo);
+                        imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, layoutName,
+                                imi, j, subtype.getLocale(), mSystemLocaleStr));
 
                         // Removing this subtype from enabledSubtypeSet because we no
                         // longer need to add an entry of this subtype to imList to avoid
@@ -262,8 +272,8 @@
                     }
                 }
             } else {
-                imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_INDEX, null,
-                        mSystemLocaleStr));
+                imList.add(new ImeSubtypeListItem(imeLabel, null /* subtypeName */,
+                        null /* layoutName */, imi, NOT_A_SUBTYPE_INDEX, null, mSystemLocaleStr));
             }
         }
         Collections.sort(imList);
@@ -311,13 +321,16 @@
                                 subtype.overridesImplicitlyEnabledSubtype() ? null : subtype
                                         .getDisplayName(userAwareContext, imi.getPackageName(),
                                                 imi.getServiceInfo().applicationInfo);
-                        imList.add(new ImeSubtypeListItem(imeLabel,
-                                subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr));
+                        final var layoutName = subtype.overridesImplicitlyEnabledSubtype() ? null
+                                : subtype.getLayoutDisplayName(userAwareContext,
+                                        imi.getServiceInfo().applicationInfo);
+                        imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, layoutName,
+                                imi, j, subtype.getLocale(), mSystemLocaleStr));
                     }
                 }
             } else {
-                imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_INDEX, null,
-                        mSystemLocaleStr));
+                imList.add(new ImeSubtypeListItem(imeLabel, null /* subtypeName */,
+                        null /* layoutName */, imi, NOT_A_SUBTYPE_INDEX, null, mSystemLocaleStr));
             }
         }
         return imList;
diff --git a/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java b/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java
deleted file mode 100644
index e831e40..0000000
--- a/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.parser;
-
-import android.annotation.Nullable;
-import android.util.Xml;
-
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.server.integrity.model.RuleMetadata;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/** Helper class for parsing rule metadata. */
-public class RuleMetadataParser {
-
-    public static final String RULE_PROVIDER_TAG = "P";
-    public static final String VERSION_TAG = "V";
-
-    /** Parse the rule metadata from an input stream. */
-    @Nullable
-    public static RuleMetadata parse(InputStream inputStream)
-            throws XmlPullParserException, IOException {
-
-        String ruleProvider = "";
-        String version = "";
-
-        TypedXmlPullParser xmlPullParser = Xml.resolvePullParser(inputStream);
-
-        int eventType;
-        while ((eventType = xmlPullParser.next()) != XmlPullParser.END_DOCUMENT) {
-            if (eventType == XmlPullParser.START_TAG) {
-                String tag = xmlPullParser.getName();
-                switch (tag) {
-                    case RULE_PROVIDER_TAG:
-                        ruleProvider = xmlPullParser.nextText();
-                        break;
-                    case VERSION_TAG:
-                        version = xmlPullParser.nextText();
-                        break;
-                    default:
-                        throw new IllegalStateException("Unknown tag in metadata: " + tag);
-                }
-            }
-        }
-
-        return new RuleMetadata(ruleProvider, version);
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
deleted file mode 100644
index 8ba5870..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.DEFAULT_FORMAT_VERSION;
-import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.INSTALLER_ALLOWED_BY_MANIFEST_START;
-import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
-import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
-import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE;
-import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
-
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.InstallerAllowedByManifestFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.IntegrityUtils;
-import android.content.integrity.Rule;
-
-import com.android.internal.util.Preconditions;
-import com.android.server.integrity.model.BitOutputStream;
-import com.android.server.integrity.model.ByteTrackedOutputStream;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-/** A helper class to serialize rules from the {@link Rule} model to Binary representation. */
-public class RuleBinarySerializer implements RuleSerializer {
-    static final int TOTAL_RULE_SIZE_LIMIT = 200000;
-    static final int INDEXED_RULE_SIZE_LIMIT = 100000;
-    static final int NONINDEXED_RULE_SIZE_LIMIT = 1000;
-
-    // Get the byte representation for a list of rules.
-    @Override
-    public byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion)
-            throws RuleSerializeException {
-        try {
-            ByteArrayOutputStream rulesOutputStream = new ByteArrayOutputStream();
-            serialize(rules, formatVersion, rulesOutputStream, new ByteArrayOutputStream());
-            return rulesOutputStream.toByteArray();
-        } catch (Exception e) {
-            throw new RuleSerializeException(e.getMessage(), e);
-        }
-    }
-
-    // Get the byte representation for a list of rules, and write them to an output stream.
-    @Override
-    public void serialize(
-            List<Rule> rules,
-            Optional<Integer> formatVersion,
-            OutputStream rulesFileOutputStream,
-            OutputStream indexingFileOutputStream)
-            throws RuleSerializeException {
-        try {
-            if (rules == null) {
-                throw new IllegalArgumentException("Null rules cannot be serialized.");
-            }
-
-            if (rules.size() > TOTAL_RULE_SIZE_LIMIT) {
-                throw new IllegalArgumentException("Too many rules provided: " + rules.size());
-            }
-
-            // Determine the indexing groups and the order of the rules within each indexed group.
-            Map<Integer, Map<String, List<Rule>>> indexedRules =
-                    RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules);
-
-            // Validate the rule blocks are not larger than expected limits.
-            verifySize(indexedRules.get(PACKAGE_NAME_INDEXED), INDEXED_RULE_SIZE_LIMIT);
-            verifySize(indexedRules.get(APP_CERTIFICATE_INDEXED), INDEXED_RULE_SIZE_LIMIT);
-            verifySize(indexedRules.get(NOT_INDEXED), NONINDEXED_RULE_SIZE_LIMIT);
-
-            // Serialize the rules.
-            ByteTrackedOutputStream ruleFileByteTrackedOutputStream =
-                    new ByteTrackedOutputStream(rulesFileOutputStream);
-            serializeRuleFileMetadata(formatVersion, ruleFileByteTrackedOutputStream);
-            LinkedHashMap<String, Integer> packageNameIndexes =
-                    serializeRuleList(
-                            indexedRules.get(PACKAGE_NAME_INDEXED),
-                            ruleFileByteTrackedOutputStream);
-            LinkedHashMap<String, Integer> appCertificateIndexes =
-                    serializeRuleList(
-                            indexedRules.get(APP_CERTIFICATE_INDEXED),
-                            ruleFileByteTrackedOutputStream);
-            LinkedHashMap<String, Integer> unindexedRulesIndexes =
-                    serializeRuleList(
-                            indexedRules.get(NOT_INDEXED), ruleFileByteTrackedOutputStream);
-
-            // Serialize their indexes.
-            BitOutputStream indexingBitOutputStream = new BitOutputStream(indexingFileOutputStream);
-            serializeIndexGroup(packageNameIndexes, indexingBitOutputStream, /* isIndexed= */ true);
-            serializeIndexGroup(
-                    appCertificateIndexes, indexingBitOutputStream, /* isIndexed= */ true);
-            serializeIndexGroup(
-                    unindexedRulesIndexes, indexingBitOutputStream, /* isIndexed= */ false);
-            indexingBitOutputStream.flush();
-        } catch (Exception e) {
-            throw new RuleSerializeException(e.getMessage(), e);
-        }
-    }
-
-    private void verifySize(Map<String, List<Rule>> ruleListMap, int ruleSizeLimit) {
-        int totalRuleCount =
-                ruleListMap.values().stream()
-                        .map(list -> list.size())
-                        .collect(Collectors.summingInt(Integer::intValue));
-        if (totalRuleCount > ruleSizeLimit) {
-            throw new IllegalArgumentException(
-                    "Too many rules provided in the indexing group. Provided "
-                            + totalRuleCount
-                            + " limit "
-                            + ruleSizeLimit);
-        }
-    }
-
-    private void serializeRuleFileMetadata(
-            Optional<Integer> formatVersion, ByteTrackedOutputStream outputStream)
-            throws IOException {
-        int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);
-
-        BitOutputStream bitOutputStream = new BitOutputStream(outputStream);
-        bitOutputStream.setNext(FORMAT_VERSION_BITS, formatVersionValue);
-        bitOutputStream.flush();
-    }
-
-    private LinkedHashMap<String, Integer> serializeRuleList(
-            Map<String, List<Rule>> rulesMap, ByteTrackedOutputStream outputStream)
-            throws IOException {
-        Preconditions.checkArgument(
-                rulesMap != null, "serializeRuleList should never be called with null rule list.");
-
-        BitOutputStream bitOutputStream = new BitOutputStream(outputStream);
-        LinkedHashMap<String, Integer> indexMapping = new LinkedHashMap();
-        indexMapping.put(START_INDEXING_KEY, outputStream.getWrittenBytesCount());
-
-        List<String> sortedKeys = rulesMap.keySet().stream().sorted().collect(Collectors.toList());
-        int indexTracker = 0;
-        for (String key : sortedKeys) {
-            if (indexTracker >= INDEXING_BLOCK_SIZE) {
-                indexMapping.put(key, outputStream.getWrittenBytesCount());
-                indexTracker = 0;
-            }
-
-            for (Rule rule : rulesMap.get(key)) {
-                serializeRule(rule, bitOutputStream);
-                bitOutputStream.flush();
-                indexTracker++;
-            }
-        }
-        indexMapping.put(END_INDEXING_KEY, outputStream.getWrittenBytesCount());
-
-        return indexMapping;
-    }
-
-    private void serializeRule(Rule rule, BitOutputStream bitOutputStream) throws IOException {
-        if (rule == null) {
-            throw new IllegalArgumentException("Null rule can not be serialized");
-        }
-
-        // Start with a '1' bit to mark the start of a rule.
-        bitOutputStream.setNext();
-
-        serializeFormula(rule.getFormula(), bitOutputStream);
-        bitOutputStream.setNext(EFFECT_BITS, rule.getEffect());
-
-        // End with a '1' bit to mark the end of a rule.
-        bitOutputStream.setNext();
-    }
-
-    private void serializeFormula(IntegrityFormula formula, BitOutputStream bitOutputStream)
-            throws IOException {
-        if (formula instanceof AtomicFormula) {
-            serializeAtomicFormula((AtomicFormula) formula, bitOutputStream);
-        } else if (formula instanceof CompoundFormula) {
-            serializeCompoundFormula((CompoundFormula) formula, bitOutputStream);
-        } else if (formula instanceof InstallerAllowedByManifestFormula) {
-            bitOutputStream.setNext(SEPARATOR_BITS, INSTALLER_ALLOWED_BY_MANIFEST_START);
-        } else {
-            throw new IllegalArgumentException(
-                    String.format("Invalid formula type: %s", formula.getClass()));
-        }
-    }
-
-    private void serializeCompoundFormula(
-            CompoundFormula compoundFormula, BitOutputStream bitOutputStream) throws IOException {
-        if (compoundFormula == null) {
-            throw new IllegalArgumentException("Null compound formula can not be serialized");
-        }
-
-        bitOutputStream.setNext(SEPARATOR_BITS, COMPOUND_FORMULA_START);
-        bitOutputStream.setNext(CONNECTOR_BITS, compoundFormula.getConnector());
-        for (IntegrityFormula formula : compoundFormula.getFormulas()) {
-            serializeFormula(formula, bitOutputStream);
-        }
-        bitOutputStream.setNext(SEPARATOR_BITS, COMPOUND_FORMULA_END);
-    }
-
-    private void serializeAtomicFormula(
-            AtomicFormula atomicFormula, BitOutputStream bitOutputStream) throws IOException {
-        if (atomicFormula == null) {
-            throw new IllegalArgumentException("Null atomic formula can not be serialized");
-        }
-
-        bitOutputStream.setNext(SEPARATOR_BITS, ATOMIC_FORMULA_START);
-        bitOutputStream.setNext(KEY_BITS, atomicFormula.getKey());
-        if (atomicFormula.getTag() == AtomicFormula.STRING_ATOMIC_FORMULA_TAG) {
-            AtomicFormula.StringAtomicFormula stringAtomicFormula =
-                    (AtomicFormula.StringAtomicFormula) atomicFormula;
-            bitOutputStream.setNext(OPERATOR_BITS, AtomicFormula.EQ);
-            serializeStringValue(
-                    stringAtomicFormula.getValue(),
-                    stringAtomicFormula.getIsHashedValue(),
-                    bitOutputStream);
-        } else if (atomicFormula.getTag() == AtomicFormula.LONG_ATOMIC_FORMULA_TAG) {
-            AtomicFormula.LongAtomicFormula longAtomicFormula =
-                    (AtomicFormula.LongAtomicFormula) atomicFormula;
-            bitOutputStream.setNext(OPERATOR_BITS, longAtomicFormula.getOperator());
-            // TODO(b/147880712): Temporary hack until we support long values in bitOutputStream
-            long value = longAtomicFormula.getValue();
-            serializeIntValue((int) (value >>> 32), bitOutputStream);
-            serializeIntValue((int) value, bitOutputStream);
-        } else if (atomicFormula.getTag() == AtomicFormula.BOOLEAN_ATOMIC_FORMULA_TAG) {
-            AtomicFormula.BooleanAtomicFormula booleanAtomicFormula =
-                    (AtomicFormula.BooleanAtomicFormula) atomicFormula;
-            bitOutputStream.setNext(OPERATOR_BITS, AtomicFormula.EQ);
-            serializeBooleanValue(booleanAtomicFormula.getValue(), bitOutputStream);
-        } else {
-            throw new IllegalArgumentException(
-                    String.format("Invalid atomic formula type: %s", atomicFormula.getClass()));
-        }
-    }
-
-    private void serializeIndexGroup(
-            LinkedHashMap<String, Integer> indexes,
-            BitOutputStream bitOutputStream,
-            boolean isIndexed)
-            throws IOException {
-        // Output the starting location of this indexing group.
-        serializeStringValue(START_INDEXING_KEY, /* isHashedValue= */ false, bitOutputStream);
-        serializeIntValue(indexes.get(START_INDEXING_KEY), bitOutputStream);
-
-        // If the group is indexed, output the locations of the indexes.
-        if (isIndexed) {
-            for (Map.Entry<String, Integer> entry : indexes.entrySet()) {
-                if (!entry.getKey().equals(START_INDEXING_KEY)
-                        && !entry.getKey().equals(END_INDEXING_KEY)) {
-                    serializeStringValue(
-                            entry.getKey(), /* isHashedValue= */ false, bitOutputStream);
-                    serializeIntValue(entry.getValue(), bitOutputStream);
-                }
-            }
-        }
-
-        // Output the end location of this indexing group.
-        serializeStringValue(END_INDEXING_KEY, /*isHashedValue= */ false, bitOutputStream);
-        serializeIntValue(indexes.get(END_INDEXING_KEY), bitOutputStream);
-    }
-
-    private void serializeStringValue(
-            String value, boolean isHashedValue, BitOutputStream bitOutputStream)
-            throws IOException {
-        if (value == null) {
-            throw new IllegalArgumentException("String value can not be null.");
-        }
-        byte[] valueBytes = getBytesForString(value, isHashedValue);
-
-        bitOutputStream.setNext(isHashedValue);
-        bitOutputStream.setNext(VALUE_SIZE_BITS, valueBytes.length);
-        for (byte valueByte : valueBytes) {
-            bitOutputStream.setNext(/* numOfBits= */ 8, valueByte);
-        }
-    }
-
-    private void serializeIntValue(int value, BitOutputStream bitOutputStream) throws IOException {
-        bitOutputStream.setNext(/* numOfBits= */ 32, value);
-    }
-
-    private void serializeBooleanValue(boolean value, BitOutputStream bitOutputStream)
-            throws IOException {
-        bitOutputStream.setNext(value);
-    }
-
-    // Get the byte array for a value.
-    // If the value is not hashed, use its byte array form directly.
-    // If the value is hashed, get the raw form decoding of the value. All hashed values are
-    // hex-encoded. Serialized values are in raw form.
-    private static byte[] getBytesForString(String value, boolean isHashedValue) {
-        if (!isHashedValue) {
-            return value.getBytes(StandardCharsets.UTF_8);
-        }
-        return IntegrityUtils.getBytesFromHexDigest(value);
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
deleted file mode 100644
index 2cbd4ede..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import android.annotation.IntDef;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/** Holds the indexing type and indexing key of a given formula. */
-class RuleIndexingDetails {
-
-    static final int NOT_INDEXED = 0;
-    static final int PACKAGE_NAME_INDEXED = 1;
-    static final int APP_CERTIFICATE_INDEXED = 2;
-
-    static final String DEFAULT_RULE_KEY = "N/A";
-
-    /** Represents which indexed file the rule should be located. */
-    @IntDef(
-            value = {
-                    NOT_INDEXED,
-                    PACKAGE_NAME_INDEXED,
-                    APP_CERTIFICATE_INDEXED
-            })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface IndexType {
-    }
-
-    private @IndexType int mIndexType;
-    private String mRuleKey;
-
-    /** Constructor without a ruleKey for {@code NOT_INDEXED}. */
-    RuleIndexingDetails(@IndexType int indexType) {
-        this.mIndexType = indexType;
-        this.mRuleKey = DEFAULT_RULE_KEY;
-    }
-
-    /** Constructor with a ruleKey for indexed rules. */
-    RuleIndexingDetails(@IndexType int indexType, String ruleKey) {
-        this.mIndexType = indexType;
-        this.mRuleKey = ruleKey;
-    }
-
-    /** Returns the indexing type for the rule. */
-    @IndexType
-    public int getIndexType() {
-        return mIndexType;
-    }
-
-    /** Returns the identified rule key. */
-    public String getRuleKey() {
-        return mRuleKey;
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
deleted file mode 100644
index e723559..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
-
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.Rule;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-/** A helper class for identifying the indexing type and key of a given rule. */
-class RuleIndexingDetailsIdentifier {
-
-    /**
-     * Splits a given rule list into three indexing categories. Each rule category is returned as a
-     * TreeMap that is sorted by their indexing keys -- where keys correspond to package name for
-     * PACKAGE_NAME_INDEXED rules, app certificate for APP_CERTIFICATE_INDEXED rules and N/A for
-     * NOT_INDEXED rules.
-     */
-    public static Map<Integer, Map<String, List<Rule>>> splitRulesIntoIndexBuckets(
-            List<Rule> rules) {
-        if (rules == null) {
-            throw new IllegalArgumentException(
-                    "Index buckets cannot be created for null rule list.");
-        }
-
-        Map<Integer, Map<String, List<Rule>>> typeOrganizedRuleMap = new HashMap();
-        typeOrganizedRuleMap.put(NOT_INDEXED, new HashMap());
-        typeOrganizedRuleMap.put(PACKAGE_NAME_INDEXED, new HashMap<>());
-        typeOrganizedRuleMap.put(APP_CERTIFICATE_INDEXED, new HashMap<>());
-
-        // Split the rules into the appropriate indexed pattern. The Tree Maps help us to keep the
-        // entries sorted by their index key.
-        for (Rule rule : rules) {
-            RuleIndexingDetails indexingDetails;
-            try {
-                indexingDetails = getIndexingDetails(rule.getFormula());
-            } catch (Exception e) {
-                throw new IllegalArgumentException(
-                        String.format("Malformed rule identified. [%s]", rule.toString()));
-            }
-
-            int ruleIndexType = indexingDetails.getIndexType();
-            String ruleKey = indexingDetails.getRuleKey();
-
-            if (!typeOrganizedRuleMap.get(ruleIndexType).containsKey(ruleKey)) {
-                typeOrganizedRuleMap.get(ruleIndexType).put(ruleKey, new ArrayList());
-            }
-
-            typeOrganizedRuleMap.get(ruleIndexType).get(ruleKey).add(rule);
-        }
-
-        return typeOrganizedRuleMap;
-    }
-
-    private static RuleIndexingDetails getIndexingDetails(IntegrityFormula formula) {
-        switch (formula.getTag()) {
-            case IntegrityFormula.COMPOUND_FORMULA_TAG:
-                return getIndexingDetailsForCompoundFormula((CompoundFormula) formula);
-            case IntegrityFormula.STRING_ATOMIC_FORMULA_TAG:
-                return getIndexingDetailsForStringAtomicFormula(
-                        (AtomicFormula.StringAtomicFormula) formula);
-            case IntegrityFormula.LONG_ATOMIC_FORMULA_TAG:
-            case IntegrityFormula.INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG:
-            case IntegrityFormula.BOOLEAN_ATOMIC_FORMULA_TAG:
-                // Package name and app certificate related formulas are string atomic formulas.
-                return new RuleIndexingDetails(NOT_INDEXED);
-            default:
-                throw new IllegalArgumentException(
-                        String.format("Invalid formula tag type: %s", formula.getTag()));
-        }
-    }
-
-    private static RuleIndexingDetails getIndexingDetailsForCompoundFormula(
-            CompoundFormula compoundFormula) {
-        int connector = compoundFormula.getConnector();
-        List<IntegrityFormula> formulas = compoundFormula.getFormulas();
-
-        switch (connector) {
-            case CompoundFormula.AND:
-            case CompoundFormula.OR:
-                // If there is a package name related atomic rule, return package name indexed.
-                Optional<RuleIndexingDetails> packageNameRule =
-                        formulas.stream()
-                                .map(formula -> getIndexingDetails(formula))
-                                .filter(ruleIndexingDetails -> ruleIndexingDetails.getIndexType()
-                                        == PACKAGE_NAME_INDEXED)
-                                .findAny();
-                if (packageNameRule.isPresent()) {
-                    return packageNameRule.get();
-                }
-
-                // If there is an app certificate related atomic rule but no package name related
-                // atomic rule, return app certificate indexed.
-                Optional<RuleIndexingDetails> appCertificateRule =
-                        formulas.stream()
-                                .map(formula -> getIndexingDetails(formula))
-                                .filter(ruleIndexingDetails -> ruleIndexingDetails.getIndexType()
-                                        == APP_CERTIFICATE_INDEXED)
-                                .findAny();
-                if (appCertificateRule.isPresent()) {
-                    return appCertificateRule.get();
-                }
-
-                // Do not index when there is not package name or app certificate indexing.
-                return new RuleIndexingDetails(NOT_INDEXED);
-            default:
-                // Having a NOT operator in the indexing messes up the indexing; e.g., deny
-                // installation if app certificate is NOT X (should not be indexed with app cert
-                // X). We will not keep these rules indexed.
-                // Also any other type of unknown operators will not be indexed.
-                return new RuleIndexingDetails(NOT_INDEXED);
-        }
-    }
-
-    private static RuleIndexingDetails getIndexingDetailsForStringAtomicFormula(
-            AtomicFormula.StringAtomicFormula atomicFormula) {
-        switch (atomicFormula.getKey()) {
-            case AtomicFormula.PACKAGE_NAME:
-                return new RuleIndexingDetails(PACKAGE_NAME_INDEXED, atomicFormula.getValue());
-            case AtomicFormula.APP_CERTIFICATE:
-                return new RuleIndexingDetails(APP_CERTIFICATE_INDEXED, atomicFormula.getValue());
-            default:
-                return new RuleIndexingDetails(NOT_INDEXED);
-        }
-    }
-}
-
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java
deleted file mode 100644
index 022b4b8..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.parser.RuleMetadataParser.RULE_PROVIDER_TAG;
-import static com.android.server.integrity.parser.RuleMetadataParser.VERSION_TAG;
-
-import android.util.Xml;
-
-import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.integrity.model.RuleMetadata;
-
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-
-/** Helper class for writing rule metadata. */
-public class RuleMetadataSerializer {
-    /** Serialize the rule metadata to an output stream. */
-    public static void serialize(RuleMetadata ruleMetadata, OutputStream outputStream)
-            throws IOException {
-        TypedXmlSerializer xmlSerializer = Xml.resolveSerializer(outputStream);
-
-        serializeTaggedValue(xmlSerializer, RULE_PROVIDER_TAG, ruleMetadata.getRuleProvider());
-        serializeTaggedValue(xmlSerializer, VERSION_TAG, ruleMetadata.getVersion());
-
-        xmlSerializer.endDocument();
-    }
-
-    private static void serializeTaggedValue(TypedXmlSerializer xmlSerializer, String tag,
-            String value) throws IOException {
-        xmlSerializer.startTag(/* namespace= */ null, tag);
-        xmlSerializer.text(value);
-        xmlSerializer.endTag(/* namespace= */ null, tag);
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java b/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java
deleted file mode 100644
index 60cfc48..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import android.annotation.NonNull;
-
-/**
- * Thrown when rule serialization fails.
- */
-public class RuleSerializeException extends Exception {
-    public RuleSerializeException(@NonNull String message) {
-        super(message);
-    }
-
-    public RuleSerializeException(@NonNull String message, @NonNull Throwable cause) {
-        super(message, cause);
-    }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java
deleted file mode 100644
index 2941856..0000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import android.content.integrity.Rule;
-
-import java.io.OutputStream;
-import java.util.List;
-import java.util.Optional;
-
-/** A helper class to serialize rules from the {@link Rule} model. */
-public interface RuleSerializer {
-
-    /** Serialize rules to an output stream */
-    void serialize(
-            List<Rule> rules,
-            Optional<Integer> formatVersion,
-            OutputStream ruleFileOutputStream,
-            OutputStream indexingFileOutputStream)
-            throws RuleSerializeException;
-
-    /** Serialize rules to a ByteArray. */
-    byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion)
-            throws RuleSerializeException;
-}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 45885f0..c314ab0 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -347,6 +347,8 @@
 
     private final StorageManagerInternal mStorageManagerInternal;
 
+    private final Object mGcWorkToken = new Object();
+
     // This class manages life cycle events for encrypted users on File Based Encryption (FBE)
     // devices. The most basic of these is to show/hide notifications about missing features until
     // the user unlocks the account and credential-encrypted storage is available.
@@ -3632,11 +3634,19 @@
      * release references to the argument.
      */
     private void scheduleGc() {
+        // Cancel any existing GC request first, so that GC requests don't pile up if lockscreen
+        // credential operations are happening very quickly, e.g. as sometimes happens during tests.
+        //
+        // This delays the already-requested GC, but that is fine in practice where lockscreen
+        // operations don't happen very quickly.  And the precise time that the sanitization happens
+        // isn't very important; doing it within a minute can be fine, for example.
+        mHandler.removeCallbacksAndMessages(mGcWorkToken);
+
         mHandler.postDelayed(() -> {
             System.gc();
             System.runFinalization();
             System.gc();
-        }, 2000);
+        }, mGcWorkToken, 2000);
     }
 
     private class DeviceProvisionedObserver extends ContentObserver {
diff --git a/services/core/java/com/android/server/media/AudioManagerRouteController.java b/services/core/java/com/android/server/media/AudioManagerRouteController.java
index 25e1c94..6bc4098 100644
--- a/services/core/java/com/android/server/media/AudioManagerRouteController.java
+++ b/services/core/java/com/android/server/media/AudioManagerRouteController.java
@@ -32,6 +32,7 @@
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.media.MediaRoute2Info;
+import android.media.audio.Flags;
 import android.media.audiopolicy.AudioProductStrategy;
 import android.os.Handler;
 import android.os.HandlerExecutor;
@@ -711,5 +712,13 @@
                         MediaRoute2Info.TYPE_DOCK,
                         /* defaultRouteId= */ "ROUTE_ID_DOCK_ANALOG",
                         /* nameResource= */ R.string.default_audio_route_name_dock_speakers));
+        if (Flags.enableMultichannelGroupDevice()) {
+            AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                    AudioDeviceInfo.TYPE_MULTICHANNEL_GROUP,
+                    new SystemRouteInfo(
+                            MediaRoute2Info.TYPE_MULTICHANNEL_SPEAKER_GROUP,
+                            /* defaultRouteId= */ "ROUTE_ID_MULTICHANNEL_SPEAKER_GROUP",
+                            /* nameResource= */ R.string.default_audio_route_name_external_device));
+        }
     }
 }
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/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index 40a27e8..a45ea1d 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -17,10 +17,20 @@
 package com.android.server.media.quality;
 
 import android.content.Context;
+import android.media.quality.AmbientBacklightSettings;
+import android.media.quality.IAmbientBacklightCallback;
 import android.media.quality.IMediaQualityManager;
+import android.media.quality.IPictureProfileCallback;
+import android.media.quality.ISoundProfileCallback;
+import android.media.quality.ParamCapability;
+import android.media.quality.PictureProfile;
+import android.media.quality.SoundProfile;
 
 import com.android.server.SystemService;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * This service manage picture profile and sound profile for TV setting. Also communicates with the
  * database to save, update the profiles.
@@ -43,5 +53,122 @@
 
     // TODO: Add additional APIs. b/373951081
     private final class BinderService extends IMediaQualityManager.Stub {
+        @Override
+        public PictureProfile createPictureProfile(PictureProfile pp) {
+            // TODO: implement
+            return pp;
+        }
+        @Override
+        public void updatePictureProfile(long id, PictureProfile pp) {
+            // TODO: implement
+        }
+        @Override
+        public void removePictureProfile(long id) {
+            // TODO: implement
+        }
+        @Override
+        public PictureProfile getPictureProfileById(long id) {
+            return null;
+        }
+        @Override
+        public List<PictureProfile> getPictureProfilesByPackage(String packageName) {
+            return new ArrayList<>();
+        }
+        @Override
+        public List<PictureProfile> getAvailablePictureProfiles() {
+            return new ArrayList<>();
+        }
+        @Override
+        public List<PictureProfile> getAllPictureProfiles() {
+            return new ArrayList<>();
+        }
+
+        @Override
+        public SoundProfile createSoundProfile(SoundProfile pp) {
+            // TODO: implement
+            return pp;
+        }
+        @Override
+        public void updateSoundProfile(long id, SoundProfile pp) {
+            // TODO: implement
+        }
+        @Override
+        public void removeSoundProfile(long id) {
+            // TODO: implement
+        }
+        @Override
+        public SoundProfile getSoundProfileById(long id) {
+            return null;
+        }
+        @Override
+        public List<SoundProfile> getSoundProfilesByPackage(String packageName) {
+            return new ArrayList<>();
+        }
+        @Override
+        public List<SoundProfile> getAvailableSoundProfiles() {
+            return new ArrayList<>();
+        }
+        @Override
+        public List<SoundProfile> getAllSoundProfiles() {
+            return new ArrayList<>();
+        }
+
+
+        @Override
+        public void registerPictureProfileCallback(final IPictureProfileCallback callback) {
+        }
+        @Override
+        public void registerSoundProfileCallback(final ISoundProfileCallback callback) {
+        }
+
+        @Override
+        public void registerAmbientBacklightCallback(IAmbientBacklightCallback callback) {
+        }
+
+        @Override
+        public void setAmbientBacklightSettings(AmbientBacklightSettings settings) {
+        }
+
+        @Override
+        public void setAmbientBacklightEnabled(boolean enabled) {
+        }
+
+        @Override
+        public List<ParamCapability> getParamCapabilities(List<String> names) {
+            return new ArrayList<>();
+        }
+
+
+        @Override
+        public boolean isSupported() {
+            return false;
+        }
+
+        @Override
+        public void setAutoPictureQualityEnabled(boolean enabled) {
+        }
+
+        @Override
+        public boolean isAutoPictureQualityEnabled() {
+            return false;
+        }
+
+        @Override
+        public void setSuperResolutionEnabled(boolean enabled) {
+        }
+
+        @Override
+        public boolean isSuperResolutionEnabled() {
+            return false;
+        }
+
+        @Override
+        public void setAutoSoundQualityEnabled(boolean enabled) {
+        }
+
+        @Override
+        public boolean isAutoSoundQualityEnabled() {
+            return false;
+        }
     }
 }
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 849f236..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())
@@ -9363,10 +9459,15 @@
                                             // a group summary or children (complete a group)
                                             mHandler.postDelayed(() -> {
                                                 synchronized (mNotificationLock) {
-                                                    mGroupHelper.onNotificationPostedWithDelay(
-                                                        r, mNotificationList, mSummaryByGroupKey);
+                                                    NotificationRecord record =
+                                                            mNotificationsByKey.get(key);
+                                                    if (record != null) {
+                                                        mGroupHelper.onNotificationPostedWithDelay(
+                                                                record, mNotificationList,
+                                                                mSummaryByGroupKey);
+                                                    }
                                                 }
-                                            }, r.getKey(), DELAY_FORCE_REGROUP_TIME);
+                                            }, key, DELAY_FORCE_REGROUP_TIME);
                                         }
                                     }
 
@@ -9412,10 +9513,15 @@
                                     if (notificationForceGrouping()) {
                                         mHandler.postDelayed(() -> {
                                             synchronized (mNotificationLock) {
-                                                mGroupHelper.onNotificationPostedWithDelay(r,
-                                                        mNotificationList, mSummaryByGroupKey);
+                                                NotificationRecord record =
+                                                        mNotificationsByKey.get(key);
+                                                if (record != null) {
+                                                    mGroupHelper.onNotificationPostedWithDelay(
+                                                            record, mNotificationList,
+                                                            mSummaryByGroupKey);
+                                                }
                                             }
-                                        }, r.getKey(), DELAY_FORCE_REGROUP_TIME);
+                                        }, key, DELAY_FORCE_REGROUP_TIME);
                                     }
                                 }
                             }
@@ -10395,7 +10501,7 @@
                 }
                 mListeners.notifyRemovedLocked(r, reason, r.getStats());
                 if (notificationForceGrouping()) {
-                    mHandler.removeCallbacksAndMessages(r.getKey());
+                    mHandler.removeCallbacksAndEqualMessages(r.getKey());
                     mHandler.post(() -> {
                         synchronized (NotificationManagerService.this.mNotificationLock) {
                             mGroupHelper.onNotificationRemoved(r, mNotificationList);
@@ -11542,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")
@@ -11629,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));
             }
         }
 
@@ -11716,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());
@@ -12155,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/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 20cca969..af2bb17 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -404,9 +404,11 @@
 
     void handlePackageRemove(String packageName, int userId) {
         initBackgroundInstalledPackages();
+        if (mBackgroundInstalledPackages.contains(userId, packageName)) {
+            mCallbackHelper.notifyAllCallbacks(userId, packageName, INSTALL_EVENT_TYPE_UNINSTALL);
+        }
         mBackgroundInstalledPackages.remove(userId, packageName);
         writeBackgroundInstalledPackagesToDisk();
-        mCallbackHelper.notifyAllCallbacks(userId, packageName, INSTALL_EVENT_TYPE_UNINSTALL);
     }
 
     void handleUsageEvent(UsageEvents.Event event, int userId) {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 355184e..d9e7696 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -85,6 +85,7 @@
 import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN;
 import static com.android.server.pm.PackageManagerService.SCAN_UPDATE_SIGNATURE;
 import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.PackageManagerService.WATCHDOG_TIMEOUT;
 import static com.android.server.pm.PackageManagerServiceUtils.comparePackageSignatures;
 import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
 import static com.android.server.pm.PackageManagerServiceUtils.compressedFileExists;
@@ -133,9 +134,11 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Message;
+import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SELinux;
+import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -174,7 +177,6 @@
 import com.android.server.EventLogTags;
 import com.android.server.SystemConfig;
 import com.android.server.criticalevents.CriticalEventLog;
-import com.android.server.pm.dex.ArtManagerService;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.parsing.PackageCacher;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
@@ -210,6 +212,10 @@
 
 
 final class InstallPackageHelper {
+    // One minute over PM WATCHDOG_TIMEOUT
+    private static final long WAKELOCK_TIMEOUT_MS = WATCHDOG_TIMEOUT + 1000 * 60;
+    private static final String INSTALLER_WAKE_LOCK_TAG = "installer:packages";
+
     private final PackageManagerService mPm;
     private final AppDataHelper mAppDataHelper;
     private final BroadcastHelper mBroadcastHelper;
@@ -218,14 +224,16 @@
     private final IncrementalManager mIncrementalManager;
     private final ApexManager mApexManager;
     private final DexManager mDexManager;
-    private final ArtManagerService mArtManagerService;
     private final Context mContext;
-    private final PackageDexOptimizer mPackageDexOptimizer;
     private final PackageAbiHelper mPackageAbiHelper;
     private final SharedLibrariesImpl mSharedLibraries;
     private final PackageManagerServiceInjector mInjector;
     private final UpdateOwnershipHelper mUpdateOwnershipHelper;
 
+    private final Object mInternalLock = new Object();
+    @GuardedBy("mInternalLock")
+    private PowerManager.WakeLock mInstallingWakeLock;
+
     // TODO(b/198166813): remove PMS dependency
     InstallPackageHelper(PackageManagerService pm,
                          AppDataHelper appDataHelper,
@@ -241,9 +249,7 @@
         mIncrementalManager = pm.mInjector.getIncrementalManager();
         mApexManager = pm.mInjector.getApexManager();
         mDexManager = pm.mInjector.getDexManager();
-        mArtManagerService = pm.mInjector.getArtManagerService();
         mContext = pm.mInjector.getContext();
-        mPackageDexOptimizer = pm.mInjector.getPackageDexOptimizer();
         mPackageAbiHelper = pm.mInjector.getAbiHelper();
         mSharedLibraries = pm.mInjector.getSharedLibrariesImpl();
         mUpdateOwnershipHelper = pm.mInjector.getUpdateOwnershipHelper();
@@ -1013,6 +1019,7 @@
         boolean success = false;
         final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size());
         final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size());
+        final long acquireTime = acquireWakeLock(requests.size());
         try {
             CriticalEventLog.getInstance().logInstallPackagesStarted();
             if (prepareInstallPackages(requests)
@@ -1033,6 +1040,46 @@
         } finally {
             completeInstallProcess(requests, createdAppId, success);
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+            releaseWakeLock(acquireTime, requests.size());
+        }
+    }
+
+    private long acquireWakeLock(int count) {
+        if (!mPm.isSystemReady()) {
+            return -1;
+        }
+        synchronized (mInternalLock) {
+            if (mInstallingWakeLock == null) {
+                PowerManager pwm = mContext.getSystemService(PowerManager.class);
+                if (pwm != null) {
+                    mInstallingWakeLock = pwm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                            INSTALLER_WAKE_LOCK_TAG);
+                } else {
+                    Slog.w(TAG, "Unable to obtain power manager while obtaining wake lock");
+                    return -1;
+                }
+            }
+
+            mInstallingWakeLock.acquire(WAKELOCK_TIMEOUT_MS * count);
+            return SystemClock.elapsedRealtime();
+        }
+    }
+
+    private void releaseWakeLock(final long acquireTime, int count) {
+        if (acquireTime < 0) {
+            return;
+        }
+        synchronized (mInternalLock) {
+            try {
+                if (mInstallingWakeLock == null) {
+                    return;
+                }
+                if (mInstallingWakeLock.isHeld()) {
+                    mInstallingWakeLock.release();
+                }
+            } catch (RuntimeException e) {
+                Slog.wtf(TAG, "Error while releasing installer lock", e);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/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/SaferIntentUtils.java b/services/core/java/com/android/server/pm/SaferIntentUtils.java
index 9a7ba0f..bc36fab 100644
--- a/services/core/java/com/android/server/pm/SaferIntentUtils.java
+++ b/services/core/java/com/android/server/pm/SaferIntentUtils.java
@@ -26,7 +26,6 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.compat.annotation.ChangeId;
-import android.compat.annotation.Disabled;
 import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.Overridable;
 import android.content.Intent;
@@ -45,6 +44,7 @@
 import android.util.Slog;
 
 import com.android.internal.pm.pkg.component.ParsedMainComponent;
+import com.android.internal.pm.pkg.component.ParsedMainComponentImpl;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.IntentResolver;
 import com.android.server.LocalServices;
@@ -88,22 +88,6 @@
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
     private static final long IMPLICIT_INTENTS_ONLY_MATCH_EXPORTED_COMPONENTS = 229362273;
 
-    /**
-     * Intents sent from apps enabling this feature will stop resolving to components with
-     * non matching intent filters, even when explicitly setting a component name, unless the
-     * target components are in the same app as the calling app.
-     * <p>
-     * When an app registers an exported component in its manifest and adds &lt;intent-filter&gt;s,
-     * the component can be started by any intent - even those that do not match the intent filter.
-     * This has proven to be something that many developers find counterintuitive.
-     * Without checking the intent when the component is started, in some circumstances this can
-     * allow 3P apps to trigger internal-only functionality.
-     */
-    @ChangeId
-    @Overridable
-    @Disabled
-    private static final long ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS = 161252188;
-
     @Nullable
     private static ParsedMainComponent infoToComponent(
             ComponentInfo info, ComponentResolverApi resolver, boolean isReceiver) {
@@ -277,11 +261,6 @@
                 ? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM)
                 : null;
 
-        final boolean enforceMatch = Flags.enforceIntentFilterMatch()
-                && args.isChangeEnabled(ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS);
-        final boolean blockNullAction = Flags.blockNullActionIntents()
-                && args.isChangeEnabled(IntentFilter.BLOCK_NULL_ACTION_INTENTS);
-
         for (int i = resolveInfos.size() - 1; i >= 0; --i) {
             final ComponentInfo info = resolveInfos.get(i).getComponentInfo();
 
@@ -296,57 +275,65 @@
                 continue;
             }
 
-            Boolean match = null;
+            boolean enforceIntentFilter = Flags.enableIntentMatchingFlags();
+            boolean allowNullAction = false;
 
-            if (args.intent.getAction() == null) {
+            if (Flags.enableIntentMatchingFlags()) {
+                int flags = comp.getIntentMatchingFlags();
+                if (flags == 0 || (flags & ParsedMainComponentImpl.INTENT_MATCHING_FLAGS_NONE)
+                        == ParsedMainComponentImpl.INTENT_MATCHING_FLAGS_NONE
+                        || (flags
+                        & ParsedMainComponentImpl.INTENT_MATCHING_FLAGS_ENFORCE_INTENT_FILTER)
+                        == 0) {
+                    enforceIntentFilter = false;
+                }
+                if ((flags & ParsedMainComponentImpl.INTENT_MATCHING_FLAGS_ALLOW_NULL_ACTION)
+                        == ParsedMainComponentImpl.INTENT_MATCHING_FLAGS_ALLOW_NULL_ACTION) {
+                    allowNullAction = true;
+                }
+            }
+
+            boolean hasNullAction = args.intent.getAction() == null;
+            boolean intentMatchesComponent = false;
+
+            for (int j = 0, size = comp.getIntents().size(); j < size; ++j) {
+                IntentFilter intentFilter = comp.getIntents().get(j).getIntentFilter();
+                if (IntentResolver.intentMatchesFilter(
+                        intentFilter, args.intent, args.resolvedType)) {
+                    intentMatchesComponent = true;
+                    break;
+                }
+            }
+
+            boolean blockIntent = false;
+            if (enforceIntentFilter) {
+                if ((hasNullAction && !allowNullAction) || !intentMatchesComponent) {
+                    blockIntent = true;
+                }
+            }
+
+            if (hasNullAction) {
                 args.reportEvent(
-                        UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NULL_ACTION_MATCH,
-                        enforceMatch && blockNullAction);
-                if (blockNullAction) {
-                    // Skip intent filter matching if blocking null action
-                    match = false;
-                }
-            }
-
-            if (match == null) {
-                // Check if any intent filter matches
-                for (int j = 0, size = comp.getIntents().size(); j < size; ++j) {
-                    IntentFilter intentFilter = comp.getIntents().get(j).getIntentFilter();
-                    if (IntentResolver.intentMatchesFilter(
-                            intentFilter, args.intent, args.resolvedType)) {
-                        match = true;
-                        break;
-                    }
-                }
-            }
-
-            // At this point, the value `match` has the following states:
-            // null : Intent does not match any intent filter
-            // false: Null action intent detected AND blockNullAction == true
-            // true : The intent matches at least one intent filter
-
-            if (match == null) {
+                        UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NULL_ACTION_MATCH, blockIntent);
+            } else if (!intentMatchesComponent) {
                 args.reportEvent(
                         UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__EXPLICIT_INTENT_FILTER_UNMATCH,
-                        enforceMatch);
-                match = false;
+                        blockIntent);
             }
 
-            if (!match) {
-                // All non-matching intents has to be marked accordingly
-                if (Flags.enforceIntentFilterMatch()) {
-                    args.intent.addExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
+            if (Flags.enforceIntentFilterMatch() && (hasNullAction || !intentMatchesComponent)) {
+                args.intent.addExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
+            }
+
+            if (blockIntent) {
+                Slog.w(TAG, "Intent does not match component's intent filter: " + args.intent);
+                Slog.w(TAG, "Access blocked: " + comp.getComponentName());
+                if (DEBUG_INTENT_MATCHING) {
+                    Slog.v(TAG, "Component intent filters:");
+                    comp.getIntents().forEach(f -> f.getIntentFilter().dump(logPrinter, "  "));
+                    Slog.v(TAG, "-----------------------------");
                 }
-                if (enforceMatch) {
-                    Slog.w(TAG, "Intent does not match component's intent filter: " + args.intent);
-                    Slog.w(TAG, "Access blocked: " + comp.getComponentName());
-                    if (DEBUG_INTENT_MATCHING) {
-                        Slog.v(TAG, "Component intent filters:");
-                        comp.getIntents().forEach(f -> f.getIntentFilter().dump(logPrinter, "  "));
-                        Slog.v(TAG, "-----------------------------");
-                    }
-                    resolveInfos.remove(i);
-                }
+                resolveInfos.remove(i);
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 6c03214..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;
 
@@ -1222,7 +1228,7 @@
         // Mark the user for removal.
         addRemovingUserIdLocked(ui.id);
         ui.partial = true;
-        ui.flags |= UserInfo.FLAG_DISABLED;
+        addUserInfoFlags(ui, UserInfo.FLAG_DISABLED);
     }
 
     /* Prunes out any partially created or partially removed users. */
@@ -1264,7 +1270,7 @@
                 if (ui.preCreated) {
                     preCreatedUsers.add(ui);
                     addRemovingUserIdLocked(ui.id);
-                    ui.flags |= UserInfo.FLAG_DISABLED;
+                    addUserInfoFlags(ui, UserInfo.FLAG_DISABLED);
                     ui.partial = true;
                 }
             }
@@ -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() {
@@ -2120,7 +2166,7 @@
                 info = getUserInfoLU(userId);
                 if (info != null && !info.isEnabled()) {
                     wasUserDisabled = true;
-                    info.flags ^= UserInfo.FLAG_DISABLED;
+                    removeUserInfoFlags(info, UserInfo.FLAG_DISABLED);
                     writeUserLP(getUserDataLU(info.id));
                 }
             }
@@ -2130,6 +2176,36 @@
         }
     }
 
+    /**
+     * This method is for monitoring flag changes on users flags and invalidate cache relevant to
+     * the change. The method add flags and invalidateOnUserInfoFlagChange for the flags which
+     * has changed.
+     * @param userInfo of existing user in mUsers list
+     * @param flags to be added to userInfo
+     */
+    private void addUserInfoFlags(UserInfo userInfo, @UserInfoFlag int flags) {
+        int diff = ~userInfo.flags & flags;
+        if (diff > 0) {
+            userInfo.flags |= diff;
+            UserManager.invalidateOnUserInfoFlagChange(diff);
+        }
+    }
+
+    /**
+     * This method is for monitoring flag changes on users flags and invalidate cache relevant to
+     * the change. The method remove flags and invalidateOnUserInfoFlagChange for the flags which
+     * has changed.
+     * @param userInfo of existing user in mUsers list
+     * @param flags to be removed from userInfo
+     */
+    private void removeUserInfoFlags(UserInfo userInfo, @UserInfoFlag int flags) {
+        int diff = userInfo.flags & flags;
+        if (diff > 0) {
+            userInfo.flags ^= diff;
+            UserManager.invalidateOnUserInfoFlagChange(diff);
+        }
+    }
+
     @Override
     public void setUserAdmin(@UserIdInt int userId) {
         checkManageUserAndAcrossUsersFullPermission("set user admin");
@@ -6245,7 +6321,7 @@
                 userData.info.guestToRemove = true;
                 // Mark it as disabled, so that it isn't returned any more when
                 // profiles are queried.
-                userData.info.flags |= UserInfo.FLAG_DISABLED;
+                addUserInfoFlags(userData.info, UserInfo.FLAG_DISABLED);
                 writeUserLP(userData);
             }
         } finally {
@@ -6390,7 +6466,7 @@
                 }
                 // Mark it as disabled, so that it isn't returned any more when
                 // profiles are queried.
-                userData.info.flags |= UserInfo.FLAG_DISABLED;
+                addUserInfoFlags(userData.info, UserInfo.FLAG_DISABLED);
                 writeUserLP(userData);
             }
 
@@ -7789,7 +7865,7 @@
                if (userInfo != null && userInfo.isEphemeral()) {
                     // Do not allow switching back to the ephemeral user again as the user is going
                     // to be deleted.
-                    userInfo.flags |= UserInfo.FLAG_DISABLED;
+                    addUserInfoFlags(userInfo, UserInfo.FLAG_DISABLED);
                     if (userInfo.isGuest()) {
                         // Indicate that the guest will be deleted after it stops.
                         userInfo.guestToRemove = true;
@@ -7872,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/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 2e8a0c6..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,9 @@
 
 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;
 
 /**
  * Used to store power related requests to every display in a
@@ -55,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;
@@ -62,6 +70,8 @@
     private final DisplayManagerInternal mDisplayManagerInternal;
     private final boolean mSupportsSandman;
     private final int mGroupId;
+    private final PowerManagerFlags mFeatureFlags;
+
     /** True if DisplayManagerService has applied all the latest display states that were requested
      *  for this group. */
     private boolean mReady;
@@ -82,10 +92,18 @@
     private long mLastWakeTime;
     /** Timestamp (milliseconds since boot) of the last time the power group was put to sleep. */
     private long mLastSleepTime;
+    /** The last reason that woke the power group. */
+    private @PowerManager.WakeReason int mLastWakeReason = PowerManager.WAKE_REASON_UNKNOWN;
+    /** The last reason that put the power group to sleep. */
+    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) {
+            boolean supportsSandman, long eventTime, PowerManagerFlags featureFlags) {
         mGroupId = groupId;
         mWakefulnessListener = wakefulnessListener;
         mNotifier = notifier;
@@ -95,10 +113,36 @@
         mSupportsSandman = supportsSandman;
         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,
-            DisplayManagerInternal displayManagerInternal, long eventTime) {
+            DisplayManagerInternal displayManagerInternal, long eventTime,
+            PowerManagerFlags featureFlags) {
         mGroupId = Display.DEFAULT_DISPLAY_GROUP;
         mWakefulnessListener = wakefulnessListener;
         mNotifier = notifier;
@@ -108,6 +152,17 @@
         mSupportsSandman = true;
         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() {
@@ -138,8 +193,14 @@
                 setLastPowerOnTimeLocked(eventTime);
                 setIsPoweringOnLocked(true);
                 mLastWakeTime = eventTime;
+                if (mFeatureFlags.isPolicyReasonInDisplayPowerRequestEnabled()) {
+                    mLastWakeReason = reason;
+                }
             } else if (isInteractive(mWakefulness) && !isInteractive(newWakefulness)) {
                 mLastSleepTime = eventTime;
+                if (mFeatureFlags.isPolicyReasonInDisplayPowerRequestEnabled()) {
+                    mLastSleepReason = reason;
+                }
             }
             mWakefulness = newWakefulness;
             mWakefulnessListener.onWakefulnessChangedLocked(mGroupId, mWakefulness, eventTime,
@@ -393,37 +454,51 @@
         return false;
     }
 
-    @VisibleForTesting
-    int getDesiredScreenPolicyLocked(boolean quiescent, boolean dozeAfterScreenOff,
+    // TODO: create and use more specific policy reasons, beyond the ones that correlate to
+    // interactivity state
+    private void updateScreenPolicyLocked(boolean quiescent, boolean dozeAfterScreenOff,
             boolean bootCompleted, boolean screenBrightnessBoostInProgress,
             boolean brightWhenDozing) {
         final int wakefulness = getWakefulnessLocked();
         final int wakeLockSummary = getWakeLockSummaryLocked();
-        if (wakefulness == WAKEFULNESS_ASLEEP || quiescent) {
-            return DisplayPowerRequest.POLICY_OFF;
+        int policyReason = Display.STATE_REASON_DEFAULT_POLICY;
+        int policy = Integer.MAX_VALUE; // do not set to real policy to start with.
+        if (quiescent) {
+            policy = DisplayPowerRequest.POLICY_OFF;
+        } else if (wakefulness == WAKEFULNESS_ASLEEP) {
+            policy = DisplayPowerRequest.POLICY_OFF;
+            policyReason = sleepReasonToDisplayStateReason(mLastSleepReason);
         } else if (wakefulness == WAKEFULNESS_DOZING) {
             if ((wakeLockSummary & WAKE_LOCK_DOZE) != 0) {
-                return DisplayPowerRequest.POLICY_DOZE;
-            }
-            if (dozeAfterScreenOff) {
-                return DisplayPowerRequest.POLICY_OFF;
-            }
-            if (brightWhenDozing) {
-                return DisplayPowerRequest.POLICY_BRIGHT;
+                policy = DisplayPowerRequest.POLICY_DOZE;
+            } else if (dozeAfterScreenOff) {
+                policy = DisplayPowerRequest.POLICY_OFF;
+            } else if (brightWhenDozing) {
+                policy = DisplayPowerRequest.POLICY_BRIGHT;
             }
             // Fall through and preserve the current screen policy if not configured to
             // bright when dozing or doze after screen off.  This causes the screen off transition
             // to be skipped.
         }
 
-        if ((wakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0
-                || !bootCompleted
-                || (getUserActivitySummaryLocked() & USER_ACTIVITY_SCREEN_BRIGHT) != 0
-                || screenBrightnessBoostInProgress) {
-            return DisplayPowerRequest.POLICY_BRIGHT;
+        if (policy == Integer.MAX_VALUE) { // policy is not set yet.
+            if (isInteractive(wakefulness)) {
+                policyReason = wakeReasonToDisplayStateReason(mLastWakeReason);
+            }
+            if ((wakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0
+                    || !bootCompleted
+                    || (getUserActivitySummaryLocked() & USER_ACTIVITY_SCREEN_BRIGHT) != 0
+                    || screenBrightnessBoostInProgress) {
+                policy = DisplayPowerRequest.POLICY_BRIGHT;
+            } else {
+                policy = DisplayPowerRequest.POLICY_DIM;
+            }
         }
 
-        return DisplayPowerRequest.POLICY_DIM;
+        if (mFeatureFlags.isPolicyReasonInDisplayPowerRequestEnabled()) {
+            mDisplayPowerRequest.policyReason = policyReason;
+        }
+        mDisplayPowerRequest.policy = policy;
     }
 
     int getPolicyLocked() {
@@ -439,7 +514,7 @@
             boolean dozeAfterScreenOff, boolean bootCompleted,
             boolean screenBrightnessBoostInProgress, boolean waitForNegativeProximity,
             boolean brightWhenDozing) {
-        mDisplayPowerRequest.policy = getDesiredScreenPolicyLocked(quiescent, dozeAfterScreenOff,
+        updateScreenPolicyLocked(quiescent, dozeAfterScreenOff,
                 bootCompleted, screenBrightnessBoostInProgress, brightWhenDozing);
         mDisplayPowerRequest.screenBrightnessOverride = screenBrightnessOverride;
         mDisplayPowerRequest.screenBrightnessOverrideTag = overrideTag;
@@ -478,6 +553,33 @@
         return ready;
     }
 
+    /** Determines the respective display state reason for a given PowerManager WakeReason. */
+    private static int wakeReasonToDisplayStateReason(@PowerManager.WakeReason int wakeReason) {
+        switch (wakeReason) {
+            case PowerManager.WAKE_REASON_POWER_BUTTON:
+            case PowerManager.WAKE_REASON_WAKE_KEY:
+                return Display.STATE_REASON_KEY;
+            case PowerManager.WAKE_REASON_WAKE_MOTION:
+                return Display.STATE_REASON_MOTION;
+            case PowerManager.WAKE_REASON_TILT:
+                return Display.STATE_REASON_TILT;
+            default:
+                return Display.STATE_REASON_DEFAULT_POLICY;
+        }
+    }
+
+    /** Determines the respective display state reason for a given PowerManager GoToSleepReason. */
+    private static int sleepReasonToDisplayStateReason(
+            @PowerManager.GoToSleepReason int sleepReason) {
+        switch (sleepReason) {
+            case PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON:
+            case PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON:
+                return Display.STATE_REASON_KEY;
+            default:
+                return Display.STATE_REASON_DEFAULT_POLICY;
+        }
+    }
+
     protected interface PowerGroupListener {
         /**
          * Informs the recipient about a wakefulness change of a {@link PowerGroup}.
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 65f2241..0acfe92 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -790,7 +790,8 @@
                         WAKEFULNESS_AWAKE,
                         /* ready= */ false,
                         supportsSandman,
-                        mClock.uptimeMillis());
+                        mClock.uptimeMillis(),
+                        mFeatureFlags);
                 mPowerGroups.append(groupId, powerGroup);
                 onPowerGroupEventLocked(DISPLAY_GROUP_ADDED, powerGroup);
             }
@@ -1375,7 +1376,8 @@
 
             mPowerGroups.append(Display.DEFAULT_DISPLAY_GROUP,
                     new PowerGroup(WAKEFULNESS_AWAKE, mPowerGroupWakefulnessChangeListener,
-                            mNotifier, mDisplayManagerInternal, mClock.uptimeMillis()));
+                            mNotifier, mDisplayManagerInternal, mClock.uptimeMillis(),
+                            mFeatureFlags));
             DisplayGroupPowerChangeListener displayGroupPowerChangeListener =
                     new DisplayGroupPowerChangeListener();
             mDisplayManagerInternal.registerDisplayGroupListener(displayGroupPowerChangeListener);
@@ -2969,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);
 
@@ -2983,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) {
@@ -3271,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 =
@@ -3738,14 +3753,6 @@
     }
 
     @VisibleForTesting
-    @GuardedBy("mLock")
-    int getDesiredScreenPolicyLocked(int groupId) {
-        return mPowerGroups.get(groupId).getDesiredScreenPolicyLocked(sQuiescent,
-                mDozeAfterScreenOff, mBootCompleted,
-                mScreenBrightnessBoostInProgress, mBrightWhenDozingConfig);
-    }
-
-    @VisibleForTesting
     int getDreamsBatteryLevelDrain() {
         return mDreamsBatteryLevelDrain;
     }
@@ -4588,7 +4595,8 @@
                     WAKEFULNESS_AWAKE,
                     /* ready= */ false,
                     /* supportsSandman= */ false,
-                    mClock.uptimeMillis());
+                    mClock.uptimeMillis(),
+                    mFeatureFlags);
             mPowerGroups.append(displayGroupId, powerGroup);
         }
         mDirty |= DIRTY_DISPLAY_GROUP_WAKEFULNESS;
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/feature/PowerManagerFlags.java b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
index db57d11..4ddf0c0 100644
--- a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
+++ b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
@@ -50,6 +50,11 @@
     private final FlagState mFrameworkWakelockInfo =
             new FlagState(Flags.FLAG_FRAMEWORK_WAKELOCK_INFO, Flags::frameworkWakelockInfo);
 
+    private final FlagState mPolicyReasonInDisplayPowerRequest = new FlagState(
+            Flags.FLAG_POLICY_REASON_IN_DISPLAY_POWER_REQUEST,
+            Flags::policyReasonInDisplayPowerRequest
+    );
+
     /** Returns whether early-screen-timeout-detector is enabled on not. */
     public boolean isEarlyScreenTimeoutDetectorEnabled() {
         return mEarlyScreenTimeoutDetectorFlagState.isEnabled();
@@ -77,6 +82,13 @@
     }
 
     /**
+     * @return Whether the wakefulness reason is populated in DisplayPowerRequest.
+     */
+    public boolean isPolicyReasonInDisplayPowerRequestEnabled() {
+        return mPolicyReasonInDisplayPowerRequest.isEnabled();
+    }
+
+    /**
      * dumps all flagstates
      * @param pw printWriter
      */
diff --git a/services/core/java/com/android/server/power/feature/power_flags.aconfig b/services/core/java/com/android/server/power/feature/power_flags.aconfig
index 8bb69ba..e27f8bb 100644
--- a/services/core/java/com/android/server/power/feature/power_flags.aconfig
+++ b/services/core/java/com/android/server/power/feature/power_flags.aconfig
@@ -33,3 +33,11 @@
     description: "Feature flag to enable statsd pulling of FrameworkWakelockInfo atoms"
     bug: "352602149"
 }
+
+flag {
+    name: "policy_reason_in_display_power_request"
+    namespace: "wear_frameworks"
+    description: "Whether the policy reason is populted in DisplayPowerRequest."
+    bug: "364349703"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 48174a6..940a509 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -809,17 +809,16 @@
         mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter, elapsedRealtimeMs);
 
         if (u.mChildUids != null) {
-            LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
-                    getCpuTimeInFreqContainer();
+            long[] delta = getCpuTimeInFreqContainer();
             int childUidCount = u.mChildUids.size();
             for (int j = childUidCount - 1; j >= 0; --j) {
                 LongArrayMultiStateCounter cpuTimeInFreqCounter =
                         u.mChildUids.valueAt(j).cpuTimeInFreqCounter;
                 if (cpuTimeInFreqCounter != null) {
                     mKernelSingleUidTimeReader.addDelta(u.mChildUids.keyAt(j),
-                            cpuTimeInFreqCounter, elapsedRealtimeMs, deltaContainer);
-                    onBatteryCounter.addCounts(deltaContainer);
-                    onBatteryScreenOffCounter.addCounts(deltaContainer);
+                            cpuTimeInFreqCounter, elapsedRealtimeMs, delta);
+                    onBatteryCounter.addCounts(delta);
+                    onBatteryScreenOffCounter.addCounts(delta);
                 }
             }
         }
@@ -890,8 +889,7 @@
                     if (childUid != null) {
                         final LongArrayMultiStateCounter counter = childUid.cpuTimeInFreqCounter;
                         if (counter != null) {
-                            final LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
-                                    getCpuTimeInFreqContainer();
+                            final long[] deltaContainer = getCpuTimeInFreqContainer();
                             mKernelSingleUidTimeReader.addDelta(uid, counter, elapsedRealtimeMs,
                                     deltaContainer);
                             onBatteryCounter.addCounts(deltaContainer);
@@ -1741,7 +1739,7 @@
 
     private long mBatteryTimeToFullSeconds = -1;
 
-    private LongArrayMultiStateCounter.LongArrayContainer mTmpCpuTimeInFreq;
+    private long[] mTmpCpuTimeInFreq;
 
     /**
      * Times spent by the system server threads handling incoming binder requests.
@@ -10956,9 +10954,7 @@
 
                     // Set initial values to all 0. This is a child UID and we want to include
                     // the entirety of its CPU time-in-freq stats into the parent's stats.
-                    cpuTimeInFreqCounter.updateValues(
-                            new LongArrayMultiStateCounter.LongArrayContainer(cpuFreqCount),
-                            timestampMs);
+                    cpuTimeInFreqCounter.updateValues(new long[cpuFreqCount], timestampMs);
                 } else {
                     cpuTimeInFreqCounter = null;
                 }
@@ -11361,11 +11357,9 @@
     }
 
     @GuardedBy("this")
-    private LongArrayMultiStateCounter.LongArrayContainer getCpuTimeInFreqContainer() {
+    private long[] getCpuTimeInFreqContainer() {
         if (mTmpCpuTimeInFreq == null) {
-            mTmpCpuTimeInFreq =
-                    new LongArrayMultiStateCounter.LongArrayContainer(
-                            mCpuScalingPolicies.getScalingStepCount());
+            mTmpCpuTimeInFreq = new long[mCpuScalingPolicies.getScalingStepCount()];
         }
         return mTmpCpuTimeInFreq;
     }
diff --git a/services/core/java/com/android/server/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/stats/pull/netstats/NetworkStatsAccumulator.java b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java
index e798bc4..3f7fcee 100644
--- a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java
+++ b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.net.NetworkStats;
 import android.net.NetworkTemplate;
+import android.util.Log;
 
 import java.util.Objects;
 
@@ -33,6 +34,7 @@
  */
 public class NetworkStatsAccumulator {
 
+    private static final String TAG = "NetworkStatsAccumulator";
     private final NetworkTemplate mTemplate;
     private final boolean mWithTags;
     private final long mBucketDurationMillis;
@@ -57,8 +59,9 @@
     @NonNull
     public NetworkStats queryStats(long currentTimeMillis,
             @NonNull StatsQueryFunction queryFunction) {
-        maybeExpandSnapshot(currentTimeMillis, queryFunction);
-        return snapshotPlusFollowingStats(currentTimeMillis, queryFunction);
+        NetworkStats completeStats = snapshotPlusFollowingStats(currentTimeMillis, queryFunction);
+        maybeExpandSnapshot(currentTimeMillis, completeStats, queryFunction);
+        return completeStats;
     }
 
     /**
@@ -72,15 +75,28 @@
      * Expands the internal cumulative stats snapshot, if possible, by querying NetworkStats.
      */
     private void maybeExpandSnapshot(long currentTimeMillis,
+            NetworkStats completeStatsUntilCurrentTime,
             @NonNull StatsQueryFunction queryFunction) {
         // Update snapshot only if it is possible to expand it by at least one full bucket, and only
         // if the new snapshot's end is not in the active bucket.
         long newEndTimeMillis = currentTimeMillis - mBucketDurationMillis;
         if (newEndTimeMillis - mSnapshotEndTimeMillis > mBucketDurationMillis) {
-            NetworkStats extraStats = queryFunction.queryNetworkStats(mTemplate, mWithTags,
-                    mSnapshotEndTimeMillis, newEndTimeMillis);
+            Log.v(TAG,
+                    "Expanding snapshot (mTemplate=" + mTemplate + ", mWithTags=" + mWithTags
+                            + ") from " + mSnapshotEndTimeMillis + " to " + newEndTimeMillis
+                            + " at " + currentTimeMillis);
+            NetworkStats extraStats = queryFunction.queryNetworkStats(
+                    mTemplate, mWithTags, mSnapshotEndTimeMillis, newEndTimeMillis);
             mSnapshot = mSnapshot.add(extraStats);
             mSnapshotEndTimeMillis = newEndTimeMillis;
+
+            // NetworkStats queries interpolate historical data using integers maths, which makes
+            // queries non-transitive: Query(t0, t1) + Query(t1, t2) <= Query(t0, t2).
+            // Compute interpolation data loss from moving the snapshot's end-point, and add it to
+            // the snapshot to avoid under-counting.
+            NetworkStats newStats = snapshotPlusFollowingStats(currentTimeMillis, queryFunction);
+            NetworkStats interpolationLoss = completeStatsUntilCurrentTime.subtract(newStats);
+            mSnapshot = mSnapshot.add(interpolationLoss);
         }
     }
 
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 457196b..465ac2f 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -21,6 +21,7 @@
 import static android.service.trust.TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE;
 
 import android.Manifest;
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -1896,8 +1897,11 @@
             }
         }
 
+        @EnforcePermission(Manifest.permission.ACCESS_FINE_LOCATION)
         @Override
         public boolean isInSignificantPlace() {
+            super.isInSignificantPlace_enforcePermission();
+
             if (android.security.Flags.significantPlaces()) {
                 mSignificantPlaceServiceWatcher.runOnBinder(
                         binder -> ISignificantPlaceProvider.Stub.asInterface(binder)
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index a492a72..6ce8685 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.net.IpSecTransformState;
 import android.net.vcn.FeatureFlags;
 import android.net.vcn.FeatureFlagsImpl;
 import android.os.Looper;
@@ -70,19 +69,6 @@
         return mIsInTestMode;
     }
 
-    public boolean isFlagIpSecTransformStateEnabled() {
-        // TODO: b/328844044: Ideally this code should gate the behavior by checking the
-        // android.net.platform.flags.ipsec_transform_state flag but that flag is not accessible
-        // right now. We should either update the code when the flag is accessible or remove the
-        // legacy behavior after VIC SDK finalization
-        try {
-            new IpSecTransformState.Builder();
-            return true;
-        } catch (Exception e) {
-            return false;
-        }
-    }
-
     @NonNull
     public FeatureFlags getFeatureFlags() {
         return mFeatureFlags;
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 189b608..2d3bc84 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -1912,8 +1912,7 @@
                 // Transforms do not need to be persisted; the IkeSession will keep them alive
                 mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform);
 
-                if (direction == IpSecManager.DIRECTION_IN
-                        && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+                if (direction == IpSecManager.DIRECTION_IN) {
                     mUnderlyingNetworkController.updateInboundTransform(mUnderlying, transform);
                 }
 
diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
index 6f1e15b..16ab51e 100644
--- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
+++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -148,12 +148,6 @@
 
         Objects.requireNonNull(deps, "Missing deps");
 
-        if (!vcnContext.isFlagIpSecTransformStateEnabled()) {
-            // Caller error
-            logWtf("ipsecTransformState flag disabled");
-            throw new IllegalAccessException("ipsecTransformState flag disabled");
-        }
-
         mHandler = new Handler(getVcnContext().getLooper());
 
         mPowerManager = getVcnContext().getContext().getSystemService(PowerManager.class);
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
index 0b9b677..3eeeece 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
@@ -204,10 +204,8 @@
         List<NetworkCallback> oldCellCallbacks = new ArrayList<>(mCellBringupCallbacks);
         mCellBringupCallbacks.clear();
 
-        if (mVcnContext.isFlagIpSecTransformStateEnabled()) {
-            for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
-                evaluator.close();
-            }
+        for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
+            evaluator.close();
         }
 
         mUnderlyingNetworkRecords.clear();
@@ -429,10 +427,7 @@
         if (oldSnapshot
                 .getAllSubIdsInGroup(mSubscriptionGroup)
                 .equals(newSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))) {
-
-            if (mVcnContext.isFlagIpSecTransformStateEnabled()) {
-                reevaluateNetworks();
-            }
+            reevaluateNetworks();
             return;
         }
         registerOrUpdateNetworkRequests();
@@ -445,11 +440,6 @@
      */
     public void updateInboundTransform(
             @NonNull UnderlyingNetworkRecord currentNetwork, @NonNull IpSecTransform transform) {
-        if (!mVcnContext.isFlagIpSecTransformStateEnabled()) {
-            logWtf("#updateInboundTransform: unexpected call; flags missing");
-            return;
-        }
-
         Objects.requireNonNull(currentNetwork, "currentNetwork is null");
         Objects.requireNonNull(transform, "transform is null");
 
@@ -572,10 +562,7 @@
 
         @Override
         public void onLost(@NonNull Network network) {
-            if (mVcnContext.isFlagIpSecTransformStateEnabled()) {
-                mUnderlyingNetworkRecords.get(network).close();
-            }
-
+            mUnderlyingNetworkRecords.get(network).close();
             mUnderlyingNetworkRecords.remove(network);
 
             reevaluateNetworks();
@@ -648,11 +635,6 @@
     class NetworkEvaluatorCallbackImpl implements NetworkEvaluatorCallback {
         @Override
         public void onEvaluationResultChanged() {
-            if (!mVcnContext.isFlagIpSecTransformStateEnabled()) {
-                logWtf("#onEvaluationResultChanged: unexpected call; flags missing");
-                return;
-            }
-
             mVcnContext.ensureRunningOnLooperThread();
             reevaluateNetworks();
         }
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
index 448a7eb..08be11e 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
@@ -102,17 +102,15 @@
         updatePriorityClass(
                 underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
 
-        if (isIpSecPacketLossDetectorEnabled()) {
-            try {
-                mMetricMonitors.add(
-                        mDependencies.newIpSecPacketLossDetector(
-                                mVcnContext,
-                                mNetworkRecordBuilder.getNetwork(),
-                                carrierConfig,
-                                new MetricMonitorCallbackImpl()));
-            } catch (IllegalAccessException e) {
-                // No action. Do not add anything to mMetricMonitors
-            }
+        try {
+            mMetricMonitors.add(
+                    mDependencies.newIpSecPacketLossDetector(
+                            mVcnContext,
+                            mNetworkRecordBuilder.getNetwork(),
+                            carrierConfig,
+                            new MetricMonitorCallbackImpl()));
+        } catch (IllegalAccessException e) {
+            // No action. Do not add anything to mMetricMonitors
         }
     }
 
@@ -188,22 +186,12 @@
         }
     }
 
-    private boolean isIpSecPacketLossDetectorEnabled() {
-        return isIpSecPacketLossDetectorEnabled(mVcnContext);
-    }
-
-    private static boolean isIpSecPacketLossDetectorEnabled(VcnContext vcnContext) {
-        return vcnContext.isFlagIpSecTransformStateEnabled();
-    }
-
     /** Get the comparator for UnderlyingNetworkEvaluator */
     public static Comparator<UnderlyingNetworkEvaluator> getComparator(VcnContext vcnContext) {
         return (left, right) -> {
-            if (isIpSecPacketLossDetectorEnabled(vcnContext)) {
-                if (left.mIsPenalized != right.mIsPenalized) {
-                    // A penalized network should have lower priority which means a larger index
-                    return left.mIsPenalized ? 1 : -1;
-                }
+            if (left.mIsPenalized != right.mIsPenalized) {
+                // A penalized network should have lower priority which means a larger index
+                return left.mIsPenalized ? 1 : -1;
             }
 
             final int leftIndex = left.mPriorityClass;
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java
index d0d6071..32a3227 100644
--- a/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java
@@ -20,6 +20,7 @@
 import android.os.Trace;
 import android.os.VibrationEffect;
 import android.os.vibrator.Flags;
+import android.os.vibrator.PwlePoint;
 import android.os.vibrator.PwleSegment;
 import android.os.vibrator.VibrationEffectSegment;
 import android.util.Slog;
@@ -57,7 +58,7 @@
             // Load the next PwleSegments to create a single composePwleV2 call to the vibrator,
             // limited to the vibrator's maximum envelope effect size.
             int limit = controller.getVibratorInfo().getMaxEnvelopeEffectSize();
-            List<PwleSegment> pwles = unrollPwleSegments(effect, segmentIndex, limit);
+            List<PwlePoint> pwles = unrollPwleSegments(effect, segmentIndex, limit);
 
             if (pwles.isEmpty()) {
                 Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposeEnvelopeStep: "
@@ -70,7 +71,7 @@
                 Slog.d(VibrationThread.TAG, "Compose " + pwles + " PWLEs on vibrator "
                         + controller.getVibratorInfo().getId());
             }
-            PwleSegment[] pwlesArray = pwles.toArray(new PwleSegment[pwles.size()]);
+            PwlePoint[] pwlesArray = pwles.toArray(new PwlePoint[pwles.size()]);
             long vibratorOnResult = controller.on(pwlesArray, getVibration().id);
             handleVibratorOnResult(vibratorOnResult);
             getVibration().stats.reportComposePwle(vibratorOnResult, pwlesArray);
@@ -82,9 +83,9 @@
         }
     }
 
-    private List<PwleSegment> unrollPwleSegments(VibrationEffect.Composed effect, int startIndex,
+    private List<PwlePoint> unrollPwleSegments(VibrationEffect.Composed effect, int startIndex,
             int limit) {
-        List<PwleSegment> segments = new ArrayList<>(limit);
+        List<PwlePoint> pwlePoints = new ArrayList<>(limit);
         float bestBreakAmplitude = 1;
         int bestBreakPosition = limit; // Exclusive index.
 
@@ -93,7 +94,7 @@
 
         // Loop once after reaching the limit to see if breaking it will really be necessary, then
         // apply the best break position found, otherwise return the full list as it fits the limit.
-        for (int i = startIndex; segments.size() <= limit; i++) {
+        for (int i = startIndex; pwlePoints.size() < limit; i++) {
             if (i == segmentCount) {
                 if (repeatIndex >= 0) {
                     i = repeatIndex;
@@ -104,12 +105,20 @@
             }
             VibrationEffectSegment segment = effect.getSegments().get(i);
             if (segment instanceof PwleSegment pwleSegment) {
-                segments.add(pwleSegment);
+                if (pwlePoints.isEmpty()) {
+                    // The initial state is defined by the starting amplitude and frequency of the
+                    // first PwleSegment. The time parameter is set to zero to indicate this is
+                    // the initial condition without any ramp up time.
+                    pwlePoints.add(new PwlePoint(pwleSegment.getStartAmplitude(),
+                            pwleSegment.getStartFrequencyHz(), /*timeMillis=*/ 0));
+                }
+                pwlePoints.add(new PwlePoint(pwleSegment.getEndAmplitude(),
+                        pwleSegment.getEndFrequencyHz(), (int) pwleSegment.getDuration()));
 
-                if (isBetterBreakPosition(segments, bestBreakAmplitude, limit)) {
+                if (isBetterBreakPosition(pwlePoints, bestBreakAmplitude, limit)) {
                     // Mark this position as the best one so far to break a long waveform.
                     bestBreakAmplitude = pwleSegment.getEndAmplitude();
-                    bestBreakPosition = segments.size(); // Break after this pwle ends.
+                    bestBreakPosition = pwlePoints.size(); // Break after this pwle ends.
                 }
             } else {
                 // First non-pwle segment, stop collecting pwles.
@@ -117,21 +126,21 @@
             }
         }
 
-        return segments.size() > limit
+        return pwlePoints.size() > limit
                 // Remove excessive segments, using the best breaking position recorded.
-                ? segments.subList(0, bestBreakPosition)
+                ? pwlePoints.subList(0, bestBreakPosition)
                 // Return all collected pwle segments.
-                : segments;
+                : pwlePoints;
     }
 
     /**
      * Returns true if the current segment list represents a better break position for a PWLE,
      * given the current amplitude being used for breaking it at a smaller size and the size limit.
      */
-    private boolean isBetterBreakPosition(List<PwleSegment> segments,
+    private boolean isBetterBreakPosition(List<PwlePoint> segments,
             float currentBestBreakAmplitude, int limit) {
-        PwleSegment lastSegment = segments.get(segments.size() - 1);
-        float breakAmplitudeCandidate = lastSegment.getEndAmplitude();
+        PwlePoint lastSegment = segments.get(segments.size() - 1);
+        float breakAmplitudeCandidate = lastSegment.getAmplitude();
         int breakPositionCandidate = segments.size();
 
         if (breakPositionCandidate > limit) {
diff --git a/services/core/java/com/android/server/vibrator/VibrationStats.java b/services/core/java/com/android/server/vibrator/VibrationStats.java
index bc4dbe7..de423f0 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStats.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStats.java
@@ -22,7 +22,7 @@
 import android.os.SystemClock;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.PwleSegment;
+import android.os.vibrator.PwlePoint;
 import android.os.vibrator.RampSegment;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
@@ -294,18 +294,22 @@
     }
 
     /** Report a call to vibrator method to trigger a vibration as a PWLE. */
-    void reportComposePwle(long halResult, PwleSegment[] segments) {
+    void reportComposePwle(long halResult, PwlePoint[] pwlePoints) {
         mVibratorComposePwleCount++;
-        mVibrationPwleTotalSize += segments.length;
+        mVibrationPwleTotalSize += pwlePoints.length;
 
         if (halResult > 0) {
             // If HAL result is positive then it represents the actual duration of the vibration.
             // Remove the zero-amplitude segments to update the total time the vibrator was ON.
-            for (PwleSegment ramp : segments) {
-                if ((ramp.getStartAmplitude() == 0) && (ramp.getEndAmplitude() == 0)) {
-                    halResult -= ramp.getDuration();
+            for (int i = 0; i < pwlePoints.length - 1; i++) {
+                PwlePoint current = pwlePoints[i];
+                PwlePoint next = pwlePoints[i + 1];
+
+                if (current.getAmplitude() == 0 && next.getAmplitude() == 0) {
+                    halResult -= next.getTimeMillis();
                 }
             }
+
             if (halResult > 0) {
                 mVibratorOnTotalDurationMillis += (int) halResult;
             }
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index f78bff8..acb31ce 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -30,7 +30,7 @@
 import android.os.VibratorInfo;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.PwleSegment;
+import android.os.vibrator.PwlePoint;
 import android.os.vibrator.RampSegment;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
@@ -415,21 +415,21 @@
     }
 
     /**
-     * Plays a composition of pwle v2 primitives, using {@code vibrationId} for completion callback
+     * Plays a composition of pwle v2 points, using {@code vibrationId} for completion callback
      * to {@link OnVibrationCompleteListener}.
      *
      * <p>This will affect the state of {@link #isVibrating()}.
      *
      * @return The duration of the effect playing, or 0 if unsupported.
      */
-    public long on(PwleSegment[] primitives, long vibrationId) {
+    public long on(PwlePoint[] pwlePoints, long vibrationId) {
         Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (PWLE v2)");
         try {
             if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2)) {
                 return 0;
             }
             synchronized (mLock) {
-                long duration = mNativeWrapper.composePwleV2(primitives, vibrationId);
+                long duration = mNativeWrapper.composePwleV2(pwlePoints, vibrationId);
                 if (duration > 0) {
                     mCurrentAmplitude = -1;
                     updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
@@ -562,7 +562,7 @@
         private static native long performPwleEffect(long nativePtr, RampSegment[] effect,
                 int braking, long vibrationId);
 
-        private static native long performPwleV2Effect(long nativePtr, PwleSegment[] effect,
+        private static native long performPwleV2Effect(long nativePtr, PwlePoint[] effect,
                 long vibrationId);
 
         private static native void setExternalControl(long nativePtr, boolean enabled);
@@ -631,9 +631,9 @@
             return performPwleEffect(mNativePtr, primitives, braking, vibrationId);
         }
 
-        /** Turns vibrator on to perform PWLE effect composed of given primitives. */
-        public long composePwleV2(PwleSegment[] primitives, long vibrationId) {
-            return performPwleV2Effect(mNativePtr, primitives, vibrationId);
+        /** Turns vibrator on to perform PWLE effect composed of given points. */
+        public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId) {
+            return performPwleV2Effect(mNativePtr, pwlePoints, vibrationId);
         }
 
         /** Enabled the device vibrator to be controlled by another service. */
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java
index c8d5a03..f09b0a1c6 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperData.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wallpaper;
 
+import static android.app.Flags.liveWallpaperContentHandling;
 import static android.app.WallpaperManager.FLAG_LOCK;
 import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
 
@@ -25,6 +26,7 @@
 import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_LOCK_ORIG;
 import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
 
+import android.annotation.NonNull;
 import android.app.IWallpaperManagerCallback;
 import android.app.WallpaperColors;
 import android.app.WallpaperManager.ScreenOrientation;
@@ -149,6 +151,7 @@
         UNKNOWN,
         CONNECT_LOCKED,
         CONNECTION_TRY_TO_REBIND,
+        FALLBACK_DEFAULT_MISSING,
         INITIALIZE_FALLBACK,
         PACKAGE_UPDATE_FINISHED,
         RESTORE_SETTINGS_LIVE_FAILURE,
@@ -183,7 +186,7 @@
     int mOrientationWhenSet = ORIENTATION_UNKNOWN;
 
     /** Description of the current wallpaper */
-    private WallpaperDescription mDescription;
+    private WallpaperDescription mDescription = new WallpaperDescription.Builder().build();
 
     WallpaperData(int userId, @SetWallpaperFlags int wallpaperType) {
         this.userId = userId;
@@ -212,6 +215,9 @@
         this.primaryColors = source.primaryColors;
         this.mWallpaperDimAmount = source.mWallpaperDimAmount;
         this.connection = source.connection;
+        if (liveWallpaperContentHandling()) {
+            this.setDescription(source.getDescription());
+        }
         if (this.connection != null) {
             this.connection.mWallpaper = this;
         }
@@ -236,19 +242,37 @@
         return result;
     }
 
-    ComponentName getComponent() {
-        return mWallpaperComponent;
+    @NonNull ComponentName getComponent() {
+        if (liveWallpaperContentHandling()) {
+            return mDescription.getComponent();
+        } else {
+            return mWallpaperComponent;
+        }
     }
 
-    void setComponent(ComponentName componentName) {
+    void setComponent(@NonNull ComponentName componentName) {
+        if (liveWallpaperContentHandling()) {
+            throw new IllegalStateException(
+                    "Use \"setDescription\" when content handling is enabled");
+        }
         this.mWallpaperComponent = componentName;
     }
 
-    WallpaperDescription getDescription() {
+    @NonNull WallpaperDescription getDescription() {
         return mDescription;
     }
 
-    void setDescription(WallpaperDescription description) {
+    void setDescription(@NonNull WallpaperDescription description) {
+        if (!liveWallpaperContentHandling()) {
+            throw new IllegalStateException(
+                    "Use \"setContent\" when content handling is disabled");
+        }
+        if (description == null) {
+            throw new IllegalArgumentException("WallpaperDescription must not be null");
+        }
+        if (description.getComponent() == null) {
+            throw new IllegalArgumentException("WallpaperDescription component must not be null");
+        }
         this.mDescription = description;
     }
 
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index cf76bf0..17a254a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -180,15 +180,8 @@
             success = true;
         } catch (FileNotFoundException e) {
             Slog.w(TAG, "no current wallpaper -- first boot?");
-        } catch (NullPointerException e) {
-            Slog.w(TAG, "failed parsing " + file + " " + e);
-        } catch (NumberFormatException e) {
-            Slog.w(TAG, "failed parsing " + file + " " + e);
-        } catch (XmlPullParserException e) {
-            Slog.w(TAG, "failed parsing " + file + " " + e);
-        } catch (IOException e) {
-            Slog.w(TAG, "failed parsing " + file + " " + e);
-        } catch (IndexOutOfBoundsException e) {
+        } catch (NullPointerException | NumberFormatException | XmlPullParserException
+                 | IOException | IndexOutOfBoundsException e) {
             Slog.w(TAG, "failed parsing " + file + " " + e);
         }
         IoUtils.closeQuietly(stream);
@@ -256,12 +249,13 @@
                     }
 
                     ComponentName comp = parseComponentName(parser);
-                    if (removeNextWallpaperComponent()) {
-                        wallpaperToParse.setComponent(comp);
-                    } else {
-                        wallpaperToParse.nextWallpaperComponent = comp;
+                    if (!liveWallpaperContentHandling()) {
+                        if (removeNextWallpaperComponent()) {
+                            wallpaperToParse.setComponent(comp);
+                        } else {
+                            wallpaperToParse.nextWallpaperComponent = comp;
+                        }
                     }
-
                     if (multiCrop()) {
                         parseWallpaperAttributes(parser, wallpaperToParse, keepDimensionHints);
                     }
@@ -745,9 +739,9 @@
 
     private static List<Pair<Integer, String>> screenDimensionPairs() {
         return List.of(
-                new Pair<>(WallpaperManager.PORTRAIT, "Portrait"),
-                new Pair<>(WallpaperManager.LANDSCAPE, "Landscape"),
-                new Pair<>(WallpaperManager.SQUARE_PORTRAIT, "SquarePortrait"),
-                new Pair<>(WallpaperManager.SQUARE_LANDSCAPE, "SquareLandscape"));
+                new Pair<>(WallpaperManager.ORIENTATION_PORTRAIT, "Portrait"),
+                new Pair<>(WallpaperManager.ORIENTATION_LANDSCAPE, "Landscape"),
+                new Pair<>(WallpaperManager.ORIENTATION_SQUARE_PORTRAIT, "SquarePortrait"),
+                new Pair<>(WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE, "SquareLandscape"));
     }
 }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index da9a676..5cff37a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -21,6 +21,7 @@
 import static android.Manifest.permission.READ_WALLPAPER_INTERNAL;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.app.Flags.fixWallpaperChanged;
+import static android.app.Flags.liveWallpaperContentHandling;
 import static android.app.Flags.removeNextWallpaperComponent;
 import static android.app.WallpaperManager.COMMAND_REAPPLY;
 import static android.app.WallpaperManager.FLAG_LOCK;
@@ -66,6 +67,8 @@
 import android.app.WallpaperManager;
 import android.app.WallpaperManager.SetWallpaperFlags;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.app.wallpaper.WallpaperDescription;
+import android.app.wallpaper.WallpaperInstance;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -849,16 +852,24 @@
             final DisplayData wpdData =
                     mWallpaperDisplayHelper.getDisplayDataOrCreate(mDisplayId);
             try {
-                connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
-                        wpdData.mWidth, wpdData.mHeight,
-                        wpdData.mPadding, mDisplayId, wallpaper.mWhich, connection.mInfo);
+                if (liveWallpaperContentHandling()) {
+                    connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
+                            wpdData.mWidth, wpdData.mHeight,
+                            wpdData.mPadding, mDisplayId, wallpaper.mWhich, connection.mInfo,
+                            wallpaper.getDescription());
+                } else {
+                    connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
+                            wpdData.mWidth, wpdData.mHeight,
+                            wpdData.mPadding, mDisplayId, wallpaper.mWhich, connection.mInfo,
+                            /* description= */ null);
+                }
             } catch (RemoteException e) {
                 Slog.w(TAG, "Failed attaching wallpaper on display", e);
                 if (wallpaper != null && !wallpaper.wallpaperUpdating
                         && connection.getConnectedEngineSize() == 0) {
                     wallpaper.mBindSource = BindSource.CONNECT_LOCKED;
-                    bindWallpaperComponentLocked(null /* componentName */, false /* force */,
-                            false /* fromUser */, wallpaper, null /* reply */);
+                    bindWallpaperComponentLocked(null, false /* force */, false /* fromUser */,
+                            wallpaper, null /* reply */);
                 }
             }
             t.traceEnd();
@@ -1324,7 +1335,11 @@
                             if (DEBUG) {
                                 Slog.v(TAG, "static system+lock to system success");
                             }
-                            lockWp.setComponent(mOriginalSystem.getComponent());
+                            if (liveWallpaperContentHandling()) {
+                                lockWp.setDescription(mOriginalSystem.getDescription());
+                            } else {
+                                lockWp.setComponent(mOriginalSystem.getComponent());
+                            }
                             lockWp.connection = mOriginalSystem.connection;
                             lockWp.connection.mWallpaper = lockWp;
                             mOriginalSystem.mWhich = FLAG_LOCK;
@@ -1391,7 +1406,7 @@
                             Slog.i(TAG, "Wallpaper " + wpService + " update has finished");
                         }
                         wallpaper.wallpaperUpdating = false;
-                        clearWallpaperComponentLocked(wallpaper);
+                        detachWallpaperLocked(wallpaper);
                         wallpaper.mBindSource = BindSource.PACKAGE_UPDATE_FINISHED;
                         if (!bindWallpaperComponentLocked(wpService, false, false,
                                 wallpaper, null)) {
@@ -1905,6 +1920,23 @@
             if ((wallpaper.mWhich & FLAG_SYSTEM) != 0) mHomeWallpaperWaitingForUnlock = false;
             if ((wallpaper.mWhich & FLAG_LOCK) != 0) mLockWallpaperWaitingForUnlock = false;
 
+            if (liveWallpaperContentHandling()) {
+                final WallpaperDescription description = wallpaper.getDescription();
+                if (!bindWallpaperDescriptionLocked(description, true, false, wallpaper, reply)) {
+                    // We failed to bind the desired wallpaper, but that might
+                    // happen if the wallpaper isn't direct-boot aware
+                    ServiceInfo si = null;
+                    try {
+                        si = mIPackageManager.getServiceInfo(description.getComponent(),
+                                PackageManager.MATCH_DIRECT_BOOT_UNAWARE, wallpaper.userId);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Failure starting previous wallpaper; clearing", e);
+                    }
+                    onSwitchWallpaperFailLocked(wallpaper, reply, si);
+                }
+                return;
+            }
+
             final ComponentName cname;
             if (removeNextWallpaperComponent()) {
                 cname = wallpaper.getComponent();
@@ -2001,9 +2033,6 @@
         try {
             if (userId != mCurrentUserId && !hasCrossUserPermission()) return;
 
-            final ComponentName component;
-            final int finalWhich;
-
             // Clear any previous ImageWallpaper related fields
             List<WallpaperData> toClear = new ArrayList<>();
             if ((which & FLAG_LOCK) > 0 && lockWallpaper != null) toClear.add(lockWallpaper);
@@ -2017,19 +2046,34 @@
                 }
             }
 
-            // lock only case: set the system wallpaper component to both screens
-            if (which == FLAG_LOCK) {
-                component = wallpaper.getComponent();
-                finalWhich = FLAG_LOCK | FLAG_SYSTEM;
+            final WallpaperDescription description;
+            final int finalWhich;
+
+            if (liveWallpaperContentHandling()) {
+                if (which == FLAG_LOCK) {
+                    // lock only case: set the system wallpaper component to both screens
+                    description = wallpaper.getDescription();
+                    finalWhich = FLAG_LOCK | FLAG_SYSTEM;
+                } else {
+                    description = new WallpaperDescription.Builder().build();
+                    finalWhich = which;
+                }
             } else {
-                component = null;
-                finalWhich = which;
+                if (which == FLAG_LOCK) {
+                    // lock only case: set the system wallpaper component to both screens
+                    description = new WallpaperDescription.Builder().setComponent(
+                            wallpaper.getComponent()).build();
+                    finalWhich = FLAG_LOCK | FLAG_SYSTEM;
+                } else {
+                    description = new WallpaperDescription.Builder().build();
+                    finalWhich = which;
+                }
             }
 
             // except for the lock case (for which we keep the system wallpaper as-is), force rebind
             boolean force = which != FLAG_LOCK;
-            boolean success = withCleanCallingIdentity(() -> setWallpaperComponentInternal(
-                    component, finalWhich, userId, force, fromForeground, reply));
+            boolean success = withCleanCallingIdentity(() -> setWallpaperDescriptionInternal(
+                    description, finalWhich, userId, force, fromForeground, reply));
             if (success) return;
         } catch (IllegalArgumentException e1) {
             e = e1;
@@ -2040,7 +2084,10 @@
         // let's not let it crash the system and just live with no
         // wallpaper.
         Slog.e(TAG, "Default wallpaper component not found!", e);
-        withCleanCallingIdentity(() -> clearWallpaperComponentLocked(wallpaper));
+        withCleanCallingIdentity(() -> {
+            wallpaper.mBindSource = BindSource.FALLBACK_DEFAULT_MISSING;
+            bindWallpaperComponentLocked(mImageWallpaper, true, false, wallpaper, reply);
+        });
         if (reply != null) {
             try {
                 reply.sendResult(null);
@@ -2412,6 +2459,9 @@
 
     @Override
     public WallpaperInfo getWallpaperInfoWithFlags(@SetWallpaperFlags int which, int userId) {
+        if (liveWallpaperContentHandling()) {
+            return getWallpaperInstance(which, userId, false).getInfo();
+        }
 
         userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                 Binder.getCallingUid(), userId, false, true, "getWallpaperInfo", null);
@@ -2425,13 +2475,45 @@
             WallpaperInfo info = wallpaper.connection.mInfo;
             if (hasPermission(READ_WALLPAPER_INTERNAL)
                     || mPackageManagerInternal.canQueryPackage(
-                            Binder.getCallingUid(), info.getComponent().getPackageName())) {
+                    Binder.getCallingUid(), info.getComponent().getPackageName())) {
                 return info;
             }
         }
         return null;
     }
 
+    @NonNull
+    @Override
+    public WallpaperInstance getWallpaperInstance(@SetWallpaperFlags int which, int userId) {
+        return getWallpaperInstance(which, userId, true);
+    }
+
+    private WallpaperInstance getWallpaperInstance(@SetWallpaperFlags int which, int userId,
+            boolean requireReadWallpaper) {
+        final WallpaperInstance defaultInstance = new WallpaperInstance(null,
+                new WallpaperDescription.Builder().build());
+        userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+                Binder.getCallingUid(), userId, false, true, "getWallpaperInfo", null);
+        synchronized (mLock) {
+            WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId)
+                    : mWallpaperMap.get(userId);
+            if (wallpaper == null
+                    || wallpaper.connection == null
+                    || wallpaper.connection.mInfo == null) {
+                return defaultInstance;
+            }
+
+            WallpaperInfo info = wallpaper.connection.mInfo;
+            boolean canQueryPackage = mPackageManagerInternal.canQueryPackage(
+                    Binder.getCallingUid(), info.getComponent().getPackageName());
+            if (hasPermission(READ_WALLPAPER_INTERNAL)
+                    || (canQueryPackage && !requireReadWallpaper)) {
+                return new WallpaperInstance(info, wallpaper.getDescription());
+            }
+        }
+        return defaultInstance;
+    }
+
     @Override
     public ParcelFileDescriptor getWallpaperInfoFile(int userId) {
         synchronized (mLock) {
@@ -3159,11 +3241,11 @@
     }
 
     @Override
-    public void setWallpaperComponentChecked(ComponentName name, String callingPackage,
-            @SetWallpaperFlags int which, int userId) {
+    public void setWallpaperComponentChecked(WallpaperDescription description,
+            String callingPackage, @SetWallpaperFlags int which, int userId) {
 
         if (isWallpaperSupported(callingPackage) && isSetWallpaperAllowed(callingPackage)) {
-            setWallpaperComponent(name, callingPackage, which, userId);
+            setWallpaperDescription(description, callingPackage, which, userId);
         }
     }
 
@@ -3176,12 +3258,23 @@
     @VisibleForTesting
     boolean setWallpaperComponent(ComponentName name, String callingPackage,
             @SetWallpaperFlags int which, int userId) {
-        boolean fromForeground = isFromForegroundApp(callingPackage);
-        return setWallpaperComponentInternal(name, which, userId, false, fromForeground, null);
+        return setWallpaperDescription(
+                new WallpaperDescription.Builder().setComponent(name).build(), callingPackage,
+                which, userId);
     }
 
-    private boolean setWallpaperComponentInternal(ComponentName name,  @SetWallpaperFlags int which,
-            int userIdIn, boolean force, boolean fromForeground, IRemoteCallback reply) {
+    @VisibleForTesting
+    boolean setWallpaperDescription(WallpaperDescription description, String callingPackage,
+            @SetWallpaperFlags int which, int userId) {
+        boolean fromForeground = isFromForegroundApp(callingPackage);
+        return setWallpaperDescriptionInternal(description, which, userId, false, fromForeground,
+                null);
+    }
+
+    private boolean setWallpaperDescriptionInternal(@NonNull WallpaperDescription description,
+            @SetWallpaperFlags int which, int userIdIn, boolean force, boolean fromForeground,
+            IRemoteCallback reply) {
+        ComponentName name = description.getComponent();
         if (DEBUG) {
             Slog.v(TAG, "Setting new live wallpaper: which=" + which + ", component: " + name);
         }
@@ -3191,11 +3284,11 @@
         checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
 
         boolean shouldNotifyColors = false;
+        final boolean bindSuccess;
 
         // If the lockscreen wallpaper is set to the same as the home screen, notify that the
         // lockscreen wallpaper colors changed, even if we don't bind a new wallpaper engine.
         boolean shouldNotifyLockscreenColors = false;
-        boolean bindSuccess;
         final WallpaperData newWallpaper;
 
         synchronized (mLock) {
@@ -3226,8 +3319,14 @@
                 final WallpaperDestinationChangeHandler
                         liveSync = new WallpaperDestinationChangeHandler(
                         newWallpaper);
-                boolean same = changingToSame(name, newWallpaper.connection,
-                        newWallpaper.getComponent());
+                boolean same;
+                if (liveWallpaperContentHandling()) {
+                    same = changingToSame(description, newWallpaper.connection,
+                            newWallpaper.getDescription());
+                } else {
+                    same = changingToSame(name, newWallpaper.connection,
+                            newWallpaper.getComponent());
+                }
 
                 /*
                  * If we have a shared system+lock wallpaper, and we reapply the same wallpaper
@@ -3238,8 +3337,13 @@
 
                 newWallpaper.mBindSource =
                         (name == null) ? BindSource.SET_LIVE_TO_CLEAR : BindSource.SET_LIVE;
-                bindSuccess = bindWallpaperComponentLocked(name, /* force */
-                        forceRebind, /* fromUser */ true, newWallpaper, reply);
+                if (liveWallpaperContentHandling()) {
+                    bindSuccess = bindWallpaperDescriptionLocked(description, forceRebind,
+                            /* fromUser */ true, newWallpaper, reply);
+                } else {
+                    bindSuccess = bindWallpaperComponentLocked(name, forceRebind,
+                            /* fromUser */ true, newWallpaper, reply);
+                }
                 if (bindSuccess) {
                     if (!same || (offloadColorExtraction() && forceRebind)) {
                         newWallpaper.primaryColors = null;
@@ -3335,6 +3439,24 @@
         return false;
     }
 
+    private boolean changingToSame(WallpaperDescription newDescription,
+            WallpaperConnection currentConnection, WallpaperDescription currentDescription) {
+        if (currentConnection == null) {
+            return false;
+        }
+        if (isDefaultComponent(newDescription.getComponent()) && isDefaultComponent(
+                currentDescription.getComponent())) {
+            if (DEBUG) Slog.v(TAG, "changingToSame: still using default");
+            // Still using default wallpaper.
+            return true;
+        } else if (newDescription.equals(currentDescription)) {
+            // Changing to same wallpaper.
+            if (DEBUG) Slog.v(TAG, "same wallpaper");
+            return true;
+        }
+        return false;
+    }
+
     /*
      * Attempt to bind the wallpaper given by `componentName`, returning true on success otherwise
      * false.
@@ -3352,12 +3474,29 @@
      */
     boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
             boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
+        return bindWallpaperDescriptionLocked(
+                new WallpaperDescription.Builder().setComponent(componentName).build(), force,
+                fromUser, wallpaper, reply);
+    }
+
+    // When `liveWallpaperContentHandling()` is false this acts exactly like the version which takes
+    // a ComponentName argument did: it uses the ComponentName from `description`, it binds the
+    // service given by that component, and updates WallpaperData with that component on success.
+    boolean bindWallpaperDescriptionLocked(WallpaperDescription description, boolean force,
+            boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
+        ComponentName componentName = description.getComponent();
         if (DEBUG_LIVE) {
             Slog.v(TAG, "bindWallpaperComponentLocked: componentName=" + componentName);
         }
-        // Has the component changed?
-        if (!force && changingToSame(componentName, wallpaper.connection,
-                wallpaper.getComponent())) {
+        boolean skipBinding;
+        if (liveWallpaperContentHandling()) {
+            skipBinding = !force && changingToSame(description, wallpaper.connection,
+                    wallpaper.getDescription());
+        } else {
+            skipBinding = !force && changingToSame(componentName, wallpaper.connection,
+                    wallpaper.getComponent());
+        }
+        if (skipBinding) {
             try {
                 if (DEBUG_LIVE) {
                     Slog.v(TAG, "Changing to the same component, ignoring");
@@ -3374,6 +3513,9 @@
         try {
             if (componentName == null) {
                 componentName = mDefaultWallpaperComponent;
+                if (liveWallpaperContentHandling()) {
+                    description = description.toBuilder().setComponent(componentName).build();
+                }
             }
             int serviceUserId = wallpaper.userId;
             ServiceInfo si = mIPackageManager.getServiceInfo(componentName,
@@ -3494,7 +3636,11 @@
                 return false;
             }
             maybeDetachLastWallpapers(wallpaper);
-            wallpaper.setComponent(componentName);
+            if (liveWallpaperContentHandling()) {
+                wallpaper.setDescription(description);
+            } else {
+                wallpaper.setComponent(componentName);
+            }
             wallpaper.connection = newConn;
             newConn.mReply = reply;
             updateCurrentWallpapers(wallpaper);
@@ -3618,11 +3764,6 @@
                 });
     }
 
-    private void clearWallpaperComponentLocked(WallpaperData wallpaper) {
-        wallpaper.setComponent(null);
-        detachWallpaperLocked(wallpaper);
-    }
-
     private void attachServiceLocked(WallpaperConnection conn, WallpaperData wallpaper) {
         TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
         t.traceBegin("WPMS.attachServiceLocked");
@@ -3858,6 +3999,7 @@
     }
 
     // Called by SystemBackupAgent after files are restored to disk.
+    // TODO(b/373875373) Remove this method
     public void settingsRestored() {
         // Verify caller is the system
         if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
@@ -3876,6 +4018,14 @@
             ComponentName componentName =
                     removeNextWallpaperComponent() ? wallpaper.getComponent()
                             : wallpaper.nextWallpaperComponent;
+
+            if (liveWallpaperContentHandling()) {
+                // Per b/373875373 this method should be removed, so we just set wallpapers to
+                // default.
+                bindWallpaperDescriptionLocked(new WallpaperDescription.Builder().build(), false,
+                        false, wallpaper, null);
+                return;
+            }
             if (componentName != null && !componentName.equals(mImageWallpaper)) {
                 wallpaper.mBindSource = BindSource.RESTORE_SETTINGS_LIVE_SUCCESS;
                 if (!bindWallpaperComponentLocked(componentName, false, false,
@@ -3894,15 +4044,20 @@
                     if (DEBUG) Slog.v(TAG, "settingsRestored: name is empty");
                     success = true;
                 } else {
-                    if (DEBUG) Slog.v(TAG, "settingsRestored: attempting to restore named resource");
+                    if (DEBUG) {
+                        Slog.v(TAG, "settingsRestored: attempting to restore named resource");
+                    }
                     success = mWallpaperDataParser.restoreNamedResourceLocked(wallpaper);
                 }
-                if (DEBUG) Slog.v(TAG, "settingsRestored: success=" + success
-                        + " id=" + wallpaper.wallpaperId);
+                if (DEBUG) {
+                    Slog.v(TAG, "settingsRestored: success=" + success + " id="
+                            + wallpaper.wallpaperId);
+                }
                 if (success) {
-                    mWallpaperCropper.generateCrop(wallpaper); // based on the new image + metadata
+                    mWallpaperCropper.generateCrop(
+                            wallpaper); // based on the new image + metadata
                     wallpaper.mBindSource = BindSource.RESTORE_SETTINGS_STATIC;
-                    bindWallpaperComponentLocked(componentName, true, false, wallpaper, null);
+                    bindWallpaperComponentLocked(null, true, false, wallpaper, null);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index 744c3da6..e1fa884 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -78,8 +78,13 @@
         mWebViewUpdatedReceiver = new BroadcastReceiver() {
                 @Override
                 public void onReceive(Context context, Intent intent) {
+                    final String action = intent.getAction();
+                    if (action == null) {
+                        return;
+                    }
+
                     int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
-                    switch (intent.getAction()) {
+                    switch (action) {
                         case Intent.ACTION_PACKAGE_REMOVED:
                             // When a package is replaced we will receive two intents, one
                             // representing the removal of the old package and one representing the
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a71b10f..73ae51c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -629,6 +629,9 @@
     // The locusId associated with this activity, if set.
     private LocusId mLocusId;
 
+    // Whether the activity is requesting to limit the system's educational dialogs
+    public boolean mShouldLimitSystemEducationDialogs;
+
     // Whether the activity was launched from a bubble.
     private boolean mLaunchedFromBubble;
 
@@ -704,9 +707,6 @@
      */
     private boolean mOccludesParent;
 
-    /** Whether the activity have style floating */
-    private boolean mStyleFloating;
-
     /**
      * Unlike {@link #mOccludesParent} which can be changed at runtime. This is a static attribute
      * from the style of activity. Because we don't want {@link WindowContainer#getOrientation()}
@@ -788,10 +788,10 @@
     // and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
     private boolean mIsEligibleForFixedOrientationLetterbox;
 
-    // activity is not displayed?
-    // TODO: rename to mNoDisplay
-    @VisibleForTesting
-    boolean noDisplay;
+    /**
+     * Whether the activity is to be displayed. See {@link android.R.attr#windowNoDisplay}.
+     */
+    private boolean mNoDisplay;
     final boolean mShowForAllUsers;
     // TODO: Make this final
     int mTargetSdk;
@@ -1175,7 +1175,7 @@
                 pw.print(" inHistory="); pw.print(inHistory);
                 pw.print(" idle="); pw.println(idle);
         pw.print(prefix); pw.print("occludesParent="); pw.print(occludesParent());
-                pw.print(" noDisplay="); pw.print(noDisplay);
+                pw.print(" mNoDisplay="); pw.print(mNoDisplay);
                 pw.print(" immersive="); pw.print(immersive);
                 pw.print(" launchMode="); pw.println(launchMode);
         pw.print(prefix); pw.print("mActivityType=");
@@ -2008,20 +2008,19 @@
         if (ent != null) {
             final boolean styleTranslucent = ent.array.getBoolean(
                     com.android.internal.R.styleable.Window_windowIsTranslucent, false);
-            mStyleFloating = ent.array.getBoolean(
+            final boolean styleFloating = ent.array.getBoolean(
                     com.android.internal.R.styleable.Window_windowIsFloating, false);
-            mOccludesParent = !(styleTranslucent || mStyleFloating)
+            mOccludesParent = !(styleTranslucent || styleFloating)
                     // This style is propagated to the main window attributes with
                     // FLAG_SHOW_WALLPAPER from PhoneWindow#generateLayout.
                     || ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false);
             mStyleFillsParent = mOccludesParent;
-            noDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false);
+            mNoDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false);
             mOptOutEdgeToEdge = ent.array.getBoolean(
                     R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false);
         } else {
-            mStyleFloating = false;
             mStyleFillsParent = mOccludesParent = true;
-            noDisplay = false;
+            mNoDisplay = false;
             mOptOutEdgeToEdge = false;
         }
 
@@ -3088,8 +3087,16 @@
         return occludesParent(true /* includingFinishing */);
     }
 
-    boolean isStyleFloating() {
-        return mStyleFloating;
+    boolean isNoDisplay() {
+        return mNoDisplay;
+    }
+
+    /**
+     * Exposed only for testing and should not be used to modify value of {@link #mNoDisplay}.
+     */
+    @VisibleForTesting
+    void setIsNoDisplay(boolean isNoDisplay) {
+        mNoDisplay = isNoDisplay;
     }
 
     /** Returns true if this activity is not finishing, is opaque and fills the entire space of
@@ -3189,6 +3196,9 @@
         if (mWmService.mConstants.isPackageOptOutIgnoreActivityOrientationRequest(packageName)) {
             return false;
         }
+        if (mAppCompatController.mAllowRestrictedResizability.getAsBoolean()) {
+            return false;
+        }
         // If the user preference respects aspect ratio, then it becomes non-resizable.
         return !mAppCompatController.getAppCompatOverrides().getAppCompatAspectRatioOverrides()
                 .shouldApplyUserMinAspectRatioOverride();
@@ -6066,7 +6076,7 @@
     void notifyUnknownVisibilityLaunchedForKeyguardTransition() {
         // No display activities never add a window, so there is no point in waiting them for
         // relayout.
-        if (noDisplay || !isKeyguardLocked()) {
+        if (mNoDisplay || !isKeyguardLocked()) {
             return;
         }
 
@@ -7300,6 +7310,16 @@
         return mLocusId;
     }
 
+    void setLimitSystemEducationDialogs(boolean limitSystemEducationDialogs) {
+        if (mShouldLimitSystemEducationDialogs == limitSystemEducationDialogs) return;
+        mShouldLimitSystemEducationDialogs = limitSystemEducationDialogs;
+        final Task task = getTask();
+        if (task != null) {
+            final boolean force = isVisibleRequested() && this == task.getTopNonFinishingActivity();
+            getTask().dispatchTaskInfoChangedIfNeeded(force);
+        }
+    }
+
     public void reportScreenCaptured() {
         if (mCaptureCallbacks != null) {
             final int n = mCaptureCallbacks.beginBroadcast();
diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
index fa5beca..ea6506a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
+++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
@@ -40,7 +40,6 @@
     @ChangeId
     static final long ENABLE_TOUCH_OPAQUE_ACTIVITIES = 194480991L;
 
-    // TODO(b/369605358) Update EnabledSince when SDK 36 version code is available.
     /**
      * If the app's target SDK is 36+, pass-through touches from a cross-uid overlaying activity is
      * blocked by default. The activity may opt in to receive pass-through touches using
@@ -52,7 +51,7 @@
      * @see ActivityOptions#setAllowPassThroughOnTouchOutside
      */
     @ChangeId
-    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT)
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.BAKLAVA)
     static final long ENABLE_OVERLAY_TOUCH_PASS_THROUGH_OPT_IN_ENFORCEMENT = 358129114L;
 
     private final ActivityRecord mActivityRecord;
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 6e97f8a..3f24da9 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -478,7 +478,7 @@
                                 intentGrants.merge(creatorIntentGrants);
                             }
                         } catch (SecurityException securityException) {
-                            ActivityStarter.logAndThrowExceptionForIntentRedirect(
+                            ActivityStarter.logAndThrowExceptionForIntentRedirect(mService.mContext,
                                     "Creator URI Grant Caused Exception.", intent, creatorUid,
                                     creatorPackage, filterCallingUid, callingPackage,
                                     securityException);
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index e15968d..2e2ca14 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -55,6 +55,7 @@
 import static android.content.pm.ActivityInfo.launchModeToString;
 import static android.os.Process.INVALID_UID;
 import static android.security.Flags.preventIntentRedirectAbortOrThrowException;
+import static android.security.Flags.preventIntentRedirectShowToast;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
@@ -105,6 +106,7 @@
 import android.compat.annotation.Disabled;
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.Overridable;
+import android.content.Context;
 import android.content.IIntentSender;
 import android.content.Intent;
 import android.content.IntentSender;
@@ -128,12 +130,14 @@
 import android.text.TextUtils;
 import android.util.Pools.SynchronizedPool;
 import android.util.Slog;
+import android.widget.Toast;
 import android.window.RemoteTransition;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.HeavyWeightSwitcherActivity;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.protolog.ProtoLog;
+import com.android.server.UiThread;
 import com.android.server.am.ActivityManagerService.IntentCreatorToken;
 import com.android.server.am.PendingIntentRecord;
 import com.android.server.pm.InstantAppResolver;
@@ -614,7 +618,7 @@
             // Check if the Intent was redirected
             if ((intent.getExtendedFlags() & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN)
                     != 0) {
-                ActivityStarter.logAndThrowExceptionForIntentRedirect(
+                logAndThrowExceptionForIntentRedirect(supervisor.mService.mContext,
                         "Unparceled intent does not have a creator token set.", intent,
                         intentCreatorUid, intentCreatorPackage, resolvedCallingUid,
                         resolvedCallingPackage, null);
@@ -650,7 +654,7 @@
                                 intentGrants.merge(creatorIntentGrants);
                             }
                         } catch (SecurityException securityException) {
-                            ActivityStarter.logAndThrowExceptionForIntentRedirect(
+                            logAndThrowExceptionForIntentRedirect(supervisor.mService.mContext,
                                     "Creator URI Grant Caused Exception.", intent, intentCreatorUid,
                                     intentCreatorPackage, resolvedCallingUid,
                                     resolvedCallingPackage, securityException);
@@ -674,7 +678,7 @@
                                 intentGrants.merge(creatorIntentGrants);
                             }
                         } catch (SecurityException securityException) {
-                            ActivityStarter.logAndThrowExceptionForIntentRedirect(
+                            logAndThrowExceptionForIntentRedirect(supervisor.mService.mContext,
                                     "Creator URI Grant Caused Exception.", intent, intentCreatorUid,
                                     intentCreatorPackage, resolvedCallingUid,
                                     resolvedCallingPackage, securityException);
@@ -1033,7 +1037,6 @@
         mLastStartReason = request.reason;
         mLastStartActivityTimeMs = System.currentTimeMillis();
 
-        final ActivityRecord previousStart = mLastStartActivityRecord;
         final IApplicationThread caller = request.caller;
         Intent intent = request.intent;
         NeededUriGrants intentGrants = request.intentGrants;
@@ -1250,27 +1253,27 @@
                         requestCode, 0, intentCreatorUid, intentCreatorPackage, "",
                         request.ignoreTargetSecurity, inTask != null, null, resultRecord,
                         resultRootTask)) {
-                    abort = logAndAbortForIntentRedirect(
+                    abort = logAndAbortForIntentRedirect(mService.mContext,
                             "Creator checkStartAnyActivityPermission Caused abortion.",
                             intent, intentCreatorUid, intentCreatorPackage, callingUid,
                             callingPackage);
                 }
             } catch (SecurityException e) {
-                logAndThrowExceptionForIntentRedirect(
+                logAndThrowExceptionForIntentRedirect(mService.mContext,
                         "Creator checkStartAnyActivityPermission Caused Exception.",
                         intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage,
                         e);
             }
             if (!mService.mIntentFirewall.checkStartActivity(intent, intentCreatorUid,
                     0, resolvedType, aInfo.applicationInfo)) {
-                abort = logAndAbortForIntentRedirect(
+                abort = logAndAbortForIntentRedirect(mService.mContext,
                         "Creator IntentFirewall.checkStartActivity Caused abortion.",
                         intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage);
             }
 
             if (!mService.getPermissionPolicyInternal().checkStartActivity(intent,
                     intentCreatorUid, intentCreatorPackage)) {
-                abort = logAndAbortForIntentRedirect(
+                abort = logAndAbortForIntentRedirect(mService.mContext,
                         "Creator PermissionPolicyService.checkStartActivity Caused abortion.",
                         intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage);
             }
@@ -2346,7 +2349,8 @@
         // When there is a reused activity and the current result is a trampoline activity,
         // set the reused activity as the result.
         if (mLastStartActivityRecord != null
-                && (mLastStartActivityRecord.finishing || mLastStartActivityRecord.noDisplay)) {
+                && (mLastStartActivityRecord.finishing
+                    || mLastStartActivityRecord.isNoDisplay())) {
             mLastStartActivityRecord = targetTaskTop;
         }
 
@@ -3596,25 +3600,38 @@
         pw.println(mInTaskFragment);
     }
 
-    static void logAndThrowExceptionForIntentRedirect(@NonNull String message,
-            @NonNull Intent intent, int intentCreatorUid, @Nullable String intentCreatorPackage,
-            int callingUid, @Nullable String callingPackage,
+    static void logAndThrowExceptionForIntentRedirect(@NonNull Context context,
+            @NonNull String message, @NonNull Intent intent, int intentCreatorUid,
+            @Nullable String intentCreatorPackage, int callingUid, @Nullable String callingPackage,
             @Nullable SecurityException originalException) {
         String msg = getIntentRedirectPreventedLogMessage(message, intent, intentCreatorUid,
                 intentCreatorPackage, callingUid, callingPackage);
         Slog.wtf(TAG, msg);
+        if (preventIntentRedirectShowToast()) {
+            UiThread.getHandler().post(
+                    () -> Toast.makeText(context,
+                            "Activity launch blocked. go/report-bug-intentRedir to report a bug",
+                            Toast.LENGTH_LONG).show());
+        }
         if (preventIntentRedirectAbortOrThrowException() && CompatChanges.isChangeEnabled(
                 ENABLE_PREVENT_INTENT_REDIRECT_TAKE_ACTION, callingUid)) {
             throw new SecurityException(msg, originalException);
         }
     }
 
-    private static boolean logAndAbortForIntentRedirect(@NonNull String message,
-            @NonNull Intent intent, int intentCreatorUid, @Nullable String intentCreatorPackage,
-            int callingUid, @Nullable String callingPackage) {
+    private static boolean logAndAbortForIntentRedirect(@NonNull Context context,
+            @NonNull String message, @NonNull Intent intent, int intentCreatorUid,
+            @Nullable String intentCreatorPackage, int callingUid,
+            @Nullable String callingPackage) {
         String msg = getIntentRedirectPreventedLogMessage(message, intent, intentCreatorUid,
                 intentCreatorPackage, callingUid, callingPackage);
         Slog.wtf(TAG, msg);
+        if (preventIntentRedirectShowToast()) {
+            UiThread.getHandler().post(
+                    () -> Toast.makeText(context,
+                            "Activity launch blocked. go/report-bug-intentRedir to report a bug",
+                            Toast.LENGTH_LONG).show());
+        }
         return preventIntentRedirectAbortOrThrowException() && CompatChanges.isChangeEnabled(
                 ENABLE_PREVENT_INTENT_REDIRECT_TAKE_ACTION, callingUid);
     }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 0745fec..198e14a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1854,6 +1854,8 @@
         }
         assertPackageMatchesCallingUid(callingPackage);
 
+        mAmInternal.addCreatorToken(intent, callingPackage);
+
         final ActivityOptions activityOptions = ActivityOptions.makeBasic();
         activityOptions.setLaunchTaskId(taskId);
         // Pass in the system UID to allow setting launch taskId with MANAGE_GAME_ACTIVITY.
@@ -3913,6 +3915,17 @@
     }
 
     @Override
+    public void setLimitSystemEducationDialogs(
+            IBinder appToken, boolean limitSystemEducationDialogs) {
+        synchronized (mGlobalLock) {
+            final ActivityRecord r = ActivityRecord.isInRootTaskLocked(appToken);
+            if (r != null) {
+                r.setLimitSystemEducationDialogs(limitSystemEducationDialogs);
+            }
+        }
+    }
+
+    @Override
     public boolean updateConfiguration(Configuration values) {
         mAmInternal.enforceCallingPermission(CHANGE_CONFIGURATION, "updateConfiguration()");
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 30b53d1..4857b02e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1725,9 +1725,6 @@
             return;
         }
         Transition transit = task.mTransitionController.requestCloseTransitionIfNeeded(task);
-        if (transit == null) {
-            transit = task.mTransitionController.getCollectingTransition();
-        }
         if (transit != null) {
             transit.collectClose(task);
             if (!task.mTransitionController.useFullReadyTracking()) {
@@ -1739,7 +1736,15 @@
                 // before anything that may need it to wait (setReady(false)).
                 transit.setReady(task, true);
             }
+        } else {
+            // If we failed to create a transition, there might be already a currently collecting
+            // transition. Let's use it if possible.
+            transit = task.mTransitionController.getCollectingTransition();
+            if (transit != null) {
+                transit.collectClose(task);
+            }
         }
+
         // Consume the stopping activities immediately so activity manager won't skip killing
         // the process because it is still foreground state, i.e. RESUMED -> PAUSING set from
         // removeActivities -> finishIfPossible.
@@ -2478,7 +2483,7 @@
     /** Notifies that the top activity of the task is forced to be resizeable. */
     private void handleForcedResizableTaskIfNeeded(Task task, int reason) {
         final ActivityRecord topActivity = task.getTopNonFinishingActivity();
-        if (topActivity == null || topActivity.noDisplay
+        if (topActivity == null || topActivity.isNoDisplay()
                 || !topActivity.canForceResizeNonResizable(task.getWindowingMode())) {
             return;
         }
@@ -2894,10 +2899,9 @@
         private boolean mIncludeInvisibleAndFinishing;
         private boolean mIgnoringKeyguard;
 
-        ActivityRecord getOpaqueActivity(
-                @NonNull WindowContainer<?> container, boolean ignoringKeyguard) {
+        ActivityRecord getOpaqueActivity(@NonNull WindowContainer<?> container) {
             mIncludeInvisibleAndFinishing = true;
-            mIgnoringKeyguard = ignoringKeyguard;
+            mIgnoringKeyguard = true;
             return container.getActivity(this,
                     true /* traverseTopToBottom */, null /* boundary */);
         }
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index 548c0a3..fa2c716 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -128,10 +128,11 @@
         }
         if (!aspectRatioOverrides.shouldOverrideMinAspectRatio()
                 && !AppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord)) {
-            if (mActivityRecord.isUniversalResizeable()) {
+            final float minAspectRatio = info.getMinAspectRatio();
+            if (minAspectRatio == 0 || mActivityRecord.isUniversalResizeable()) {
                 return 0;
             }
-            return info.getMinAspectRatio();
+            return minAspectRatio;
         }
 
         if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
@@ -173,10 +174,11 @@
         if (mTransparentPolicy.isRunning()) {
             return mTransparentPolicy.getInheritedMaxAspectRatio();
         }
-        if (mActivityRecord.isUniversalResizeable()) {
+        final float maxAspectRatio = mActivityRecord.info.getMaxAspectRatio();
+        if (maxAspectRatio == 0 || mActivityRecord.isUniversalResizeable()) {
             return 0;
         }
-        return mActivityRecord.info.getMaxAspectRatio();
+        return maxAspectRatio;
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 6c344c6..145a376 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -15,12 +15,15 @@
  */
 package com.android.server.wm;
 
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY;
+
 import android.annotation.NonNull;
 import android.content.pm.PackageManager;
 
 import com.android.server.wm.utils.OptPropFactory;
 
 import java.io.PrintWriter;
+import java.util.function.BooleanSupplier;
 
 /**
  * Allows the interaction with all the app compat policies and configurations
@@ -47,6 +50,8 @@
     private final AppCompatLetterboxPolicy mAppCompatLetterboxPolicy;
     @NonNull
     private final AppCompatSizeCompatModePolicy mAppCompatSizeCompatModePolicy;
+    @NonNull
+    final BooleanSupplier mAllowRestrictedResizability;
 
     AppCompatController(@NonNull WindowManagerService wmService,
                         @NonNull ActivityRecord activityRecord) {
@@ -70,6 +75,17 @@
                 mAppCompatOverrides, mTransparentPolicy, wmService.mAppCompatConfiguration);
         mAppCompatSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(mActivityRecord,
                 mAppCompatOverrides);
+        mAllowRestrictedResizability = AppCompatUtils.asLazy(() -> {
+            try {
+                return packageManager.getPropertyAsUser(
+                        PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY,
+                        mActivityRecord.mActivityComponent.getPackageName(),
+                        mActivityRecord.mActivityComponent.getClassName(),
+                        mActivityRecord.mUserId).getBoolean();
+            } catch (PackageManager.NameNotFoundException e) {
+                return false;
+            }
+        });
     }
 
     @NonNull
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index db76eb9..ebb50db 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -164,41 +164,46 @@
 
         appCompatTaskInfo.setIsFromLetterboxDoubleTap(reachabilityOverrides.isFromDoubleTap());
 
-        final Rect bounds = top.getBounds();
-        final Rect appBounds = getAppBounds(top);
-        appCompatTaskInfo.topActivityLetterboxWidth = bounds.width();
-        appCompatTaskInfo.topActivityLetterboxHeight = bounds.height();
-        appCompatTaskInfo.topActivityLetterboxAppWidth = appBounds.width();
-        appCompatTaskInfo.topActivityLetterboxAppHeight = appBounds.height();
+        final boolean isTopActivityLetterboxed = top.areBoundsLetterboxed();
+        appCompatTaskInfo.setTopActivityLetterboxed(isTopActivityLetterboxed);
+        if (isTopActivityLetterboxed) {
+            final Rect bounds = top.getBounds();
+            final Rect appBounds = getAppBounds(top);
+            appCompatTaskInfo.topActivityLetterboxWidth = bounds.width();
+            appCompatTaskInfo.topActivityLetterboxHeight = bounds.height();
+            appCompatTaskInfo.topActivityLetterboxAppWidth = appBounds.width();
+            appCompatTaskInfo.topActivityLetterboxAppHeight = appBounds.height();
 
-        // We need to consider if letterboxed or pillarboxed.
-        // TODO(b/336807329) Encapsulate reachability logic
-        appCompatTaskInfo.setLetterboxDoubleTapEnabled(reachabilityOverrides
-                .isLetterboxDoubleTapEducationEnabled());
-        if (appCompatTaskInfo.isLetterboxDoubleTapEnabled()) {
-            if (appCompatTaskInfo.isTopActivityPillarboxed()) {
-                if (reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox()) {
-                    // Pillarboxed.
-                    appCompatTaskInfo.topActivityLetterboxHorizontalPosition =
-                            reachabilityOverrides.getLetterboxPositionForHorizontalReachability();
+            // We need to consider if letterboxed or pillarboxed.
+            // TODO(b/336807329) Encapsulate reachability logic
+            appCompatTaskInfo.setLetterboxDoubleTapEnabled(reachabilityOverrides
+                    .isLetterboxDoubleTapEducationEnabled());
+            if (appCompatTaskInfo.isLetterboxDoubleTapEnabled()) {
+                if (appCompatTaskInfo.isTopActivityPillarboxShaped()) {
+                    if (reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox()) {
+                        // Pillarboxed.
+                        appCompatTaskInfo.topActivityLetterboxHorizontalPosition =
+                                reachabilityOverrides
+                                        .getLetterboxPositionForHorizontalReachability();
+                    } else {
+                        appCompatTaskInfo.setLetterboxDoubleTapEnabled(false);
+                    }
                 } else {
-                    appCompatTaskInfo.setLetterboxDoubleTapEnabled(false);
-                }
-            } else {
-                if (reachabilityOverrides.allowVerticalReachabilityForThinLetterbox()) {
-                    // Letterboxed.
-                    appCompatTaskInfo.topActivityLetterboxVerticalPosition =
-                            reachabilityOverrides.getLetterboxPositionForVerticalReachability();
-                } else {
-                    appCompatTaskInfo.setLetterboxDoubleTapEnabled(false);
+                    if (reachabilityOverrides.allowVerticalReachabilityForThinLetterbox()) {
+                        // Letterboxed.
+                        appCompatTaskInfo.topActivityLetterboxVerticalPosition =
+                                reachabilityOverrides.getLetterboxPositionForVerticalReachability();
+                    } else {
+                        appCompatTaskInfo.setLetterboxDoubleTapEnabled(false);
+                    }
                 }
             }
         }
+
         final boolean eligibleForAspectRatioButton =
                 !info.isTopActivityTransparent && !appCompatTaskInfo.isTopActivityInSizeCompat()
                         && aspectRatioOverrides.shouldEnableUserAspectRatioSettings();
         appCompatTaskInfo.setEligibleForUserAspectRatioButton(eligibleForAspectRatioButton);
-        appCompatTaskInfo.setTopActivityLetterboxed(top.areBoundsLetterboxed());
         appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode =
                 AppCompatCameraPolicy.getCameraCompatFreeformMode(top);
         appCompatTaskInfo.setHasMinAspectRatioOverride(top.mAppCompatController
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 2fe023e..4ed8b09 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -26,6 +26,8 @@
 import static android.view.WindowManager.TRANSIT_OLD_NONE;
 import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_FINISH_AND_REMOVE_TASK;
+import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED;
 
 import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_BACK_PREVIEW;
 import static com.android.server.wm.BackNavigationProto.ANIMATION_IN_PROGRESS;
@@ -60,6 +62,7 @@
 import android.window.IBackAnimationFinishedCallback;
 import android.window.IWindowlessStartingSurfaceCallback;
 import android.window.OnBackInvokedCallbackInfo;
+import android.window.SystemOverrideOnBackInvokedCallback;
 import android.window.TaskSnapshot;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -226,10 +229,19 @@
                     && callbackInfo.isAnimationCallback());
             mNavigationMonitor.startMonitor(window, navigationObserver);
 
+            int requestOverride = callbackInfo.getOverrideBehavior();
             ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation currentTask=%s, "
                             + "topRunningActivity=%s, callbackInfo=%s, currentFocus=%s",
                     currentTask, currentActivity, callbackInfo, window);
-
+            if (requestOverride == OVERRIDE_FINISH_AND_REMOVE_TASK) {
+                final ActivityRecord rootR = currentTask != null ? currentTask.getRootActivity()
+                        : null;
+                if (currentActivity != null && rootR != currentActivity) {
+                    // The top activity is not root activity, the activity cannot remove task when
+                    // finishAndRemoveTask called.
+                    requestOverride = OVERRIDE_UNDEFINED;
+                }
+            }
             // Clear the pointer down outside focus if any.
             mWindowManagerService.clearPointerDownOutsideFocusRunnable();
 
@@ -274,7 +286,8 @@
             } else if (hasTranslucentActivity(currentActivity, prevActivities)) {
                 // skip if one of participant activity is translucent
                 backType = BackNavigationInfo.TYPE_CALLBACK;
-            } else if (prevActivities.size() > 0) {
+            } else if (prevActivities.size() > 0
+                    && requestOverride == SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED) {
                 if ((!isOccluded || isAllActivitiesCanShowWhenLocked(prevActivities))
                         && isAllActivitiesCreated(prevActivities)) {
                     // We have another Activity in the same currentTask to go to
@@ -1025,6 +1038,12 @@
             return;
         }
 
+        if (mWindowManagerService.mRoot.mTransitionController.isCollecting()) {
+            Slog.v(TAG, "Skip predictive back transition, another transition is collecting");
+            cancelPendingAnimation();
+            return;
+        }
+
         // Ensure the final animation targets which hidden by transition could be visible.
         for (int i = 0; i < targets.size(); i++) {
             final WindowContainer wc = targets.get(i).mContainer;
@@ -1053,6 +1072,7 @@
             Slog.e(TAG, "Remote animation gone", e);
         }
         mPendingAnimationBuilder = null;
+        mNavigationMonitor.stopMonitorTransition();
     }
 
     /**
@@ -1550,6 +1570,9 @@
             }
 
             void createStartingSurface(@Nullable TaskSnapshot snapshot) {
+                if (Flags.deferPredictiveAnimationIfNoSnapshot() && snapshot == null) {
+                    return;
+                }
                 if (mAdaptors[0].mSwitchType == DIALOG_CLOSE) {
                     return;
                 }
@@ -1851,7 +1874,7 @@
                     tc.requestStartTransition(prepareOpen,
                             null /*startTask */, null /* remoteTransition */,
                             null /* displayChange */);
-                    prepareOpen.setReady(makeVisibles.get(0), true);
+                    prepareOpen.setReady(mCloseTarget, true);
                     return prepareOpen;
                 } else if (mSnapshot == null) {
                     return setLaunchBehind(visibleOpenActivities);
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 1933408..ec171c5 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -49,7 +49,6 @@
 import static com.android.window.flags.Flags.balImproveRealCallerVisibilityCheck;
 import static com.android.window.flags.Flags.balImprovedMetrics;
 import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
-import static com.android.window.flags.Flags.balRequireOptInSameUid;
 import static com.android.window.flags.Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid;
 import static com.android.window.flags.Flags.balShowToastsBlocked;
 import static com.android.window.flags.Flags.balStrictModeRo;
@@ -359,7 +358,7 @@
             } else if (mIsCallForResult) {
                 mAutoOptInReason = AUTO_OPT_IN_CALL_FOR_RESULT;
                 mAutoOptInCaller = false;
-            } else if (callingUid == realCallingUid && !balRequireOptInSameUid()) {
+            } else if (callingUid == realCallingUid) {
                 mAutoOptInReason = AUTO_OPT_IN_SAME_UID;
                 mAutoOptInCaller = false;
             } else if (realCallerBackgroundActivityStartMode
@@ -604,7 +603,6 @@
                     .append(balImproveRealCallerVisibilityCheck());
             sb.append("; balRequireOptInByPendingIntentCreator: ")
                     .append(balRequireOptInByPendingIntentCreator());
-            sb.append("; balRequireOptInSameUid: ").append(balRequireOptInSameUid());
             sb.append("; balRespectAppSwitchStateWhenCheckBoundByForegroundUid: ")
                     .append(balRespectAppSwitchStateWhenCheckBoundByForegroundUid());
             sb.append("; balDontBringExistingBackgroundTaskStackToFg: ")
@@ -1164,8 +1162,9 @@
      * or {@link #BAL_BLOCK} if the launch should be blocked
      */
     BalVerdict checkBackgroundActivityStartAllowedByRealCallerInBackground(BalState state) {
-        if (state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
-                == MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
+        boolean allowAlways = state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+                == MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
+        if (allowAlways
                 && hasBalPermission(state.mRealCallingUid, state.mRealCallingPid)) {
             return new BalVerdict(BAL_ALLOW_PERMISSION,
                     /*background*/ false,
@@ -1177,8 +1176,7 @@
                 + state.mRealCallingPid + ", " + state.mRealCallingPackage + ") "
                 + balStartModeToString(
                 state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()));
-        if (state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
-                == MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
+        if (allowAlways
                 && mService.hasSystemAlertWindowPermission(state.mRealCallingUid,
                 state.mRealCallingPid, state.mRealCallingPackage)) {
             Slog.w(
@@ -1192,7 +1190,7 @@
 
         // if the realCallingUid is a persistent system process, abort if the IntentSender
         // wasn't allowed to start an activity
-        if (state.mAllowBalExemptionForSystemProcess
+        if ((allowAlways || state.mAllowBalExemptionForSystemProcess)
                 && state.mIsRealCallingUidPersistentSystemProcess) {
             return new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID,
                     /*background*/ false,
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e827f44..e9e550e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -159,7 +159,6 @@
 import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
 import static com.android.server.wm.utils.RegionUtils.forEachRectReverse;
 import static com.android.server.wm.utils.RegionUtils.rectListToRegion;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -3426,14 +3425,6 @@
         if (!mWmService.mSupportsHighPerfTransitions) {
             return;
         }
-        if (!explicitRefreshRateHints()) {
-            if (enable) {
-                getPendingTransaction().setEarlyWakeupStart();
-            } else {
-                getPendingTransaction().setEarlyWakeupEnd();
-            }
-            return;
-        }
         if (enable) {
             if (mTransitionPrefSession == null) {
                 mTransitionPrefSession = mWmService.mSystemPerformanceHinter.createSession(
@@ -3446,10 +3437,6 @@
     }
 
     void enableHighFrameRate(boolean enable) {
-        if (!explicitRefreshRateHints()) {
-            // Done by RefreshRatePolicy.
-            return;
-        }
         if (enable) {
             if (mHighFrameRateSession == null) {
                 mHighFrameRateSession = mWmService.mSystemPerformanceHinter.createSession(
@@ -7072,7 +7059,7 @@
         @Override
         public void setImeInputTargetRequestedVisibility(boolean visible) {
             if (android.view.inputmethod.Flags.refactorInsetsController()) {
-                // TODO(b/329229469) we won't have the statsToken in all cases, but should still log
+                // TODO(b/353463205) we won't have the statsToken in all cases, but should still log
                 try {
                     mRemoteInsetsController.setImeInputTargetRequestedVisibility(visible);
                 } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index c7d57fe..76e8a70 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1735,9 +1735,9 @@
         }
 
         // Show IME over the keyguard if the target allows it.
-        final boolean showImeOverKeyguard = imeTarget != null && imeTarget.isVisible()
-                && win.mIsImWindow && (imeTarget.canShowWhenLocked()
-                        || !imeTarget.canBeHiddenByKeyguard());
+        final boolean showImeOverKeyguard =
+                imeTarget != null && win.mIsImWindow && imeTarget.isDisplayed() && (
+                        imeTarget.canShowWhenLocked() || !imeTarget.canBeHiddenByKeyguard());
         if (showImeOverKeyguard) {
             return false;
         }
@@ -3053,7 +3053,7 @@
             @InsetsType int insetsType) {
         for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
             final InsetsSource source = insetsState.sourceAt(i);
-            if ((source.getType() & insetsType) == 0 || !source.isVisible()) {
+            if ((source.getType() & insetsType) == 0) {
                 continue;
             }
             if (Rect.intersects(bounds, source.getFrame())) {
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index e9c6e93..5ed9612 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -100,13 +100,13 @@
             // isLeashReadyForDispatching (used to dispatch the leash of the control) is
             // depending on mGivenInsetsReady. Therefore, triggering notifyControlChanged here
             // again, so that the control with leash can be eventually dispatched
-            if (!mGivenInsetsReady && mServerVisible && !givenInsetsPending) {
+            if (!mGivenInsetsReady && isServerVisible() && !givenInsetsPending) {
                 mGivenInsetsReady = true;
                 ImeTracker.forLogging().onProgress(mStatsToken,
                         ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED);
                 mStateController.notifyControlChanged(mControlTarget, this);
                 setImeShowing(true);
-            } else if (wasServerVisible && mServerVisible && mGivenInsetsReady
+            } else if (wasServerVisible && isServerVisible() && mGivenInsetsReady
                     && givenInsetsPending) {
                 // If the server visibility didn't change (still visible), and mGivenInsetsReady
                 // is set, we won't call into notifyControlChanged. Therefore, we can reset the
@@ -114,7 +114,7 @@
                 ImeTracker.forLogging().onCancelled(mStatsToken,
                         ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED);
                 mStatsToken = null;
-            } else if (wasServerVisible && !mServerVisible) {
+            } else if (wasServerVisible && !isServerVisible()) {
                 setImeShowing(false);
             }
         }
@@ -134,11 +134,15 @@
     @Override
     protected boolean isLeashReadyForDispatching() {
         if (android.view.inputmethod.Flags.refactorInsetsController()) {
+            // We should only dispatch the leash, if the following conditions are fulfilled:
+            // 1. parent isLeashReadyForDispatching, 2. mGivenInsetsReady (means there are no
+            // givenInsetsPending), 3. the IME surface is drawn, 4. either the IME is
+            // serverVisible (the unfrozen state)
             final WindowState ws =
                     mWindowContainer != null ? mWindowContainer.asWindowState() : null;
             final boolean isDrawn = ws != null && ws.isDrawn();
             return super.isLeashReadyForDispatching()
-                    && mServerVisible && isDrawn && mGivenInsetsReady;
+                    && isServerVisible() && isDrawn && mGivenInsetsReady;
         } else {
             return super.isLeashReadyForDispatching();
         }
@@ -254,7 +258,7 @@
             // Refer WindowState#getImeControlTarget().
             target = target.getWindow().getImeControlTarget();
         }
-        // TODO(b/329229469) make sure that the statsToken of all callers is non-null (currently
+        // TODO(b/353463205) make sure that the statsToken of all callers is non-null (currently
         //  not the case)
         super.updateControlForTarget(target, force, statsToken);
         if (Flags.refactorInsetsController()) {
@@ -290,12 +294,14 @@
         changed |= mDisplayContent.onImeInsetsClientVisibilityUpdate();
         if (Flags.refactorInsetsController()) {
             if (changed) {
+                ImeTracker.forLogging().onProgress(statsToken,
+                        ImeTracker.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY);
                 invokeOnImeRequestedChangedListener(mDisplayContent.getImeInputTarget(),
                         statsToken);
             } else {
-                // TODO(b/329229469) change phase and check cancelled / failed
+                // TODO(b/353463205) check cancelled / failed
                 ImeTracker.forLogging().onCancelled(statsToken,
-                        ImeTracker.PHASE_CLIENT_REPORT_REQUESTED_VISIBLE_TYPES);
+                        ImeTracker.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY);
             }
         }
         return changed;
@@ -460,7 +466,7 @@
             // This can later become ready, so we don't want to cancel the pending request here.
             return;
         }
-        // TODO(b/329229469) check if this is still triggered, as we don't go into STATE_SHOW_IME
+        // TODO(b/353463205) check if this is still triggered, as we don't go into STATE_SHOW_IME
         //  (DefaultImeVisibilityApplier)
         if (android.view.inputmethod.Flags.refactorInsetsController()) {
             // The IME is drawn, so call into {@link WindowState#notifyInsetsControlChanged}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 1d4d6eb..7276007 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -730,6 +730,10 @@
         return mFakeControlTarget;
     }
 
+    boolean isServerVisible() {
+        return mServerVisible;
+    }
+
     boolean isClientVisible() {
         return mClientVisible;
     }
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 5dddf36..4b2d454 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -317,9 +317,9 @@
             // aborted.
             provider.updateFakeControlTarget(target);
         } else {
-            // TODO(b/329229469) if the IME controlTarget changes, any pending requests should fail
+            // TODO(b/353463205) if the IME controlTarget changes, any pending requests should fail
             provider.updateControlForTarget(target, false /* force */,
-                    null /* TODO(b/329229469) check if needed here */);
+                    null /* TODO(b/353463205) check if needed here */);
 
             // Get control target again in case the provider didn't accept the one we passed to it.
             target = provider.getControlTarget();
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index 8cab7d9..e4c34ed 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -19,8 +19,6 @@
 import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
 import static android.hardware.display.DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY;
 
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
-
 import android.hardware.display.DisplayManager;
 import android.view.Display;
 import android.view.Display.Mode;
@@ -60,7 +58,6 @@
     }
 
     private final DisplayInfo mDisplayInfo;
-    private final Mode mDefaultMode;
     private final Mode mLowRefreshRateMode;
     private final PackageRefreshRate mNonHighRefreshRatePackages = new PackageRefreshRate();
     private final HighRefreshRateDenylist mHighRefreshRateDenylist;
@@ -92,8 +89,7 @@
     RefreshRatePolicy(WindowManagerService wmService, DisplayInfo displayInfo,
             HighRefreshRateDenylist denylist) {
         mDisplayInfo = displayInfo;
-        mDefaultMode = displayInfo.getDefaultMode();
-        mLowRefreshRateMode = findLowRefreshRateMode(displayInfo, mDefaultMode);
+        mLowRefreshRateMode = findLowRefreshRateMode(displayInfo);
         mHighRefreshRateDenylist = denylist;
         mWmService = wmService;
     }
@@ -102,7 +98,8 @@
      * Finds the mode id with the lowest refresh rate which is >= 60hz and same resolution as the
      * default mode.
      */
-    private Mode findLowRefreshRateMode(DisplayInfo displayInfo, Mode defaultMode) {
+    private Mode findLowRefreshRateMode(DisplayInfo displayInfo) {
+        final Mode defaultMode = displayInfo.getDefaultMode();
         float[] refreshRates = displayInfo.getDefaultRefreshRates();
         float bestRefreshRate = defaultMode.getRefreshRate();
         mMinSupportedRefreshRate = bestRefreshRate;
@@ -135,33 +132,6 @@
             // Unspecified, use default mode.
             return 0;
         }
-
-        // If app is animating, it's not able to control refresh rate because we want the animation
-        // to run in default refresh rate. But if the display size of default mode is different
-        // from the using preferred mode, then still keep the preferred mode to avoid disturbing
-        // the animation.
-        if (!explicitRefreshRateHints() && w.isAnimationRunningSelfOrParent()) {
-            Display.Mode preferredMode = null;
-            for (Display.Mode mode : mDisplayInfo.supportedModes) {
-                if (preferredDisplayModeId == mode.getModeId()) {
-                    preferredMode = mode;
-                    break;
-                }
-            }
-            if (preferredMode != null) {
-                final int pW = preferredMode.getPhysicalWidth();
-                final int pH = preferredMode.getPhysicalHeight();
-                if ((pW != mDefaultMode.getPhysicalWidth()
-                        || pH != mDefaultMode.getPhysicalHeight())
-                        && pW == mDisplayInfo.getNaturalWidth()
-                        && pH == mDisplayInfo.getNaturalHeight()) {
-                    // Prefer not to change display size when animating.
-                    return preferredDisplayModeId;
-                }
-            }
-            return 0;
-        }
-
         return preferredDisplayModeId;
     }
 
@@ -264,12 +234,6 @@
             return w.mFrameRateVote.reset();
         }
 
-        // If app is animating, it's not able to control refresh rate because we want the animation
-        // to run in default refresh rate.
-        if (!explicitRefreshRateHints() && w.isAnimationRunningSelfOrParent()) {
-            return w.mFrameRateVote.reset();
-        }
-
         // If the app set a preferredDisplayModeId, the preferred refresh rate is the refresh rate
         // of that mode id.
         if (refreshRateSwitchingType != SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY) {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 077127c..1bb4c41 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -715,7 +715,7 @@
                 if (embeddedWindow != null) {
                     // If there is no WindowState for the IWindow, it could be still an
                     // EmbeddedWindow. Therefore, check the EmbeddedWindowController as well
-                    // TODO(b/329229469) Use different phase here
+                    // TODO(b/353463205) Use different phase here
                     ImeTracker.forLogging().onProgress(imeStatsToken,
                             ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES);
                     embeddedWindow.setRequestedVisibleTypes(
diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index 52994c7..3ee2e60 100644
--- a/services/core/java/com/android/server/wm/SnapshotController.java
+++ b/services/core/java/com/android/server/wm/SnapshotController.java
@@ -152,7 +152,10 @@
                 if (mOpenActivities.isEmpty()) {
                     return false;
                 }
-                if (Flags.alwaysCaptureActivitySnapshot()) {
+                // TODO (b/362183912) always capture activity snapshot will cause performance
+                //  regression, remove flag after ramp up
+                if (!Flags.deferPredictiveAnimationIfNoSnapshot()
+                        && Flags.alwaysCaptureActivitySnapshot()) {
                     return true;
                 }
                 for (int i = mOpenActivities.size() - 1; i >= 0; --i) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8aa0530..352dc52 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3423,6 +3423,8 @@
                 ? top.getLastParentBeforePip().mTaskId : INVALID_TASK_ID;
         info.shouldDockBigOverlays = top != null && top.shouldDockBigOverlays;
         info.mTopActivityLocusId = top != null ? top.getLocusId() : null;
+        info.isTopActivityLimitSystemEducationDialogs = top != null
+              ? top.mShouldLimitSystemEducationDialogs : false;
         final Task parentTask = getParent() != null ? getParent().asTask() : null;
         info.parentTaskId = parentTask != null && parentTask.mCreatedByOrganizer
                 ? parentTask.mTaskId
@@ -3430,9 +3432,9 @@
         info.isFocused = isFocused();
         info.isVisible = hasVisibleChildren();
         info.isVisibleRequested = isVisibleRequested();
+        info.isTopActivityNoDisplay = top != null && top.isNoDisplay();
         info.isSleeping = shouldSleepActivities();
         info.isTopActivityTransparent = top != null && !top.fillsParent();
-        info.isTopActivityStyleFloating = top != null && top.isStyleFloating();
         info.lastNonFullscreenBounds = topTask.mLastNonFullscreenBounds;
         final WindowState windowState = top != null ? top.findMainWindow() : null;
         info.requestedVisibleTypes = (windowState != null && Flags.enableFullyImmersiveInDesktop())
@@ -3916,9 +3918,13 @@
             sb.append(" aI=");
             sb.append(affinityIntent.getComponent().flattenToShortString());
         }
-        sb.append(" isResizeable=").append(isResizeable());
-        sb.append(" minWidth=").append(mMinWidth);
-        sb.append(" minHeight=").append(mMinHeight);
+        if (!isResizeable()) {
+            sb.append(" nonResizable");
+        }
+        if (mMinWidth != INVALID_MIN_SIZE || mMinHeight != INVALID_MIN_SIZE) {
+            sb.append(" minWidth=").append(mMinWidth);
+            sb.append(" minHeight=").append(mMinHeight);
+        }
         sb.append('}');
         return stringName = sb.toString();
     }
@@ -4718,7 +4724,7 @@
             }
         }
         if (likelyResolvedMode != WINDOWING_MODE_FULLSCREEN
-                && topActivity != null && !topActivity.noDisplay
+                && topActivity != null && !topActivity.isNoDisplay()
                 && topActivity.canForceResizeNonResizable(likelyResolvedMode)) {
             // Inform the user that they are starting an app that may not work correctly in
             // multi-window mode.
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 606d51d..e090b19 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1090,8 +1090,7 @@
             return true;
         }
         // Including finishing Activity if the TaskFragment is becoming invisible in the transition.
-        return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this,
-                true /* ignoringKeyguard */) == null;
+        return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this) == null;
     }
 
     /**
@@ -1734,6 +1733,12 @@
         if (!hasDirectChildActivities()) {
             return false;
         }
+        if (mResumedActivity != null && mTransitionController.isTransientLaunch(mResumedActivity)) {
+            // Even if the transient activity is occluded, defer pausing (addToStopping will still
+            // be called) it until the transient transition is done. So the current resuming
+            // activity won't need to wait for additional pause complete.
+            return false;
+        }
 
         ProtoLog.d(WM_DEBUG_STATES, "startPausing: taskFrag =%s " + "mResumedActivity=%s", this,
                 mResumedActivity);
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 5c9a84d..c39671d 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -449,7 +449,7 @@
 
         // If the source activity is a no-display activity, pass on the launch display area token
         // from source activity as currently preferred.
-        if (taskDisplayArea == null && source != null && source.noDisplay) {
+        if (taskDisplayArea == null && source != null && source.isNoDisplay()) {
             taskDisplayArea = source.mHandoverTaskDisplayArea;
             if (taskDisplayArea != null) {
                 if (DEBUG) appendLog("display-area-from-no-display-source=" + taskDisplayArea);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index fc46600..a603466 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -237,15 +237,16 @@
     private final ArraySet<WindowToken> mVisibleAtTransitionEndTokens = new ArraySet<>();
 
     /**
-     * Set of transient activities (lifecycle initially tied to this transition) and their
+     * Map of transient activities (lifecycle initially tied to this transition) to their
      * restore-below tasks.
      */
     private ArrayMap<ActivityRecord, Task> mTransientLaunches = null;
 
     /**
      * The tasks that may be occluded by the transient activity. Assume the task stack is
-     * [Home, A(opaque), B(opaque), C(translucent)] (bottom to top), then A is the restore-below
-     * task, and [B, C] are the transient-hide tasks.
+     * [Home, A(opaque), B(opaque), C(translucent)] (bottom to top), and Home is started in a
+     * transient-launch activity, then A is the restore-below task, and [B, C] are the
+     * transient-hide tasks.
      */
     private ArrayList<Task> mTransientHideTasks;
 
@@ -401,18 +402,26 @@
         mTransientLaunches.put(activity, restoreBelow);
         setTransientLaunchToChanges(activity);
 
-        final Task transientRootTask = activity.getRootTask();
+        final int restoreBelowTaskId = restoreBelow != null ? restoreBelow.mTaskId : -1;
+        ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
+                + "transient-launch restoreBelowTaskId=%d", mSyncId, activity, restoreBelowTaskId);
+
+        final Task transientLaunchRootTask = activity.getRootTask();
         final WindowContainer<?> parent = restoreBelow != null ? restoreBelow.getParent()
-                : (transientRootTask != null ? transientRootTask.getParent() : null);
+                : (transientLaunchRootTask != null ? transientLaunchRootTask.getParent() : null);
         if (parent != null) {
             // Collect all visible tasks which can be occluded by the transient activity to
             // make sure they are in the participants so their visibilities can be updated when
             // finishing transition.
+            // Note: This currently assumes that the parent is a DA containing the full set of
+            //       visible tasks
             parent.forAllTasks(t -> {
                 // Skip transient-launch task
-                if (t == transientRootTask) return false;
+                if (t == transientLaunchRootTask) return false;
                 if (t.isVisibleRequested() && !t.isAlwaysOnTop()) {
                     if (t.isRootTask()) {
+                        ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+                                "  transient hide: taskId=%d", t.mTaskId);
                         mTransientHideTasks.add(t);
                     }
                     if (t.isLeafTask()) {
@@ -442,9 +451,6 @@
             // the gesture threshold.
             activity.getTask().setCanAffectSystemUiFlags(false);
         }
-
-        ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
-                + "transient-launch", mSyncId, activity);
     }
 
     /** @return whether `wc` is a descendent of a transient-hide window. */
@@ -462,6 +468,8 @@
     /** Returns {@code true} if the task should keep visible if this is a transient transition. */
     boolean isTransientVisible(@NonNull Task task) {
         if (mTransientLaunches == null) return false;
+
+        // Check if all the transient-launch activities are occluded
         int occludedCount = 0;
         final int numTransient = mTransientLaunches.size();
         for (int i = numTransient - 1; i >= 0; --i) {
@@ -469,23 +477,22 @@
             if (transientRoot == null) continue;
             final WindowContainer<?> rootParent = transientRoot.getParent();
             if (rootParent == null || rootParent.getTopChild() == transientRoot) continue;
-            final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor.mOpaqueActivityHelper
-                    .getOpaqueActivity(rootParent, true /* ignoringKeyguard */);
-            if (transientRoot.compareTo(topOpaque.getRootTask()) < 0) {
-                occludedCount++;
+            for (int j = rootParent.getChildCount() - 1; j >= 0; --j) {
+                final WindowContainer<?> sibling = rootParent.getChildAt(j);
+                if (sibling == transientRoot) break;
+                if (!sibling.getWindowConfiguration().isAlwaysOnTop() && mController.mAtm
+                        .mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(sibling) != null) {
+                    occludedCount++;
+                    break;
+                }
             }
         }
         if (occludedCount == numTransient) {
-            for (int i = mTransientLaunches.size() - 1; i >= 0; --i) {
-                if (mTransientLaunches.keyAt(i).isDescendantOf(task)) {
-                    // Keep transient activity visible until transition finished, so it won't pause
-                    // with transient-hide tasks that may delay resuming the next top.
-                    return true;
-                }
-            }
             // Let transient-hide activities pause before transition is finished.
             return false;
         }
+
+        // If this task is currently transient-hide, then keep it visible
         return isInTransientHide(task);
     }
 
@@ -608,7 +615,8 @@
     }
 
     /**
-     * Only set flag to the parent tasks and activity itself.
+     * Sets the FLAG_TRANSIENT_LAUNCH flag to all changes associated with the given activity
+     * container and parent tasks.
      */
     private void setTransientLaunchToChanges(@NonNull WindowContainer wc) {
         for (WindowContainer curr = wc; curr != null && mChanges.containsKey(curr);
@@ -774,7 +782,7 @@
         // Only look at tasks, taskfragments, or activities
         if (wc.asTaskFragment() == null && wc.asActivityRecord() == null) return;
         if (!isInTransientHide(wc)) return;
-        info.mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH;
+        info.mFlags |= ChangeInfo.FLAG_TRANSIENT_HIDE;
     }
 
     private void recordDisplay(DisplayContent dc) {
@@ -2448,6 +2456,13 @@
         sb.append(" type=" + transitTypeToString(mType));
         sb.append(" flags=0x" + Integer.toHexString(mFlags));
         sb.append(" overrideAnimOptions=" + mOverrideOptions);
+        if (!mChanges.isEmpty()) {
+            sb.append(" c=[");
+            for (int i = 0; i < mChanges.size(); i++) {
+                sb.append("\n").append("   ").append(mChanges.valueAt(i).toString());
+            }
+            sb.append("\n]\n");
+        }
         sb.append('}');
         return sb.toString();
     }
@@ -3396,8 +3411,14 @@
          * seamless rotation. This is currently only used by DisplayContent during fixed-rotation.
          */
         private static final int FLAG_SEAMLESS_ROTATION = 1;
+        /**
+         * Identifies the associated WindowContainer as a transient-launch task or activity.
+         */
         private static final int FLAG_TRANSIENT_LAUNCH = 2;
-        private static final int FLAG_ABOVE_TRANSIENT_LAUNCH = 4;
+        /**
+         * Identifies the associated WindowContainer as a transient-hide task or activity.
+         */
+        private static final int FLAG_TRANSIENT_HIDE = 4;
 
         /** This container explicitly requested no-animation (usually Activity level). */
         private static final int FLAG_CHANGE_NO_ANIMATION = 0x8;
@@ -3429,7 +3450,7 @@
                 FLAG_NONE,
                 FLAG_SEAMLESS_ROTATION,
                 FLAG_TRANSIENT_LAUNCH,
-                FLAG_ABOVE_TRANSIENT_LAUNCH,
+                FLAG_TRANSIENT_HIDE,
                 FLAG_CHANGE_NO_ANIMATION,
                 FLAG_CHANGE_YES_ANIMATION,
                 FLAG_CHANGE_MOVED_TO_TOP,
@@ -3438,7 +3459,7 @@
                 FLAG_BELOW_BACK_GESTURE_ANIMATION
         })
         @Retention(RetentionPolicy.SOURCE)
-        @interface Flag {}
+        @interface ChangeInfoFlag {}
 
         @NonNull final WindowContainer mContainer;
         /**
@@ -3467,7 +3488,7 @@
         @ActivityInfo.Config int mKnownConfigChanges;
 
         /** Extra information about this change. */
-        @Flag int mFlags = FLAG_NONE;
+        @ChangeInfoFlag int mFlags = FLAG_NONE;
 
         /** Snapshot surface and luma, if relevant. */
         SurfaceControl mSnapshot;
@@ -3502,14 +3523,20 @@
 
         @Override
         public String toString() {
-            return mContainer.toString();
+            StringBuilder sb = new StringBuilder(64);
+            sb.append("ChangeInfo{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" container=").append(mContainer);
+            sb.append(" flags=0x").append(Integer.toHexString(mFlags));
+            sb.append('}');
+            return sb.toString();
         }
 
         boolean hasChanged() {
             final boolean currVisible = mContainer.isVisibleRequested();
             // the task including transient launch must promote to root task
             if (currVisible && ((mFlags & ChangeInfo.FLAG_TRANSIENT_LAUNCH) != 0
-                    || (mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0)
+                    || (mFlags & ChangeInfo.FLAG_TRANSIENT_HIDE) != 0)
                     || (mFlags & ChangeInfo.FLAG_BACK_GESTURE_ANIMATION) != 0
                     || (mFlags & ChangeInfo.FLAG_BELOW_BACK_GESTURE_ANIMATION) != 0) {
                 return true;
@@ -3530,7 +3557,7 @@
 
         @TransitionInfo.TransitionMode
         int getTransitMode(@NonNull WindowContainer wc) {
-            if ((mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) {
+            if ((mFlags & ChangeInfo.FLAG_TRANSIENT_HIDE) != 0) {
                 return mExistenceChanged ? TRANSIT_CLOSE : TRANSIT_TO_BACK;
             }
             if ((mFlags & ChangeInfo.FLAG_BELOW_BACK_GESTURE_ANIMATION) != 0) {
@@ -3559,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/TransparentPolicy.java b/services/core/java/com/android/server/wm/TransparentPolicy.java
index 85a118d..edd9924 100644
--- a/services/core/java/com/android/server/wm/TransparentPolicy.java
+++ b/services/core/java/com/android/server/wm/TransparentPolicy.java
@@ -196,10 +196,11 @@
 
     // We evaluate the case when the policy should not be applied.
     private boolean shouldSkipTransparentPolicy(@Nullable ActivityRecord opaqueActivity) {
-        if (opaqueActivity == null || opaqueActivity.isEmbedded()) {
+        if (opaqueActivity == null || opaqueActivity.isEmbedded()
+                || !opaqueActivity.areBoundsLetterboxed()) {
             // We skip letterboxing if the translucent activity doesn't have any
             // opaque activities beneath or the activity below is embedded which
-            // never has letterbox.
+            // never has letterbox or the activity is not letterboxed at all.
             return true;
         }
         final AppCompatSizeCompatModePolicy scmPolicy = mActivityRecord.mAppCompatController
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 6c92ae6..e0c473d 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1117,7 +1117,9 @@
      */
     void onDisplayChanged(DisplayContent dc) {
         if (mDisplayContent != null && mDisplayContent != dc) {
-            mTransitionController.collect(this);
+            if (asWindowState() == null) {
+                mTransitionController.collect(this);
+            }
             // Cancel any change transition queued-up for this container on the old display when
             // this container is moved from the old display.
             mDisplayContent.mClosingChangingContainers.remove(this);
@@ -2119,7 +2121,8 @@
     }
 
     /**
-     * For all tasks at or below this container call the callback.
+     * Calls the given {@param callback} for all tasks in depth-first top-down z-order at or below
+     * this container.
      *
      * @param callback Calls the {@link ToBooleanFunction#apply} method for each task found and
      *                 stops the search if {@link ToBooleanFunction#apply} returns {@code true}.
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 166d74b..dac8f69 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -813,6 +813,10 @@
             mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
             if (deferResume) {
                 mService.mTaskSupervisor.endDeferResume();
+                // Transient launching the Recents via HIERARCHY_OP_TYPE_PENDING_INTENT directly
+                // resume the Recents activity with no TRANSACT_EFFECTS_LIFECYCLE. Explicitly
+                // checks if the top resumed activity should be updated after defer-resume ended.
+                mService.mTaskSupervisor.updateTopResumedActivityIfNeeded("endWCT");
             }
             mService.continueWindowLayout();
         }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 585e617..079170a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -157,6 +157,7 @@
 import static com.android.server.wm.WindowStateProto.ANIMATOR;
 import static com.android.server.wm.WindowStateProto.ATTRIBUTES;
 import static com.android.server.wm.WindowStateProto.DESTROYING;
+import static com.android.server.wm.WindowStateProto.DIM_BOUNDS;
 import static com.android.server.wm.WindowStateProto.DISPLAY_ID;
 import static com.android.server.wm.WindowStateProto.FORCE_SEAMLESS_ROTATION;
 import static com.android.server.wm.WindowStateProto.GIVEN_CONTENT_INSETS;
@@ -181,7 +182,6 @@
 import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
 import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
 import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
 import static com.android.window.flags.Flags.secureWindowState;
 import static com.android.window.flags.Flags.surfaceTrustedOverlay;
 
@@ -258,6 +258,7 @@
 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
 import com.android.server.wm.RefreshRatePolicy.FrameRateVote;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
+import com.android.server.wm.utils.RegionUtils;
 import com.android.window.flags.Flags;
 
 import dalvik.annotation.optimization.NeverCompile;
@@ -2327,7 +2328,8 @@
             if (mStartingData != null && mStartingData.mAssociatedTask == null
                     && mTempConfiguration.windowConfiguration.getRotation()
                             == selfConfiguration.windowConfiguration.getRotation()
-                    && !mTempConfiguration.windowConfiguration.getBounds().equals(getBounds())) {
+                    && !RegionUtils.sizeEquals(
+                            mTempConfiguration.windowConfiguration.getBounds(), getBounds())) {
                 mStartingData.mResizedFromTransfer = true;
                 // Lock the starting window to task, so it won't resize from transfer anymore.
                 mActivityRecord.associateStartingWindowWithTaskIfNeeded();
@@ -4116,6 +4118,12 @@
                 mMergedLocalInsetsSources.valueAt(i).dumpDebug(proto, MERGED_LOCAL_INSETS_SOURCES);
             }
         }
+        if (getDimController() != null) {
+            final Rect dimBounds = getDimController().getDimBounds();
+            if (dimBounds != null) {
+                dimBounds.dumpDebug(proto, DIM_BOUNDS);
+            }
+        }
         proto.end(token);
     }
 
@@ -5295,12 +5303,9 @@
         if (voteChanged) {
             getPendingTransaction()
                     .setFrameRate(mSurfaceControl, mFrameRateVote.mRefreshRate,
-                        mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS);
-            if (explicitRefreshRateHints()) {
-                getPendingTransaction().setFrameRateSelectionStrategy(mSurfaceControl,
-                        mFrameRateVote.mSelectionStrategy);
-            }
-
+                            mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS)
+                    .setFrameRateSelectionStrategy(mSurfaceControl,
+                            mFrameRateVote.mSelectionStrategy);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 5bde8b5..44e237a 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -614,6 +614,12 @@
         final int rotation = getRelativeDisplayRotation();
         if (rotation == Surface.ROTATION_0) return mFixedRotationTransformLeash;
         if (mFixedRotationTransformLeash != null) return mFixedRotationTransformLeash;
+        if (ActivityTaskManagerService.isPip2ExperimentEnabled() && asActivityRecord() != null
+                && mTransitionController.getWindowingModeAtStart(
+                asActivityRecord()) == WINDOWING_MODE_PINNED) {
+            // PiP handles fixed rotation animation in Shell, so do not create the rotation leash.
+            return null;
+        }
 
         final SurfaceControl leash = makeSurface().setContainerLayer()
                 .setParent(getParentSurfaceControl())
diff --git a/services/core/java/com/android/server/wm/utils/RegionUtils.java b/services/core/java/com/android/server/wm/utils/RegionUtils.java
index ff23145..6c5da17 100644
--- a/services/core/java/com/android/server/wm/utils/RegionUtils.java
+++ b/services/core/java/com/android/server/wm/utils/RegionUtils.java
@@ -92,4 +92,9 @@
         }
         return area;
     }
+
+    /** Returns whether the sizes between the two Rects are equal. */
+    public static boolean sizeEquals(Rect a, Rect b) {
+        return a.width() == b.width() && a.height() == b.height();
+    }
 }
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index ea0b02c..4dc3ca5 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -55,7 +55,6 @@
         "com_android_server_powerstats_PowerStatsService.cpp",
         "com_android_server_power_stats_CpuPowerStatsCollector.cpp",
         "com_android_server_hint_HintManagerService.cpp",
-        "com_android_server_SerialService.cpp",
         "com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp",
         "com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp",
         "com_android_server_stats_pull_StatsPullAtomService.cpp",
diff --git a/services/core/jni/com_android_server_SerialService.cpp b/services/core/jni/com_android_server_SerialService.cpp
deleted file mode 100644
index 6600c98..0000000
--- a/services/core/jni/com_android_server_SerialService.cpp
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "SerialServiceJNI"
-#include "utils/Log.h"
-
-#include "jni.h"
-#include <nativehelper/JNIPlatformHelp.h>
-#include "android_runtime/AndroidRuntime.h"
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-
-namespace android
-{
-
-static struct parcel_file_descriptor_offsets_t
-{
-    jclass mClass;
-    jmethodID mConstructor;
-} gParcelFileDescriptorOffsets;
-
-static jobject android_server_SerialService_open(JNIEnv *env, jobject /* thiz */, jstring path)
-{
-    const char *pathStr = env->GetStringUTFChars(path, NULL);
-
-    int fd = open(pathStr, O_RDWR | O_NOCTTY);
-    if (fd < 0) {
-        ALOGE("could not open %s", pathStr);
-        env->ReleaseStringUTFChars(path, pathStr);
-        return NULL;
-    }
-    env->ReleaseStringUTFChars(path, pathStr);
-
-    jobject fileDescriptor = jniCreateFileDescriptor(env, fd);
-    if (fileDescriptor == NULL) {
-        close(fd);
-        return NULL;
-    }
-    return env->NewObject(gParcelFileDescriptorOffsets.mClass,
-        gParcelFileDescriptorOffsets.mConstructor, fileDescriptor);
-}
-
-
-static const JNINativeMethod method_table[] = {
-    { "native_open",                "(Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;",
-                                    (void*)android_server_SerialService_open },
-};
-
-int register_android_server_SerialService(JNIEnv *env)
-{
-    jclass clazz = env->FindClass("com/android/server/SerialService");
-    if (clazz == NULL) {
-        ALOGE("Can't find com/android/server/SerialService");
-        return -1;
-    }
-
-    clazz = env->FindClass("android/os/ParcelFileDescriptor");
-    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor");
-    gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
-    gParcelFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "(Ljava/io/FileDescriptor;)V");
-    LOG_FATAL_IF(gParcelFileDescriptorOffsets.mConstructor == NULL,
-                 "Unable to find constructor for android.os.ParcelFileDescriptor");
-
-    return jniRegisterNativeMethods(env, "com/android/server/SerialService",
-            method_table, NELEM(method_table));
-}
-
-};
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 416e60f..e4ac826 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -110,6 +110,7 @@
     jmethodID notifyInputDevicesChanged;
     jmethodID notifyTouchpadHardwareState;
     jmethodID notifyTouchpadGestureInfo;
+    jmethodID notifyTouchpadThreeFingerTap;
     jmethodID notifySwitch;
     jmethodID notifyInputChannelBroken;
     jmethodID notifyNoFocusedWindowAnr;
@@ -345,6 +346,7 @@
     void setTouchpadTapDraggingEnabled(bool enabled);
     void setShouldNotifyTouchpadHardwareState(bool enabled);
     void setTouchpadRightClickZoneEnabled(bool enabled);
+    void setTouchpadThreeFingerTapShortcutEnabled(bool enabled);
     void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
     void setShowTouches(bool enabled);
     void setNonInteractiveDisplays(const std::set<ui::LogicalDisplayId>& displayIds);
@@ -370,6 +372,7 @@
     void notifyTouchpadHardwareState(const SelfContainedHardwareState& schs,
                                      int32_t deviceId) override;
     void notifyTouchpadGestureInfo(enum GestureType type, int32_t deviceId) override;
+    void notifyTouchpadThreeFingerTap() override;
     std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
             const InputDeviceIdentifier& identifier,
             const std::optional<KeyboardLayoutInfo> keyboardLayoutInfo) override;
@@ -510,6 +513,10 @@
         // into context (a.k.a. "right") clicks.
         bool touchpadRightClickZoneEnabled{false};
 
+        // True to use three-finger tap as a customizable shortcut; false to use it as a
+        // middle-click.
+        bool touchpadThreeFingerTapShortcutEnabled{false};
+
         // True if a pointer icon should be shown for stylus pointers.
         bool stylusPointerIconEnabled{false};
 
@@ -780,6 +787,8 @@
         outConfig->touchpadTapDraggingEnabled = mLocked.touchpadTapDraggingEnabled;
         outConfig->shouldNotifyTouchpadHardwareState = mLocked.shouldNotifyTouchpadHardwareState;
         outConfig->touchpadRightClickZoneEnabled = mLocked.touchpadRightClickZoneEnabled;
+        outConfig->touchpadThreeFingerTapShortcutEnabled =
+                mLocked.touchpadThreeFingerTapShortcutEnabled;
 
         outConfig->disabledDevices = mLocked.disabledInputDevices;
 
@@ -1034,6 +1043,13 @@
     checkAndClearExceptionFromCallback(env, "notifyTouchpadGestureInfo");
 }
 
+void NativeInputManager::notifyTouchpadThreeFingerTap() {
+    ATRACE_CALL();
+    JNIEnv* env = jniEnv();
+    env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyTouchpadThreeFingerTap);
+    checkAndClearExceptionFromCallback(env, "notifyTouchpadThreeFingerTap");
+}
+
 std::shared_ptr<KeyCharacterMap> NativeInputManager::getKeyboardLayoutOverlay(
         const InputDeviceIdentifier& identifier,
         const std::optional<KeyboardLayoutInfo> keyboardLayoutInfo) {
@@ -1495,6 +1511,22 @@
             InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
 }
 
+void NativeInputManager::setTouchpadThreeFingerTapShortcutEnabled(bool enabled) {
+    { // acquire lock
+        std::scoped_lock _l(mLock);
+
+        if (mLocked.touchpadThreeFingerTapShortcutEnabled == enabled) {
+            return;
+        }
+
+        ALOGI("Setting touchpad three finger tap shortcut to %s.", toString(enabled));
+        mLocked.touchpadThreeFingerTapShortcutEnabled = enabled;
+    } // release lock
+
+    mInputManager->getReader().requestRefreshConfiguration(
+            InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
+}
+
 void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) {
     bool refresh = false;
 
@@ -2437,6 +2469,11 @@
     im->setTouchpadRightClickZoneEnabled(enabled);
 }
 
+static void nativeSetTouchpadThreeFingerTapShortcutEnabled(JNIEnv* env, jobject nativeImplObj,
+                                                           jboolean enabled) {
+    getNativeInputManager(env, nativeImplObj)->setTouchpadThreeFingerTapShortcutEnabled(enabled);
+}
+
 static void nativeSetShowTouches(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
@@ -3119,6 +3156,8 @@
         {"setShouldNotifyTouchpadHardwareState", "(Z)V",
          (void*)nativeSetShouldNotifyTouchpadHardwareState},
         {"setTouchpadRightClickZoneEnabled", "(Z)V", (void*)nativeSetTouchpadRightClickZoneEnabled},
+        {"setTouchpadThreeFingerTapShortcutEnabled", "(Z)V",
+         (void*)nativeSetTouchpadThreeFingerTapShortcutEnabled},
         {"setShowTouches", "(Z)V", (void*)nativeSetShowTouches},
         {"setNonInteractiveDisplays", "([I)V", (void*)nativeSetNonInteractiveDisplays},
         {"reloadCalibration", "()V", (void*)nativeReloadCalibration},
@@ -3229,6 +3268,8 @@
     GET_METHOD_ID(gServiceClassInfo.notifyTouchpadGestureInfo, clazz, "notifyTouchpadGestureInfo",
                   "(II)V")
 
+    GET_METHOD_ID(gServiceClassInfo.notifyTouchpadThreeFingerTap, clazz,
+                  "notifyTouchpadThreeFingerTap", "()V")
     GET_METHOD_ID(gServiceClassInfo.notifySwitch, clazz,
             "notifySwitch", "(JII)V");
 
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index 59dbf28..0ecc0a8 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -74,12 +74,10 @@
     jfieldID duration;
 } sRampClassInfo;
 static struct {
-    jfieldID startAmplitude;
-    jfieldID endAmplitude;
-    jfieldID startFrequencyHz;
-    jfieldID endFrequencyHz;
-    jfieldID duration;
-} sPwleClassInfo;
+    jfieldID amplitude;
+    jfieldID frequencyHz;
+    jfieldID timeMillis;
+} sPwlePointClassInfo;
 
 static_assert(static_cast<uint8_t>(V1_0::EffectStrength::LIGHT) ==
               static_cast<uint8_t>(Aidl::EffectStrength::LIGHT));
@@ -191,10 +189,11 @@
 
 static Aidl::PwleV2Primitive pwleV2PrimitiveFromJavaPrimitive(JNIEnv* env, jobject pwleObj) {
     Aidl::PwleV2Primitive pwle;
-    pwle.amplitude = static_cast<float>(env->GetFloatField(pwleObj, sPwleClassInfo.endAmplitude));
+    pwle.amplitude = static_cast<float>(env->GetFloatField(pwleObj, sPwlePointClassInfo.amplitude));
     pwle.frequencyHz =
-            static_cast<float>(env->GetFloatField(pwleObj, sPwleClassInfo.endFrequencyHz));
-    pwle.timeMillis = static_cast<int32_t>(env->GetIntField(pwleObj, sPwleClassInfo.duration));
+            static_cast<float>(env->GetFloatField(pwleObj, sPwlePointClassInfo.frequencyHz));
+    pwle.timeMillis =
+            static_cast<int32_t>(env->GetIntField(pwleObj, sPwlePointClassInfo.timeMillis));
     return pwle;
 }
 
@@ -620,7 +619,7 @@
          (void*)vibratorPerformComposedEffect},
         {"performPwleEffect", "(J[Landroid/os/vibrator/RampSegment;IJ)J",
          (void*)vibratorPerformPwleEffect},
-        {"performPwleV2Effect", "(J[Landroid/os/vibrator/PwleSegment;J)J",
+        {"performPwleV2Effect", "(J[Landroid/os/vibrator/PwlePoint;J)J",
          (void*)vibratorPerformPwleV2Effect},
         {"setExternalControl", "(JZ)V", (void*)vibratorSetExternalControl},
         {"alwaysOnEnable", "(JJJJ)V", (void*)vibratorAlwaysOnEnable},
@@ -647,12 +646,10 @@
     sRampClassInfo.endFrequencyHz = GetFieldIDOrDie(env, rampClass, "mEndFrequencyHz", "F");
     sRampClassInfo.duration = GetFieldIDOrDie(env, rampClass, "mDuration", "I");
 
-    jclass pwleClass = FindClassOrDie(env, "android/os/vibrator/PwleSegment");
-    sPwleClassInfo.startAmplitude = GetFieldIDOrDie(env, pwleClass, "mStartAmplitude", "F");
-    sPwleClassInfo.endAmplitude = GetFieldIDOrDie(env, pwleClass, "mEndAmplitude", "F");
-    sPwleClassInfo.startFrequencyHz = GetFieldIDOrDie(env, pwleClass, "mStartFrequencyHz", "F");
-    sPwleClassInfo.endFrequencyHz = GetFieldIDOrDie(env, pwleClass, "mEndFrequencyHz", "F");
-    sPwleClassInfo.duration = GetFieldIDOrDie(env, pwleClass, "mDuration", "I");
+    jclass pwlePointClass = FindClassOrDie(env, "android/os/vibrator/PwlePoint");
+    sPwlePointClassInfo.amplitude = GetFieldIDOrDie(env, pwlePointClass, "mAmplitude", "F");
+    sPwlePointClassInfo.frequencyHz = GetFieldIDOrDie(env, pwlePointClass, "mFrequencyHz", "F");
+    sPwlePointClassInfo.timeMillis = GetFieldIDOrDie(env, pwlePointClass, "mTimeMillis", "I");
 
     jclass frequencyProfileLegacyClass =
             FindClassOrDie(env, "android/os/VibratorInfo$FrequencyProfileLegacy");
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 3c55d18..59d7365 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -33,7 +33,6 @@
 int register_android_server_power_stats_CpuPowerStatsCollector(JNIEnv* env);
 int register_android_server_HintManagerService(JNIEnv* env);
 int register_android_server_storage_AppFuse(JNIEnv* env);
-int register_android_server_SerialService(JNIEnv* env);
 int register_android_server_SystemServer(JNIEnv* env);
 int register_android_server_UsbAlsaJackDetector(JNIEnv* env);
 int register_android_server_UsbAlsaMidiDevice(JNIEnv* env);
@@ -94,7 +93,6 @@
     register_android_server_PowerStatsService(env);
     register_android_server_power_stats_CpuPowerStatsCollector(env);
     register_android_server_HintManagerService(env);
-    register_android_server_SerialService(env);
     register_android_server_InputManager(env);
     register_android_server_LightsService(env);
     register_android_server_UsbDeviceManager(vm, env);
diff --git a/services/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/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index a58da81..9841058 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -90,6 +90,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
 
 /**
  * Class responsible for setting, resolving, and enforcing policies set by multiple management
@@ -1030,11 +1031,11 @@
         }
     }
 
-    private <V> void enforcePolicy(PolicyDefinition<V> policyDefinition,
+    private <V> CompletableFuture<Boolean> enforcePolicy(PolicyDefinition<V> policyDefinition,
             @Nullable PolicyValue<V> policyValue, int userId) {
         // null policyValue means remove any enforced policies, ensure callbacks handle this
         // properly
-        policyDefinition.enforcePolicy(
+        return policyDefinition.enforcePolicy(
                 policyValue == null ? null : policyValue.getValue(), mContext, userId);
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index aca6f72..c653038 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -255,7 +255,6 @@
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING;
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
-import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER;
 import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
 import static android.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED;
 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
@@ -462,7 +461,6 @@
 import android.provider.CalendarContract;
 import android.provider.ContactsContract.QuickContact;
 import android.provider.ContactsInternal;
-import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.provider.Telephony;
@@ -908,10 +906,6 @@
                     + "management app's authentication policy";
     private static final String NOT_SYSTEM_CALLER_MSG = "Only the system can %s";
 
-    private static final String PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG =
-            "enable_permission_based_access";
-    private static final boolean DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG = false;
-
     private static final int RETRY_COPY_ACCOUNT_ATTEMPTS = 3;
 
     /**
@@ -3486,7 +3480,7 @@
     @GuardedBy("getLockObject()")
     private boolean maybeMigrateSuspendedPackagesLocked(String backupId) {
         Slog.i(LOG_TAG, "Migrating suspended packages to policy engine");
-        if (!Flags.unmanagedModeMigration()) {
+        if (!Flags.suspendPackagesCoexistence()) {
             return false;
         }
         if (mOwners.isSuspendedPackagesMigrated()) {
@@ -3557,6 +3551,46 @@
         return true;
     }
 
+
+
+    @GuardedBy("getLockObject()")
+    private boolean maybeMigrateMemoryTaggingLocked(String backupId) {
+        if (!Flags.setMtePolicyCoexistence()) {
+            Slog.i(LOG_TAG, "Memory Tagging not migrated because coexistence "
+                    + "support is disabled.");
+            return false;
+        }
+        if (mOwners.isMemoryTaggingMigrated()) {
+            // TODO: Remove log after Flags.setMtePolicyCoexistence full rollout.
+            Slog.v(LOG_TAG, "Memory Tagging was previously migrated to policy engine.");
+            return false;
+        }
+
+        Slog.i(LOG_TAG, "Migrating Memory Tagging to policy engine");
+
+        // Create backup if none exists
+        mDevicePolicyEngine.createBackup(backupId);
+        try {
+            iterateThroughDpcAdminsLocked((admin, enforcingAdmin) -> {
+                if (admin.mtePolicy != 0) {
+                    Slog.i(LOG_TAG, "Setting Memory Tagging policy");
+                    mDevicePolicyEngine.setGlobalPolicy(
+                            PolicyDefinition.MEMORY_TAGGING,
+                            enforcingAdmin,
+                            new IntegerPolicyValue(admin.mtePolicy),
+                            true /* No need to re-set system properties */);
+                }
+            });
+        } catch (Exception e) {
+            Slog.wtf(LOG_TAG,
+                    "Failed to migrate Memory Tagging to policy engine", e);
+        }
+
+        Slog.i(LOG_TAG, "Marking Memory Tagging migration complete");
+        mOwners.markMemoryTaggingMigrated();
+        return true;
+    }
+
     /** Register callbacks for statsd pulled atoms. */
     private void registerStatsCallbacks() {
         final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
@@ -4646,22 +4680,13 @@
     @GuardedBy("getLockObject()")
     private List<ActiveAdmin> getActiveAdminsForLockscreenPoliciesLocked(int userHandle) {
         if (isSeparateProfileChallengeEnabled(userHandle)) {
-
-            if (isPermissionCheckFlagEnabled()) {
-                return getActiveAdminsForAffectedUserInclPermissionBasedAdminLocked(userHandle);
-            }
             // If this user has a separate challenge, only return its restrictions.
             return getUserDataUnchecked(userHandle).mAdminList;
         }
         // If isSeparateProfileChallengeEnabled is false and userHandle points to a managed profile
         // we need to query the parent user who owns the credential.
-        if (isPermissionCheckFlagEnabled()) {
-            return getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(getProfileParentId(userHandle),
-                    (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id));
-        } else {
-            return getActiveAdminsForUserAndItsManagedProfilesLocked(getProfileParentId(userHandle),
-                    (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id));
-        }
+        return getActiveAdminsForUserAndItsManagedProfilesLocked(getProfileParentId(userHandle),
+                (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id));
 
     }
 
@@ -4684,33 +4709,6 @@
                 (user) -> mLockPatternUtils.isProfileWithUnifiedChallenge(user.id));
 
     }
-    /**
-     * Get the list of active admins for an affected user:
-     * <ul>
-     * <li>The active admins associated with the userHandle itself</li>
-     * <li>The parent active admins for each managed profile associated with the userHandle</li>
-     * <li>The permission based admin associated with the userHandle itself</li>
-     * </ul>
-     *
-     * @param userHandle the affected user for whom to get the active admins
-     * @return the list of active admins for the affected user
-     */
-    @GuardedBy("getLockObject()")
-    private List<ActiveAdmin> getActiveAdminsForAffectedUserInclPermissionBasedAdminLocked(
-            int userHandle) {
-        List<ActiveAdmin> list;
-
-        if (isManagedProfile(userHandle)) {
-            list = getUserDataUnchecked(userHandle).mAdminList;
-        }
-        list = getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(userHandle,
-                /* shouldIncludeProfileAdmins */ (user) -> false);
-
-        if (getUserData(userHandle).mPermissionBasedAdmin != null) {
-            list.add(getUserData(userHandle).mPermissionBasedAdmin);
-        }
-        return list;
-    }
 
     /**
      * Returns the list of admins on the given user, as well as parent admins for each managed
@@ -4763,44 +4761,6 @@
         return mDevicePolicyEngine.getResolvedPolicyAcrossUsers(policyDefinition, users);
     }
 
-    /**
-     * Returns the list of admins on the given user, as well as parent admins for each managed
-     * profile associated with the given user. Optionally also include the admin of each managed
-     * profile.
-     * <p> Should not be called on a profile user.
-     */
-    @GuardedBy("getLockObject()")
-    private List<ActiveAdmin> getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(int userHandle,
-            Predicate<UserInfo> shouldIncludeProfileAdmins) {
-        ArrayList<ActiveAdmin> admins = new ArrayList<>();
-        mInjector.binderWithCleanCallingIdentity(() -> {
-            for (UserInfo userInfo : mUserManager.getProfiles(userHandle)) {
-                DevicePolicyData policy = getUserDataUnchecked(userInfo.id);
-                if (userInfo.id == userHandle) {
-                    admins.addAll(policy.mAdminList);
-                    if (policy.mPermissionBasedAdmin != null) {
-                        admins.add(policy.mPermissionBasedAdmin);
-                    }
-                } else if (userInfo.isManagedProfile()) {
-                    for (int i = 0; i < policy.mAdminList.size(); i++) {
-                        ActiveAdmin admin = policy.mAdminList.get(i);
-                        if (admin.hasParentActiveAdmin()) {
-                            admins.add(admin.getParentActiveAdmin());
-                        }
-                        if (shouldIncludeProfileAdmins.test(userInfo)) {
-                            admins.add(admin);
-                        }
-                    }
-                    if (policy.mPermissionBasedAdmin != null
-                            && shouldIncludeProfileAdmins.test(userInfo)) {
-                        admins.add(policy.mPermissionBasedAdmin);
-                    }
-                }
-            }
-        });
-        return admins;
-    }
-
     private boolean isSeparateProfileChallengeEnabled(int userHandle) {
         return mInjector.binderWithCleanCallingIdentity(() ->
                 mLockPatternUtils.isSeparateProfileChallengeEnabled(userHandle));
@@ -4893,25 +4853,15 @@
         if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return;
         }
-        if (!isPermissionCheckFlagEnabled()) {
-            Objects.requireNonNull(who, "ComponentName is null");
-        }
+        Objects.requireNonNull(who, "ComponentName is null");
 
         Preconditions.checkArgumentNonnegative(timeout, "Timeout must be >= 0 ms");
         int userHandle = mInjector.userHandleGetCallingUserId();
         int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
         synchronized (getLockObject()) {
             ActiveAdmin ap;
-            if (isPermissionCheckFlagEnabled()) {
-                CallerIdentity caller = getCallerIdentity(who, callerPackageName);
-                ap = enforcePermissionAndGetEnforcingAdmin(
-                        who, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
-                        caller.getPackageName(), affectedUserId)
-                        .getActiveAdmin();
-            } else {
-                ap = getActiveAdminForCallerLocked(
-                        who, DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD, parent);
-            }
+            ap = getActiveAdminForCallerLocked(
+                    who, DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD, parent);
             // Calling this API automatically bumps the expiration date
             final long expiration = timeout > 0L ? (timeout + System.currentTimeMillis()) : 0L;
             ap.passwordExpirationDate = expiration;
@@ -4972,28 +4922,14 @@
     @Override
     public boolean addCrossProfileWidgetProvider(ComponentName admin, String callerPackageName,
             String packageName) {
-        CallerIdentity caller;
+        CallerIdentity caller = getCallerIdentity(admin);
 
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(admin, callerPackageName);
-        } else {
-            caller = getCallerIdentity(admin);
-        }
+        Objects.requireNonNull(admin, "ComponentName is null");
+        Preconditions.checkCallAuthorization(isProfileOwner(caller));
+
         ActiveAdmin activeAdmin;
-
-        if (isPermissionCheckFlagEnabled()) {
-            EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
-                    admin,
-                    MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
-                    caller.getPackageName(),
-                    caller.getUserId());
-            activeAdmin = enforcingAdmin.getActiveAdmin();
-        } else {
-            Objects.requireNonNull(admin, "ComponentName is null");
-            Preconditions.checkCallAuthorization(isProfileOwner(caller));
-            synchronized (getLockObject()) {
-                activeAdmin = getProfileOwnerLocked(caller.getUserId());
-            }
+        synchronized (getLockObject()) {
+            activeAdmin = getProfileOwnerLocked(caller.getUserId());
         }
         List<String> changedProviders = null;
 
@@ -5026,28 +4962,14 @@
     @Override
     public boolean removeCrossProfileWidgetProvider(ComponentName admin, String callerPackageName,
             String packageName) {
-        CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(admin, callerPackageName);
-        } else {
-            caller = getCallerIdentity(admin);
-        }
+        CallerIdentity caller = getCallerIdentity(admin);
+
+        Objects.requireNonNull(admin, "ComponentName is null");
+        Preconditions.checkCallAuthorization(isProfileOwner(caller));
 
         ActiveAdmin activeAdmin;
-
-        if (isPermissionCheckFlagEnabled()) {
-            EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
-                    admin,
-                    MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
-                    caller.getPackageName(),
-                    caller.getUserId());
-            activeAdmin = enforcingAdmin.getActiveAdmin();
-        } else {
-            Objects.requireNonNull(admin, "ComponentName is null");
-            Preconditions.checkCallAuthorization(isProfileOwner(caller));
-            synchronized (getLockObject()) {
-                activeAdmin = getProfileOwnerLocked(caller.getUserId());
-            }
+        synchronized (getLockObject()) {
+            activeAdmin = getProfileOwnerLocked(caller.getUserId());
         }
         List<String> changedProviders = null;
 
@@ -5080,27 +5002,14 @@
     @Override
     public List<String> getCrossProfileWidgetProviders(ComponentName admin,
             String callerPackageName) {
-        CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(admin, callerPackageName);
-        } else {
-            caller = getCallerIdentity(admin);
-        }
-        ActiveAdmin activeAdmin;
+        CallerIdentity caller = getCallerIdentity(admin);
 
-        if (isPermissionCheckFlagEnabled()) {
-            EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin(
-                    admin,
-                    MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
-                    caller.getPackageName(),
-                    caller.getUserId());
-            activeAdmin = enforcingAdmin.getActiveAdmin();
-        } else {
-            Objects.requireNonNull(admin, "ComponentName is null");
-            Preconditions.checkCallAuthorization(isProfileOwner(caller));
-            synchronized (getLockObject()) {
-                activeAdmin = getProfileOwnerLocked(caller.getUserId());
-            }
+        Objects.requireNonNull(admin, "ComponentName is null");
+        Preconditions.checkCallAuthorization(isProfileOwner(caller));
+
+        ActiveAdmin activeAdmin;
+        synchronized (getLockObject()) {
+            activeAdmin = getProfileOwnerLocked(caller.getUserId());
         }
 
         synchronized (getLockObject()) {
@@ -5449,24 +5358,17 @@
         enforceUserUnlocked(userHandle, parent);
 
         synchronized (getLockObject()) {
-            if (isPermissionCheckFlagEnabled()) {
-                int affectedUser = parent ? getProfileParentId(userHandle) : userHandle;
-                enforcePermission(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
-                        callerPackageName, affectedUser);
-            } else {
-                // This API can only be called by an active device admin,
-                // so try to retrieve it to check that the caller is one.
-                getActiveAdminForCallerLocked(
-                        null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
-            }
+            // This API can only be called by an active device admin,
+            // so try to retrieve it to check that the caller is one.
+            getActiveAdminForCallerLocked(
+                    null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
 
             int credentialOwner = getCredentialOwner(userHandle, parent);
             DevicePolicyData policy = getUserDataUnchecked(credentialOwner);
             PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(credentialOwner);
             final int userToCheck = getProfileParentUserIfRequested(userHandle, parent);
-            boolean activePasswordSufficientForUserLocked = isActivePasswordSufficientForUserLocked(
+            return isActivePasswordSufficientForUserLocked(
                     policy.mPasswordValidAtLastCheckpoint, metrics, userToCheck);
-            return activePasswordSufficientForUserLocked;
         }
     }
 
@@ -5622,21 +5524,11 @@
                     isDefaultDeviceOwner(caller) || isProfileOwner(caller) || isSystemUid(caller),
                     "Only profile owner, device owner and system may call this method on parent.");
         } else {
-            if (isPermissionCheckFlagEnabled()) {
-                Preconditions.checkCallAuthorization(
-                        hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY)
-                                || hasCallingOrSelfPermission(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS)
-                                || isDefaultDeviceOwner(caller) || isProfileOwner(caller),
-                        "Must have " + REQUEST_PASSWORD_COMPLEXITY + " or " +
-                                MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS
-                                + " permissions, or be a profile owner or device owner.");
-            } else {
-                Preconditions.checkCallAuthorization(
-                        hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY)
-                                || isDefaultDeviceOwner(caller) || isProfileOwner(caller),
-                        "Must have " + REQUEST_PASSWORD_COMPLEXITY
-                                + " permission, or be a profile owner or device owner.");
-            }
+            Preconditions.checkCallAuthorization(
+                    hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY)
+                            || isDefaultDeviceOwner(caller) || isProfileOwner(caller),
+                    "Must have " + REQUEST_PASSWORD_COMPLEXITY
+                            + " permission, or be a profile owner or device owner.");
         }
 
         synchronized (getLockObject()) {
@@ -5728,26 +5620,15 @@
     private void setRequiredPasswordComplexityPreCoexistence(
             String callerPackageName, int passwordComplexity, boolean calledOnParent) {
         CallerIdentity caller = getCallerIdentity(callerPackageName);
-        if (!isPermissionCheckFlagEnabled()) {
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller) || isProfileOwner(caller));
-            Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
-        }
+
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
+        Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
 
         synchronized (getLockObject()) {
             ActiveAdmin admin;
-            if (isPermissionCheckFlagEnabled()) {
-                // TODO: Make sure this returns the parent of the fake admin
-                // TODO: Deal with null componentname
-                int affectedUser = calledOnParent
-                        ? getProfileParentId(caller.getUserId()) : caller.getUserId();
-                admin = enforcePermissionAndGetEnforcingAdmin(
-                        null, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
-                        caller.getPackageName(), affectedUser).getActiveAdmin();
-            } else {
-                admin = getParentOfAdminIfRequired(
-                        getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), calledOnParent);
-            }
+            admin = getParentOfAdminIfRequired(
+                    getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), calledOnParent);
 
             if (admin.mPasswordComplexity != passwordComplexity) {
                 // We require the caller to explicitly clear any password quality requirements set
@@ -5907,14 +5788,8 @@
             if (!isSystemUid(caller)) {
                 // This API can be called by an active device admin or by keyguard code.
                 if (!hasCallingPermission(permission.ACCESS_KEYGUARD_SECURE_STORAGE)) {
-                    if (isPermissionCheckFlagEnabled()) {
-                        int affectedUser = parent ? getProfileParentId(userHandle) : userHandle;
-                        enforcePermission(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
-                                callerPackageName, affectedUser);
-                    } else {
-                        getActiveAdminForCallerLocked(
-                                null, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent);
-                    }
+                    getActiveAdminForCallerLocked(
+                            null, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent);
                 }
             }
 
@@ -5931,31 +5806,18 @@
             return;
         }
 
-        if (!isPermissionCheckFlagEnabled()) {
-            Objects.requireNonNull(who, "ComponentName is null");
-        }
-
+        Objects.requireNonNull(who, "ComponentName is null");
 
         int userId = mInjector.userHandleGetCallingUserId();
         int affectedUserId = parent ? getProfileParentId(userId) : userId;
 
         synchronized (getLockObject()) {
-            ActiveAdmin ap;
-            if (isPermissionCheckFlagEnabled()) {
-                CallerIdentity caller = getCallerIdentity(who, callerPackageName);
-                ap = enforcePermissionAndGetEnforcingAdmin(
-                        who,
-                        /*permission=*/ MANAGE_DEVICE_POLICY_WIPE_DATA,
-                        /* adminPolicy=*/ DeviceAdminInfo.USES_POLICY_WIPE_DATA,
-                        caller.getPackageName(), affectedUserId).getActiveAdmin();
-            } else {
-                // This API can only be called by an active device admin,
-                // so try to retrieve it to check that the caller is one.
-                getActiveAdminForCallerLocked(
-                        who, DeviceAdminInfo.USES_POLICY_WIPE_DATA, parent);
-                ap = getActiveAdminForCallerLocked(
-                        who, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent);
-            }
+            // This API can only be called by an active device admin,
+            // so try to retrieve it to check that the caller is one.
+            getActiveAdminForCallerLocked(
+                    who, DeviceAdminInfo.USES_POLICY_WIPE_DATA, parent);
+            ActiveAdmin ap = getActiveAdminForCallerLocked(
+                    who, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent);
 
             if (ap.maximumFailedPasswordsForWipe != num) {
                 ap.maximumFailedPasswordsForWipe = num;
@@ -6210,25 +6072,14 @@
         if (!mHasFeature) {
             return;
         }
-        if (!isPermissionCheckFlagEnabled()) {
-            Objects.requireNonNull(who, "ComponentName is null");
-        }
+
+        Objects.requireNonNull(who, "ComponentName is null");
+
         int userHandle = mInjector.userHandleGetCallingUserId();
         int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
         synchronized (getLockObject()) {
-            ActiveAdmin ap;
-            if (isPermissionCheckFlagEnabled()) {
-                CallerIdentity caller = getCallerIdentity(who, callerPackageName);
-                ap = enforcePermissionAndGetEnforcingAdmin(
-                        who,
-                        /*permission=*/ MANAGE_DEVICE_POLICY_LOCK,
-                        /*AdminPolicy=*/DeviceAdminInfo.USES_POLICY_FORCE_LOCK,
-                        caller.getPackageName(),
-                        affectedUserId).getActiveAdmin();
-            } else {
-                ap = getActiveAdminForCallerLocked(
-                        who, DeviceAdminInfo.USES_POLICY_FORCE_LOCK, parent);
-            }
+            ActiveAdmin ap = getActiveAdminForCallerLocked(
+                    who, DeviceAdminInfo.USES_POLICY_FORCE_LOCK, parent);
 
             if (ap.maximumTimeToUnlock != timeMs) {
                 ap.maximumTimeToUnlock = timeMs;
@@ -6334,16 +6185,13 @@
         if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
             return;
         }
+
         Preconditions.checkArgument(timeoutMs >= 0, "Timeout must not be a negative number.");
-        CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(who, callerPackageName);
-        } else {
-            caller = getCallerIdentity(who);
-            Objects.requireNonNull(who, "ComponentName is null");
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller) || isProfileOwner(caller));
-        }
+        CallerIdentity caller = getCallerIdentity(who);
+        Objects.requireNonNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
+
         // timeoutMs with value 0 means that the admin doesn't participate
         // timeoutMs is clamped to the interval in case the internal constants change in the future
         final long minimumStrongAuthTimeout = getMinimumStrongAuthTimeoutMs();
@@ -6357,17 +6205,8 @@
         final int userHandle = caller.getUserId();
         boolean changed = false;
         synchronized (getLockObject()) {
-            ActiveAdmin ap;
-            if (isPermissionCheckFlagEnabled()) {
-                int affectedUser = parent
-                        ? getProfileParentId(caller.getUserId()) : caller.getUserId();
-                ap = enforcePermissionAndGetEnforcingAdmin(
-                        who, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
-                        caller.getPackageName(), affectedUser).getActiveAdmin();
-            } else {
-                ap = getParentOfAdminIfRequired(
-                        getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent);
-            }
+            ActiveAdmin ap = getParentOfAdminIfRequired(
+                    getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent);
             if (ap.strongAuthUnlockTimeout != timeoutMs) {
                 ap.strongAuthUnlockTimeout = timeoutMs;
                 saveSettingsLocked(userHandle);
@@ -6664,16 +6503,9 @@
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
         final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
-        if (isPermissionCheckFlagEnabled()) {
-            Preconditions.checkCallAuthorization(
-                    hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES,
-                            caller.getPackageName(), caller.getUserId())
-                            || isCredentialManagementApp);
-        }  else {
-            Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                    && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
-                    || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
-        }
+        Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
+                || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
         if (isCredentialManagementApp) {
             Preconditions.checkCallAuthorization(!isUserSelectable, "The credential "
                     + "management app is not allowed to install a user selectable key pair");
@@ -6733,16 +6565,9 @@
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
         final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
-        if (isPermissionCheckFlagEnabled()) {
-            Preconditions.checkCallAuthorization(
-                    hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES,
-                            caller.getPackageName(), caller.getUserId())
-                            || isCredentialManagementApp);
-        }  else {
-            Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                    && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
-                    || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
-        }
+        Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
+                || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
         if (isCredentialManagementApp) {
             Preconditions.checkCallAuthorization(
                     isAliasInCredentialManagementAppPolicy(caller, alias),
@@ -6802,13 +6627,8 @@
     }
 
     private boolean canInstallCertificates(CallerIdentity caller) {
-        if (isPermissionCheckFlagEnabled()) {
-            return hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES,
-                    caller.getPackageName(), caller.getUserId());
-        }  else {
-            return isProfileOwner(caller) || isDefaultDeviceOwner(caller)
-                    || isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
-        }
+        return isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+                || isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
     }
 
     private boolean canChooseCertificates(CallerIdentity caller) {
@@ -7001,16 +6821,9 @@
                     caller.getPackageName(), caller.getUid()));
             enforceIndividualAttestationSupportedIfRequested(attestationUtilsFlags);
         } else {
-            if (isPermissionCheckFlagEnabled()) {
-                Preconditions.checkCallAuthorization(
-                        hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES,
-                                caller.getPackageName(), caller.getUserId())
-                                || isCredentialManagementApp);
-            }  else {
-                Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner(
-                        caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && (
-                        isCallerDelegate || isCredentialManagementApp)));
-            }
+            Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner(
+                    caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && (
+                    isCallerDelegate || isCredentialManagementApp)));
             if (isCredentialManagementApp) {
                 Preconditions.checkCallAuthorization(
                         isAliasInCredentialManagementAppPolicy(caller, alias),
@@ -7143,16 +6956,9 @@
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
         final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
         final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
-        if (isPermissionCheckFlagEnabled()) {
-            Preconditions.checkCallAuthorization(
-                    hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES,
-                            caller.getPackageName(), caller.getUserId())
-                            || isCredentialManagementApp);
-        } else {
-            Preconditions.checkCallAuthorization((caller.hasAdminComponent()
-                    && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
-                    || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
-        }
+        Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+                && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
+                || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
         if (isCredentialManagementApp) {
             Preconditions.checkCallAuthorization(
                     isAliasInCredentialManagementAppPolicy(caller, alias),
@@ -8285,29 +8091,21 @@
         if (!mHasFeature) {
             return;
         }
-        if (!isPermissionCheckFlagEnabled()) {
-            Preconditions.checkNotNull(who, "ComponentName is null");
-        }
+
+        Preconditions.checkNotNull(who, "ComponentName is null");
+
         CallerIdentity caller = getCallerIdentity(who, callerPackageName);
-        if (!isPermissionCheckFlagEnabled()) {
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller)
-                            || isProfileOwnerOfOrganizationOwnedDevice(caller));
-        }
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller)
+                        || isProfileOwnerOfOrganizationOwnedDevice(caller));
+
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager
                 .OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY);
 
         final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow();
         synchronized (getLockObject()) {
             ActiveAdmin admin;
-            if (isPermissionCheckFlagEnabled()) {
-                admin = enforcePermissionAndGetEnforcingAdmin(
-                        who, MANAGE_DEVICE_POLICY_FACTORY_RESET, caller.getPackageName(),
-                        UserHandle.USER_ALL)
-                        .getActiveAdmin();
-            } else {
-                admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
-            }
+            admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             admin.mFactoryResetProtectionPolicy = policy;
             saveSettingsLocked(caller.getUserId());
         }
@@ -8347,7 +8145,7 @@
                                 || hasCallingPermission(permission.MASTER_CLEAR)
                                 || hasCallingPermission(MANAGE_DEVICE_POLICY_FACTORY_RESET),
                         "Must be called by the FRP management agent on device");
-                admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked();
+                admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
             } else {
                 Preconditions.checkCallAuthorization(
                         isDefaultDeviceOwner(caller)
@@ -10247,15 +10045,6 @@
         return admin;
     }
 
-    ActiveAdmin getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked() {
-        ensureLocked();
-        ActiveAdmin doOrPo = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
-        if (isPermissionCheckFlagEnabled() && doOrPo == null) {
-            return getUserData(0).mPermissionBasedAdmin;
-        }
-        return doOrPo;
-    }
-
     @Override
     public void clearDeviceOwner(String packageName) {
         Objects.requireNonNull(packageName, "packageName is null");
@@ -10998,8 +10787,6 @@
      *     (2.1.1) The caller is the profile owner.
      *     (2.1.2) The caller is from another app in the same user as the profile owner, AND
      *             the caller is the delegated cert installer.
-     * (3) The caller holds the
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission.
      *
      *  For the device owner case, simply check that the caller is the device owner or the
      *  delegated certificate installer.
@@ -11013,24 +10800,18 @@
     @VisibleForTesting
     boolean hasDeviceIdAccessUnchecked(String packageName, int uid) {
         final int userId = UserHandle.getUserId(uid);
-        // TODO(b/280048070): Introduce a permission to handle device ID access
-        if (isPermissionCheckFlagEnabled()
-                && !(isUidProfileOwnerLocked(uid) || isUidDeviceOwnerLocked(uid))) {
-            return hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, packageName, userId);
-        } else {
-            ComponentName deviceOwner = getDeviceOwnerComponent(true);
-            if (deviceOwner != null && (deviceOwner.getPackageName().equals(packageName)
-                    || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) {
-                return true;
-            }
-            ComponentName profileOwner = getProfileOwnerAsUser(userId);
-            final boolean isCallerProfileOwnerOrDelegate = profileOwner != null
-                    && (profileOwner.getPackageName().equals(packageName)
-                    || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL));
-            if (isCallerProfileOwnerOrDelegate && (isProfileOwnerOfOrganizationOwnedDevice(userId)
-                    || isUserAffiliatedWithDevice(userId))) {
-                return true;
-            }
+        ComponentName deviceOwner = getDeviceOwnerComponent(true);
+        if (deviceOwner != null && (deviceOwner.getPackageName().equals(packageName)
+                || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) {
+            return true;
+        }
+        ComponentName profileOwner = getProfileOwnerAsUser(userId);
+        final boolean isCallerProfileOwnerOrDelegate = profileOwner != null
+                && (profileOwner.getPackageName().equals(packageName)
+                || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL));
+        if (isCallerProfileOwnerOrDelegate && (isProfileOwnerOfOrganizationOwnedDevice(userId)
+                || isUserAffiliatedWithDevice(userId))) {
+            return true;
         }
         return false;
     }
@@ -11731,25 +11512,12 @@
     @Override
     public void setDefaultSmsApplication(ComponentName admin, String callerPackageName,
             String packageName, boolean parent) {
-        CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(admin, callerPackageName);
-        } else {
-            caller = getCallerIdentity(admin);
-        }
+        CallerIdentity caller = getCallerIdentity(admin);
 
-        final int userId;
 
-        if (isPermissionCheckFlagEnabled()) {
-            enforcePermission(
-                    MANAGE_DEVICE_POLICY_DEFAULT_SMS,
-                    caller.getPackageName(),
-                    getAffectedUser(parent));
-        } else {
-            Objects.requireNonNull(admin, "ComponentName is null");
-            Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
-                    || isProfileOwnerOfOrganizationOwnedDevice(caller));
-        }
+        Objects.requireNonNull(admin, "ComponentName is null");
+        Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+                || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         if (!parent && isManagedProfile(caller.getUserId())
                 && getManagedSubscriptionsPolicy().getPolicyType()
@@ -11759,6 +11527,7 @@
                             + "ManagedSubscriptions policy is set");
         }
 
+        final int userId;
         if (parent) {
             userId = getProfileParentId(mInjector.userHandleGetCallingUserId());
             mInjector.binderWithCleanCallingIdentity(() -> enforcePackageIsSystemPackage(
@@ -11957,10 +11726,7 @@
             return;
         }
 
-        if (!isPermissionCheckFlagEnabled()) {
-            Objects.requireNonNull(admin, "admin is null");
-        }
-
+        Objects.requireNonNull(admin, "admin is null");
         Objects.requireNonNull(agent, "agent is null");
 
         PolicySizeVerifier.enforceMaxPackageNameLength(agent.getPackageName());
@@ -11972,19 +11738,8 @@
 
         int userHandle = mInjector.userHandleGetCallingUserId();
         synchronized (getLockObject()) {
-            ActiveAdmin ap;
-            if (isPermissionCheckFlagEnabled()) {
-                CallerIdentity caller = getCallerIdentity(admin, callerPackageName);
-                int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
-                ap = enforcePermissionAndGetEnforcingAdmin(
-                        admin,
-                        /*permission=*/MANAGE_DEVICE_POLICY_KEYGUARD,
-                        /*adminPolicy=*/DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES,
-                        caller.getPackageName(), affectedUserId).getActiveAdmin();
-            } else {
-                ap = getActiveAdminForCallerLocked(admin,
-                        DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, parent);
-            }
+            ActiveAdmin ap = getActiveAdminForCallerLocked(admin,
+                    DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, parent);
             checkCanExecuteOrThrowUnsafe(
                     DevicePolicyManager.OPERATION_SET_TRUST_AGENT_CONFIGURATION);
 
@@ -12080,27 +11835,16 @@
     @Override
     public void addCrossProfileIntentFilter(ComponentName who, String callerPackageName,
             IntentFilter filter, int flags) {
-        CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(who, callerPackageName);
-        } else {
-            caller = getCallerIdentity(who);
-        }
-        int callingUserId = caller.getUserId();
+        CallerIdentity caller = getCallerIdentity(who);
 
-        if (isPermissionCheckFlagEnabled()) {
-            enforcePermission(
-                    MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
-                    caller.getPackageName(),
-                    callingUserId);
-        } else {
-            Objects.requireNonNull(who, "ComponentName is null");
-            Preconditions.checkCallAuthorization(
-                    isProfileOwner(caller) || isDefaultDeviceOwner(caller));
-        }
+        Objects.requireNonNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
+
         synchronized (getLockObject()) {
             long id = mInjector.binderClearCallingIdentity();
             try {
+                int callingUserId = caller.getUserId();
                 UserInfo parent = mUserManager.getProfileParent(callingUserId);
                 if (parent == null) {
                     Slogf.e(LOG_TAG, "Cannot call addCrossProfileIntentFilter if there is no "
@@ -12144,28 +11888,16 @@
 
     @Override
     public void clearCrossProfileIntentFilters(ComponentName who, String callerPackageName) {
-        CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(who, callerPackageName);
-        } else {
-            caller = getCallerIdentity(who);
-        }
-        int callingUserId = caller.getUserId();
+        CallerIdentity caller = getCallerIdentity(who);
 
-        if (isPermissionCheckFlagEnabled()) {
-            enforcePermission(
-                    MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
-                    caller.getPackageName(),
-                    callingUserId);
-        } else {
-            Objects.requireNonNull(who, "ComponentName is null");
-            Preconditions.checkCallAuthorization(
-                    isProfileOwner(caller) || isDefaultDeviceOwner(caller));
-        }
+        Objects.requireNonNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(
+                isProfileOwner(caller) || isDefaultDeviceOwner(caller));
 
         synchronized (getLockObject()) {
             long id = mInjector.binderClearCallingIdentity();
             try {
+                int callingUserId = caller.getUserId();
                 UserInfo parent = mUserManager.getProfileParent(callingUserId);
                 if (parent == null) {
                     Slogf.e(LOG_TAG, "Cannot call clearCrossProfileIntentFilter if there is no "
@@ -13360,7 +13092,7 @@
     @Override
     public String[] setPackagesSuspended(ComponentName who, String callerPackage,
             String[] packageNames, boolean suspended) {
-        if (!Flags.unmanagedModeMigration()) {
+        if (!Flags.suspendPackagesCoexistence()) {
             return setPackagesSuspendedPreCoexistence(who, callerPackage, packageNames, suspended);
         }
 
@@ -13450,7 +13182,7 @@
     public boolean isPackageSuspended(ComponentName who, String callerPackage, String packageName) {
         final CallerIdentity caller = getCallerIdentity(who, callerPackage);
 
-        if (Flags.unmanagedModeMigration()) {
+        if (Flags.suspendPackagesCoexistence()) {
             enforcePermission(
                     MANAGE_DEVICE_POLICY_PACKAGE_STATE,
                     caller.getPackageName(),
@@ -15166,19 +14898,12 @@
         if (!mHasFeature) {
             return;
         }
-        CallerIdentity caller;
 
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(who, callerPackageName);
-            enforcePermission(MANAGE_DEVICE_POLICY_WIFI, caller.getPackageName(),
-                    UserHandle.USER_ALL);
-        } else {
-            caller = getCallerIdentity(who);
-            Preconditions.checkNotNull(who, "ComponentName is null");
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller)
-                            || isProfileOwnerOfOrganizationOwnedDevice(caller));
-        }
+        CallerIdentity caller = getCallerIdentity(who);
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller)
+                        || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         mInjector.binderWithCleanCallingIdentity(() ->
                 mInjector.settingsGlobalPutInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN,
@@ -15197,16 +14922,10 @@
             return false;
         }
         CallerIdentity caller = getCallerIdentity(who);
-        if (isPermissionCheckFlagEnabled()) {
-            enforcePermission(MANAGE_DEVICE_POLICY_WIFI, who.getPackageName(),
-                    UserHandle.USER_ALL);
-        } else {
-            Preconditions.checkNotNull(who, "ComponentName is null");
-
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller)
-                            || isProfileOwnerOfOrganizationOwnedDevice(caller));
-        }
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller)
+                        || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         return mInjector.binderWithCleanCallingIdentity(() ->
                 mInjector.settingsGlobalGetInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) > 0);
@@ -15294,18 +15013,11 @@
 
     @Override
     public boolean setTime(@Nullable ComponentName who, String callerPackageName, long millis) {
-        CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(who, callerPackageName);
-            // This is a global action.
-            enforcePermission(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL);
-        } else {
-            caller = getCallerIdentity(who);
-            Objects.requireNonNull(who, "ComponentName is null");
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller)
-                            || isProfileOwnerOfOrganizationOwnedDevice(caller));
-        }
+        CallerIdentity caller = getCallerIdentity(who);
+        Objects.requireNonNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller)
+                        || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         // Don't allow set time when auto time is on.
         if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) == 1) {
@@ -15322,18 +15034,11 @@
     @Override
     public boolean setTimeZone(@Nullable ComponentName who, String callerPackageName,
             String timeZone) {
-        CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(who, callerPackageName);
-            // This is a global action.
-            enforcePermission(SET_TIME_ZONE, caller.getPackageName(), UserHandle.USER_ALL);
-        } else {
-            caller = getCallerIdentity(who);
-            Objects.requireNonNull(who, "ComponentName is null");
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller)
-                            || isProfileOwnerOfOrganizationOwnedDevice(caller));
-        }
+        CallerIdentity caller = getCallerIdentity(who);
+        Objects.requireNonNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller)
+                        || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         // Don't allow set timezone when auto timezone is on.
         if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) == 1) {
@@ -16537,22 +16242,15 @@
             policy.validateAgainstPreviousFreezePeriod(record.first, record.second,
                     LocalDate.now());
         }
-        CallerIdentity caller;
+
+        CallerIdentity caller = getCallerIdentity(who);
+        Preconditions.checkCallAuthorization(
+                isProfileOwnerOfOrganizationOwnedDevice(caller)
+                        || isDefaultDeviceOwner(caller));
+
+        checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY);
 
         synchronized (getLockObject()) {
-            if (isPermissionCheckFlagEnabled()) {
-                caller = getCallerIdentity(who, callerPackageName);
-                enforcePermission(MANAGE_DEVICE_POLICY_SYSTEM_UPDATES, caller.getPackageName(),
-                        UserHandle.USER_ALL);
-            } else {
-                caller = getCallerIdentity(who);
-                Preconditions.checkCallAuthorization(
-                        isProfileOwnerOfOrganizationOwnedDevice(caller)
-                        || isDefaultDeviceOwner(caller));
-            }
-
-            checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY);
-
             if (policy == null) {
                 mOwners.clearSystemUpdatePolicy();
             } else {
@@ -16699,7 +16397,6 @@
             if (!mUserManager.getUserInfo(UserHandle.getCallingUserId()).isMain()) {
                 Slogf.w(LOG_TAG, "Only the system update service in the main user can broadcast "
                         + "update information.");
-                return;
             }
         });
 
@@ -16723,7 +16420,7 @@
                 }
             }
             // Get running users.
-            final int runningUserIds[];
+            final int[] runningUserIds;
             try {
                 runningUserIds = mInjector.getIActivityManager().getRunningUserIds();
             } catch (RemoteException e) {
@@ -16966,10 +16663,7 @@
                 return false;
             }
         }
-        if (!isRuntimePermission(permission)) {
-            return false;
-        }
-        return true;
+        return isRuntimePermission(permission);
     }
 
     private void enforcePermissionGrantStateOnFinancedDevice(
@@ -17384,18 +17078,12 @@
 
     @Override
     public String getWifiMacAddress(ComponentName admin, String callerPackageName) {
-//        if (!isPermissionCheckFlagEnabled()) {
-            Objects.requireNonNull(admin, "ComponentName is null");
-//        }
+        Objects.requireNonNull(admin, "ComponentName is null");
 
         final CallerIdentity caller = getCallerIdentity(admin, callerPackageName);
-//        if (isPermissionCheckFlagEnabled()) {
-//            enforcePermission(MANAGE_DEVICE_POLICY_WIFI, UserHandle.USER_ALL);
-//        } else {
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller)
-                            || isProfileOwnerOfOrganizationOwnedDevice(caller));
-//        }
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller)
+                        || isProfileOwnerOfOrganizationOwnedDevice(caller));
 
         return mInjector.binderWithCleanCallingIdentity(() -> {
             String[] macAddresses = mInjector.getWifiManager().getFactoryMacAddresses();
@@ -17462,25 +17150,15 @@
         if (!mHasFeature) {
             return;
         }
-        CallerIdentity caller;
-        ActiveAdmin admin;
 
         message = PolicySizeVerifier.truncateIfLonger(message, MAX_SHORT_SUPPORT_MESSAGE_LENGTH);
 
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(who, callerPackageName);
-            EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
-                    who,
-                    MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
-                    caller.getPackageName(),
-                    caller.getUserId());
-            admin = enforcingAdmin.getActiveAdmin();
-        } else {
-            caller = getCallerIdentity(who);
-            Objects.requireNonNull(who, "ComponentName is null");
-            synchronized (getLockObject()) {
-                admin = getActiveAdminForUidLocked(who, caller.getUid());
-            }
+        CallerIdentity caller = getCallerIdentity(who);
+        Objects.requireNonNull(who, "ComponentName is null");
+
+        ActiveAdmin admin;
+        synchronized (getLockObject()) {
+            admin = getActiveAdminForUidLocked(who, caller.getUid());
         }
 
         synchronized (getLockObject()) {
@@ -17501,23 +17179,13 @@
         if (!mHasFeature) {
             return null;
         }
-        CallerIdentity caller;
-        ActiveAdmin admin;
 
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(who, callerPackageName);
-            EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
-                    who,
-                    MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
-                    caller.getPackageName(),
-                    caller.getUserId());
-            admin = enforcingAdmin.getActiveAdmin();
-        } else {
-            caller = getCallerIdentity(who);
-            Objects.requireNonNull(who, "ComponentName is null");
-            synchronized (getLockObject()) {
-                admin = getActiveAdminForUidLocked(who, caller.getUid());
-            }
+        CallerIdentity caller = getCallerIdentity(who);
+        Objects.requireNonNull(who, "ComponentName is null");
+
+        ActiveAdmin admin;
+        synchronized (getLockObject()) {
+            admin = getActiveAdminForUidLocked(who, caller.getUid());
         }
         return admin.shortSupportMessage;
     }
@@ -17680,26 +17348,14 @@
             return;
         }
         CallerIdentity caller = getCallerIdentity(who);
-        ActiveAdmin admin = null;
 
-        if (isPermissionCheckFlagEnabled()) {
-            EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
-                    who,
-                    MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
-                    caller.getPackageName(),
-                    caller.getUserId());
-            admin = enforcingAdmin.getActiveAdmin();
-        } else {
-            Objects.requireNonNull(who, "ComponentName is null");
-            Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
-        }
+        Objects.requireNonNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
 
         text = PolicySizeVerifier.truncateIfLonger(text, MAX_ORG_NAME_LENGTH);
 
         synchronized (getLockObject()) {
-            if (!isPermissionCheckFlagEnabled()) {
-                admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
-            }
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             if (!TextUtils.equals(admin.organizationName, text)) {
                 admin.organizationName = (text == null || text.length() == 0)
                         ? null : text.toString();
@@ -17714,23 +17370,14 @@
             return null;
         }
         CallerIdentity caller = getCallerIdentity(who);
+
+        Objects.requireNonNull(who, "ComponentName is null");
+        Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
+        Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+
         ActiveAdmin admin;
-
-        if (isPermissionCheckFlagEnabled()) {
-            EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin(
-                    who,
-                    MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
-                    caller.getPackageName(),
-                    caller.getUserId());
-            admin = enforcingAdmin.getActiveAdmin();
-        } else {
-            Objects.requireNonNull(who, "ComponentName is null");
-            Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
-            Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
-
-            synchronized (getLockObject()) {
-                admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
-            }
+        synchronized (getLockObject()) {
+            admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
         }
 
         return admin.organizationName;
@@ -18214,28 +17861,19 @@
         }
 
         final CallerIdentity caller = getCallerIdentity(admin, packageName);
-        if (isPermissionCheckFlagEnabled()) {
-            synchronized (getLockObject()) {
-                Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
-                        || areAllUsersAffiliatedWithDeviceLocked());
-                enforcePermission(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, caller.getPackageName(),
-                        UserHandle.USER_ALL);
-            }
+        if (admin != null) {
+            Preconditions.checkCallAuthorization(
+                    isProfileOwnerOfOrganizationOwnedDevice(caller)
+                            || isDefaultDeviceOwner(caller));
         } else {
-            if (admin != null) {
-                Preconditions.checkCallAuthorization(
-                        isProfileOwnerOfOrganizationOwnedDevice(caller)
-                                || isDefaultDeviceOwner(caller));
-            } else {
-                // A delegate app passes a null admin component, which is expected
-                Preconditions.checkCallAuthorization(
-                        isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING));
-            }
+            // A delegate app passes a null admin component, which is expected
+            Preconditions.checkCallAuthorization(
+                    isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING));
+        }
 
-            synchronized (getLockObject()) {
-                Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
-                        || areAllUsersAffiliatedWithDeviceLocked());
-            }
+        synchronized (getLockObject()) {
+            Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
+                    || areAllUsersAffiliatedWithDeviceLocked());
         }
 
         DevicePolicyEventLogger
@@ -18259,7 +17897,7 @@
             return new ParceledListSlice<SecurityEvent>(output);
         } catch (IOException e) {
             Slogf.w(LOG_TAG, "Fail to read previous events" , e);
-            return new ParceledListSlice<SecurityEvent>(Collections.<SecurityEvent>emptyList());
+            return new ParceledListSlice<SecurityEvent>(Collections.emptyList());
         }
     }
 
@@ -18752,8 +18390,8 @@
     }
 
     private boolean hasIncompatibleAccounts(int userId) {
-        return mHasIncompatibleAccounts == null ? true
-                : mHasIncompatibleAccounts.getOrDefault(userId, /* default= */ false);
+        return mHasIncompatibleAccounts == null || mHasIncompatibleAccounts.getOrDefault(
+                userId, /* default= */ false);
     }
 
     /**
@@ -18870,7 +18508,7 @@
                 return false;
             }
         }
-    };
+    }
 
     private boolean isAdb(CallerIdentity caller) {
         return isShellUid(caller) || isRootUid(caller);
@@ -20168,21 +19806,12 @@
     @Override
     public void installUpdateFromFile(ComponentName admin, String callerPackageName,
             ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback callback) {
-        if (!isPermissionCheckFlagEnabled()) {
-            Objects.requireNonNull(admin, "ComponentName is null");
-        }
+        Objects.requireNonNull(admin, "ComponentName is null");
 
-        CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(admin, callerPackageName);
-            enforcePermission(MANAGE_DEVICE_POLICY_SYSTEM_UPDATES, caller.getPackageName(),
-                    UserHandle.USER_ALL);
-        } else {
-            caller = getCallerIdentity(admin);
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller)
-                            || isProfileOwnerOfOrganizationOwnedDevice(caller));
-        }
+        CallerIdentity caller = getCallerIdentity(admin);
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller)
+                        || isProfileOwnerOfOrganizationOwnedDevice(caller));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_INSTALL_SYSTEM_UPDATE);
 
         DevicePolicyEventLogger
@@ -20752,32 +20381,15 @@
     @Override
     public void setCommonCriteriaModeEnabled(ComponentName who, String callerPackageName,
             boolean enabled) {
-        CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(who, callerPackageName);
-        } else {
-            caller = getCallerIdentity(who);
-        }
-        final ActiveAdmin admin;
+        CallerIdentity caller = getCallerIdentity(who);
 
-        if (isPermissionCheckFlagEnabled()) {
-            EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
-                    who,
-                    MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE,
-                    caller.getPackageName(),
-                    caller.getUserId());
-            admin = enforcingAdmin.getActiveAdmin();
-        } else {
-            Objects.requireNonNull(who, "ComponentName is null");
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
-                    "Common Criteria mode can only be controlled by a device owner or "
-                            + "a profile owner on an organization-owned device.");
-            synchronized (getLockObject()) {
-                admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
-            }
-        }
+        Objects.requireNonNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                "Common Criteria mode can only be controlled by a device owner or "
+                        + "a profile owner on an organization-owned device.");
         synchronized (getLockObject()) {
+            final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             admin.mCommonCriteriaMode = enabled;
             saveSettingsLocked(caller.getUserId());
         }
@@ -20809,7 +20421,7 @@
             // their ActiveAdmin, instead of iterating through all admins.
             ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
 
-            return admin != null ? admin.mCommonCriteriaMode : false;
+            return admin != null && admin.mCommonCriteriaMode;
         }
     }
 
@@ -22209,7 +21821,7 @@
             } else {
                 owner = getDeviceOrProfileOwnerAdminLocked(userId);
             }
-            boolean canGrant = owner != null ? owner.mAdminCanGrantSensorsPermissions : false;
+            boolean canGrant = owner != null && owner.mAdminCanGrantSensorsPermissions;
             mPolicyCache.setAdminCanGrantSensorsPermissions(canGrant);
         }
     }
@@ -22408,27 +22020,15 @@
 
     @Override
     public void setMinimumRequiredWifiSecurityLevel(String callerPackageName, int level) {
-        CallerIdentity caller;
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(callerPackageName);
-        } else {
-            caller = getCallerIdentity();
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
-                    "Wi-Fi minimum security level can only be controlled by a device owner or "
-                            + "a profile owner on an organization-owned device.");
-        }
+        CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                "Wi-Fi minimum security level can only be controlled by a device owner or "
+                        + "a profile owner on an organization-owned device.");
 
         boolean valueChanged = false;
         synchronized (getLockObject()) {
-            ActiveAdmin admin;
-            if (isPermissionCheckFlagEnabled()) {
-                admin = enforcePermissionAndGetEnforcingAdmin(/* admin= */ null,
-                        MANAGE_DEVICE_POLICY_WIFI, caller.getPackageName(), caller.getUserId())
-                        .getActiveAdmin();
-            } else {
-                admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
-            }
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             if (admin.mWifiMinimumSecurityLevel != level) {
                 admin.mWifiMinimumSecurityLevel = level;
                 saveSettingsLocked(caller.getUserId());
@@ -22450,21 +22050,16 @@
     @Override
     public WifiSsidPolicy getWifiSsidPolicy(String callerPackageName) {
         final CallerIdentity caller = getCallerIdentity();
-        if (isPermissionCheckFlagEnabled()) {
-            enforcePermission(MANAGE_DEVICE_POLICY_WIFI, callerPackageName,
-                    caller.getUserId());
-        } else {
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller)
-                            || isProfileOwnerOfOrganizationOwnedDevice(caller)
-                            || canQueryAdminPolicy(caller),
-                    "SSID policy can only be retrieved by a device owner or "
-                            + "a profile owner on an organization-owned device or "
-                            + "an app with the QUERY_ADMIN_POLICY permission.");
-        }
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller)
+                        || isProfileOwnerOfOrganizationOwnedDevice(caller)
+                        || canQueryAdminPolicy(caller),
+                "SSID policy can only be retrieved by a device owner or "
+                        + "a profile owner on an organization-owned device or "
+                        + "an app with the QUERY_ADMIN_POLICY permission.");
         synchronized (getLockObject()) {
             ActiveAdmin admin;
-            admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked();
+            admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
             return admin != null ? admin.mWifiSsidPolicy : null;
         }
     }
@@ -22485,29 +22080,15 @@
 
     @Override
     public void setWifiSsidPolicy(String callerPackageName, WifiSsidPolicy policy) {
-        CallerIdentity caller;
-
-        if (isPermissionCheckFlagEnabled()) {
-            caller = getCallerIdentity(callerPackageName);
-        } else {
-            caller = getCallerIdentity();
-            Preconditions.checkCallAuthorization(
-                    isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
-                    "SSID denylist can only be controlled by a device owner or "
-                            + "a profile owner on an organization-owned device.");
-        }
+        CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                "SSID denylist can only be controlled by a device owner or "
+                        + "a profile owner on an organization-owned device.");
 
         boolean changed = false;
         synchronized (getLockObject()) {
-            ActiveAdmin admin;
-            if (isPermissionCheckFlagEnabled()) {
-                admin = enforcePermissionAndGetEnforcingAdmin(
-                        /* admin= */ null, MANAGE_DEVICE_POLICY_WIFI,
-                        caller.getPackageName(),
-                        caller.getUserId()).getActiveAdmin();
-            } else {
-                admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
-            }
+            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
             if (!Objects.equals(policy, admin.mWifiSsidPolicy)) {
                 admin.mWifiSsidPolicy = policy;
                 changed = true;
@@ -22715,7 +22296,7 @@
     }
 
     private final class DevicePolicyManagementRoleObserver implements OnRoleHoldersChangedListener {
-        private RoleManager mRm;
+        private final RoleManager mRm;
         private final Executor mExecutor;
         private final Context mContext;
 
@@ -22732,13 +22313,11 @@
         @Override
         public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
             mDevicePolicyEngine.handleRoleChanged(roleName, user.getIdentifier());
-            if (RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT.equals(roleName)) {
-                handleDevicePolicyManagementRoleChange(user);
-                return;
-            }
-            if (RoleManager.ROLE_FINANCED_DEVICE_KIOSK.equals(roleName)) {
-                handleFinancedDeviceKioskRoleChange();
-                return;
+            switch (roleName) {
+                case RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT ->
+                        handleDevicePolicyManagementRoleChange(user);
+                case RoleManager.ROLE_FINANCED_DEVICE_KIOSK ->
+                        handleFinancedDeviceKioskRoleChange();
             }
         }
 
@@ -23390,26 +22969,6 @@
 
     /**
      * Checks if the calling process has been granted permission to apply a device policy on a
-     * specific user.
-     * The given permission will be checked along with its associated cross-user permission if it
-     * exists and the target user is different to the calling user.
-     * Returns an {@link EnforcingAdmin} for the caller.
-     *
-     * @param admin the component name of the admin.
-     * @param callerPackageName The package name  of the calling application.
-     * @param permission The name of the permission being checked.
-     * @param deviceAdminPolicy The userId of the user which the caller needs permission to act on.
-     * @throws SecurityException if the caller has not been granted the given permission,
-     * the associated cross-user permission if the caller's user is different to the target user.
-     */
-    private EnforcingAdmin enforcePermissionAndGetEnforcingAdmin(@Nullable ComponentName admin,
-            String permission, int deviceAdminPolicy, String callerPackageName, int targetUserId) {
-        enforcePermission(permission, deviceAdminPolicy, callerPackageName, targetUserId);
-        return getEnforcingAdminForCaller(admin, callerPackageName);
-    }
-
-    /**
-     * Checks if the calling process has been granted permission to apply a device policy on a
      * specific user.  Only one permission provided in the list needs to be granted to pass this
      * check.
      * The given permissions will be checked along with their associated cross-user permissions if
@@ -23431,23 +22990,6 @@
     }
 
     /**
-     * Checks whether the calling process has been granted permission to query a device policy on
-     * a specific user.
-     * The given permission will be checked along with its associated cross-user permission if it
-     * exists and the target user is different to the calling user.
-     *
-     * @param permission The name of the permission being checked.
-     * @param targetUserId The userId of the user which the caller needs permission to act on.
-     * @throws SecurityException if the caller has not been granted the given permission,
-     * the associated cross-user permission if the caller's user is different to the target user.
-     */
-    private EnforcingAdmin enforceCanQueryAndGetEnforcingAdmin(@Nullable ComponentName admin,
-            String permission, String callerPackageName, int targetUserId) {
-        enforceCanQuery(permission, callerPackageName, targetUserId);
-        return getEnforcingAdminForCaller(admin, callerPackageName);
-    }
-
-    /**
      * Checks if the calling process has been granted permission to apply a device policy.
      *
      * @param callerPackageName The package name  of the calling application.
@@ -23754,13 +23296,6 @@
         return NOT_A_DPC;
     }
 
-    private boolean isPermissionCheckFlagEnabled() {
-        return DeviceConfig.getBoolean(
-                NAMESPACE_DEVICE_POLICY_MANAGER,
-                PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG,
-                DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG);
-    }
-
     private static boolean isSetStatusBarDisabledCoexistenceEnabled() {
         return false;
     }
@@ -23837,58 +23372,83 @@
             Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
         }
 
-        if (isPermissionCheckFlagEnabled()) {
+        if (Flags.setMtePolicyCoexistence()) {
             enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(),
                     UserHandle.USER_ALL);
         } else {
             Preconditions.checkCallAuthorization(
                     isDefaultDeviceOwner(caller)
-                            || isProfileOwnerOfOrganizationOwnedDevice(caller));
+                    || isProfileOwnerOfOrganizationOwnedDevice(caller));
         }
+
         synchronized (getLockObject()) {
-            ActiveAdmin admin =
-                        getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
-
-            if (admin != null) {
-                final String memtagProperty = "arm64.memtag.bootctl";
-                if (flags == DevicePolicyManager.MTE_ENABLED) {
-                    mInjector.systemPropertiesSet(memtagProperty, "memtag");
-                } else if (flags == DevicePolicyManager.MTE_DISABLED) {
-                    mInjector.systemPropertiesSet(memtagProperty, "memtag-off");
-                } else if (flags == DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
-                    if (admin.mtePolicy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
-                        mInjector.systemPropertiesSet(memtagProperty, "default");
-                    }
+            if (Flags.setMtePolicyCoexistence()) {
+                final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null,
+                        MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId());
+                if (flags != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+                    mDevicePolicyEngine.setGlobalPolicy(
+                            PolicyDefinition.MEMORY_TAGGING,
+                            admin,
+                            new IntegerPolicyValue(flags));
+                } else {
+                    mDevicePolicyEngine.removeGlobalPolicy(
+                            PolicyDefinition.MEMORY_TAGGING,
+                            admin);
                 }
-                admin.mtePolicy = flags;
-                saveSettingsLocked(caller.getUserId());
-
-                DevicePolicyEventLogger.createEvent(DevicePolicyEnums.SET_MTE_POLICY)
-                        .setInt(flags)
-                        .setAdmin(caller.getPackageName())
-                        .write();
+            } else {
+                ActiveAdmin admin =
+                        getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
+                if (admin != null) {
+                    final String memtagProperty = "arm64.memtag.bootctl";
+                    if (flags == DevicePolicyManager.MTE_ENABLED) {
+                        mInjector.systemPropertiesSet(memtagProperty, "memtag");
+                    } else if (flags == DevicePolicyManager.MTE_DISABLED) {
+                        mInjector.systemPropertiesSet(memtagProperty, "memtag-off");
+                    } else if (flags == DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+                        if (admin.mtePolicy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+                            mInjector.systemPropertiesSet(memtagProperty, "default");
+                        }
+                    }
+                    admin.mtePolicy = flags;
+                    saveSettingsLocked(caller.getUserId());
+                }
             }
+
+            DevicePolicyEventLogger.createEvent(DevicePolicyEnums.SET_MTE_POLICY)
+                    .setInt(flags)
+                    .setAdmin(caller.getPackageName())
+                    .write();
         }
     }
 
     @Override
     public int getMtePolicy(String callerPackageName) {
         final CallerIdentity caller = getCallerIdentity(callerPackageName);
-        if (isPermissionCheckFlagEnabled()) {
+        if (Flags.setMtePolicyCoexistence()) {
             enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(),
                     UserHandle.USER_ALL);
         } else {
             Preconditions.checkCallAuthorization(
                     isDefaultDeviceOwner(caller)
-                            || isProfileOwnerOfOrganizationOwnedDevice(caller)
-                            || isSystemUid(caller));
+                    || isProfileOwnerOfOrganizationOwnedDevice(caller)
+                    || isSystemUid(caller));
         }
+
         synchronized (getLockObject()) {
-            ActiveAdmin admin =
+            if (Flags.setMtePolicyCoexistence()) {
+                final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null,
+                        MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId());
+                final Integer policyFromAdmin = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
+                        PolicyDefinition.MEMORY_TAGGING, admin);
+                return (policyFromAdmin != null ? policyFromAdmin
+                        : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY);
+            } else {
+                ActiveAdmin admin =
                         getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
-            return admin != null
-                    ? admin.mtePolicy
-                    : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
+                return admin != null
+                        ? admin.mtePolicy
+                        : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
+            }
         }
     }
 
@@ -24235,21 +23795,24 @@
         maybeMigrateSecurityLoggingPolicyLocked();
         // ID format: <sdk-int>.<auto_increment_id>.<descriptions>'
         String unmanagedBackupId = "35.1.unmanaged-mode";
-        boolean unmanagedMigrated = false;
-        unmanagedMigrated =
-                unmanagedMigrated | maybeMigrateRequiredPasswordComplexityLocked(unmanagedBackupId);
-        unmanagedMigrated =
-                unmanagedMigrated | maybeMigrateSuspendedPackagesLocked(unmanagedBackupId);
+        boolean unmanagedMigrated = maybeMigrateRequiredPasswordComplexityLocked(unmanagedBackupId);
         if (unmanagedMigrated) {
             Slogf.i(LOG_TAG, "Backup made: " + unmanagedBackupId);
         }
 
         String supervisionBackupId = "36.2.supervision-support";
         boolean supervisionMigrated = maybeMigrateResetPasswordTokenLocked(supervisionBackupId);
+        supervisionMigrated |= maybeMigrateSuspendedPackagesLocked(supervisionBackupId);
         if (supervisionMigrated) {
             Slogf.i(LOG_TAG, "Backup made: " + supervisionBackupId);
         }
 
+        String memoryTaggingBackupId = "36.3.memory-tagging";
+        boolean memoryTaggingMigrated = maybeMigrateMemoryTaggingLocked(memoryTaggingBackupId);
+        if (memoryTaggingMigrated) {
+            Slogf.i(LOG_TAG, "Backup made: " + memoryTaggingBackupId);
+        }
+
         // Additional migration steps should repeat the pattern above with a new backupId.
     }
 
@@ -24666,7 +24229,7 @@
                 || isCallerDevicePolicyManagementRoleHolder(caller)
                 || isCallerSystemSupervisionRoleHolder(caller));
         return getFinancedDeviceKioskRoleHolderOnAnyUser() != null;
-    };
+    }
 
     @Override
     public String getFinancedDeviceKioskRoleHolder(String callerPackageName) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index b3c8408..be4eea4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -682,6 +682,19 @@
         }
     }
 
+    void markMemoryTaggingMigrated() {
+        synchronized (mData) {
+            mData.mMemoryTaggingMigrated = true;
+            mData.writeDeviceOwner();
+        }
+    }
+
+    boolean isMemoryTaggingMigrated() {
+        synchronized (mData) {
+            return mData.mMemoryTaggingMigrated;
+        }
+    }
+
     @GuardedBy("mData")
     void pushToAppOpsLocked() {
         if (!mSystemReady) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 10e43d9..1cae924 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -93,6 +93,9 @@
     private static final String ATTR_SUSPENDED_PACKAGES_MIGRATED = "suspendedPackagesMigrated";
     private static final String ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED =
             "resetPasswordWithTokenMigrated";
+    private static final String ATTR_MEMORY_TAGGING_MIGRATED =
+            "memoryTaggingMigrated";
+
     private static final String ATTR_MIGRATED_POST_UPGRADE = "migratedPostUpgrade";
 
     // Internal state for the device owner package.
@@ -125,6 +128,7 @@
     boolean mRequiredPasswordComplexityMigrated = false;
     boolean mSuspendedPackagesMigrated = false;
     boolean mResetPasswordWithTokenMigrated = false;
+    boolean mMemoryTaggingMigrated = false;
 
     boolean mPoliciesMigratedPostUpdate = false;
 
@@ -416,6 +420,8 @@
             if (Flags.unmanagedModeMigration()) {
                 out.attributeBoolean(null, ATTR_REQUIRED_PASSWORD_COMPLEXITY_MIGRATED,
                         mRequiredPasswordComplexityMigrated);
+            }
+            if (Flags.suspendPackagesCoexistence()) {
                 out.attributeBoolean(null, ATTR_SUSPENDED_PACKAGES_MIGRATED,
                         mSuspendedPackagesMigrated);
 
@@ -424,6 +430,10 @@
                 out.attributeBoolean(null, ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED,
                         mResetPasswordWithTokenMigrated);
             }
+            if (Flags.setMtePolicyCoexistence()) {
+                out.attributeBoolean(null, ATTR_MEMORY_TAGGING_MIGRATED,
+                        mMemoryTaggingMigrated);
+            }
             out.endTag(null, TAG_POLICY_ENGINE_MIGRATION);
 
         }
@@ -491,13 +501,15 @@
                     mRequiredPasswordComplexityMigrated = Flags.unmanagedModeMigration()
                             && parser.getAttributeBoolean(null,
                                     ATTR_REQUIRED_PASSWORD_COMPLEXITY_MIGRATED, false);
-                    mSuspendedPackagesMigrated = Flags.unmanagedModeMigration()
+                    mSuspendedPackagesMigrated = Flags.suspendPackagesCoexistence()
                             && parser.getAttributeBoolean(null,
                                     ATTR_SUSPENDED_PACKAGES_MIGRATED, false);
                     mResetPasswordWithTokenMigrated = Flags.resetPasswordWithTokenCoexistence()
                             && parser.getAttributeBoolean(null,
                             ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED, false);
-
+                    mMemoryTaggingMigrated = Flags.setMtePolicyCoexistence()
+                            && parser.getAttributeBoolean(null,
+                            ATTR_MEMORY_TAGGING_MIGRATED, false);
                     break;
                 default:
                     Slog.e(TAG, "Unexpected tag: " + tag);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index f271162..f1711f5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -53,6 +53,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
 
 final class PolicyDefinition<V> {
 
@@ -336,6 +337,13 @@
                     PolicyEnforcerCallbacks::noOp,
                     new PackageSetPolicySerializer());
 
+    static PolicyDefinition<Integer> MEMORY_TAGGING = new PolicyDefinition<>(
+                    new NoArgsPolicyKey(
+                            DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY),
+                    new TopPriority<>(List.of(EnforcingAdmin.DPC_AUTHORITY)),
+                    PolicyEnforcerCallbacks::setMtePolicy,
+                    new IntegerPolicySerializer());
+
     private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>();
     private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>();
 
@@ -382,6 +390,8 @@
                 PASSWORD_COMPLEXITY);
         POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PACKAGES_SUSPENDED_POLICY,
                 PACKAGES_SUSPENDED);
+        POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY,
+                MEMORY_TAGGING);
 
         // User Restriction Policies
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MODIFY_ACCOUNTS, /* flags= */ 0);
@@ -504,7 +514,8 @@
     private final int mPolicyFlags;
     // A function that accepts  policy to apply, context, userId, callback arguments, and returns
     // true if the policy has been enforced successfully.
-    private final QuadFunction<V, Context, Integer, PolicyKey, Boolean> mPolicyEnforcerCallback;
+    private final QuadFunction<V, Context, Integer, PolicyKey, CompletableFuture<Boolean>>
+            mPolicyEnforcerCallback;
     private final PolicySerializer<V> mPolicySerializer;
 
     private PolicyDefinition<V> createPolicyDefinition(PolicyKey key) {
@@ -574,7 +585,7 @@
         return mResolutionMechanism.resolve(adminsPolicy);
     }
 
-    boolean enforcePolicy(@Nullable V value, Context context, int userId) {
+    CompletableFuture<Boolean> enforcePolicy(@Nullable V value, Context context, int userId) {
         return mPolicyEnforcerCallback.apply(value, context, userId, mPolicyKey);
     }
 
@@ -592,7 +603,6 @@
         POLICY_DEFINITIONS.put(key.getIdentifier(), definition);
     }
 
-
     /**
      * Callers must ensure that {@code policyType} have implemented an appropriate
      * {@link Object#equals} implementation.
@@ -600,7 +610,8 @@
     private PolicyDefinition(
             @NonNull  PolicyKey key,
             ResolutionMechanism<V> resolutionMechanism,
-            QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback,
+            QuadFunction<V, Context, Integer, PolicyKey, CompletableFuture<Boolean>>
+                    policyEnforcerCallback,
             PolicySerializer<V> policySerializer) {
         this(key, resolutionMechanism, POLICY_FLAG_NONE, policyEnforcerCallback, policySerializer);
     }
@@ -610,10 +621,11 @@
      * {@link Object#equals} and {@link Object#hashCode()} implementation.
      */
     private PolicyDefinition(
-            @NonNull  PolicyKey policyKey,
+            @NonNull PolicyKey policyKey,
             ResolutionMechanism<V> resolutionMechanism,
             int policyFlags,
-            QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback,
+            QuadFunction<V, Context, Integer, PolicyKey, CompletableFuture<Boolean>>
+                    policyEnforcerCallback,
             PolicySerializer<V> policySerializer) {
         Objects.requireNonNull(policyKey);
         mPolicyKey = policyKey;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 4d9abf1..fdc0ec1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -47,6 +47,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.permission.AdminPermissionControlParams;
 import android.permission.PermissionControllerManager;
@@ -55,6 +56,7 @@
 import android.util.Slog;
 import android.view.IWindowManager;
 
+import com.android.internal.infra.AndroidFuture;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.LocalServices;
@@ -65,6 +67,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
@@ -73,33 +76,36 @@
 
     private static final String LOG_TAG = "PolicyEnforcerCallbacks";
 
-    static <T> boolean noOp(T value, Context context, Integer userId, PolicyKey policyKey) {
-        return true;
+    static <T> CompletableFuture<Boolean> noOp(T value, Context context, Integer userId,
+            PolicyKey policyKey) {
+        return AndroidFuture.completedFuture(true);
     }
 
-    static boolean setAutoTimezoneEnabled(@Nullable Boolean enabled, @NonNull Context context) {
+    static CompletableFuture<Boolean> setAutoTimezoneEnabled(@Nullable Boolean enabled,
+            @NonNull Context context) {
         if (!Flags.setAutoTimeZoneEnabledCoexistence()) {
             Slogf.w(LOG_TAG, "Trying to enforce setAutoTimezoneEnabled while flag is off.");
-            return true;
+            return AndroidFuture.completedFuture(true);
         }
         return Binder.withCleanCallingIdentity(() -> {
             Objects.requireNonNull(context);
 
             int value = enabled != null && enabled ? 1 : 0;
-            return Settings.Global.putInt(
-                    context.getContentResolver(), Settings.Global.AUTO_TIME_ZONE,
-                    value);
+            return AndroidFuture.completedFuture(
+                    Settings.Global.putInt(
+                            context.getContentResolver(), Settings.Global.AUTO_TIME_ZONE,
+                    value));
         });
     }
 
-    static boolean setPermissionGrantState(
+    static CompletableFuture<Boolean> setPermissionGrantState(
             @Nullable Integer grantState, @NonNull Context context, int userId,
             @NonNull PolicyKey policyKey) {
         if (!Flags.setPermissionGrantStateCoexistence()) {
             Slogf.w(LOG_TAG, "Trying to enforce setPermissionGrantState while flag is off.");
-            return true;
+            return AndroidFuture.completedFuture(true);
         }
-        return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+        return Binder.withCleanCallingIdentity(() -> {
             if (!(policyKey instanceof PackagePermissionPolicyKey)) {
                 throw new IllegalArgumentException("policyKey is not of type "
                         + "PermissionGrantStatePolicyKey, passed in policyKey is: " + policyKey);
@@ -125,12 +131,13 @@
                     .setRuntimePermissionGrantStateByDeviceAdmin(context.getPackageName(),
                             permissionParams, context.getMainExecutor(), callback::trigger);
             try {
-                return callback.await(20_000, TimeUnit.MILLISECONDS);
+                return AndroidFuture.completedFuture(
+                        callback.await(20_000, TimeUnit.MILLISECONDS));
             } catch (Exception e) {
                 // TODO: add logging
-                return false;
+                return AndroidFuture.completedFuture(false);
             }
-        }));
+        });
     }
 
     @NonNull
@@ -149,23 +156,23 @@
         }
     }
 
-    static boolean enforceSecurityLogging(
+    static CompletableFuture<Boolean> enforceSecurityLogging(
             @Nullable Boolean value, @NonNull Context context, int userId,
             @NonNull PolicyKey policyKey) {
         final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
         dpmi.enforceSecurityLoggingPolicy(Boolean.TRUE.equals(value));
-        return true;
+        return AndroidFuture.completedFuture(true);
     }
 
-    static boolean enforceAuditLogging(
+    static CompletableFuture<Boolean> enforceAuditLogging(
             @Nullable Boolean value, @NonNull Context context, int userId,
             @NonNull PolicyKey policyKey) {
         final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
         dpmi.enforceAuditLoggingPolicy(Boolean.TRUE.equals(value));
-        return true;
+        return AndroidFuture.completedFuture(true);
     }
 
-    static boolean setLockTask(
+    static CompletableFuture<Boolean> setLockTask(
             @Nullable LockTaskPolicy policy, @NonNull Context context, int userId) {
         List<String> packages = Collections.emptyList();
         int flags = LockTaskPolicy.DEFAULT_LOCK_TASK_FLAG;
@@ -175,7 +182,7 @@
         }
         DevicePolicyManagerService.updateLockTaskPackagesLocked(context, packages, userId);
         DevicePolicyManagerService.updateLockTaskFeaturesLocked(flags, userId);
-        return true;
+        return AndroidFuture.completedFuture(true);
     }
 
 
@@ -187,8 +194,8 @@
      * rely on the POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED flag so DPE only invokes this callback
      * when the policy is set, and not during system boot or other situations.
      */
-    static boolean setApplicationRestrictions(Bundle bundle, Context context, Integer userId,
-            PolicyKey policyKey) {
+    static CompletableFuture<Boolean> setApplicationRestrictions(Bundle bundle, Context context,
+            Integer userId, PolicyKey policyKey) {
         Binder.withCleanCallingIdentity(() -> {
             PackagePolicyKey key = (PackagePolicyKey) policyKey;
             String packageName = key.getPackageName();
@@ -198,12 +205,13 @@
             changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
             context.sendBroadcastAsUser(changeIntent, UserHandle.of(userId));
         });
-        return true;
+        return AndroidFuture.completedFuture(true);
     }
 
     private static class BlockingCallback {
         private final CountDownLatch mLatch = new CountDownLatch(1);
         private final AtomicReference<Boolean> mValue = new AtomicReference<>();
+
         public void trigger(Boolean value) {
             mValue.set(value);
             mLatch.countDown();
@@ -220,7 +228,7 @@
     // TODO: when a local policy exists for a user, this callback will be invoked for this user
     // individually as well as for USER_ALL. This can be optimized by separating local and global
     // enforcement in the policy engine.
-    static boolean setUserControlDisabledPackages(
+    static CompletableFuture<Boolean> setUserControlDisabledPackages(
             @Nullable Set<String> packages, Context context, int userId, PolicyKey policyKey) {
         Binder.withCleanCallingIdentity(() -> {
             PackageManagerInternal pmi =
@@ -246,7 +254,7 @@
                 }
             }
         });
-        return true;
+        return AndroidFuture.completedFuture(true);
     }
 
     /** Handles USER_ALL expanding it into the list of all intact users. */
@@ -271,7 +279,7 @@
         }
     }
 
-    static boolean addPersistentPreferredActivity(
+    static CompletableFuture<Boolean> addPersistentPreferredActivity(
             @Nullable ComponentName preferredActivity, @NonNull Context context, int userId,
             @NonNull PolicyKey policyKey) {
         Binder.withCleanCallingIdentity(() -> {
@@ -297,13 +305,13 @@
                 Slog.wtf(LOG_TAG, "Error adding/removing persistent preferred activity", re);
             }
         });
-        return true;
+        return AndroidFuture.completedFuture(true);
     }
 
-    static boolean setUninstallBlocked(
+    static CompletableFuture<Boolean> setUninstallBlocked(
             @Nullable Boolean uninstallBlocked, @NonNull Context context, int userId,
             @NonNull PolicyKey policyKey) {
-        return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+        return Binder.withCleanCallingIdentity(() -> {
             if (!(policyKey instanceof PackagePolicyKey)) {
                 throw new IllegalArgumentException("policyKey is not of type "
                         + "PackagePolicyKey, passed in policyKey is: " + policyKey);
@@ -314,14 +322,14 @@
                     packageName,
                     uninstallBlocked != null && uninstallBlocked,
                     userId);
-            return true;
-        }));
+            return AndroidFuture.completedFuture(true);
+        });
     }
 
-    static boolean setUserRestriction(
+    static CompletableFuture<Boolean> setUserRestriction(
             @Nullable Boolean enabled, @NonNull Context context, int userId,
             @NonNull PolicyKey policyKey) {
-        return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+        return Binder.withCleanCallingIdentity(() -> {
             if (!(policyKey instanceof UserRestrictionPolicyKey)) {
                 throw new IllegalArgumentException("policyKey is not of type "
                         + "UserRestrictionPolicyKey, passed in policyKey is: " + policyKey);
@@ -331,14 +339,14 @@
             UserManagerInternal userManager = LocalServices.getService(UserManagerInternal.class);
             userManager.setUserRestriction(
                     userId, parsedKey.getRestriction(), enabled != null && enabled);
-            return true;
-        }));
+            return AndroidFuture.completedFuture(true);
+        });
     }
 
-    static boolean setApplicationHidden(
+    static CompletableFuture<Boolean> setApplicationHidden(
             @Nullable Boolean hide, @NonNull Context context, int userId,
             @NonNull PolicyKey policyKey) {
-        return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+        return Binder.withCleanCallingIdentity(() -> {
             if (!(policyKey instanceof PackagePolicyKey)) {
                 throw new IllegalArgumentException("policyKey is not of type "
                         + "PackagePolicyKey, passed in policyKey is: " + policyKey);
@@ -346,12 +354,13 @@
             PackagePolicyKey parsedKey = (PackagePolicyKey) policyKey;
             String packageName = Objects.requireNonNull(parsedKey.getPackageName());
             IPackageManager packageManager = AppGlobals.getPackageManager();
-            return packageManager.setApplicationHiddenSettingAsUser(
-                    packageName, hide != null && hide, userId);
-        }));
+            return AndroidFuture.completedFuture(
+                    packageManager.setApplicationHiddenSettingAsUser(
+                            packageName, hide != null && hide, userId));
+        });
     }
 
-    static boolean setScreenCaptureDisabled(
+    static CompletableFuture<Boolean> setScreenCaptureDisabled(
             @Nullable Boolean disabled, @NonNull Context context, int userId,
             @NonNull PolicyKey policyKey) {
         Binder.withCleanCallingIdentity(() -> {
@@ -363,10 +372,10 @@
                 updateScreenCaptureDisabled();
             }
         });
-        return true;
+        return AndroidFuture.completedFuture(true);
     }
 
-    static boolean setContentProtectionPolicy(
+    static CompletableFuture<Boolean> setContentProtectionPolicy(
             @Nullable Integer value,
             @NonNull Context context,
             @UserIdInt Integer userId,
@@ -378,7 +387,7 @@
                         cacheImpl.setContentProtectionPolicy(userId, value);
                     }
                 });
-        return true;
+        return AndroidFuture.completedFuture(true);
     }
 
     private static void updateScreenCaptureDisabled() {
@@ -393,7 +402,7 @@
         });
     }
 
-    static boolean setPersonalAppsSuspended(
+    static CompletableFuture<Boolean> setPersonalAppsSuspended(
             @Nullable Boolean suspended, @NonNull Context context, int userId,
             @NonNull PolicyKey policyKey) {
         Binder.withCleanCallingIdentity(() -> {
@@ -404,7 +413,7 @@
                         .unsuspendAdminSuspendedPackages(userId);
             }
         });
-        return true;
+        return AndroidFuture.completedFuture(true);
     }
 
     private static void suspendPersonalAppsInPackageManager(Context context, int userId) {
@@ -418,13 +427,53 @@
         }
     }
 
-    static boolean setUsbDataSignalingEnabled(@Nullable Boolean value, @NonNull Context context) {
+    static CompletableFuture<Boolean> setUsbDataSignalingEnabled(@Nullable Boolean value,
+            @NonNull Context context) {
         return Binder.withCleanCallingIdentity(() -> {
             Objects.requireNonNull(context);
 
             boolean enabled = value == null || value;
             DevicePolicyManagerService.updateUsbDataSignal(context, enabled);
-            return true;
+            return AndroidFuture.completedFuture(true);
         });
     }
+
+    static CompletableFuture<Boolean> setMtePolicy(
+            @Nullable Integer mtePolicy, @NonNull Context context, int userId,
+            @NonNull PolicyKey policyKey) {
+        if (mtePolicy == null) {
+            mtePolicy = DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
+        }
+        final Set<Integer> allowedModes =
+                Set.of(
+                        DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY,
+                        DevicePolicyManager.MTE_DISABLED,
+                        DevicePolicyManager.MTE_ENABLED);
+        if (!allowedModes.contains(mtePolicy)) {
+            Slog.wtf(LOG_TAG, "MTE policy is not a known one: " + mtePolicy);
+            return AndroidFuture.completedFuture(false);
+        }
+
+        final String mteDpmSystemProperty =
+                "ro.arm64.memtag.bootctl_device_policy_manager";
+        final String mteSettingsSystemProperty =
+                "ro.arm64.memtag.bootctl_settings_toggle";
+        final String mteControlProperty = "arm64.memtag.bootctl";
+
+        final boolean isAvailable = SystemProperties.getBoolean(mteDpmSystemProperty,
+                SystemProperties.getBoolean(mteSettingsSystemProperty, false));
+        if (!isAvailable) {
+            return AndroidFuture.completedFuture(false);
+        }
+
+        if (mtePolicy == DevicePolicyManager.MTE_ENABLED) {
+            SystemProperties.set(mteControlProperty, "memtag");
+        } else if (mtePolicy == DevicePolicyManager.MTE_DISABLED) {
+            SystemProperties.set(mteControlProperty, "memtag-off");
+        } else if (mtePolicy == DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+            SystemProperties.set(mteControlProperty, "default");
+        }
+
+        return AndroidFuture.completedFuture(true);
+    }
 }
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index a804f24..c30ab73 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -101,13 +101,13 @@
                 TEST_SETTING_ACTIVITY_NAME, subtypes, TEST_IS_DEFAULT_RES_ID,
                 TEST_FORCE_DEFAULT, supportsSwitchingToNextInputMethod, TEST_IS_VR_IME);
         if (subtypes == null) {
-            items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi,
-                    NOT_A_SUBTYPE_INDEX, null, SYSTEM_LOCALE));
+            items.add(new ImeSubtypeListItem(imeName, null /* subtypeName */, null /* layoutName */,
+                    imi, NOT_A_SUBTYPE_INDEX, null, SYSTEM_LOCALE));
         } else {
             for (int i = 0; i < subtypes.size(); ++i) {
                 final String subtypeLocale = subtypeLocales.get(i);
-                items.add(new ImeSubtypeListItem(imeName, subtypeLocale, imi, i, subtypeLocale,
-                        SYSTEM_LOCALE));
+                items.add(new ImeSubtypeListItem(imeName, subtypeLocale, null /* layoutName */,
+                        imi, i, subtypeLocale, SYSTEM_LOCALE));
             }
         }
     }
@@ -138,8 +138,8 @@
         final InputMethodInfo imi = new InputMethodInfo(ri, TEST_IS_AUX_IME,
                 TEST_SETTING_ACTIVITY_NAME, subtypes, TEST_IS_DEFAULT_RES_ID,
                 TEST_FORCE_DEFAULT, true /* supportsSwitchingToNextInputMethod */, TEST_IS_VR_IME);
-        return new ImeSubtypeListItem(imeName, subtypeName, imi, subtypeIndex, subtypeLocale,
-                SYSTEM_LOCALE);
+        return new ImeSubtypeListItem(imeName, subtypeName, null /* layoutName */, imi,
+                subtypeIndex, subtypeLocale, SYSTEM_LOCALE);
     }
 
     @NonNull
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index f439770..69714f3 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -288,6 +288,7 @@
         AndroidPackage::isUpdatableSystem,
         AndroidPackage::getEmergencyInstaller,
         AndroidPackage::isAllowCrossUidActivitySwitchFromBelow,
+        AndroidPackage::getIntentMatchingFlags,
     )
 
     override fun extraParams() = listOf(
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
index 349b831..8782b1a 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
@@ -55,7 +55,8 @@
         ParsedActivity::getUiOptions,
         ParsedActivity::isSupportsSizeChanges,
         ParsedActivity::getRequiredDisplayCategory,
-        ParsedActivity::getRequireContentUriPermissionFromCaller
+        ParsedActivity::getRequireContentUriPermissionFromCaller,
+        ParsedActivity::getIntentMatchingFlags,
     )
 
     override fun mainComponentSubclassExtraParams() = listOf(
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
index 1e84470..3e34979 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
@@ -38,6 +38,7 @@
         ParsedProvider::isForceUriPermissions,
         ParsedProvider::isMultiProcess,
         ParsedProvider::getInitOrder,
+        ParsedProvider::getIntentMatchingFlags,
     )
 
     override fun mainComponentSubclassExtraParams() = listOf(
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt
index 79d5a4f..694db47 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt
@@ -30,5 +30,6 @@
     override val mainComponentSubclassBaseParams = listOf(
         ParsedService::getForegroundServiceType,
         ParsedService::getPermission,
+        ParsedService::getIntentMatchingFlags,
     )
 }
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/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
index de53266..fc4cc25 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
@@ -63,11 +63,12 @@
                 DisplayManagerInternal.DisplayPowerRequest.class);
 
         displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+        displayPowerRequest.policyReason = Display.STATE_REASON_KEY;
         Pair<Integer, Integer> stateAndReason =
                 mDisplayStateController.updateDisplayState(
                         displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
         assertTrue(Display.STATE_OFF == stateAndReason.first);
-        assertTrue(Display.STATE_REASON_DEFAULT_POLICY == stateAndReason.second);
+        assertTrue(Display.STATE_REASON_KEY == stateAndReason.second);
         verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
                 Display.STATE_OFF);
         assertEquals(true, mDisplayStateController.shouldPerformScreenOffTransition());
@@ -105,11 +106,12 @@
                 DisplayManagerInternal.DisplayPowerRequest.class);
 
         displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        displayPowerRequest.policyReason = Display.STATE_REASON_KEY;
         Pair<Integer, Integer> stateAndReason =
                 mDisplayStateController.updateDisplayState(
                         displayPowerRequest, !DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
         assertTrue(Display.STATE_OFF == stateAndReason.first);
-        assertTrue(Display.STATE_REASON_DEFAULT_POLICY == stateAndReason.second);
+        assertTrue(Display.STATE_REASON_KEY == stateAndReason.second);
         verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
                 Display.STATE_ON);
         assertEquals(false, mDisplayStateController.shouldPerformScreenOffTransition());
@@ -123,11 +125,12 @@
                 DisplayManagerInternal.DisplayPowerRequest.class);
 
         displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        displayPowerRequest.policyReason = Display.STATE_REASON_MOTION;
         Pair<Integer, Integer> stateAndReason =
                 mDisplayStateController.updateDisplayState(
                         displayPowerRequest, DISPLAY_ENABLED, DISPLAY_IN_TRANSITION);
         assertTrue(Display.STATE_OFF == stateAndReason.first);
-        assertTrue(Display.STATE_REASON_DEFAULT_POLICY == stateAndReason.second);
+        assertTrue(Display.STATE_REASON_MOTION == stateAndReason.second);
         verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
                 Display.STATE_ON);
         assertEquals(false, mDisplayStateController.shouldPerformScreenOffTransition());
@@ -141,6 +144,7 @@
                 DisplayManagerInternal.DisplayPowerRequest.class);
 
         displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        displayPowerRequest.policyReason = Display.STATE_REASON_DEFAULT_POLICY;
         Pair<Integer, Integer> stateAndReason =
                 mDisplayStateController.updateDisplayState(
                         displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
@@ -156,6 +160,7 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 new DisplayManagerInternal.DisplayPowerRequest();
         displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+        displayPowerRequest.policyReason = Display.STATE_REASON_DRAW_WAKE_LOCK;
         mDisplayStateController.overrideDozeScreenState(
                 Display.STATE_DOZE_SUSPEND, Display.STATE_REASON_OFFLOAD);
 
@@ -172,8 +177,9 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 new DisplayManagerInternal.DisplayPowerRequest();
         displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+        displayPowerRequest.policyReason = Display.STATE_REASON_DEFAULT_POLICY;
         mDisplayStateController.overrideDozeScreenState(
-                Display.STATE_DOZE_SUSPEND, Display.STATE_REASON_DEFAULT_POLICY);
+                Display.STATE_DOZE_SUSPEND, Display.STATE_REASON_DRAW_WAKE_LOCK);
 
         Pair<Integer, Integer> stateAndReason =
                 mDisplayStateController.updateDisplayState(
@@ -183,6 +189,53 @@
         assertTrue(Display.STATE_REASON_DEFAULT_POLICY == stateAndReason.second);
     }
 
+    @Test
+    public void policyOff_usespolicyReasonFromRequest() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+                new DisplayManagerInternal.DisplayPowerRequest();
+        displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+        displayPowerRequest.policyReason = Display.STATE_REASON_KEY;
+
+        Pair<Integer, Integer> stateAndReason =
+                mDisplayStateController.updateDisplayState(
+                        displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
+
+        assertTrue(Display.STATE_OFF == stateAndReason.first);
+        assertTrue(Display.STATE_REASON_KEY == stateAndReason.second);
+    }
+
+    @Test
+    public void policyBright_usespolicyReasonFromRequest() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+                new DisplayManagerInternal.DisplayPowerRequest();
+        displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        displayPowerRequest.policyReason = Display.STATE_REASON_DREAM_MANAGER;
+
+        Pair<Integer, Integer> stateAndReason =
+                mDisplayStateController.updateDisplayState(
+                        displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
+
+        assertTrue(Display.STATE_ON == stateAndReason.first);
+        assertTrue(Display.STATE_REASON_DREAM_MANAGER == stateAndReason.second);
+    }
+
+    @Test
+    public void policyRequestHasDozeScreenState_usesPolicyDozeScreenStateReason() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+                new DisplayManagerInternal.DisplayPowerRequest();
+        displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+        displayPowerRequest.policyReason = Display.STATE_REASON_MOTION;
+        displayPowerRequest.dozeScreenState = Display.STATE_ON;
+        displayPowerRequest.dozeScreenStateReason = Display.STATE_REASON_OFFLOAD;
+
+        Pair<Integer, Integer> stateAndReason =
+                mDisplayStateController.updateDisplayState(
+                        displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
+
+        assertTrue(Display.STATE_ON == stateAndReason.first);
+        assertTrue(Display.STATE_REASON_OFFLOAD == stateAndReason.second);
+    }
+
     private void validDisplayState(int policy, int displayState, boolean isEnabled,
             boolean isInTransition) {
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
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/OWNERS b/services/tests/mockingservicestests/src/com/android/server/OWNERS
index dc5cb8d6..0c9f70c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/OWNERS
@@ -1,6 +1,6 @@
 per-file *Alarm* = file:/apex/jobscheduler/OWNERS
 per-file *AppStateTracker* = file:/apex/jobscheduler/OWNERS
-per-file *DeviceIdleController* = file:/apex/jobscheduler/OWNERS
+per-file *DeviceIdleController* = file:/apex/jobscheduler/DEVICE_IDLE_OWNERS
 per-file SensitiveContentProtectionManagerService* = file:/core/java/android/permission/OWNERS
 per-file RescuePartyTest.java = file:/packages/CrashRecovery/OWNERS
 per-file *Storage* = file:/core/java/android/os/storage/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/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/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index b005358..4a131558 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -60,6 +60,7 @@
 import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ;
 import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
 import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ;
 import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
 import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT;
 import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW;
@@ -129,6 +130,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
 
 /**
  * Test class for {@link OomAdjuster}.
@@ -899,8 +901,25 @@
 
     @SuppressWarnings("GuardedBy")
     @Test
+    public void testUpdateOomAdj_DoPending_PreviousApp() {
+        testUpdateOomAdj_PreviousApp(apps -> {
+            for (ProcessRecord app : apps) {
+                mProcessStateController.enqueueUpdateTarget(app);
+            }
+            mProcessStateController.runPendingUpdate(OOM_ADJ_REASON_NONE);
+        });
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
     public void testUpdateOomAdj_DoAll_PreviousApp() {
-        final int numberOfApps = 15;
+        testUpdateOomAdj_PreviousApp(apps -> {
+            mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
+        });
+    }
+
+    private void testUpdateOomAdj_PreviousApp(Consumer<ProcessRecord[]> updater) {
+        final int numberOfApps = 105;
         final ProcessRecord[] apps = new ProcessRecord[numberOfApps];
         for (int i = 0; i < numberOfApps; i++) {
             apps[i] = spy(makeDefaultProcessRecord(MOCKAPP_PID + i, MOCKAPP_UID + i,
@@ -911,10 +930,11 @@
         }
         setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
         setProcessesToLru(apps);
-        mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
-
+        updater.accept(apps);
         for (int i = 0; i < numberOfApps; i++) {
-            assertProcStates(apps[i], PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
+            final int mruIndex = numberOfApps - i - 1;
+            final int expectedAdj = Math.min(PREVIOUS_APP_ADJ + mruIndex, PREVIOUS_APP_MAX_ADJ);
+            assertProcStates(apps[i], PROCESS_STATE_LAST_ACTIVITY, expectedAdj,
                     SCHED_GROUP_BACKGROUND, "previous");
         }
 
@@ -3184,7 +3204,8 @@
         setProcessesToLru(app1, app2);
         mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
 
-        assertProcStates(app1, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
+        assertProcStates(app1, PROCESS_STATE_LAST_ACTIVITY,
+                PREVIOUS_APP_ADJ + (Flags.oomadjusterPrevLaddering() ? 1 : 0),
                 SCHED_GROUP_BACKGROUND, "recent-provider");
         assertProcStates(app2, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
                 SCHED_GROUP_BACKGROUND, "recent-provider");
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/OWNERS b/services/tests/mockingservicestests/src/com/android/server/am/OWNERS
index 4fac647..809b7bb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/am/OWNERS
@@ -2,3 +2,4 @@
 
 per-file ApplicationStartInfoTest.java = yforta@google.com, carmenjackson@google.com, jji@google.com
 per-file CachedAppOptimizerTest.java = file:/PERFORMANCE_OWNERS
+per-file BaseBroadcastQueueTest.java = file:/BROADCASTS_OWNERS
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/job/JobServiceContextTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java
new file mode 100644
index 0000000..8c66fd0
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java
@@ -0,0 +1,270 @@
+/*
+ * 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.job;
+
+import static android.app.job.Flags.FLAG_HANDLE_ABANDONED_JOBS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.AppGlobals;
+import android.app.job.JobParameters;
+import android.content.Context;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.server.job.JobServiceContext.JobCallback;
+import com.android.server.job.controllers.JobStatus;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.ZoneOffset;
+
+public class JobServiceContextTest {
+    private static final String TAG = JobServiceContextTest.class.getSimpleName();
+    @ClassRule
+    public static final SetFlagsRule.ClassRule mSetFlagsClassRule = new SetFlagsRule.ClassRule();
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = mSetFlagsClassRule.createSetFlagsRule();
+    @Mock
+    private JobSchedulerService mMockJobSchedulerService;
+    @Mock
+    private JobConcurrencyManager mMockConcurrencyManager;
+    @Mock
+    private JobNotificationCoordinator mMockNotificationCoordinator;
+    @Mock
+    private IBatteryStats.Stub mMockBatteryStats;
+    @Mock
+    private JobPackageTracker mMockJobPackageTracker;
+    @Mock
+    private Looper mMockLooper;
+    @Mock
+    private Context mMockContext;
+    @Mock
+    private JobStatus mMockJobStatus;
+    @Mock
+    private JobParameters mMockJobParameters;
+    @Mock
+    private JobCallback mMockJobCallback;
+    private MockitoSession mMockingSession;
+    private JobServiceContext mJobServiceContext;
+    private Object mLock;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockingSession =
+                mockitoSession()
+                        .initMocks(this)
+                        .mockStatic(AppGlobals.class)
+                        .strictness(Strictness.LENIENT)
+                        .startMocking();
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
+        doReturn(mock(PowerManager.class)).when(mMockContext).getSystemService(PowerManager.class);
+        doReturn(mMockContext).when(mMockJobSchedulerService).getContext();
+        mLock = new Object();
+        doReturn(mLock).when(mMockJobSchedulerService).getLock();
+        mJobServiceContext =
+                new JobServiceContext(
+                        mMockJobSchedulerService,
+                        mMockConcurrencyManager,
+                        mMockNotificationCoordinator,
+                        mMockBatteryStats,
+                        mMockJobPackageTracker,
+                        mMockLooper);
+        spyOn(mJobServiceContext);
+        mJobServiceContext.setJobParamsLockedForTest(mMockJobParameters);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mMockingSession != null) {
+            mMockingSession.finishMocking();
+        }
+    }
+
+    private Clock getAdvancedClock(Clock clock, long incrementMs) {
+        return Clock.offset(clock, Duration.ofMillis(incrementMs));
+    }
+
+    private void advanceElapsedClock(long incrementMs) {
+        JobSchedulerService.sElapsedRealtimeClock =
+                getAdvancedClock(JobSchedulerService.sElapsedRealtimeClock, incrementMs);
+    }
+
+    /**
+     * Test that Abandoned jobs that are timed out are stopped with the correct stop reason
+     */
+    @Test
+    @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+    public void testJobServiceContext_TimeoutAbandonedJob() {
+        mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+        ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+        doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture());
+
+        advanceElapsedClock(30 * MINUTE_IN_MILLIS); // 30 minutes
+        mJobServiceContext.setPendingStopReasonLockedForTest(JobParameters.STOP_REASON_UNDEFINED);
+
+        mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+        doReturn(true).when(mMockJobStatus).isAbandoned();
+        mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+
+        mJobServiceContext.handleOpTimeoutLocked();
+
+        String stopMessage = captor.getValue();
+        assertEquals("timeout while executing and maybe abandoned", stopMessage);
+        verify(mMockJobParameters)
+                .setStopReason(
+                        JobParameters.STOP_REASON_TIMEOUT_ABANDONED,
+                        JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED,
+                        "client timed out and maybe abandoned");
+    }
+
+    /**
+     * Test that non-abandoned jobs that are timed out are stopped with the correct stop reason
+     */
+    @Test
+    @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+    public void testJobServiceContext_TimeoutNoAbandonedJob() {
+        mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+        ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+        doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture());
+
+        advanceElapsedClock(30 * MINUTE_IN_MILLIS); // 30 minutes
+        mJobServiceContext.setPendingStopReasonLockedForTest(JobParameters.STOP_REASON_UNDEFINED);
+
+        mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+        doReturn(false).when(mMockJobStatus).isAbandoned();
+        mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+
+        mJobServiceContext.handleOpTimeoutLocked();
+
+        String stopMessage = captor.getValue();
+        assertEquals("timeout while executing", stopMessage);
+        verify(mMockJobParameters)
+                .setStopReason(
+                        JobParameters.STOP_REASON_TIMEOUT,
+                        JobParameters.INTERNAL_STOP_REASON_TIMEOUT,
+                        "client timed out");
+    }
+
+    /**
+     * Test that abandoned jobs that are timed out while the flag is disabled
+     * are stopped with the correct stop reason
+     */
+    @Test
+    @DisableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+    public void testJobServiceContext_TimeoutAbandonedJob_flagHandleAbandonedJobsDisabled() {
+        mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+        ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+        doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture());
+
+        advanceElapsedClock(30 * MINUTE_IN_MILLIS); // 30 minutes
+        mJobServiceContext.setPendingStopReasonLockedForTest(JobParameters.STOP_REASON_UNDEFINED);
+
+        mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+        doReturn(true).when(mMockJobStatus).isAbandoned();
+        mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+
+        mJobServiceContext.handleOpTimeoutLocked();
+
+        String stopMessage = captor.getValue();
+        assertEquals("timeout while executing", stopMessage);
+        verify(mMockJobParameters)
+                .setStopReason(
+                        JobParameters.STOP_REASON_TIMEOUT,
+                        JobParameters.INTERNAL_STOP_REASON_TIMEOUT,
+                        "client timed out");
+    }
+
+    /**
+     * Test that the JobStatus is marked as abandoned when the JobServiceContext
+     * receives a MSG_HANDLE_ABANDONED_JOB message
+     */
+    @Test
+    @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+    public void testJobServiceContext_HandleAbandonedJob() {
+        final int jobId = 123;
+        mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+        mJobServiceContext.setRunningCallbackLockedForTest(mMockJobCallback);
+        doReturn(jobId).when(mMockJobStatus).getJobId();
+
+        mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId);
+
+        verify(mMockJobStatus).setAbandoned(true);
+    }
+
+    /**
+     * Test that the JobStatus is not marked as abandoned when the
+     * JobServiceContext receives a MSG_HANDLE_ABANDONED_JOB message and the
+     * JobServiceContext is not running a job
+     */
+    @Test
+    @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+    public void testJobServiceContext_HandleAbandonedJob_notRunningJob() {
+        final int jobId = 123;
+        mJobServiceContext.setRunningJobLockedForTest(null);
+        mJobServiceContext.setRunningCallbackLockedForTest(mMockJobCallback);
+
+        mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId);
+
+        verify(mMockJobStatus, never()).setAbandoned(true);
+    }
+
+    /**
+     * Test that the JobStatus is not marked as abandoned when the
+     * JobServiceContext receives a MSG_HANDLE_ABANDONED_JOB message and the
+     * JobServiceContext is running a job with a different jobId
+     */
+    @Test
+    @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+    public void testJobServiceContext_HandleAbandonedJob_differentJobId() {
+        final int jobId = 123;
+        final int differentJobId = 456;
+        mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+        mJobServiceContext.setRunningCallbackLockedForTest(mMockJobCallback);
+        doReturn(differentJobId).when(mMockJobStatus).getJobId();
+
+        mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId);
+
+        verify(mMockJobStatus, never()).setAbandoned(true);
+    }
+
+}
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/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
index f1bf86f..b53f6fb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
@@ -16,11 +16,11 @@
 
 package com.android.server.wallpaper;
 
-import static android.app.WallpaperManager.LANDSCAPE;
+import static android.app.WallpaperManager.ORIENTATION_LANDSCAPE;
 import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
-import static android.app.WallpaperManager.PORTRAIT;
-import static android.app.WallpaperManager.SQUARE_LANDSCAPE;
-import static android.app.WallpaperManager.SQUARE_PORTRAIT;
+import static android.app.WallpaperManager.ORIENTATION_PORTRAIT;
+import static android.app.WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE;
+import static android.app.WallpaperManager.ORIENTATION_SQUARE_PORTRAIT;
 import static android.app.WallpaperManager.getOrientation;
 import static android.app.WallpaperManager.getRotatedOrientation;
 
@@ -406,15 +406,16 @@
         setUpWithDisplays(STANDARD_DISPLAY);
         Point bitmapSize = new Point(800, 1000);
         SparseArray<Rect> suggestedCrops = new SparseArray<>();
-        suggestedCrops.put(PORTRAIT, new Rect(0, 0, 400, 800));
-        for (int otherOrientation: List.of(LANDSCAPE, SQUARE_LANDSCAPE, SQUARE_PORTRAIT)) {
+        suggestedCrops.put(ORIENTATION_PORTRAIT, new Rect(0, 0, 400, 800));
+        for (int otherOrientation: List.of(ORIENTATION_LANDSCAPE, ORIENTATION_SQUARE_LANDSCAPE,
+                ORIENTATION_SQUARE_PORTRAIT)) {
             suggestedCrops.put(otherOrientation, new Rect(0, 0, 10, 10));
         }
 
         for (boolean rtl : List.of(false, true)) {
             assertThat(mWallpaperCropper.getCrop(
                     new Point(300, 800), bitmapSize, suggestedCrops, rtl))
-                    .isEqualTo(suggestedCrops.get(PORTRAIT));
+                    .isEqualTo(suggestedCrops.get(ORIENTATION_PORTRAIT));
             assertThat(mWallpaperCropper.getCrop(
                     new Point(500, 800), bitmapSize, suggestedCrops, rtl))
                     .isEqualTo(new Rect(0, 0, 500, 800));
@@ -440,8 +441,8 @@
         Point landscape = new Point(PORTRAIT_ONE.y, PORTRAIT_ONE.x);
         Point squarePortrait = SQUARE_PORTRAIT_ONE;
         Point squareLandscape = new Point(SQUARE_PORTRAIT_ONE.y, SQUARE_PORTRAIT_ONE.y);
-        suggestedCrops.put(PORTRAIT, centerOf(bitmapRect, portrait));
-        suggestedCrops.put(SQUARE_LANDSCAPE, centerOf(bitmapRect, squareLandscape));
+        suggestedCrops.put(ORIENTATION_PORTRAIT, centerOf(bitmapRect, portrait));
+        suggestedCrops.put(ORIENTATION_SQUARE_LANDSCAPE, centerOf(bitmapRect, squareLandscape));
         for (boolean rtl : List.of(false, true)) {
             assertThat(mWallpaperCropper.getCrop(
                     landscape, bitmapSize, suggestedCrops, rtl))
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index 1ea3674..db323f1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -46,6 +46,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 
@@ -535,7 +536,8 @@
         doReturn(wallpaper).when(mService).getWallpaperSafeLocked(wallpaper.userId, FLAG_SYSTEM);
         doNothing().when(mService).switchWallpaper(any(), any());
         doReturn(true).when(mService)
-                .bindWallpaperComponentLocked(any(), anyBoolean(), anyBoolean(), any(), any());
+                .bindWallpaperComponentLocked(isA(ComponentName.class), anyBoolean(), anyBoolean(),
+                        any(), any());
         doNothing().when(mService).saveSettingsLocked(wallpaper.userId);
         spyOn(mService.mWallpaperCropper);
         doNothing().when(mService.mWallpaperCropper).generateCrop(wallpaper);
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 473c8c5..4e29e74 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
@@ -27,10 +27,13 @@
 import static android.os.PowerManager.GO_TO_SLEEP_REASON_TIMEOUT;
 import static android.os.PowerManager.WAKE_REASON_GESTURE;
 import static android.os.PowerManager.WAKE_REASON_PLUGGED_IN;
+import static android.os.PowerManager.WAKE_REASON_WAKE_MOTION;
 import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
 import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
 import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
 import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING;
+import static android.view.Display.STATE_REASON_DEFAULT_POLICY;
+import static android.view.Display.STATE_REASON_MOTION;
 
 import static com.android.server.power.PowerManagerService.USER_ACTIVITY_SCREEN_BRIGHT;
 import static com.android.server.power.PowerManagerService.WAKE_LOCK_DOZE;
@@ -44,17 +47,25 @@
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.never;
 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;
@@ -68,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;
@@ -79,18 +93,29 @@
     private static final float BRIGHTNESS = 0.99f;
     private static final float BRIGHTNESS_DOZE = 0.5f;
 
+    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;
 
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        when(mFeatureFlags.isPolicyReasonInDisplayPowerRequestEnabled()).thenReturn(true);
         mPowerGroup = new PowerGroup(GROUP_ID, mWakefulnessCallbackMock, mNotifier,
                 mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true,
-                /* supportsSandman= */ true, TIMESTAMP_CREATE);
+                /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags);
     }
 
     @Test
@@ -101,10 +126,8 @@
                 eq(UID), /* opUid= */anyInt(), /* opPackageName= */ isNull(), /* details= */
                 isNull());
         String details = "wake PowerGroup1";
-        LatencyTracker latencyTracker = LatencyTracker.getInstance(
-                InstrumentationRegistry.getInstrumentation().getContext());
         mPowerGroup.wakeUpLocked(TIMESTAMP2, WAKE_REASON_PLUGGED_IN, details, UID,
-                /* opPackageName= */ null, /* opUid= */ 0, latencyTracker);
+                /* opPackageName= */ null, /* opUid= */ 0, LATENCY_TRACKER);
         verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
                 eq(WAKEFULNESS_AWAKE), eq(TIMESTAMP2), eq(WAKE_REASON_PLUGGED_IN), eq(UID),
                 /* opUid= */ anyInt(), /* opPackageName= */ isNull(), eq(details));
@@ -247,6 +270,10 @@
 
     @Test
     public void testUpdateWhileAwake_UpdatesDisplayPowerRequest() {
+        mPowerGroup.dozeLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_APPLICATION);
+        mPowerGroup.wakeUpLocked(TIMESTAMP2, WAKE_REASON_WAKE_MOTION, "details", UID,
+                /* opPackageName= */ null, /* opUid= */ 0, LATENCY_TRACKER);
+
         final boolean batterySaverEnabled = true;
         float brightnessFactor = 0.7f;
         PowerSaveState powerSaveState = new PowerSaveState.Builder()
@@ -274,6 +301,7 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DIM);
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_MOTION);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
         assertThat(displayPowerRequest.screenBrightnessOverrideTag.toString()).isEqualTo(tag);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(false);
@@ -288,6 +316,55 @@
     }
 
     @Test
+    public void testWakefulnessReasonInDisplayPowerRequestDisabled_wakefulnessReasonNotPopulated() {
+        when(mFeatureFlags.isPolicyReasonInDisplayPowerRequestEnabled()).thenReturn(false);
+        mPowerGroup.dozeLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_APPLICATION);
+        mPowerGroup.wakeUpLocked(TIMESTAMP2, WAKE_REASON_WAKE_MOTION, "details", UID,
+                /* opPackageName= */ null, /* opUid= */ 0, LATENCY_TRACKER);
+
+        mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+                /* overrideTag= */ "my/tag",
+                /* useProximitySensor= */ false,
+                /* boostScreenBrightness= */ false,
+                /* dozeScreenStateOverride= */ Display.STATE_ON,
+                /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
+                /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+                /* useNormalBrightnessForDoze= */ false,
+                /* overrideDrawWakeLock= */ false,
+                new PowerSaveState.Builder().build(),
+                /* quiescent= */ false,
+                /* dozeAfterScreenOff= */ false,
+                /* bootCompleted= */ true,
+                /* screenBrightnessBoostInProgress= */ false,
+                /* waitForNegativeProximity= */ false,
+                /* brightWhenDozing= */ false);
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+                mPowerGroup.mDisplayPowerRequest;
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
+
+        mPowerGroup.wakeUpLocked(TIMESTAMP2, WAKE_REASON_PLUGGED_IN, "details", UID,
+                /* opPackageName= */ null, /* opUid= */ 0, LATENCY_TRACKER);
+        mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+                /* overrideTag= */ "my/tag",
+                /* useProximitySensor= */ false,
+                /* boostScreenBrightness= */ false,
+                /* dozeScreenStateOverride= */ Display.STATE_ON,
+                /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
+                /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+                /* useNormalBrightnessForDoze= */ false,
+                /* overrideDrawWakeLock= */ false,
+                new PowerSaveState.Builder().build(),
+                /* quiescent= */ false,
+                /* dozeAfterScreenOff= */ false,
+                /* bootCompleted= */ true,
+                /* screenBrightnessBoostInProgress= */ false,
+                /* waitForNegativeProximity= */ false,
+                /* brightWhenDozing= */ false);
+        displayPowerRequest = mPowerGroup.mDisplayPowerRequest;
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
+    }
+
+    @Test
     public void testUpdateWhileDozing_UpdatesDisplayPowerRequest() {
         final boolean useNormalBrightnessForDoze = false;
         final boolean batterySaverEnabled = false;
@@ -319,6 +396,7 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DOZE);
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -363,6 +441,7 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DOZE);
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -405,6 +484,7 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -419,6 +499,10 @@
 
     @Test
     public void testUpdateQuiescent() {
+        mPowerGroup.dozeLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_APPLICATION);
+        mPowerGroup.wakeUpLocked(TIMESTAMP2, WAKE_REASON_WAKE_MOTION, "details", UID,
+                /* opPackageName= */ null, /* opUid= */ 0, LATENCY_TRACKER);
+
         final boolean batterySaverEnabled = false;
         float brightnessFactor = 0.3f;
         PowerSaveState powerSaveState = new PowerSaveState.Builder()
@@ -446,6 +530,11 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
+        // Note how the reason is STATE_REASON_DEFAULT_POLICY, instead of STATE_REASON_MOTION.
+        // This is because - although there was a wake up request from a motion, the quiescent state
+        // preceded and forced the policy to be OFF, so we ignore the reason associated with the
+        // wake up request.
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -487,6 +576,7 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -529,6 +619,7 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -569,6 +660,7 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -610,6 +702,7 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -650,6 +743,7 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -661,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 e9e21de..359cf63 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -70,7 +70,6 @@
 import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.hardware.display.DisplayManagerInternal;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
 import android.hardware.power.Boost;
 import android.hardware.power.Mode;
 import android.os.BatteryManager;
@@ -86,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;
@@ -103,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;
@@ -168,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;
@@ -247,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());
@@ -1201,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() {
@@ -1894,15 +1950,6 @@
     }
 
     @Test
-    public void testBoot_DesiredScreenPolicyShouldBeBright() {
-        createService();
-        startSystem();
-
-        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
-                DisplayPowerRequest.POLICY_BRIGHT);
-    }
-
-    @Test
     public void testQuiescentBoot_ShouldBeAsleep() {
         when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
         createService();
@@ -1914,40 +1961,6 @@
     }
 
     @Test
-    public void testQuiescentBoot_DesiredScreenPolicyShouldBeOff() {
-        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
-        createService();
-        startSystem();
-        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
-                DisplayPowerRequest.POLICY_OFF);
-    }
-
-    @Test
-    public void testQuiescentBoot_WakeUp_DesiredScreenPolicyShouldBeBright() {
-        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
-        createService();
-        startSystem();
-        forceAwake();
-        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
-                DisplayPowerRequest.POLICY_BRIGHT);
-    }
-
-    @Test
-    public void testQuiescentBoot_WakeKeyBeforeBootCompleted_AwakeAfterBootCompleted() {
-        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
-        createService();
-        startSystem();
-
-        mService.getBinderServiceInstance().wakeUp(mClock.now(),
-                PowerManager.WAKE_REASON_UNKNOWN, "testing IPowerManager.wakeUp()", "pkg.name");
-
-        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
-                DisplayPowerRequest.POLICY_BRIGHT);
-    }
-
-    @Test
     public void testIsAmbientDisplayAvailable_available() {
         createService();
         when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true);
@@ -3392,7 +3405,8 @@
                         null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
                         null /* callback */);
             }
-            assertThat(mService.getScreenOffTimeoutOverrideLocked(screenTimeout, screenDimTimeout))
+            assertThat(mService.getDefaultGroupScreenOffTimeoutOverrideLocked(screenTimeout,
+                           screenDimTimeout))
                     .isEqualTo(expect[2]);
             if (acquireWakeLock) {
                 mService.getBinderServiceInstance().releaseWakeLock(token, 0);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
index 0a1fc31..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
@@ -438,10 +440,7 @@
         doAnswer(invocation -> {
             LongArrayMultiStateCounter counter = invocation.getArgument(1);
             long timestampMs = invocation.getArgument(2);
-            LongArrayMultiStateCounter.LongArrayContainer container =
-                    new LongArrayMultiStateCounter.LongArrayContainer(NUM_CPU_FREQS);
-            container.setValues(cpuTimes);
-            counter.updateValues(container, timestampMs);
+            counter.updateValues(cpuTimes, timestampMs);
             return null;
         }).when(mKernelSingleUidTimeReader).addDelta(eq(testUid),
                 any(LongArrayMultiStateCounter.class), anyLong());
@@ -451,20 +450,13 @@
         doAnswer(invocation -> {
             LongArrayMultiStateCounter counter = invocation.getArgument(1);
             long timestampMs = invocation.getArgument(2);
-            LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
-                    invocation.getArgument(3);
-
-            LongArrayMultiStateCounter.LongArrayContainer container =
-                    new LongArrayMultiStateCounter.LongArrayContainer(NUM_CPU_FREQS);
-            container.setValues(cpuTimes);
-            counter.updateValues(container, timestampMs);
-            if (deltaContainer != null) {
-                deltaContainer.setValues(delta);
-            }
+            long[] deltaOut = invocation.getArgument(3);
+            counter.updateValues(cpuTimes, timestampMs);
+            System.arraycopy(delta, 0, deltaOut, 0, delta.length);
             return null;
         }).when(mKernelSingleUidTimeReader).addDelta(eq(testUid),
                 any(LongArrayMultiStateCounter.class), anyLong(),
-                any(LongArrayMultiStateCounter.LongArrayContainer.class));
+                any(long[].class));
     }
 
     @Test
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/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/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
index 71a65c8..4cea728 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
@@ -394,10 +394,7 @@
         doAnswer(invocation -> {
             LongArrayMultiStateCounter counter = invocation.getArgument(1);
             long timestampMs = invocation.getArgument(2);
-            LongArrayMultiStateCounter.LongArrayContainer container =
-                    new LongArrayMultiStateCounter.LongArrayContainer(NUM_CPU_FREQS);
-            container.setValues(cpuTimes);
-            counter.updateValues(container, timestampMs);
+            counter.updateValues(cpuTimes, timestampMs);
             return null;
         }).when(mMockKernelSingleUidTimeReader).addDelta(eq(uid),
                 any(LongArrayMultiStateCounter.class), anyLong());
diff --git a/services/tests/servicestests/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/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
index 510dd4d..cf628ad 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -315,7 +315,7 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_PRIVATE_SPACE_BP)
+    @RequiresFlagsEnabled(Flags.FLAG_EFFECTIVE_USER_BP)
     public void testCredentialOwnerIdAsUserId() throws Exception {
         when(mUserManager.getCredentialOwnerProfile(USER_ID)).thenReturn(OWNER_ID);
 
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/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index c741c6c..077bb03 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -2562,7 +2562,13 @@
         mTestLooper.dispatchAll();
 
         // User interacted with the DUT, so the device will not go to standby.
-        skipActiveSourceLostUi(0, true, true);
+        mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() {
+            @Override
+            public void onComplete(int result) {
+            }
+        });
+        mTestLooper.dispatchAll();
+
         assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
         assertThat(mPowerManager.isInteractive()).isTrue();
         assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
deleted file mode 100644
index 9ed2e88..0000000
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
+++ /dev/null
@@ -1,914 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.DEFAULT_FORMAT_VERSION;
-import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
-import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
-import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE;
-import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
-import static com.android.server.integrity.serializer.RuleBinarySerializer.INDEXED_RULE_SIZE_LIMIT;
-import static com.android.server.integrity.serializer.RuleBinarySerializer.NONINDEXED_RULE_SIZE_LIMIT;
-import static com.android.server.integrity.utils.TestUtils.getBits;
-import static com.android.server.integrity.utils.TestUtils.getBytes;
-import static com.android.server.integrity.utils.TestUtils.getValueBits;
-import static com.android.server.testutils.TestUtils.assertExpectException;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.integrity.AppInstallMetadata;
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.IntegrityUtils;
-import android.content.integrity.Rule;
-
-import androidx.annotation.NonNull;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.ByteArrayOutputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-
-@RunWith(JUnit4.class)
-public class RuleBinarySerializerTest {
-
-    private static final String SAMPLE_INSTALLER_NAME = "com.test.installer";
-    private static final String SAMPLE_INSTALLER_CERT = "installer_cert";
-
-    private static final String COMPOUND_FORMULA_START_BITS =
-            getBits(COMPOUND_FORMULA_START, SEPARATOR_BITS);
-    private static final String COMPOUND_FORMULA_END_BITS =
-            getBits(COMPOUND_FORMULA_END, SEPARATOR_BITS);
-    private static final String ATOMIC_FORMULA_START_BITS =
-            getBits(ATOMIC_FORMULA_START, SEPARATOR_BITS);
-
-    private static final String NOT = getBits(CompoundFormula.NOT, CONNECTOR_BITS);
-    private static final String AND = getBits(CompoundFormula.AND, CONNECTOR_BITS);
-    private static final String OR = getBits(CompoundFormula.OR, CONNECTOR_BITS);
-
-    private static final String PACKAGE_NAME = getBits(AtomicFormula.PACKAGE_NAME, KEY_BITS);
-    private static final String APP_CERTIFICATE = getBits(AtomicFormula.APP_CERTIFICATE, KEY_BITS);
-    private static final String INSTALLER_NAME = getBits(AtomicFormula.INSTALLER_NAME, KEY_BITS);
-    private static final String INSTALLER_CERTIFICATE =
-            getBits(AtomicFormula.INSTALLER_CERTIFICATE, KEY_BITS);
-    private static final String VERSION_CODE = getBits(AtomicFormula.VERSION_CODE, KEY_BITS);
-    private static final String PRE_INSTALLED = getBits(AtomicFormula.PRE_INSTALLED, KEY_BITS);
-
-    private static final String EQ = getBits(AtomicFormula.EQ, OPERATOR_BITS);
-
-    private static final String IS_NOT_HASHED = "0";
-    private static final String IS_HASHED = "1";
-
-    private static final String DENY = getBits(Rule.DENY, EFFECT_BITS);
-
-    private static final String START_BIT = "1";
-    private static final String END_BIT = "1";
-
-    private static final byte[] DEFAULT_FORMAT_VERSION_BYTES =
-            getBytes(getBits(DEFAULT_FORMAT_VERSION, FORMAT_VERSION_BITS));
-
-    private static final String SERIALIZED_START_INDEXING_KEY =
-            IS_NOT_HASHED
-                    + getBits(START_INDEXING_KEY.length(), VALUE_SIZE_BITS)
-                    + getValueBits(START_INDEXING_KEY);
-    private static final String SERIALIZED_END_INDEXING_KEY =
-            IS_NOT_HASHED
-                    + getBits(END_INDEXING_KEY.length(), VALUE_SIZE_BITS)
-                    + getValueBits(END_INDEXING_KEY);
-
-    @Test
-    public void testBinaryString_serializeNullRules() {
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-
-        assertExpectException(
-                RuleSerializeException.class,
-                /* expectedExceptionMessageRegex= */ "Null rules cannot be serialized.",
-                () -> binarySerializer.serialize(null, /* formatVersion= */ Optional.empty()));
-    }
-
-    @Test
-    public void testBinaryString_emptyRules() throws Exception {
-        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-
-        binarySerializer.serialize(
-                Collections.emptyList(),
-                /* formatVersion= */ Optional.empty(),
-                ruleOutputStream,
-                indexingOutputStream);
-
-        ByteArrayOutputStream expectedRuleOutputStream = new ByteArrayOutputStream();
-        expectedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        assertThat(ruleOutputStream.toByteArray())
-                .isEqualTo(expectedRuleOutputStream.toByteArray());
-
-        ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream();
-        String serializedIndexingBytes =
-                SERIALIZED_START_INDEXING_KEY
-                        + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
-                        + SERIALIZED_END_INDEXING_KEY
-                        + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32);
-        byte[] expectedIndexingBytes =
-                getBytes(
-                        serializedIndexingBytes
-                                + serializedIndexingBytes
-                                + serializedIndexingBytes);
-        expectedIndexingOutputStream.write(expectedIndexingBytes);
-        assertThat(indexingOutputStream.toByteArray())
-                .isEqualTo(expectedIndexingOutputStream.toByteArray());
-    }
-
-    @Test
-    public void testBinaryStream_serializeValidCompoundFormula() throws Exception {
-        String packageName = "com.test.app";
-        Rule rule =
-                new Rule(
-                        new CompoundFormula(
-                                CompoundFormula.NOT,
-                                Collections.singletonList(
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.PACKAGE_NAME,
-                                                packageName,
-                                                /* isHashedValue= */ false))),
-                        Rule.DENY);
-
-        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        binarySerializer.serialize(
-                Collections.singletonList(rule),
-                /* formatVersion= */ Optional.empty(),
-                ruleOutputStream,
-                indexingOutputStream);
-
-        String expectedBits =
-                START_BIT
-                        + COMPOUND_FORMULA_START_BITS
-                        + NOT
-                        + ATOMIC_FORMULA_START_BITS
-                        + PACKAGE_NAME
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(packageName.length(), VALUE_SIZE_BITS)
-                        + getValueBits(packageName)
-                        + COMPOUND_FORMULA_END_BITS
-                        + DENY
-                        + END_BIT;
-        ByteArrayOutputStream expectedRuleOutputStream = new ByteArrayOutputStream();
-        expectedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        expectedRuleOutputStream.write(getBytes(expectedBits));
-        assertThat(ruleOutputStream.toByteArray())
-                .isEqualTo(expectedRuleOutputStream.toByteArray());
-
-        ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream();
-        String expectedIndexingBitsForIndexed =
-                SERIALIZED_START_INDEXING_KEY
-                        + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
-                        + SERIALIZED_END_INDEXING_KEY
-                        + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32);
-        String expectedIndexingBitsForUnindexed =
-                SERIALIZED_START_INDEXING_KEY
-                        + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
-                        + SERIALIZED_END_INDEXING_KEY
-                        + getBits(
-                                DEFAULT_FORMAT_VERSION_BYTES.length + getBytes(expectedBits).length,
-                                /* numOfBits= */ 32);
-        expectedIndexingOutputStream.write(
-                getBytes(
-                        expectedIndexingBitsForIndexed
-                                + expectedIndexingBitsForIndexed
-                                + expectedIndexingBitsForUnindexed));
-
-        assertThat(indexingOutputStream.toByteArray())
-                .isEqualTo(expectedIndexingOutputStream.toByteArray());
-    }
-
-    @Test
-    public void testBinaryString_serializeValidCompoundFormula_notConnector() throws Exception {
-        String packageName = "com.test.app";
-        Rule rule =
-                new Rule(
-                        new CompoundFormula(
-                                CompoundFormula.NOT,
-                                Collections.singletonList(
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.PACKAGE_NAME,
-                                                packageName,
-                                                /* isHashedValue= */ false))),
-                        Rule.DENY);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits =
-                START_BIT
-                        + COMPOUND_FORMULA_START_BITS
-                        + NOT
-                        + ATOMIC_FORMULA_START_BITS
-                        + PACKAGE_NAME
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(packageName.length(), VALUE_SIZE_BITS)
-                        + getValueBits(packageName)
-                        + COMPOUND_FORMULA_END_BITS
-                        + DENY
-                        + END_BIT;
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        byteArrayOutputStream.write(getBytes(expectedBits));
-        byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_serializeValidCompoundFormula_andConnector() throws Exception {
-        String packageName = "com.test.app";
-        String appCertificate = "test_cert";
-        Rule rule =
-                new Rule(
-                        new CompoundFormula(
-                                CompoundFormula.AND,
-                                Arrays.asList(
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.PACKAGE_NAME,
-                                                packageName,
-                                                /* isHashedValue= */ false),
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.APP_CERTIFICATE,
-                                                appCertificate,
-                                                /* isHashedValue= */ false))),
-                        Rule.DENY);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits =
-                START_BIT
-                        + COMPOUND_FORMULA_START_BITS
-                        + AND
-                        + ATOMIC_FORMULA_START_BITS
-                        + PACKAGE_NAME
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(packageName.length(), VALUE_SIZE_BITS)
-                        + getValueBits(packageName)
-                        + ATOMIC_FORMULA_START_BITS
-                        + APP_CERTIFICATE
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(appCertificate.length(), VALUE_SIZE_BITS)
-                        + getValueBits(appCertificate)
-                        + COMPOUND_FORMULA_END_BITS
-                        + DENY
-                        + END_BIT;
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        byteArrayOutputStream.write(getBytes(expectedBits));
-        byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_serializeValidCompoundFormula_orConnector() throws Exception {
-        String packageName = "com.test.app";
-        String appCertificate = "test_cert";
-        Rule rule =
-                new Rule(
-                        new CompoundFormula(
-                                CompoundFormula.OR,
-                                Arrays.asList(
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.PACKAGE_NAME,
-                                                packageName,
-                                                /* isHashedValue= */ false),
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.APP_CERTIFICATE,
-                                                appCertificate,
-                                                /* isHashedValue= */ false))),
-                        Rule.DENY);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits =
-                START_BIT
-                        + COMPOUND_FORMULA_START_BITS
-                        + OR
-                        + ATOMIC_FORMULA_START_BITS
-                        + PACKAGE_NAME
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(packageName.length(), VALUE_SIZE_BITS)
-                        + getValueBits(packageName)
-                        + ATOMIC_FORMULA_START_BITS
-                        + APP_CERTIFICATE
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(appCertificate.length(), VALUE_SIZE_BITS)
-                        + getValueBits(appCertificate)
-                        + COMPOUND_FORMULA_END_BITS
-                        + DENY
-                        + END_BIT;
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        byteArrayOutputStream.write(getBytes(expectedBits));
-        byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_serializeValidAtomicFormula_stringValue() throws Exception {
-        String packageName = "com.test.app";
-        Rule rule =
-                new Rule(
-                        new AtomicFormula.StringAtomicFormula(
-                                AtomicFormula.PACKAGE_NAME,
-                                packageName,
-                                /* isHashedValue= */ false),
-                        Rule.DENY);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits =
-                START_BIT
-                        + ATOMIC_FORMULA_START_BITS
-                        + PACKAGE_NAME
-                        + EQ
-                        + IS_NOT_HASHED
-                        + getBits(packageName.length(), VALUE_SIZE_BITS)
-                        + getValueBits(packageName)
-                        + DENY
-                        + END_BIT;
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        byteArrayOutputStream.write(getBytes(expectedBits));
-        byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_serializeValidAtomicFormula_hashedValue() throws Exception {
-        String appCertificate = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
-        Rule rule =
-                new Rule(
-                        new AtomicFormula.StringAtomicFormula(
-                                AtomicFormula.APP_CERTIFICATE,
-                                IntegrityUtils.getHexDigest(
-                                        appCertificate.getBytes(StandardCharsets.UTF_8)),
-                                /* isHashedValue= */ true),
-                        Rule.DENY);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits =
-                START_BIT
-                        + ATOMIC_FORMULA_START_BITS
-                        + APP_CERTIFICATE
-                        + EQ
-                        + IS_HASHED
-                        + getBits(appCertificate.length(), VALUE_SIZE_BITS)
-                        + getValueBits(appCertificate)
-                        + DENY
-                        + END_BIT;
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        byteArrayOutputStream.write(getBytes(expectedBits));
-        byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_serializeValidAtomicFormula_integerValue() throws Exception {
-        long versionCode = 1;
-        Rule rule =
-                new Rule(
-                        new AtomicFormula.LongAtomicFormula(
-                                AtomicFormula.VERSION_CODE, AtomicFormula.EQ, versionCode),
-                        Rule.DENY);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits =
-                START_BIT
-                        + ATOMIC_FORMULA_START_BITS
-                        + VERSION_CODE
-                        + EQ
-                        + getBits(versionCode, /* numOfBits= */ 64)
-                        + DENY
-                        + END_BIT;
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        byteArrayOutputStream.write(getBytes(expectedBits));
-        byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_serializeValidAtomicFormula_booleanValue() throws Exception {
-        String preInstalled = "1";
-        Rule rule =
-                new Rule(
-                        new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
-                        Rule.DENY);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits =
-                START_BIT
-                        + ATOMIC_FORMULA_START_BITS
-                        + PRE_INSTALLED
-                        + EQ
-                        + preInstalled
-                        + DENY
-                        + END_BIT;
-        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-        byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        byteArrayOutputStream.write(getBytes(expectedBits));
-        byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_serializeInvalidFormulaType() throws Exception {
-        IntegrityFormula invalidFormula = getInvalidFormula();
-        Rule rule = new Rule(invalidFormula, Rule.DENY);
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-
-        assertExpectException(
-                RuleSerializeException.class,
-                /* expectedExceptionMessageRegex= */ "Malformed rule identified.",
-                () ->
-                        binarySerializer.serialize(
-                                Collections.singletonList(rule),
-                                /* formatVersion= */ Optional.empty()));
-    }
-
-    @Test
-    public void testBinaryString_serializeFormatVersion() throws Exception {
-        int formatVersion = 1;
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        String expectedBits = getBits(formatVersion, FORMAT_VERSION_BITS);
-        byte[] expectedRules = getBytes(expectedBits);
-
-        byte[] actualRules =
-                binarySerializer.serialize(
-                        Collections.emptyList(), /* formatVersion= */ Optional.of(formatVersion));
-
-        assertThat(actualRules).isEqualTo(expectedRules);
-    }
-
-    @Test
-    public void testBinaryString_verifyManyRulesAreIndexedCorrectly() throws Exception {
-        int ruleCount = 225;
-        String packagePrefix = "package.name.";
-        String appCertificatePrefix = "app.cert.";
-        String installerNamePrefix = "installer.";
-
-        // Create the rule set with 225 package name based rules, 225 app certificate indexed rules,
-        // and 225 non-indexed rules..
-        List<Rule> ruleList = new ArrayList();
-        for (int count = 0; count < ruleCount; count++) {
-            ruleList.add(
-                    getRuleWithPackageNameAndSampleInstallerName(
-                            String.format("%s%04d", packagePrefix, count)));
-        }
-        for (int count = 0; count < ruleCount; count++) {
-            ruleList.add(
-                    getRuleWithAppCertificateAndSampleInstallerName(
-                            String.format("%s%04d", appCertificatePrefix, count)));
-        }
-        for (int count = 0; count < ruleCount; count++) {
-            ruleList.add(
-                    getNonIndexedRuleWithInstallerName(
-                            String.format("%s%04d", installerNamePrefix, count)));
-        }
-
-        // Serialize the rules.
-        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-        binarySerializer.serialize(
-                ruleList,
-                /* formatVersion= */ Optional.empty(),
-                ruleOutputStream,
-                indexingOutputStream);
-
-        // Verify the rules file and index files.
-        ByteArrayOutputStream expectedOrderedRuleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream();
-
-        expectedOrderedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-        int totalBytesWritten = DEFAULT_FORMAT_VERSION_BYTES.length;
-
-        String expectedIndexingBytesForPackageNameIndexed =
-                SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-        for (int count = 0; count < ruleCount; count++) {
-            String packageName = String.format("%s%04d", packagePrefix, count);
-            if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) {
-                expectedIndexingBytesForPackageNameIndexed +=
-                        IS_NOT_HASHED
-                                + getBits(packageName.length(), VALUE_SIZE_BITS)
-                                + getValueBits(packageName)
-                                + getBits(totalBytesWritten, /* numOfBits= */ 32);
-            }
-
-            byte[] bytesForPackage =
-                    getBytes(
-                            getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
-                                    packageName));
-            expectedOrderedRuleOutputStream.write(bytesForPackage);
-            totalBytesWritten += bytesForPackage.length;
-        }
-        expectedIndexingBytesForPackageNameIndexed +=
-                SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-
-        String expectedIndexingBytesForAppCertificateIndexed =
-                SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-        for (int count = 0; count < ruleCount; count++) {
-            String appCertificate = String.format("%s%04d", appCertificatePrefix, count);
-            if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) {
-                expectedIndexingBytesForAppCertificateIndexed +=
-                        IS_NOT_HASHED
-                                + getBits(appCertificate.length(), VALUE_SIZE_BITS)
-                                + getValueBits(appCertificate)
-                                + getBits(totalBytesWritten, /* numOfBits= */ 32);
-            }
-
-            byte[] bytesForPackage =
-                    getBytes(
-                            getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
-                                    appCertificate));
-            expectedOrderedRuleOutputStream.write(bytesForPackage);
-            totalBytesWritten += bytesForPackage.length;
-        }
-        expectedIndexingBytesForAppCertificateIndexed +=
-                SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-
-        String expectedIndexingBytesForUnindexed =
-                SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-        for (int count = 0; count < ruleCount; count++) {
-            byte[] bytesForPackage =
-                    getBytes(
-                            getSerializedCompoundRuleWithInstallerNameAndInstallerCert(
-                                    String.format("%s%04d", installerNamePrefix, count)));
-            expectedOrderedRuleOutputStream.write(bytesForPackage);
-            totalBytesWritten += bytesForPackage.length;
-        }
-        expectedIndexingBytesForUnindexed +=
-                SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-        expectedIndexingOutputStream.write(
-                getBytes(
-                        expectedIndexingBytesForPackageNameIndexed
-                                + expectedIndexingBytesForAppCertificateIndexed
-                                + expectedIndexingBytesForUnindexed));
-
-        assertThat(ruleOutputStream.toByteArray())
-                .isEqualTo(expectedOrderedRuleOutputStream.toByteArray());
-        assertThat(indexingOutputStream.toByteArray())
-                .isEqualTo(expectedIndexingOutputStream.toByteArray());
-    }
-
-    @Test
-    public void testBinaryString_totalRuleSizeLimitReached() {
-        int ruleCount = INDEXED_RULE_SIZE_LIMIT - 1;
-        String packagePrefix = "package.name.";
-        String appCertificatePrefix = "app.cert.";
-        String installerNamePrefix = "installer.";
-
-        // Create the rule set with more rules than the system can handle in total.
-        List<Rule> ruleList = new ArrayList();
-        for (int count = 0; count < ruleCount; count++) {
-            ruleList.add(
-                    getRuleWithPackageNameAndSampleInstallerName(
-                            String.format("%s%04d", packagePrefix, count)));
-        }
-        for (int count = 0; count < ruleCount; count++) {
-            ruleList.add(
-                    getRuleWithAppCertificateAndSampleInstallerName(
-                            String.format("%s%04d", appCertificatePrefix, count)));
-        }
-        for (int count = 0; count < NONINDEXED_RULE_SIZE_LIMIT - 1; count++) {
-            ruleList.add(
-                    getNonIndexedRuleWithInstallerName(
-                            String.format("%s%04d", installerNamePrefix, count)));
-        }
-
-        // Serialize the rules.
-        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-
-        assertExpectException(
-                RuleSerializeException.class,
-                "Too many rules provided",
-                () ->
-                        binarySerializer.serialize(
-                                ruleList,
-                                /* formatVersion= */ Optional.empty(),
-                                ruleOutputStream,
-                                indexingOutputStream));
-    }
-
-    @Test
-    public void testBinaryString_tooManyPackageNameIndexedRules() {
-        String packagePrefix = "package.name.";
-
-        // Create a rule set with too many package name indexed rules.
-        List<Rule> ruleList = new ArrayList();
-        for (int count = 0; count < INDEXED_RULE_SIZE_LIMIT + 1; count++) {
-            ruleList.add(
-                    getRuleWithPackageNameAndSampleInstallerName(
-                            String.format("%s%04d", packagePrefix, count)));
-        }
-
-        // Serialize the rules.
-        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-
-        assertExpectException(
-                RuleSerializeException.class,
-                "Too many rules provided in the indexing group.",
-                () ->
-                        binarySerializer.serialize(
-                                ruleList,
-                                /* formatVersion= */ Optional.empty(),
-                                ruleOutputStream,
-                                indexingOutputStream));
-    }
-
-    @Test
-    public void testBinaryString_tooManyAppCertificateIndexedRules() {
-        String appCertificatePrefix = "app.cert.";
-
-        // Create a rule set with too many app certificate indexed rules.
-        List<Rule> ruleList = new ArrayList();
-        for (int count = 0; count < INDEXED_RULE_SIZE_LIMIT + 1; count++) {
-            ruleList.add(
-                    getRuleWithAppCertificateAndSampleInstallerName(
-                            String.format("%s%04d", appCertificatePrefix, count)));
-        }
-
-        // Serialize the rules.
-        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-
-        assertExpectException(
-                RuleSerializeException.class,
-                "Too many rules provided in the indexing group.",
-                () ->
-                        binarySerializer.serialize(
-                                ruleList,
-                                /* formatVersion= */ Optional.empty(),
-                                ruleOutputStream,
-                                indexingOutputStream));
-    }
-
-    @Test
-    public void testBinaryString_tooManyNonIndexedRules() {
-        String installerNamePrefix = "installer.";
-
-        // Create a rule set with too many unindexed rules.
-        List<Rule> ruleList = new ArrayList();
-        for (int count = 0; count < NONINDEXED_RULE_SIZE_LIMIT + 1; count++) {
-            ruleList.add(
-                    getNonIndexedRuleWithInstallerName(
-                            String.format("%s%04d", installerNamePrefix, count)));
-        }
-
-        // Serialize the rules.
-        ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
-        ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
-        RuleSerializer binarySerializer = new RuleBinarySerializer();
-
-        assertExpectException(
-                RuleSerializeException.class,
-                "Too many rules provided in the indexing group.",
-                () ->
-                        binarySerializer.serialize(
-                                ruleList,
-                                /* formatVersion= */ Optional.empty(),
-                                ruleOutputStream,
-                                indexingOutputStream));
-    }
-
-    private Rule getRuleWithPackageNameAndSampleInstallerName(String packageName) {
-        return new Rule(
-                new CompoundFormula(
-                        CompoundFormula.AND,
-                        Arrays.asList(
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.PACKAGE_NAME,
-                                        packageName,
-                                        /* isHashedValue= */ false),
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.INSTALLER_NAME,
-                                        SAMPLE_INSTALLER_NAME,
-                                        /* isHashedValue= */ false))),
-                Rule.DENY);
-    }
-
-    private String getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
-            String packageName) {
-        return START_BIT
-                + COMPOUND_FORMULA_START_BITS
-                + AND
-                + ATOMIC_FORMULA_START_BITS
-                + PACKAGE_NAME
-                + EQ
-                + IS_NOT_HASHED
-                + getBits(packageName.length(), VALUE_SIZE_BITS)
-                + getValueBits(packageName)
-                + ATOMIC_FORMULA_START_BITS
-                + INSTALLER_NAME
-                + EQ
-                + IS_NOT_HASHED
-                + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS)
-                + getValueBits(SAMPLE_INSTALLER_NAME)
-                + COMPOUND_FORMULA_END_BITS
-                + DENY
-                + END_BIT;
-    }
-
-    private Rule getRuleWithAppCertificateAndSampleInstallerName(String certificate) {
-        return new Rule(
-                new CompoundFormula(
-                        CompoundFormula.AND,
-                        Arrays.asList(
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.APP_CERTIFICATE,
-                                        certificate,
-                                        /* isHashedValue= */ false),
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.INSTALLER_NAME,
-                                        SAMPLE_INSTALLER_NAME,
-                                        /* isHashedValue= */ false))),
-                Rule.DENY);
-    }
-
-    private String getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
-            String appCertificate) {
-        return START_BIT
-                + COMPOUND_FORMULA_START_BITS
-                + AND
-                + ATOMIC_FORMULA_START_BITS
-                + APP_CERTIFICATE
-                + EQ
-                + IS_NOT_HASHED
-                + getBits(appCertificate.length(), VALUE_SIZE_BITS)
-                + getValueBits(appCertificate)
-                + ATOMIC_FORMULA_START_BITS
-                + INSTALLER_NAME
-                + EQ
-                + IS_NOT_HASHED
-                + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS)
-                + getValueBits(SAMPLE_INSTALLER_NAME)
-                + COMPOUND_FORMULA_END_BITS
-                + DENY
-                + END_BIT;
-    }
-
-    private Rule getNonIndexedRuleWithInstallerName(String installerName) {
-        return new Rule(
-                new CompoundFormula(
-                        CompoundFormula.AND,
-                        Arrays.asList(
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.INSTALLER_NAME,
-                                        installerName,
-                                        /* isHashedValue= */ false),
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.INSTALLER_CERTIFICATE,
-                                        SAMPLE_INSTALLER_CERT,
-                                        /* isHashedValue= */ false))),
-                Rule.DENY);
-    }
-
-    private String getSerializedCompoundRuleWithInstallerNameAndInstallerCert(
-            String installerName) {
-        return START_BIT
-                + COMPOUND_FORMULA_START_BITS
-                + AND
-                + ATOMIC_FORMULA_START_BITS
-                + INSTALLER_NAME
-                + EQ
-                + IS_NOT_HASHED
-                + getBits(installerName.length(), VALUE_SIZE_BITS)
-                + getValueBits(installerName)
-                + ATOMIC_FORMULA_START_BITS
-                + INSTALLER_CERTIFICATE
-                + EQ
-                + IS_NOT_HASHED
-                + getBits(SAMPLE_INSTALLER_CERT.length(), VALUE_SIZE_BITS)
-                + getValueBits(SAMPLE_INSTALLER_CERT)
-                + COMPOUND_FORMULA_END_BITS
-                + DENY
-                + END_BIT;
-    }
-
-    private static IntegrityFormula getInvalidFormula() {
-        return new AtomicFormula(0) {
-            @Override
-            public int getTag() {
-                return 0;
-            }
-
-            @Override
-            public boolean matches(AppInstallMetadata appInstallMetadata) {
-                return false;
-            }
-
-            @Override
-            public boolean isAppCertificateFormula() {
-                return false;
-            }
-
-            @Override
-            public boolean isAppCertificateLineageFormula() {
-                return false;
-            }
-
-            @Override
-            public boolean isInstallerFormula() {
-                return false;
-            }
-
-            @Override
-            public int hashCode() {
-                return super.hashCode();
-            }
-
-            @Override
-            public boolean equals(Object obj) {
-                return super.equals(obj);
-            }
-
-            @NonNull
-            @Override
-            protected Object clone() throws CloneNotSupportedException {
-                return super.clone();
-            }
-
-            @Override
-            public String toString() {
-                return super.toString();
-            }
-
-            @Override
-            protected void finalize() throws Throwable {
-                super.finalize();
-            }
-        };
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
deleted file mode 100644
index 6dccdf5..0000000
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets;
-import static com.android.server.testutils.TestUtils.assertExpectException;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.integrity.AppInstallMetadata;
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.Rule;
-
-import androidx.annotation.NonNull;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
-/** Unit tests for {@link RuleIndexingDetailsIdentifier}. */
-@RunWith(JUnit4.class)
-public class RuleIndexingDetailsIdentifierTest {
-
-    private static final String SAMPLE_APP_CERTIFICATE = "testcert";
-    private static final String SAMPLE_INSTALLER_NAME = "com.test.installer";
-    private static final String SAMPLE_INSTALLER_CERTIFICATE = "installercert";
-    private static final String SAMPLE_PACKAGE_NAME = "com.test.package";
-
-    private static final AtomicFormula ATOMIC_FORMULA_WITH_PACKAGE_NAME =
-            new AtomicFormula.StringAtomicFormula(
-                    AtomicFormula.PACKAGE_NAME,
-                    SAMPLE_PACKAGE_NAME,
-                    /* isHashedValue= */ false);
-    private static final AtomicFormula ATOMIC_FORMULA_WITH_APP_CERTIFICATE =
-            new AtomicFormula.StringAtomicFormula(
-                    AtomicFormula.APP_CERTIFICATE,
-                    SAMPLE_APP_CERTIFICATE,
-                    /* isHashedValue= */ false);
-    private static final AtomicFormula ATOMIC_FORMULA_WITH_INSTALLER_NAME =
-            new AtomicFormula.StringAtomicFormula(
-                    AtomicFormula.INSTALLER_NAME,
-                    SAMPLE_INSTALLER_NAME,
-                    /* isHashedValue= */ false);
-    private static final AtomicFormula ATOMIC_FORMULA_WITH_INSTALLER_CERTIFICATE =
-            new AtomicFormula.StringAtomicFormula(
-                    AtomicFormula.INSTALLER_CERTIFICATE,
-                    SAMPLE_INSTALLER_CERTIFICATE,
-                    /* isHashedValue= */ false);
-    private static final AtomicFormula ATOMIC_FORMULA_WITH_VERSION_CODE =
-            new AtomicFormula.LongAtomicFormula(AtomicFormula.VERSION_CODE,
-                    AtomicFormula.EQ, 12);
-    private static final AtomicFormula ATOMIC_FORMULA_WITH_ISPREINSTALLED =
-            new AtomicFormula.BooleanAtomicFormula(
-                    AtomicFormula.PRE_INSTALLED, /* booleanValue= */
-                    true);
-
-
-    private static final Rule RULE_WITH_PACKAGE_NAME =
-            new Rule(
-                    new CompoundFormula(
-                            CompoundFormula.AND,
-                            Arrays.asList(
-                                    ATOMIC_FORMULA_WITH_PACKAGE_NAME,
-                                    ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
-                    Rule.DENY);
-    private static final Rule RULE_WITH_APP_CERTIFICATE =
-            new Rule(
-                    new CompoundFormula(
-                            CompoundFormula.AND,
-                            Arrays.asList(
-                                    ATOMIC_FORMULA_WITH_APP_CERTIFICATE,
-                                    ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
-                    Rule.DENY);
-    private static final Rule RULE_WITH_INSTALLER_RESTRICTIONS =
-            new Rule(
-                    new CompoundFormula(
-                            CompoundFormula.AND,
-                            Arrays.asList(
-                                    ATOMIC_FORMULA_WITH_INSTALLER_NAME,
-                                    ATOMIC_FORMULA_WITH_INSTALLER_CERTIFICATE)),
-                    Rule.DENY);
-
-    private static final Rule RULE_WITH_NONSTRING_RESTRICTIONS =
-            new Rule(
-                    new CompoundFormula(
-                            CompoundFormula.AND,
-                            Arrays.asList(
-                                    ATOMIC_FORMULA_WITH_VERSION_CODE,
-                                    ATOMIC_FORMULA_WITH_ISPREINSTALLED)),
-                    Rule.DENY);
-    public static final int INVALID_FORMULA_TAG = -1;
-
-    @Test
-    public void getIndexType_nullRule() {
-        List<Rule> ruleList = null;
-
-        assertExpectException(
-                IllegalArgumentException.class,
-                /* expectedExceptionMessageRegex= */
-                "Index buckets cannot be created for null rule list.",
-                () -> splitRulesIntoIndexBuckets(ruleList));
-    }
-
-    @Test
-    public void getIndexType_invalidFormula() {
-        List<Rule> ruleList = new ArrayList();
-        ruleList.add(new Rule(getInvalidFormula(), Rule.DENY));
-
-        assertExpectException(
-                IllegalArgumentException.class,
-                /* expectedExceptionMessageRegex= */ "Malformed rule identified.",
-                () -> splitRulesIntoIndexBuckets(ruleList));
-    }
-
-    @Test
-    public void getIndexType_ruleContainingPackageNameFormula() {
-        List<Rule> ruleList = new ArrayList();
-        ruleList.add(RULE_WITH_PACKAGE_NAME);
-
-        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
-        // Verify the resulting map content.
-        assertThat(result.keySet())
-                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
-        assertThat(result.get(NOT_INDEXED)).isEmpty();
-        assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
-        assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly(SAMPLE_PACKAGE_NAME);
-        assertThat(result.get(PACKAGE_NAME_INDEXED).get(SAMPLE_PACKAGE_NAME))
-                .containsExactly(RULE_WITH_PACKAGE_NAME);
-    }
-
-    @Test
-    public void getIndexType_ruleContainingAppCertificateFormula() {
-        List<Rule> ruleList = new ArrayList();
-        ruleList.add(RULE_WITH_APP_CERTIFICATE);
-
-        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
-        assertThat(result.keySet())
-                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
-        assertThat(result.get(NOT_INDEXED)).isEmpty();
-        assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
-        assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet())
-                .containsExactly(SAMPLE_APP_CERTIFICATE);
-        assertThat(result.get(APP_CERTIFICATE_INDEXED).get(SAMPLE_APP_CERTIFICATE))
-                .containsExactly(RULE_WITH_APP_CERTIFICATE);
-    }
-
-    @Test
-    public void getIndexType_ruleWithUnindexedCompoundFormula() {
-        List<Rule> ruleList = new ArrayList();
-        ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);
-
-        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
-        assertThat(result.keySet())
-                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
-        assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
-        assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
-        assertThat(result.get(NOT_INDEXED).get("N/A"))
-                .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS);
-    }
-
-    @Test
-    public void getIndexType_ruleContainingCompoundFormulaWithIntAndBoolean() {
-        List<Rule> ruleList = new ArrayList();
-        ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);
-
-        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
-        assertThat(result.keySet())
-                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
-        assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
-        assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
-        assertThat(result.get(NOT_INDEXED).get("N/A"))
-                .containsExactly(RULE_WITH_NONSTRING_RESTRICTIONS);
-    }
-
-    @Test
-    public void getIndexType_negatedRuleContainingPackageNameFormula() {
-        Rule negatedRule =
-                new Rule(
-                        new CompoundFormula(
-                                CompoundFormula.NOT,
-                                Arrays.asList(
-                                        new CompoundFormula(
-                                                CompoundFormula.AND,
-                                                Arrays.asList(
-                                                        ATOMIC_FORMULA_WITH_PACKAGE_NAME,
-                                                        ATOMIC_FORMULA_WITH_APP_CERTIFICATE)))),
-                        Rule.DENY);
-        List<Rule> ruleList = new ArrayList();
-        ruleList.add(negatedRule);
-
-        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
-        assertThat(result.keySet())
-                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
-        assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
-        assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
-        assertThat(result.get(NOT_INDEXED).get("N/A")).containsExactly(negatedRule);
-    }
-
-    @Test
-    public void getIndexType_allRulesTogetherSplitCorrectly() {
-        Rule packageNameRuleA = getRuleWithPackageName("aaa");
-        Rule packageNameRuleB = getRuleWithPackageName("bbb");
-        Rule packageNameRuleC = getRuleWithPackageName("ccc");
-        Rule certificateRule1 = getRuleWithAppCertificate("cert1");
-        Rule certificateRule2 = getRuleWithAppCertificate("cert2");
-        Rule certificateRule3 = getRuleWithAppCertificate("cert3");
-
-        List<Rule> ruleList = new ArrayList();
-        ruleList.add(packageNameRuleB);
-        ruleList.add(packageNameRuleC);
-        ruleList.add(packageNameRuleA);
-        ruleList.add(certificateRule3);
-        ruleList.add(certificateRule2);
-        ruleList.add(certificateRule1);
-        ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);
-        ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);
-
-        Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
-        assertThat(result.keySet())
-                .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
-
-        // We check asserts this way to ensure ordering based on package name.
-        assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly("aaa", "bbb", "ccc");
-
-        // We check asserts this way to ensure ordering based on app certificate.
-        assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet()).containsExactly("cert1", "cert2",
-                "cert3");
-
-        assertThat(result.get(NOT_INDEXED).get("N/A"))
-                .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS,
-                        RULE_WITH_NONSTRING_RESTRICTIONS);
-    }
-
-    private Rule getRuleWithPackageName(String packageName) {
-        return new Rule(
-                new CompoundFormula(
-                        CompoundFormula.AND,
-                        Arrays.asList(
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.PACKAGE_NAME,
-                                        packageName,
-                                        /* isHashedValue= */ false),
-                                ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
-                Rule.DENY);
-    }
-
-    private Rule getRuleWithAppCertificate(String certificate) {
-        return new Rule(
-                new CompoundFormula(
-                        CompoundFormula.AND,
-                        Arrays.asList(
-                                new AtomicFormula.StringAtomicFormula(
-                                        AtomicFormula.APP_CERTIFICATE,
-                                        certificate,
-                                        /* isHashedValue= */ false),
-                                ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
-                Rule.DENY);
-    }
-
-    private IntegrityFormula getInvalidFormula() {
-        return new AtomicFormula(0) {
-            @Override
-            public int getTag() {
-                return INVALID_FORMULA_TAG;
-            }
-
-            @Override
-            public boolean matches(AppInstallMetadata appInstallMetadata) {
-                return false;
-            }
-
-            @Override
-            public boolean isAppCertificateFormula() {
-                return false;
-            }
-
-            @Override
-            public boolean isAppCertificateLineageFormula() {
-                return false;
-            }
-
-            @Override
-            public boolean isInstallerFormula() {
-                return false;
-            }
-
-            @Override
-            public int hashCode() {
-                return super.hashCode();
-            }
-
-            @Override
-            public boolean equals(Object obj) {
-                return super.equals(obj);
-            }
-
-            @NonNull
-            @Override
-            protected Object clone() throws CloneNotSupportedException {
-                return super.clone();
-            }
-
-            @Override
-            public String toString() {
-                return super.toString();
-            }
-
-            @Override
-            protected void finalize() throws Throwable {
-                super.finalize();
-            }
-        };
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
index 4a43c2e..9d7b6a1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
@@ -977,11 +977,19 @@
         assertEquals(USER_ID_1, UserHandle.getUserId(uid));
 
         mPackageListObserver.onPackageRemoved(PACKAGE_NAME_1, uid);
+        // Test that notifyAllCallbacks doesn't trigger for non-background-installed package
+        mPackageListObserver.onPackageRemoved(PACKAGE_NAME_3, uid);
         mTestLooper.dispatchAll();
 
         assertEquals(1, packages.size());
         assertFalse(packages.contains(USER_ID_1, PACKAGE_NAME_1));
         assertTrue(packages.contains(USER_ID_2, PACKAGE_NAME_2));
+
+        verify(mCallbackHelper)
+                .notifyAllCallbacks(
+                        USER_ID_1,
+                        PACKAGE_NAME_1,
+                        BackgroundInstallControlService.INSTALL_EVENT_TYPE_UNINSTALL);
     }
 
     @Test
diff --git a/services/tests/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 ac021e1..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";
@@ -2895,6 +2929,44 @@
     }
 
     @Test
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+            android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
+    public void testRemoveScheduledForceGroup_onNotificationCanceled() throws Exception {
+        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, "tag", null,
+                false);
+        when(mGroupHelper.onNotificationPosted(any(), anyBoolean())).thenReturn(false);
+        mService.addEnqueuedNotification(r);
+        NotificationManagerService.PostNotificationRunnable runnable =
+                mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+                r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
+        runnable.run();
+        waitForIdle();
+
+        // Post an update to the notification
+        NotificationRecord r_update =
+                generateNotificationRecord(mTestNotificationChannel, 0, "tag", null, false);
+        mService.addEnqueuedNotification(r_update);
+        runnable = mService.new PostNotificationRunnable(r_update.getKey(),
+                r_update.getSbn().getPackageName(), r_update.getUid(),
+                mPostNotificationTrackerFactory.newTracker(null));
+        runnable.run();
+        waitForIdle();
+
+        // Cancel the notification
+        mBinderService.cancelNotificationWithTag(r.getSbn().getPackageName(),
+                r.getSbn().getPackageName(), r.getSbn().getTag(),
+                r.getSbn().getId(), r.getSbn().getUserId());
+        waitForIdle();
+
+        mTestableLooper.moveTimeForward(DELAY_FORCE_REGROUP_TIME);
+        waitForIdle();
+
+        // Check that onNotificationPostedWithDelay was canceled
+        verify(mGroupHelper, times(1)).onNotificationPosted(any(), anyBoolean());
+        verify(mGroupHelper, never()).onNotificationPostedWithDelay(any(), any(), any());
+    }
+
+    @Test
     @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
     public void testEnqueueNotification_forceGrouped_clearsSummaryFlag() throws Exception {
         final String originalGroupName = "originalGroup";
@@ -7442,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);
@@ -14327,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) {
@@ -14345,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);
     }
@@ -14362,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(
@@ -17029,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/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 8aa8a84..0933590 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -59,7 +59,7 @@
 import android.os.vibrator.Flags;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.PwleSegment;
+import android.os.vibrator.PwlePoint;
 import android.os.vibrator.RampSegment;
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationConfig;
@@ -888,7 +888,7 @@
         fakeVibrator.setMinEnvelopeEffectControlPointDurationMillis(20);
 
         VibrationEffect effect = VibrationEffect.startWaveformEnvelope()
-                .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20)
+                .addControlPoint(/*amplitude=*/ 0.1f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20)
                 .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 30)
                 .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 20)
                 .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 30)
@@ -902,20 +902,47 @@
         verifyCallbacksTriggered(vibration, Status.FINISHED);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
         assertEquals(Arrays.asList(
-                        expectedPwle(/* startAmplitude= */ 0.0f, /* endAmplitude= */ 0.0f,
-                                /* startFrequencyHz= */ 60f, /* endFrequencyHz= */ 60f,
-                                /* duration= */ 20),
-                        expectedPwle(/* startAmplitude= */ 0.0f, /* endAmplitude= */ 0.3f,
-                                /* startFrequencyHz= */ 60f, /* endFrequencyHz= */ 100f,
-                                /* duration= */ 30),
-                        expectedPwle(/* startAmplitude= */ 0.3f, /* endAmplitude= */ 0.4f,
-                                /* startFrequencyHz= */ 100f, /* endFrequencyHz= */ 120f,
-                                /* duration= */ 20),
-                        expectedPwle(/* startAmplitude= */ 0.4f, /* endAmplitude= */ 0.0f,
-                                /* startFrequencyHz= */ 120f, /* endFrequencyHz= */ 120f,
-                                /* duration= */ 30)
-                ),
-                fakeVibrator.getEffectSegments(vibration.id));
+                expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 0),
+                expectedPwle(/*amplitude=*/ 0.1f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20),
+                expectedPwle(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 30),
+                expectedPwle(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 20),
+                expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 30)
+        ), fakeVibrator.getEffectPwlePoints(vibration.id));
+
+    }
+
+    @Test
+    @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void vibrate_singleVibratorPwle_withInitialFrequency_runsComposePwleV2() {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
+        fakeVibrator.setResonantFrequency(150);
+        fakeVibrator.setFrequenciesHz(new float[]{30f, 50f, 100f, 120f, 150f});
+        fakeVibrator.setOutputAccelerationsGs(new float[]{0.3f, 0.5f, 1.0f, 0.8f, 0.6f});
+        fakeVibrator.setMaxEnvelopeEffectSize(10);
+        fakeVibrator.setMinEnvelopeEffectControlPointDurationMillis(20);
+
+        VibrationEffect effect = VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 30)
+                .addControlPoint(/*amplitude=*/ 0.1f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20)
+                .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 30)
+                .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 20)
+                .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 30)
+                .build();
+        HalVibration vibration = startThreadAndDispatcher(effect);
+        waitForCompletion();
+
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verifyCallbacksTriggered(vibration, Status.FINISHED);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertEquals(Arrays.asList(
+                expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 30f, /*timeMillis=*/ 0),
+                expectedPwle(/*amplitude=*/ 0.1f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20),
+                expectedPwle(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 30),
+                expectedPwle(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 20),
+                expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 30)
+        ), fakeVibrator.getEffectPwlePoints(vibration.id));
 
     }
 
@@ -2013,10 +2040,8 @@
                 duration);
     }
 
-    private VibrationEffectSegment expectedPwle(float startAmplitude, float endAmplitude,
-            float startFrequencyHz, float endFrequencyHz, int duration) {
-        return new PwleSegment(startAmplitude, endAmplitude, startFrequencyHz, endFrequencyHz,
-                duration);
+    private PwlePoint expectedPwle(float amplitude, float frequencyHz, int timeMillis) {
+        return new PwlePoint(amplitude, frequencyHz, timeMillis);
     }
 
     private List<Float> expectedAmplitudes(int... amplitudes) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
index bc8db3b..0978f48 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
@@ -44,7 +44,7 @@
 import android.os.test.TestLooper;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.PwleSegment;
+import android.os.vibrator.PwlePoint;
 import android.os.vibrator.RampSegment;
 
 import androidx.test.InstrumentationRegistry;
@@ -274,9 +274,9 @@
         when(mNativeWrapperMock.composePwleV2(any(), anyLong())).thenReturn(15L);
         VibratorController controller = createController();
 
-        PwleSegment[] primitives = new PwleSegment[]{
-                new PwleSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1,
-                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 200, /* duration= */ 10)
+        PwlePoint[] primitives = new PwlePoint[]{
+                new PwlePoint(/*amplitude=*/ 0, /*frequencyHz=*/ 100, /*timeMillis=*/ 0),
+                new PwlePoint(/*amplitude=*/ 1, /*frequencyHz=*/ 200, /*timeMillis=*/ 10)
         };
         assertEquals(15L, controller.on(primitives, 12));
         assertTrue(controller.isVibrating());
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
index 2c3e9b2..4dc59c2 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -26,7 +26,7 @@
 import android.os.VibratorInfo;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.PwleSegment;
+import android.os.vibrator.PwlePoint;
 import android.os.vibrator.RampSegment;
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationEffectSegment;
@@ -50,6 +50,7 @@
     private final Map<Long, PrebakedSegment> mEnabledAlwaysOnEffects = new HashMap<>();
     private final Map<Long, List<VibrationEffectSegment>> mEffectSegments = new TreeMap<>();
     private final Map<Long, List<VibrationEffect.VendorEffect>> mVendorEffects = new TreeMap<>();
+    private final Map<Long, List<PwlePoint>> mEffectPwlePoints = new TreeMap<>();
     private final Map<Long, List<Integer>> mBraking = new HashMap<>();
     private final List<Float> mAmplitudes = new ArrayList<>();
     private final List<Boolean> mExternalControlStates = new ArrayList<>();
@@ -91,6 +92,10 @@
         mVendorEffects.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(vendorEffect);
     }
 
+    void recordEffectPwlePoint(long vibrationId, PwlePoint pwlePoint) {
+        mEffectPwlePoints.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(pwlePoint);
+    }
+
     void recordBraking(long vibrationId, int braking) {
         mBraking.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(braking);
     }
@@ -195,11 +200,11 @@
         }
 
         @Override
-        public long composePwleV2(PwleSegment[] primitives, long vibrationId) {
+        public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId) {
             long duration = 0;
-            for (PwleSegment primitive: primitives) {
-                duration += primitive.getDuration();
-                recordEffectSegment(vibrationId, primitive);
+            for (PwlePoint pwlePoint: pwlePoints) {
+                duration += pwlePoint.getTimeMillis();
+                recordEffectPwlePoint(vibrationId, pwlePoint);
             }
             applyLatency(mOnLatency);
             scheduleListener(duration, vibrationId);
@@ -482,6 +487,28 @@
         return result;
     }
 
+    /** Return list of {@link PwlePoint} played by this controller, in order. */
+    public List<PwlePoint> getEffectPwlePoints(long vibrationId) {
+        if (mEffectPwlePoints.containsKey(vibrationId)) {
+            return new ArrayList<>(mEffectPwlePoints.get(vibrationId));
+        } else {
+            return new ArrayList<>();
+        }
+    }
+
+    /**
+     * Returns a list of all vibrations' {@link PwlePoint}s, for external-use where vibration
+     * IDs aren't exposed.
+     */
+    public List<PwlePoint> getAllEffectPwlePoints() {
+        // Returns segments in order of vibrationId, which increases over time. TreeMap gives order.
+        ArrayList<PwlePoint> result = new ArrayList<>();
+        for (List<PwlePoint> subList : mEffectPwlePoints.values()) {
+            result.addAll(subList);
+        }
+        return result;
+    }
+
     /** Return list of states set for external control to the fake vibrator hardware. */
     public List<Boolean> getExternalControlStates() {
         return mExternalControlStates;
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
index ad11c26..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
@@ -331,6 +331,7 @@
         mPhoneWindowManager.setupAssistForLaunch();
         mPhoneWindowManager.overrideTogglePanel();
         mPhoneWindowManager.overrideInjectKeyEvent();
+        mPhoneWindowManager.overrideRoleManager();
     }
 
     @Test
@@ -396,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",
@@ -744,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/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index d4ba3b2..9e7575f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -538,7 +538,7 @@
     public void testConsecutiveLaunchNewTask() {
         final IBinder launchCookie = mock(IBinder.class);
         final WindowContainerToken launchRootTask = mock(WindowContainerToken.class);
-        mTrampolineActivity.noDisplay = true;
+        mTrampolineActivity.setIsNoDisplay(true);
         mTrampolineActivity.mLaunchCookie = launchCookie;
         mTrampolineActivity.mLaunchRootTask = launchRootTask;
         onActivityLaunched(mTrampolineActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index b8cfa7c..00c9691 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -259,6 +259,10 @@
         doReturn(embedded).when(mActivityStack.top()).isEmbedded();
     }
 
+    void setTopActivityHasLetterboxedBounds(boolean letterboxed) {
+        doReturn(letterboxed).when(mActivityStack.top()).areBoundsLetterboxed();
+    }
+
     void setTopActivityVisible(boolean isVisible) {
         doReturn(isVisible).when(mActivityStack.top()).isVisible();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 20dcdde..d6be915 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -24,7 +24,9 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
 import static android.window.BackNavigationInfo.typeToString;
+import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
@@ -103,6 +105,13 @@
 
     @Before
     public void setUp() throws Exception {
+        final TransitionController transitionController = mAtm.getTransitionController();
+        final Transition fakeTransition = new Transition(TRANSIT_PREPARE_BACK_NAVIGATION,
+                0 /* flag */, transitionController, transitionController.mSyncEngine);
+        spyOn(transitionController);
+        doReturn(fakeTransition).when(transitionController)
+                .createTransition(anyInt(), anyInt());
+
         final BackNavigationController original = new BackNavigationController();
         original.setWindowManager(mWm);
         mBackNavigationController = Mockito.spy(original);
@@ -111,6 +120,7 @@
         LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal);
         mBackAnimationAdapter = mock(BackAnimationAdapter.class);
         doReturn(true).when(mBackAnimationAdapter).isAnimatable(anyInt());
+        Mockito.doNothing().when(mBackNavigationController).startAnimation();
         mNavigationMonitor = mock(BackNavigationController.NavigationMonitor.class);
         mRootHomeTask = initHomeActivity();
     }
@@ -446,7 +456,7 @@
                 new OnBackInvokedCallbackInfo(
                         callback,
                         OnBackInvokedDispatcher.PRIORITY_DEFAULT,
-                        /* isAnimationCallback = */ false));
+                        /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED));
 
         BackNavigationInfo backNavigationInfo = startBackNavigation();
         assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
@@ -467,7 +477,7 @@
                 new OnBackInvokedCallbackInfo(
                         callback,
                         OnBackInvokedDispatcher.PRIORITY_DEFAULT,
-                        /* isAnimationCallback = */ true));
+                        /* isAnimationCallback = */ true, OVERRIDE_UNDEFINED));
 
         BackNavigationInfo backNavigationInfo = startBackNavigation();
         assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
@@ -608,7 +618,7 @@
                 new OnBackInvokedCallbackInfo(
                         callback,
                         OnBackInvokedDispatcher.PRIORITY_DEFAULT,
-                        /* isAnimationCallback = */ false));
+                        /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED));
 
         BackNavigationInfo backNavigationInfo = startBackNavigation();
         assertThat(backNavigationInfo).isNull();
@@ -722,7 +732,7 @@
                 new OnBackInvokedCallbackInfo(
                         callback,
                         OnBackInvokedDispatcher.PRIORITY_SYSTEM,
-                        /* isAnimationCallback = */ false));
+                        /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED));
         return callback;
     }
 
@@ -732,7 +742,7 @@
                 new OnBackInvokedCallbackInfo(
                         callback,
                         OnBackInvokedDispatcher.PRIORITY_DEFAULT,
-                        /* isAnimationCallback = */ false));
+                        /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED));
         return callback;
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
index eacb8e9..a0c5b54 100644
--- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -25,7 +25,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -190,14 +189,9 @@
         verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
                 eq(appWindow.getSurfaceControl()), anyFloat(),
                 eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS));
-        if (explicitRefreshRateHints()) {
-            verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
-                    appWindow.getSurfaceControl(),
-                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
-        } else {
-            verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
-                    any(SurfaceControl.class), anyInt());
-        }
+        verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+                appWindow.getSurfaceControl(),
+                SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     }
 
     @Test
@@ -226,14 +220,9 @@
         verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
                 eq(appWindow.getSurfaceControl()), anyFloat(),
                 eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS));
-        if (explicitRefreshRateHints()) {
-            verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
-                    appWindow.getSurfaceControl(),
-                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
-        } else {
-            verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
-                    any(SurfaceControl.class), anyInt());
-        }
+        verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+                appWindow.getSurfaceControl(),
+                SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     }
 
     @Test
@@ -288,14 +277,9 @@
         verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
                 appWindow.getSurfaceControl(), 60,
                 Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS);
-        if (explicitRefreshRateHints()) {
-            verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
-                    appWindow.getSurfaceControl(),
-                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
-        } else {
-            verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
-                    any(SurfaceControl.class), anyInt());
-        }
+        verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+                appWindow.getSurfaceControl(),
+                SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     }
 
     @Test
@@ -352,13 +336,8 @@
         verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
                 appWindow.getSurfaceControl(), 60,
                 Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, Surface.CHANGE_FRAME_RATE_ALWAYS);
-        if (explicitRefreshRateHints()) {
-            verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
-                    appWindow.getSurfaceControl(),
-                    SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
-        } else {
-            verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
-                    any(SurfaceControl.class), anyInt());
-        }
+        verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+                appWindow.getSurfaceControl(),
+                SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index 3fa38bf..3d08ca2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -21,14 +21,11 @@
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import android.hardware.display.DisplayManager;
@@ -36,7 +33,6 @@
 import android.platform.test.annotations.Presubmit;
 import android.view.Display.Mode;
 import android.view.Surface;
-import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 
 import androidx.test.filters.SmallTest;
@@ -274,97 +270,6 @@
     }
 
     @Test
-    public void testAnimatingAppOverridePreferredModeId() {
-        final WindowState overrideWindow = createWindow("overrideWindow");
-        overrideWindow.mAttrs.packageName = "com.android.test";
-        overrideWindow.mAttrs.preferredDisplayModeId = LOW_MODE_ID;
-        parcelLayoutParams(overrideWindow);
-        assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
-        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
-        assertEquals(FRAME_RATE_VOTE_LOW_EXACT, overrideWindow.mFrameRateVote);
-        assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-        assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-
-        if (explicitRefreshRateHints()) {
-            return;
-        }
-        overrideWindow.mActivityRecord.mSurfaceAnimator.startAnimation(
-                overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class),
-                false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
-        assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
-        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
-        assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote);
-        assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-        assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-
-        // Use default mode if it is animating by shell transition.
-        overrideWindow.mActivityRecord.mSurfaceAnimator.cancelAnimation();
-        registerTestTransitionPlayer();
-        final Transition transition = overrideWindow.mTransitionController.createTransition(
-                WindowManager.TRANSIT_OPEN);
-        transition.collect(overrideWindow.mActivityRecord);
-        assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
-
-        // If there will be display size change when switching from preferred mode to default mode,
-        // then keep the current preferred mode during animating.
-        mDisplayInfo = spy(mDisplayInfo);
-        final Mode defaultMode = new Mode(4321 /* width */, 1234 /* height */, LOW_REFRESH_RATE);
-        doReturn(defaultMode).when(mDisplayInfo).getDefaultMode();
-        mPolicy = new RefreshRatePolicy(mWm, mDisplayInfo, mDenylist);
-        assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
-    }
-
-    @Test
-    public void testAnimatingAppOverridePreferredRefreshRate() {
-        final WindowState overrideWindow = createWindow("overrideWindow");
-        overrideWindow.mAttrs.packageName = "com.android.test";
-        overrideWindow.mAttrs.preferredRefreshRate = LOW_REFRESH_RATE;
-        parcelLayoutParams(overrideWindow);
-        assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
-        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
-        assertEquals(FRAME_RATE_VOTE_LOW_PREFERRED, overrideWindow.mFrameRateVote);
-        assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-        assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-
-        if (explicitRefreshRateHints()) {
-            return;
-        }
-        overrideWindow.mActivityRecord.mSurfaceAnimator.startAnimation(
-                overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class),
-                false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
-        assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
-        assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
-        assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote);
-        assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-        assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-    }
-
-    @Test
-    public void testAnimatingDenylist() {
-        final WindowState window = createWindow("overrideWindow");
-        window.mAttrs.packageName = "com.android.test";
-        parcelLayoutParams(window);
-        when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
-        assertEquals(0, mPolicy.getPreferredModeId(window));
-        assertTrue(mPolicy.updateFrameRateVote(window));
-        assertEquals(FRAME_RATE_VOTE_DENY_LIST, window.mFrameRateVote);
-        assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
-        assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
-
-        if (explicitRefreshRateHints()) {
-            return;
-        }
-        window.mActivityRecord.mSurfaceAnimator.startAnimation(
-                window.getPendingTransaction(), mock(AnimationAdapter.class),
-                false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
-        assertEquals(0, mPolicy.getPreferredModeId(window));
-        assertTrue(mPolicy.updateFrameRateVote(window));
-        assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
-        assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
-        assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
-    }
-
-    @Test
     public void testAnimatingCamera() {
         final WindowState cameraUsingWindow = createWindow("cameraUsingWindow");
         cameraUsingWindow.mAttrs.packageName = "com.android.test";
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 1c87802..62a4711 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4812,6 +4812,23 @@
         assertFalse(mActivity.isResizeable());
         assertEquals(maxAspect, aspectRatioPolicy.getMaxAspectRatio(), 0 /* delta */);
         assertNotEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOverrideOrientation());
+
+        // Activity can opt-out the resizability by component level property.
+        final ComponentName name = getUniqueComponentName(mContext.getPackageName());
+        final PackageManager pm = mContext.getPackageManager();
+        spyOn(pm);
+        final PackageManager.Property property = new PackageManager.Property("propertyName",
+                true /* value */, name.getPackageName(), name.getClassName());
+        try {
+            doReturn(property).when(pm).getPropertyAsUser(
+                    WindowManager.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY,
+                    name.getPackageName(), name.getClassName(), 0 /* userId */);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+        final ActivityRecord optOutActivity = new ActivityBuilder(mAtm)
+                .setComponent(name).setTask(mTask).build();
+        assertFalse(optOutActivity.isUniversalResizeable());
     }
 
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index 3c921c6..4568c77 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -249,7 +249,7 @@
         ActivityRecord reusableActivity = createSourceActivity(fullscreenDisplay);
         ActivityRecord source = createSourceActivity(freeformDisplay);
         source.mHandoverLaunchDisplayId = freeformDisplay.mDisplayId;
-        source.noDisplay = true;
+        source.setIsNoDisplay(true);
 
         assertEquals(RESULT_CONTINUE,
                 new CalculateRequestBuilder()
@@ -272,7 +272,7 @@
         ActivityRecord reusableActivity = createSourceActivity(fullscreenDisplay);
         ActivityRecord source = createSourceActivity(freeformDisplay);
         source.mHandoverTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
-        source.noDisplay = true;
+        source.setIsNoDisplay(true);
 
         assertEquals(RESULT_CONTINUE,
                 new CalculateRequestBuilder()
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index e8779c2..039a3dd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -51,7 +51,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -1632,6 +1631,8 @@
         transition.collect(taskA);
         transition.setTransientLaunch(recent, taskA);
         taskRecent.moveToFront("move-recent-to-front");
+        recent.setVisibility(true);
+        recent.setState(ActivityRecord.State.RESUMED, "test");
 
         // During collecting and playing, the recent is on top so it is visible naturally.
         // While B needs isTransientVisible to keep visibility because it is occluded by recents.
@@ -1644,15 +1645,21 @@
 
         // Switch to another task. For example, use gesture navigation to switch tasks.
         taskB.moveToFront("move-b-to-front");
+        appB.setVisibility(true);
         // The previous app (taskA) should be paused first so it loses transient visible. Because
         // visually it is taskA -> taskB, the pause -> resume order should be the same.
         assertFalse(controller.isTransientVisible(taskA));
-        // Keep the recent visible so there won't be 2 activities pausing at the same time. It is
-        // to avoid the latency to resume the current top, i.e. appB.
-        assertTrue(controller.isTransientVisible(taskRecent));
-        // The recent is paused after the transient transition is finished.
-        controller.finishTransition(ActionChain.testFinish(transition));
+        // The recent is occluded by appB.
         assertFalse(controller.isTransientVisible(taskRecent));
+        // Active transient launch won't be paused if the transition is not finished. It is to
+        // avoid the latency to resume the current top (appB) by waiting for both recent and appA
+        // to complete pause.
+        assertEquals(recent, taskRecent.getResumedActivity());
+        assertFalse(taskRecent.startPausing(false /* uiSleeping */, appB /* resuming */, "test"));
+        // ActivityRecord#makeInvisible will add the invisible recent to the stopping list.
+        // So when the transition finished, the recent can still be notified to pause and stop.
+        mDisplayContent.ensureActivitiesVisible(null /* starting */, true /* notifyClients */);
+        assertTrue(mSupervisor.mStoppingActivities.contains(recent));
     }
 
     @Test
@@ -2883,17 +2890,14 @@
 
     @Test
     public void testTransitionsTriggerPerformanceHints() {
-        final boolean explicitRefreshRateHints = explicitRefreshRateHints();
         final var session = new SystemPerformanceHinter.HighPerfSession[1];
-        if (explicitRefreshRateHints) {
-            final SystemPerformanceHinter perfHinter = mWm.mSystemPerformanceHinter;
-            spyOn(perfHinter);
-            doAnswer(invocation -> {
-                session[0] = (SystemPerformanceHinter.HighPerfSession) invocation.callRealMethod();
-                spyOn(session[0]);
-                return session[0];
-            }).when(perfHinter).createSession(anyInt(), anyInt(), anyString());
-        }
+        final SystemPerformanceHinter perfHinter = mWm.mSystemPerformanceHinter;
+        spyOn(perfHinter);
+        doAnswer(invocation -> {
+            session[0] = (SystemPerformanceHinter.HighPerfSession) invocation.callRealMethod();
+            spyOn(session[0]);
+            return session[0];
+        }).when(perfHinter).createSession(anyInt(), anyInt(), anyString());
         final TransitionController controller = mDisplayContent.mTransitionController;
         final TestTransitionPlayer player = registerTestTransitionPlayer();
         final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
@@ -2905,15 +2909,11 @@
         player.start();
 
         verify(mDisplayContent).enableHighPerfTransition(true);
-        if (explicitRefreshRateHints) {
-            verify(session[0]).start();
-        }
+        verify(session[0]).start();
 
         player.finish();
         verify(mDisplayContent).enableHighPerfTransition(false);
-        if (explicitRefreshRateHints) {
-            verify(session[0]).close();
-        }
+        verify(session[0]).close();
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
index 5187f87..42752c3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
@@ -217,6 +217,19 @@
         });
     }
 
+    @Test
+    public void testNotApplyStrategyToTranslucentActivitiesOverNotLetterboxedActivities() {
+        runTestScenario((robot) -> {
+            robot.transparentActivity((ta) -> {
+                ta.activity().setTopActivityHasLetterboxedBounds(false);
+                ta.launchTransparentActivityInTask();
+
+                ta.checkTopActivityTransparentPolicyStartNotInvoked();
+                ta.checkTopActivityTransparentPolicyStateIsRunning(/* running */ false);
+            });
+        });
+    }
+
     @EnableFlags(com.android.window.flags.Flags.FLAG_RESPECT_NON_TOP_VISIBLE_FIXED_ORIENTATION)
     @Test
     public void testNotRunStrategyToTranslucentActivitiesIfRespectOrientation() {
@@ -388,6 +401,7 @@
             mTransparentActivityRobot = new AppCompatTransparentActivityRobot(activity());
             // We always create at least an opaque activity in a Task
             activity().createNewTaskWithBaseActivity();
+            activity().setTopActivityHasLetterboxedBounds(true);
         }
 
         @Override
diff --git a/services/usb/OWNERS b/services/usb/OWNERS
index d35dbb56..2dff392 100644
--- a/services/usb/OWNERS
+++ b/services/usb/OWNERS
@@ -1,9 +1,9 @@
-aprasath@google.com
-kumarashishg@google.com
-sarup@google.com
 anothermark@google.com
+febinthattil@google.com
+aprasath@google.com
 badhri@google.com
 elaurent@google.com
 albertccwang@google.com
 jameswei@google.com
-howardyen@google.com
\ No newline at end of file
+howardyen@google.com
+kumarashishg@google.com
\ No newline at end of file
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 45a7faf..07969bd 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -304,11 +304,17 @@
         @Override
         public void unloadModel(int modelHandle) {
             synchronized (SoundTriggerModule.this) {
-                int sessionId;
                 checkValid();
-                sessionId = mLoadedModels.get(modelHandle).unload();
-                mAudioSessionProvider.releaseSession(sessionId);
+                final var session = mLoadedModels.get(modelHandle).getSession();
+                mLoadedModels.remove(modelHandle);
+                mAudioSessionProvider.releaseSession(session.mSessionHandle);
             }
+            // We don't need to post-synchronize on anything once the HAL has finished the unload
+            // and dispatched any appropriate callbacks -- since we don't do any state checking
+            // onModelUnloaded regardless.
+            // This is generally safe since there is no post-condition on the framework side when
+            // a model is unloaded. We assume that we won't ever have a modelHandle collision.
+            mHalService.unloadSoundModel(modelHandle);
         }
 
         @Override
@@ -402,6 +408,10 @@
                 return mState;
             }
 
+            private SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession getSession() {
+                return mSession;
+            }
+
             private void setState(@NonNull ModelState state) {
                 mState = state;
                 SoundTriggerModule.this.notifyAll();
@@ -426,16 +436,6 @@
                 return mHandle;
             }
 
-            /**
-             * Unloads the model.
-             * @return The audio session handle.
-             */
-            private int unload() {
-                mHalService.unloadSoundModel(mHandle);
-                mLoadedModels.remove(mHandle);
-                return mSession.mSessionHandle;
-            }
-
             private IBinder startRecognition(@NonNull RecognitionConfig config) {
                 if (mIsStopping == true) {
                     throw new RecoverableException(Status.INTERNAL_ERROR, "Race occurred");
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index 9b83719..be34619 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -47,8 +47,6 @@
 import android.util.Log;
 import android.util.SparseArray;
 
-import com.android.internal.annotations.VisibleForTesting;
-
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
@@ -538,7 +536,13 @@
     }
 
     private static String getDefaultSmsPackage(Context context, int userId) {
-        return context.getSystemService(RoleManager.class).getSmsRoleHolder(userId);
+        // RoleManager might be null in unit tests running older mockito versions that do not
+        // support mocking final classes.
+        RoleManager roleManager = context.getSystemService(RoleManager.class);
+        if (roleManager == null) {
+            return "";
+        }
+        return roleManager.getSmsRoleHolder(userId);
     }
 
     /**
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 6535b9b..2a06c3d 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -10153,6 +10153,15 @@
             "satellite_roaming_esos_inactivity_timeout_sec_int";
 
     /**
+     * A string array containing the list of messaging package names that support satellite.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final String KEY_SATELLITE_SUPPORTED_MSG_APPS_STRING_ARRAY =
+            "satellite_supported_msg_apps_string_array";
+
+    /**
      * Indicating whether DUN APN should be disabled when the device is roaming. In that case,
      * the default APN (i.e. internet) will be used for tethering.
      *
@@ -11415,6 +11424,7 @@
         sDefaults.putIntArray(KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY, new int[]{1, 2, 3});
         sDefaults.putInt(KEY_WEAR_CONNECTIVITY_BT_TO_CELL_DELAY_MS_INT, -1);
         sDefaults.putInt(KEY_WEAR_CONNECTIVITY_EXTEND_BT_TO_CELL_DELAY_ON_WIFI_MS_INT, -1);
+        sDefaults.putInt(KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE, 255);
     }
 
     /**
diff --git a/telephony/java/android/telephony/satellite/ISatelliteDisallowedReasonsCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteDisallowedReasonsCallback.aidl
new file mode 100644
index 0000000..9a6f6b8
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/ISatelliteDisallowedReasonsCallback.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+/**
+ * Interface for satellite disallowed reason change callback.
+ *
+ * @hide
+ */
+oneway interface ISatelliteDisallowedReasonsCallback {
+    /**
+     * Indicates that disallowed reason of satellite has changed.
+     * @param disallowedReasons list of disallowed reasons.
+     */
+    void onSatelliteDisallowedReasonsChanged(in int[] disallowedReasons);
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java b/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java
new file mode 100644
index 0000000..5e276aa
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+
+import com.android.internal.telephony.flags.Flags;
+
+/**
+ * A callback class for disallowed reason of satellite change events.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+public interface SatelliteDisallowedReasonsCallback {
+
+    /**
+     * Called when disallowed reason of satellite has changed.
+     * @param disallowedReasons Integer array of disallowed reasons.
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    void onSatelliteDisallowedReasonsChanged(@NonNull int[] disallowedReasons);
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index e332d0f..7be3f33 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -26,6 +26,7 @@
 import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.annotation.SystemService;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Binder;
@@ -39,6 +40,7 @@
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.TelephonyManager;
+import android.telephony.TelephonyRegistryManager;
 
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.ITelephony;
@@ -61,13 +63,19 @@
 import java.util.stream.Collectors;
 
 /**
- * Manages satellite operations such as provisioning, pointing, messaging, location sharing, etc.
- * To get the object, call {@link Context#getSystemService(String)}.
+ * Manages satellite states such as monitoring enabled state and operations such as provisioning,
+ * pointing, messaging, location sharing, etc.
  *
- * @hide
+ * <p>To get the object, call {@link Context#getSystemService(String)} with
+ * {@link Context#SATELLITE_SERVICE}.
+ *
+ * <p>SatelliteManager is intended for use on devices with feature
+ * {@link PackageManager#FEATURE_TELEPHONY_SATELLITE}. On devices without the feature, the behavior
+ * is not reliable.
  */
+@SystemService(Context.SATELLITE_SERVICE)
+@FlaggedApi(Flags.FLAG_SATELLITE_STATE_CHANGE_LISTENER)
 @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SATELLITE)
-@SystemApi
 public final class SatelliteManager {
     private static final String TAG = "SatelliteManager";
 
@@ -96,6 +104,11 @@
             sSatelliteCommunicationAllowedStateCallbackMap =
             new ConcurrentHashMap<>();
 
+    private static final ConcurrentHashMap<SatelliteDisallowedReasonsCallback,
+            ISatelliteDisallowedReasonsCallback>
+            sSatelliteDisallowedReasonsCallbackMap =
+            new ConcurrentHashMap<>();
+
     private final int mSubId;
 
     /**
@@ -103,6 +116,8 @@
      */
     @Nullable private final Context mContext;
 
+    private TelephonyRegistryManager mTelephonyRegistryMgr;
+
     /**
      * Create an instance of the SatelliteManager.
      *
@@ -736,6 +751,65 @@
             "android.telephony.METADATA_SATELLITE_MANUAL_CONNECT_P2P_SUPPORT";
 
     /**
+     * Registers a {@link SatelliteStateChangeListener} to receive callbacks when the satellite
+     * state may have changed.
+     *
+     * <p>The callback method is immediately triggered with latest state on invoking this method if
+     * the state change has been notified before.
+     *
+     * @param executor The {@link Executor} where the {@code listener} will be invoked
+     * @param listener The listener to monitor the satellite state change
+     *
+     * @see SatelliteStateChangeListener
+     * @see TelephonyManager#hasCarrierPrivileges()
+     */
+    @FlaggedApi(Flags.FLAG_SATELLITE_STATE_CHANGE_LISTENER)
+    @RequiresPermission(anyOf = {android.Manifest.permission.READ_BASIC_PHONE_STATE,
+            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            android.Manifest.permission.READ_PHONE_STATE,
+            "carrier privileges"})
+    public void registerStateChangeListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull SatelliteStateChangeListener listener) {
+        if (mContext == null) {
+            throw new IllegalStateException("Telephony service is null");
+        }
+
+        mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);
+        if (mTelephonyRegistryMgr == null) {
+            throw new IllegalStateException("Telephony registry service is null");
+        }
+        mTelephonyRegistryMgr.addSatelliteStateChangeListener(executor, listener);
+    }
+
+    /**
+     * Unregisters the {@link SatelliteStateChangeListener} previously registered with
+     * {@link #registerStateChangeListener(Executor, SatelliteStateChangeListener)}.
+     *
+     * <p>It will be a no-op if the {@code listener} is not currently registered.
+     *
+     * @param listener The listener to unregister
+     *
+     * @see SatelliteStateChangeListener
+     * @see TelephonyManager#hasCarrierPrivileges()
+     */
+    @FlaggedApi(Flags.FLAG_SATELLITE_STATE_CHANGE_LISTENER)
+    @RequiresPermission(anyOf = {android.Manifest.permission.READ_BASIC_PHONE_STATE,
+            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            android.Manifest.permission.READ_PHONE_STATE,
+            "carrier privileges"})
+    public void unregisterStateChangeListener(@NonNull SatelliteStateChangeListener listener) {
+        if (mContext == null) {
+            throw new IllegalStateException("Telephony service is null");
+        }
+
+        mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);
+        if (mTelephonyRegistryMgr == null) {
+            throw new IllegalStateException("Telephony registry service is null");
+        }
+        mTelephonyRegistryMgr.removeSatelliteStateChangeListener(listener);
+    }
+
+    /**
      * Request to enable or disable the satellite modem and demo mode.
      * If satellite modem and cellular modem cannot work concurrently,
      * then this will disable the cellular modem if satellite modem is enabled,
@@ -1418,6 +1492,47 @@
     public @interface SatelliteCommunicationRestrictionReason {}
 
     /**
+     * Satellite is disallowed because it is not supported.
+     * @hide
+     */
+    public static final int SATELLITE_DISALLOWED_REASON_NOT_SUPPORTED = 0;
+
+    /**
+     * Satellite is disallowed because it has not been provisioned.
+     * @hide
+     */
+    public static final int SATELLITE_DISALLOWED_REASON_NOT_PROVISIONED = 1;
+
+    /**
+     * Satellite is disallowed because it is currently outside an allowed region.
+     * @hide
+     */
+    public static final int SATELLITE_DISALLOWED_REASON_NOT_IN_ALLOWED_REGION = 2;
+
+    /**
+     * Satellite is disallowed because an unsupported default message application is being used.
+     * @hide
+     */
+    public static final int SATELLITE_DISALLOWED_REASON_UNSUPPORTED_DEFAULT_MSG_APP = 3;
+
+    /**
+     * Satellite is disallowed because location settings have been disabled.
+     * @hide
+     */
+    public static final int SATELLITE_DISALLOWED_REASON_LOCATION_DISABLED = 4;
+
+    /** @hide */
+    @IntDef(prefix = "SATELLITE_DISALLOWED_REASON_", value = {
+            SATELLITE_DISALLOWED_REASON_NOT_SUPPORTED,
+            SATELLITE_DISALLOWED_REASON_NOT_PROVISIONED,
+            SATELLITE_DISALLOWED_REASON_NOT_IN_ALLOWED_REGION,
+            SATELLITE_DISALLOWED_REASON_UNSUPPORTED_DEFAULT_MSG_APP,
+            SATELLITE_DISALLOWED_REASON_LOCATION_DISABLED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SatelliteDisallowedReason {}
+
+    /**
      * Start receiving satellite transmission updates.
      * This can be called by the pointing UI when the user starts pointing to the satellite.
      * Modem should continue to report the pointing input as the device or satellite moves.
@@ -2510,6 +2625,119 @@
     }
 
     /**
+     * Returns list of disallowed reasons of satellite.
+     *
+     * @return list of disallowed reasons of satellite.
+     *
+     * @throws SecurityException     if caller doesn't have required permission.
+     * @throws IllegalStateException if Telephony process isn't available.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @SatelliteDisallowedReason
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @NonNull
+    public List<Integer> getSatelliteDisallowedReasons() {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                int[] receivedArray = telephony.getSatelliteDisallowedReasons();
+                if (receivedArray.length == 0) {
+                    logd("receivedArray is empty, create empty list");
+                    return new ArrayList<>();
+                } else {
+                    return Arrays.stream(receivedArray).boxed().collect(Collectors.toList());
+                }
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("getSatelliteDisallowedReasons() RemoteException: " + ex);
+            ex.rethrowAsRuntimeException();
+        }
+        return new ArrayList<>();
+    }
+
+    /**
+     * Registers for disallowed reasons change event from satellite service.
+     *
+     * @param executor The executor on which the callback will be called.
+     * @param callback The callback to handle disallowed reasons changed event.
+     *
+     * @throws SecurityException     if caller doesn't have required permission.
+     * @throws IllegalStateException if Telephony process is not available.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public void registerForSatelliteDisallowedReasonsChanged(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull SatelliteDisallowedReasonsCallback callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                ISatelliteDisallowedReasonsCallback internalCallback =
+                        new ISatelliteDisallowedReasonsCallback.Stub() {
+                            @Override
+                            public void onSatelliteDisallowedReasonsChanged(
+                                    int[] disallowedReasons) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onSatelliteDisallowedReasonsChanged(
+                                                disallowedReasons)));
+                            }
+                        };
+                telephony.registerForSatelliteDisallowedReasonsChanged(internalCallback);
+                sSatelliteDisallowedReasonsCallbackMap.put(callback, internalCallback);
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("registerForSatelliteDisallowedReasonsChanged() RemoteException" + ex);
+            ex.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Unregisters for disallowed reasons change event from satellite service.
+     *
+     * @param callback The callback that was passed to
+     * {@link #registerForSatelliteDisallowedReasonsChanged(
+     * Executor, SatelliteDisallowedReasonsCallback)}
+     *
+     * @throws SecurityException     if caller doesn't have required permission.
+     * @throws IllegalStateException if Telephony process is not available.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public void unregisterForSatelliteDisallowedReasonsChanged(
+            @NonNull SatelliteDisallowedReasonsCallback callback) {
+        Objects.requireNonNull(callback);
+        ISatelliteDisallowedReasonsCallback internalCallback =
+                sSatelliteDisallowedReasonsCallbackMap.remove(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                if (internalCallback != null) {
+                    telephony.unregisterForSatelliteDisallowedReasonsChanged(internalCallback);
+                } else {
+                    loge("unregisterForSatelliteDisallowedReasonsChanged: No internal callback.");
+                    throw new IllegalArgumentException("callback is not valid");
+                }
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("unregisterForSatelliteDisallowedReasonsChanged() RemoteException: " + ex);
+            ex.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Request to get the signal strength of the satellite connection.
      *
      * <p>
diff --git a/telephony/java/android/telephony/satellite/SatelliteStateChangeListener.java b/telephony/java/android/telephony/satellite/SatelliteStateChangeListener.java
new file mode 100644
index 0000000..3aa910d
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteStateChangeListener.java
@@ -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 android.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+
+import com.android.internal.telephony.flags.Flags;
+
+import java.util.concurrent.Executor;
+
+/**
+ * A listener interface to monitor satellite state change events.
+ *
+ * <p>Call
+ * {@link SatelliteManager#registerStateChangeListener(Executor, SatelliteStateChangeListener)}
+ * to monitor. Call
+ * {@link SatelliteManager#unregisterStateChangeListener(SatelliteStateChangeListener)} to cancel.
+ *
+ * @see SatelliteManager#registerStateChangeListener(Executor, SatelliteStateChangeListener)
+ * @see SatelliteManager#unregisterStateChangeListener(SatelliteStateChangeListener)
+ */
+@FlaggedApi(Flags.FLAG_SATELLITE_STATE_CHANGE_LISTENER)
+public interface SatelliteStateChangeListener {
+    /**
+     * Called when satellite modem enabled state may have changed.
+     *
+     * <p>Note:there is no guarantee that this callback will only be invoked upon a change of state.
+     * In other word, in some cases, the callback may report with the same enabled states. It is the
+     * caller's responsibility to filter uninterested states.
+     *
+     * <p>Note:satellite enabled state is a device state that is NOT associated with subscription or
+     * SIM slot.
+     *
+     * @param isEnabled {@code true} means satellite modem is enabled.
+     */
+    void onEnabledStateChanged(boolean isEnabled);
+}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 231c8f5..62cbb02 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -71,6 +71,7 @@
 import android.telephony.satellite.ISatelliteCapabilitiesCallback;
 import android.telephony.satellite.ISatelliteCommunicationAllowedStateCallback;
 import android.telephony.satellite.ISatelliteDatagramCallback;
+import android.telephony.satellite.ISatelliteDisallowedReasonsCallback;
 import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
 import android.telephony.satellite.ISatelliteSupportedStateCallback;
@@ -2955,6 +2956,37 @@
             in boolean needFullScreenPointingUI, IIntegerConsumer callback);
 
     /**
+     * Returns integer array of disallowed reasons of satellite.
+     *
+     * @return Integer array of disallowed reasons of satellite.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    int[] getSatelliteDisallowedReasons();
+
+    /**
+     * Registers for disallowed reasons change event from satellite service.
+     *
+     * @param callback The callback to handle disallowed reasons changed event.
+     *
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    void registerForSatelliteDisallowedReasonsChanged(
+            ISatelliteDisallowedReasonsCallback callback);
+
+    /**
+     * Unregisters for disallowed reasons change event from satellite service.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * @param callback The callback to handle disallowed reasons changed event.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    void unregisterForSatelliteDisallowedReasonsChanged(
+            ISatelliteDisallowedReasonsCallback callback);
+
+    /**
      * Request to get whether satellite communication is allowed for the current location.
      *
      * @param subId The subId of the subscription to get whether satellite communication is allowed
diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java b/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java
new file mode 100644
index 0000000..2cd625e
--- /dev/null
+++ b/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.jank.tests;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.jank.Flags;
+import android.app.jank.JankDataProcessor;
+import android.app.jank.StateTracker;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.Choreographer;
+import android.view.SurfaceControl;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class JankDataProcessorTest {
+
+    private Choreographer mChoreographer;
+    private StateTracker mStateTracker;
+    private JankDataProcessor mJankDataProcessor;
+    private static final int NANOS_PER_MS = 1_000_000;
+    private static String sActivityName;
+    private static ActivityScenario<EmptyActivity> sEmptyActivityActivityScenario;
+    private static final int APP_ID = 25;
+
+    @BeforeClass
+    public static void classSetup() {
+        sEmptyActivityActivityScenario = ActivityScenario.launch(EmptyActivity.class);
+        sActivityName = sEmptyActivityActivityScenario.toString();
+    }
+
+    @AfterClass
+    public static void classTearDown() {
+        sEmptyActivityActivityScenario.close();
+    }
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Before
+    @UiThreadTest
+    public void setup() {
+        mChoreographer = Choreographer.getInstance();
+        mStateTracker = new StateTracker(mChoreographer);
+        mJankDataProcessor = new JankDataProcessor(mStateTracker);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+    public void processJankData_multipleFramesAndStates_attributesTotalFramesCorrectly() {
+        List<SurfaceControl.JankData> jankData = getMockJankData_vsyncId_inRange();
+        mStateTracker.addPendingStateData(getMockStateData_vsyncId_inRange());
+
+        mJankDataProcessor.processJankData(jankData, sActivityName, APP_ID);
+
+        long totalFramesAttributed = getTotalFramesCounted();
+
+        // Each state is active for each frame that is passed in, there are two states being tested
+        // which is why jankData.size is multiplied by 2.
+        assertEquals(jankData.size() * 2, totalFramesAttributed);
+    }
+
+    /**
+     * Each JankData frame has an associated vsyncid, only frames that have vsyncids between the
+     * StatData start and end vsyncids should be counted.  This test confirms that if JankData
+     * does not share any frames with the states then no jank stats are added.
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+    public void processJankData_outOfRangeVsyncId_skipOutOfRangeVsyncIds() {
+        List<SurfaceControl.JankData> jankData = getMockJankData_vsyncId_inRange();
+        mStateTracker.addPendingStateData(getMockStateData_vsyncId_outOfRange());
+
+        mJankDataProcessor.processJankData(jankData, sActivityName, APP_ID);
+
+        assertEquals(0, mJankDataProcessor.getPendingJankStats().size());
+    }
+
+    /**
+     * It's expected to see many duplicate widget states, if a user is scrolling then
+     * pauses and resumes scrolling again, we may get three widget states two of which are the same.
+     * State 1: {Scroll,WidgetId,Scrolling} State 2: {Scroll,WidgetId,None}
+     * State 3: {Scroll,WidgetId,Scrolling}
+     * These duplicate states should coalesce into only one Jank stat. This test confirms that
+     * behavior.
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+    public void processJankData_duplicateStates_confirmDuplicatesCoalesce() {
+        // getMockStateData will return 10 states 5 of which are set to none and 5 of which are
+        // scrolling.
+        mStateTracker.addPendingStateData(getMockStateData_vsyncId_inRange());
+
+        mJankDataProcessor.processJankData(getMockJankData_vsyncId_inRange(), sActivityName,
+                APP_ID);
+
+        // Confirm the duplicate states are coalesced down to 2 stats 1 for the scrolling state
+        // another for the none state.
+        assertEquals(2, mJankDataProcessor.getPendingJankStats().size());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+    public void processJankData_inRangeVsyncIds_confirmOnlyInRangeFramesCounted() {
+        List<SurfaceControl.JankData> jankData = getMockJankData_vsyncId_inRange();
+        int inRangeFrameCount = jankData.size();
+
+        mStateTracker.addPendingStateData(getMockStateData_vsyncId_inRange());
+        mJankDataProcessor.processJankData(jankData, sActivityName, APP_ID);
+
+        // Two states are active for each frame which is why inRangeFrameCount is multiplied by 2.
+        assertEquals(inRangeFrameCount * 2, getTotalFramesCounted());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+    public void processJankData_inRangeVsyncIds_confirmHistogramCountMatchesFrameCount() {
+        List<SurfaceControl.JankData> jankData = getMockJankData_vsyncId_inRange();
+        mStateTracker.addPendingStateData(getMockStateData_vsyncId_inRange());
+        mJankDataProcessor.processJankData(jankData, sActivityName, APP_ID);
+
+        long totalFrames = getTotalFramesCounted();
+        long histogramFrames = getHistogramFrameCount();
+
+        assertEquals(totalFrames, histogramFrames);
+    }
+
+    // TODO b/375005277 add tests that cover logging and releasing resources back to pool.
+
+    private long getTotalFramesCounted() {
+        return mJankDataProcessor.getPendingJankStats().values()
+                .stream().mapToLong(stat -> stat.getTotalFrames()).sum();
+    }
+
+    private long getHistogramFrameCount() {
+        long totalHistogramFrames = 0;
+
+        for (JankDataProcessor.PendingJankStat stats :
+                mJankDataProcessor.getPendingJankStats().values()) {
+            int[] overrunHistogram = stats.getFrameOverrunBuckets();
+
+            for (int i = 0; i < overrunHistogram.length; i++) {
+                totalHistogramFrames += overrunHistogram[i];
+            }
+        }
+
+        return totalHistogramFrames;
+    }
+
+    /**
+     * Out of range data will have a mVsyncIdStart and mVsyncIdEnd values set to below 25.
+     */
+    private List<StateTracker.StateData> getMockStateData_vsyncId_outOfRange() {
+        ArrayList<StateTracker.StateData> stateData = new ArrayList<StateTracker.StateData>();
+        StateTracker.StateData newStateData = new StateTracker.StateData();
+        newStateData.mVsyncIdEnd = 20;
+        newStateData.mStateDataKey = "Test1_OutBand";
+        newStateData.mVsyncIdStart = 1;
+        newStateData.mWidgetState = "scrolling";
+        newStateData.mWidgetId = "widgetId";
+        newStateData.mWidgetCategory = "Scroll";
+        stateData.add(newStateData);
+
+        newStateData = new StateTracker.StateData();
+        newStateData.mVsyncIdEnd = 24;
+        newStateData.mStateDataKey = "Test1_InBand";
+        newStateData.mVsyncIdStart = 20;
+        newStateData.mWidgetState = "Idle";
+        newStateData.mWidgetId = "widgetId";
+        newStateData.mWidgetCategory = "Scroll";
+        stateData.add(newStateData);
+
+        newStateData = new StateTracker.StateData();
+        newStateData.mVsyncIdEnd = 20;
+        newStateData.mStateDataKey = "Test1_OutBand";
+        newStateData.mVsyncIdStart = 12;
+        newStateData.mWidgetState = "Idle";
+        newStateData.mWidgetId = "widgetId";
+        newStateData.mWidgetCategory = "Scroll";
+        stateData.add(newStateData);
+
+        return stateData;
+    }
+
+    /**
+     * This method returns two unique states, one state is set to scrolling the other is set
+     * to none. Both states will have the same startvsyncid to ensure each state is counted the same
+     * number of times. This keeps logic in asserts easier to reason about. Both states will have
+     * a startVsyncId between 25 and 35.
+     */
+    private List<StateTracker.StateData> getMockStateData_vsyncId_inRange() {
+        ArrayList<StateTracker.StateData> stateData = new ArrayList<StateTracker.StateData>();
+
+        for (int i = 0; i < 10; i++) {
+            StateTracker.StateData newStateData = new StateTracker.StateData();
+            newStateData.mVsyncIdEnd = Long.MAX_VALUE;
+            newStateData.mStateDataKey = "Test1_" + (i % 2 == 0 ? "scrolling" : "none");
+            // Divide i by two to ensure both the scrolling and none states get the same vsyncid
+            // This makes asserts in tests easier to reason about as each state should be counted
+            // the same number of times.
+            newStateData.mVsyncIdStart = 25 + (i / 2);
+            newStateData.mWidgetState = i % 2 == 0 ? "scrolling" : "none";
+            newStateData.mWidgetId = "widgetId";
+            newStateData.mWidgetCategory = "Scroll";
+
+            stateData.add(newStateData);
+        }
+
+        return stateData;
+    }
+
+    /**
+     * In range data will have a frameVsyncId value between 25 and 35.
+     */
+    private List<SurfaceControl.JankData> getMockJankData_vsyncId_inRange() {
+        ArrayList<SurfaceControl.JankData> mockData = new ArrayList<>();
+
+        for (int i = 0; i < 10; i++) {
+            mockData.add(new SurfaceControl.JankData(
+                    /*frameVsyncId*/25 + i,
+                    SurfaceControl.JankData.JANK_NONE,
+                    NANOS_PER_MS * ((long) i),
+                    NANOS_PER_MS * ((long) i),
+                    NANOS_PER_MS * ((long) i)));
+
+        }
+
+        return mockData;
+    }
+
+    /**
+     * Out of range data will have frameVsyncId values below 25.
+     */
+    private List<SurfaceControl.JankData> getMockJankData_vsyncId_outOfRange() {
+        ArrayList<SurfaceControl.JankData> mockData = new ArrayList<>();
+
+        for (int i = 0; i < 10; i++) {
+            mockData.add(new SurfaceControl.JankData(
+                    /*frameVsyncId*/i,
+                    SurfaceControl.JankData.JANK_NONE,
+                    NANOS_PER_MS * ((long) i),
+                    NANOS_PER_MS * ((long) i),
+                    NANOS_PER_MS * ((long) i)));
+
+        }
+
+        return mockData;
+    }
+
+}
diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java
new file mode 100644
index 0000000..a3e5533
--- /dev/null
+++ b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.jank.tests;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.jank.Flags;
+import android.app.jank.JankTracker;
+import android.app.jank.StateTracker;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.Choreographer;
+import android.view.View;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+public class JankTrackerTest {
+    private Choreographer mChoreographer;
+    private JankTracker mJankTracker;
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    /**
+     * Start an empty activity so decore view is not null when creating the JankTracker instance.
+     */
+    private static ActivityScenario<EmptyActivity> sEmptyActivityRule;
+
+    private static String sActivityName;
+
+    private static View sActivityDecorView;
+
+    @BeforeClass
+    public static void classSetup() {
+        sEmptyActivityRule = ActivityScenario.launch(EmptyActivity.class);
+        sEmptyActivityRule.onActivity(activity -> {
+            sActivityDecorView = activity.getWindow().getDecorView();
+            sActivityName = activity.toString();
+        });
+    }
+
+    @AfterClass
+    public static void classTearDown() {
+        sEmptyActivityRule.close();
+    }
+
+    @Before
+    @UiThreadTest
+    public void setup() {
+        mChoreographer = Choreographer.getInstance();
+        mJankTracker = new JankTracker(mChoreographer, sActivityDecorView);
+        mJankTracker.setActivityName(sActivityName);
+    }
+
+    /**
+     * When jank tracking is enabled the activity name should be added as a state to associate
+     * frames to it.
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+    public void jankTracking_WhenEnabled_ActivityAdded() {
+        mJankTracker.enableAppJankTracking();
+
+        ArrayList<StateTracker.StateData> stateData = new ArrayList<>();
+        mJankTracker.getAllUiStates(stateData);
+
+        assertEquals(1, stateData.size());
+
+        StateTracker.StateData firstState = stateData.getFirst();
+
+        assertEquals(sActivityName, firstState.mWidgetId);
+    }
+
+    /**
+     * No states should be added when tracking is disabled.
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+    public void jankTrackingDisabled_StatesShouldNot_BeAddedToTracker() {
+        mJankTracker.disableAppJankTracking();
+
+        mJankTracker.addUiState("FAKE_CATEGORY", "FAKE_ID",
+                "FAKE_STATE");
+
+        ArrayList<StateTracker.StateData> stateData = new ArrayList<>();
+        mJankTracker.getAllUiStates(stateData);
+
+        assertEquals(0, stateData.size());
+    }
+
+    /**
+     * The activity name as well as the test state should be added for frame association.
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+    public void jankTrackingEnabled_StatesShould_BeAddedToTracker() {
+        mJankTracker.forceListenerRegistration();
+
+        mJankTracker.enableAppJankTracking();
+        mJankTracker.addUiState("FAKE_CATEGORY", "FAKE_ID",
+                "FAKE_STATE");
+
+        ArrayList<StateTracker.StateData> stateData = new ArrayList<>();
+        mJankTracker.getAllUiStates(stateData);
+
+        assertEquals(2, stateData.size());
+    }
+
+    /**
+     * Activity state should only be added once even if jank tracking is enabled multiple times.
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+    public void jankTrackingEnabled_EnabledCalledTwice_ActivityStateOnlyAddedOnce() {
+        mJankTracker.enableAppJankTracking();
+
+        ArrayList<StateTracker.StateData> stateData = new ArrayList<>();
+        mJankTracker.getAllUiStates(stateData);
+
+        assertEquals(1, stateData.size());
+
+        stateData.clear();
+
+        mJankTracker.enableAppJankTracking();
+        mJankTracker.getAllUiStates(stateData);
+
+        assertEquals(1, stateData.size());
+    }
+}
diff --git a/tests/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/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 1c6bd11..332b9b8 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -132,6 +132,24 @@
         wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
     }
 
+    private fun getMinimizeButtonForTheApp(caption: UiObject2?): UiObject2 {
+        return caption
+            ?.children
+            ?.find { it.resourceName.endsWith(MINIMIZE_BUTTON_VIEW) }
+            ?: error("Unable to find resource $MINIMIZE_BUTTON_VIEW\n")
+    }
+
+    fun minimizeDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice) {
+        val caption = getCaptionForTheApp(wmHelper, device)
+        val minimizeButton = getMinimizeButtonForTheApp(caption)
+        minimizeButton.click()
+        wmHelper
+            .StateSyncBuilder()
+            .withAppTransitionIdle()
+            .withWindowSurfaceDisappeared(innerHelper)
+            .waitForAndVerify()
+    }
+
     /** Open maximize menu and click snap resize button on the app header for the given app. */
     fun snapResizeDesktopApp(
         wmHelper: WindowManagerStateHelper,
@@ -400,6 +418,7 @@
         const val DESKTOP_MODE_BUTTON: String = "desktop_button"
         const val SNAP_LEFT_BUTTON: String = "maximize_menu_snap_left_button"
         const val SNAP_RIGHT_BUTTON: String = "maximize_menu_snap_right_button"
+        const val MINIMIZE_BUTTON_VIEW: String = "minimize_window"
         val caption: BySelector
             get() = By.res(SYSTEMUI_PACKAGE, CAPTION)
     }
diff --git a/tests/Input/res/xml/bookmarks.xml b/tests/Input/res/xml/bookmarks.xml
new file mode 100644
index 0000000..ba3f187
--- /dev/null
+++ b/tests/Input/res/xml/bookmarks.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<bookmarks>
+    <!-- the key combinations for the following shortcuts must be in sync
+         with the key combinations sent by the test in KeyGestureControllerTests.java -->
+    <bookmark
+        role="android.app.role.BROWSER"
+        shortcut="b" />
+    <bookmark
+        category="android.intent.category.APP_CONTACTS"
+        shortcut="c" />
+    <bookmark
+        category="android.intent.category.APP_EMAIL"
+        shortcut="e" />
+    <bookmark
+        category="android.intent.category.APP_CALENDAR"
+        shortcut="k" />
+    <bookmark
+        category="android.intent.category.APP_MAPS"
+        shortcut="m" />
+    <bookmark
+        category="android.intent.category.APP_MUSIC"
+        shortcut="p" />
+    <bookmark
+        role="android.app.role.SMS"
+        shortcut="s" />
+    <bookmark
+        category="android.intent.category.APP_CALCULATOR"
+        shortcut="u" />
+
+    <bookmark
+        role="android.app.role.BROWSER"
+        shortcut="b"
+        shift="true" />
+
+    <bookmark
+        category="android.intent.category.APP_CONTACTS"
+        shortcut="c"
+        shift="true" />
+
+    <bookmark
+        package="com.test"
+        class="com.test.BookmarkTest"
+        shortcut="j"
+        shift="true" />
+</bookmarks>
\ No newline at end of file
diff --git a/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt b/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt
index 01c56b7..862886c 100644
--- a/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt
@@ -21,6 +21,7 @@
 import android.hardware.input.KeyGestureEvent
 import android.platform.test.annotations.Presubmit
 import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
@@ -42,7 +43,7 @@
 
     @Before
     fun setup() {
-        inputGestureManager = InputGestureManager()
+        inputGestureManager = InputGestureManager(ApplicationProvider.getApplicationContext())
     }
 
     @Test
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 927958e..6eb0045 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -95,8 +95,11 @@
 
     @get:Rule
     val extendedMockitoRule =
-        ExtendedMockitoRule.Builder(this).mockStatic(LocalServices::class.java)
-            .mockStatic(PermissionChecker::class.java).build()!!
+        ExtendedMockitoRule.Builder(this)
+            .mockStatic(LocalServices::class.java)
+            .mockStatic(PermissionChecker::class.java)
+            .mockStatic(KeyCharacterMap::class.java)
+            .build()!!
 
     @get:Rule
     val setFlagsRule = SetFlagsRule()
@@ -122,6 +125,9 @@
     @Mock
     private lateinit var kbdController: InputManagerService.KeyboardBacklightControllerInterface
 
+    @Mock
+    private lateinit var kcm: KeyCharacterMap
+
     private lateinit var service: InputManagerService
     private lateinit var localService: InputManagerInternal
     private lateinit var context: Context
@@ -171,6 +177,9 @@
         ExtendedMockito.doReturn(packageManagerInternal).`when` {
             LocalServices.getService(eq(PackageManagerInternal::class.java))
         }
+        ExtendedMockito.doReturn(kcm).`when` {
+            KeyCharacterMap.load(anyInt())
+        }
 
         assertTrue("Local service must be registered", this::localService.isInitialized)
         service.setWindowManagerCallbacks(wmCallbacks)
@@ -206,6 +215,7 @@
         verify(native).setTouchpadTapDraggingEnabled(anyBoolean())
         verify(native).setShouldNotifyTouchpadHardwareState(anyBoolean())
         verify(native).setTouchpadRightClickZoneEnabled(anyBoolean())
+        verify(native).setTouchpadThreeFingerTapShortcutEnabled(anyBoolean())
         verify(native).setShowTouches(anyBoolean())
         verify(native).setMotionClassifierEnabled(anyBoolean())
         verify(native).setMaximumObscuringOpacityForTouch(anyFloat())
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index a7e2214..1574d1b 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -16,14 +16,19 @@
 
 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
 import android.hardware.input.InputManager
 import android.hardware.input.InputManagerGlobal
 import android.hardware.input.KeyGestureEvent
@@ -32,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
@@ -97,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
@@ -114,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>()
@@ -121,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()
@@ -137,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))
@@ -150,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)
@@ -157,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,
@@ -169,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",
@@ -190,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",
@@ -201,7 +222,7 @@
 
     @Test
     fun testKeyGestureEvent_multipleGestureHandlers() {
-        val keyGestureController = KeyGestureController(context, testLooper.looper)
+        setupKeyGestureController()
 
         // Set up two callbacks.
         var callbackCount1 = 0
@@ -232,7 +253,7 @@
         keyGestureController.handleKeyGesture(/* deviceId = */ 0, intArrayOf(KeyEvent.KEYCODE_HOME),
             /* modifierState = */ 0, KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
             KeyGestureEvent.ACTION_GESTURE_COMPLETE, /* displayId */ 0,
-            /* focusedToken = */ null, /* flags = */ 0
+            /* focusedToken = */ null, /* flags = */ 0, /* appLaunchData = */null
         )
 
         assertEquals(
@@ -259,12 +280,13 @@
         val expectedKeys: IntArray,
         val expectedModifierState: Int,
         val expectedActions: IntArray,
+        val expectedAppLaunchData: AppLaunchData? = null,
     ) {
         override fun toString(): String = name
     }
 
     @Keep
-    private fun keyGestureEventHandlerTestArguments(): Array<TestData> {
+    private fun systemGesturesTestArguments(): Array<TestData> {
         return arrayOf(
             TestData(
                 "META + A -> Launch Assistant",
@@ -275,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,
@@ -462,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,
@@ -550,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,
@@ -632,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,
@@ -672,7 +1035,7 @@
         keyGestureController.registerKeyGestureHandler(handler, 0)
 
         for (key in testKeys) {
-            sendKeys(keyGestureController, intArrayOf(key), assertNotSentToApps = true)
+            sendKeys(intArrayOf(key), assertNotSentToApps = true)
         }
     }
 
@@ -680,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),
         )
@@ -692,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),
@@ -710,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),
         )
@@ -722,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),
@@ -740,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),
@@ -755,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",
@@ -984,7 +1133,7 @@
     }
 
     @Keep
-    private fun keyGestureEventHandlerTestArguments_forKeyCombinations(): Array<TestData> {
+    private fun systemGesturesTestArguments_forKeyCombinations(): Array<TestData> {
         return arrayOf(
             TestData(
                 "VOLUME_DOWN + POWER -> Screenshot Chord",
@@ -1045,34 +1194,168 @@
     }
 
     @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)
     }
 
-    private fun testKeyGestureInternal(keyGestureController: KeyGestureController, test: TestData) {
-        var handleEvents = mutableListOf<KeyGestureEvent>()
+    @Keep
+    private fun customInputGesturesTestArguments(): Array<TestData> {
+        return arrayOf(
+            TestData(
+                "META + ALT + Q -> Go Home",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_Q
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
+                intArrayOf(KeyEvent.KEYCODE_Q),
+                KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+                intArrayOf(
+                    KeyGestureEvent.ACTION_GESTURE_COMPLETE
+                )
+            ),
+            TestData(
+                "META + ALT + Q -> Launch app",
+                intArrayOf(
+                    KeyEvent.KEYCODE_CTRL_LEFT,
+                    KeyEvent.KEYCODE_SHIFT_LEFT,
+                    KeyEvent.KEYCODE_Q
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_Q),
+                KeyEvent.META_CTRL_ON or KeyEvent.META_SHIFT_ON,
+                intArrayOf(
+                    KeyGestureEvent.ACTION_GESTURE_COMPLETE
+                ),
+                AppLaunchData.createLaunchDataForComponent("com.test", "com.test.BookmarkTest")
+            ),
+        )
+    }
+
+    @Test
+    @Parameters(method = "customInputGesturesTestArguments")
+    fun testCustomKeyGestures(test: TestData) {
+        setupKeyGestureController()
+        val builder = InputGestureData.Builder()
+            .setKeyGestureType(test.expectedKeyGestureType)
+            .setTrigger(
+                InputGestureData.createKeyTrigger(
+                    test.expectedKeys[0],
+                    test.expectedModifierState
+                )
+            )
+        if (test.expectedAppLaunchData != null) {
+            builder.setAppLaunchData(test.expectedAppLaunchData)
+        }
+        val inputGestureData = builder.build()
+
+        keyGestureController.addCustomInputGesture(0, inputGestureData.aidlData)
+        testKeyGestureInternal(test)
+    }
+
+    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,
@@ -1093,33 +1376,30 @@
                 test.expectedActions[i],
                 event.action
             )
+            assertEquals(
+                "Test: $test doesn't produce correct app launch data",
+                test.expectedAppLaunchData,
+                event.appLaunchData
+            )
         }
 
         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) {
@@ -1128,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()
@@ -1141,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()
@@ -1149,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/animation/AnimatorTestRuleToolkit.kt b/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt
index b27b826..ded4679 100644
--- a/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt
+++ b/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt
@@ -17,7 +17,13 @@
 package android.animation
 
 import android.animation.AnimatorTestRuleToolkit.Companion.TAG
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
 import android.util.Log
+import android.view.View
+import androidx.core.graphics.drawable.toBitmap
+import androidx.test.core.app.ActivityScenario
+import java.util.concurrent.TimeUnit
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -36,13 +42,45 @@
 import platform.test.motion.golden.TimeSeries
 import platform.test.motion.golden.TimeSeriesCaptureScope
 import platform.test.motion.golden.TimestampFrameId
+import platform.test.screenshot.captureToBitmapAsync
 
-class AnimatorTestRuleToolkit(val animatorTestRule: AnimatorTestRule, val testScope: TestScope) {
+class AnimatorTestRuleToolkit(
+    internal val animatorTestRule: AnimatorTestRule,
+    internal val testScope: TestScope,
+    internal val currentActivityScenario: () -> ActivityScenario<*>,
+) {
     internal companion object {
         const val TAG = "AnimatorRuleToolkit"
     }
 }
 
+/** Capture utility to extract a [Bitmap] from a [drawable]. */
+fun captureDrawable(drawable: Drawable): Bitmap {
+    val width = drawable.bounds.right - drawable.bounds.left
+    val height = drawable.bounds.bottom - drawable.bounds.top
+
+    // If either dimension is 0 this will fail, so we set it to 1 pixel instead.
+    return drawable.toBitmap(
+        width =
+        if (width > 0) {
+            width
+        } else {
+            1
+        },
+        height =
+        if (height > 0) {
+            height
+        } else {
+            1
+        },
+    )
+}
+
+/** Capture utility to extract a [Bitmap] from a [view]. */
+fun captureView(view: View): Bitmap {
+    return view.captureToBitmapAsync().get(10, TimeUnit.SECONDS)
+}
+
 /**
  * Controls the timing of the motion recording.
  *
@@ -71,24 +109,39 @@
     /** Time interval between frame captures, in milliseconds. */
     val frameDurationMs: Long = 16L,
 
-    /**  Produces the time-series, invoked on each animation frame. */
+    /** Whether a sequence of screenshots should also be recorded. */
+    val visualCapture: ((captureRoot: T) -> Bitmap)? = null,
+
+    /** Produces the time-series, invoked on each animation frame. */
     val timeSeriesCapture: TimeSeriesCaptureScope<T>.() -> Unit,
 )
 
 /** Records the time-series of the features specified in [recordingSpec]. */
 fun <T> MotionTestRule<AnimatorTestRuleToolkit>.recordMotion(
-    recordingSpec: AnimatorRuleRecordingSpec<T>,
+    recordingSpec: AnimatorRuleRecordingSpec<T>
 ): RecordedMotion {
     with(toolkit.animatorTestRule) {
+        val activityScenario = toolkit.currentActivityScenario()
         val frameIdCollector = mutableListOf<FrameId>()
         val propertyCollector = mutableMapOf<String, MutableList<DataPoint<*>>>()
+        val screenshotCollector =
+            if (recordingSpec.visualCapture != null) {
+                mutableListOf<Bitmap>()
+            } else {
+                null
+            }
 
         fun recordFrame(frameId: FrameId) {
             Log.i(TAG, "recordFrame($frameId)")
             frameIdCollector.add(frameId)
-            recordingSpec.timeSeriesCapture.invoke(
-                TimeSeriesCaptureScope(recordingSpec.captureRoot, propertyCollector)
-            )
+            activityScenario.onActivity {
+                recordingSpec.timeSeriesCapture.invoke(
+                    TimeSeriesCaptureScope(recordingSpec.captureRoot, propertyCollector)
+                )
+            }
+
+            val bitmap = recordingSpec.visualCapture?.invoke(recordingSpec.captureRoot)
+            if (bitmap != null) screenshotCollector!!.add(bitmap)
         }
 
         val motionControl =
@@ -101,10 +154,13 @@
 
         Log.i(TAG, "recordMotion() begin recording")
 
-        val startFrameTime = currentTime
+        var startFrameTime: Long? = null
+        toolkit.currentActivityScenario().onActivity { startFrameTime = currentTime }
         while (!motionControl.recordingEnded) {
-            recordFrame(TimestampFrameId(currentTime - startFrameTime))
-            motionControl.nextFrame()
+            var time: Long? = null
+            toolkit.currentActivityScenario().onActivity { time = currentTime }
+            recordFrame(TimestampFrameId(time!! - startFrameTime!!))
+            toolkit.currentActivityScenario().onActivity { motionControl.nextFrame() }
         }
 
         Log.i(TAG, "recordMotion() end recording")
@@ -115,7 +171,7 @@
                 propertyCollector.entries.map { entry -> Feature(entry.key, entry.value) },
             )
 
-        return create(timeSeries, null)
+        return create(timeSeries, screenshotCollector)
     }
 }
 
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/testables/tests/Android.bp b/tests/testables/tests/Android.bp
index 7110564..f0cda53 100644
--- a/tests/testables/tests/Android.bp
+++ b/tests/testables/tests/Android.bp
@@ -37,10 +37,11 @@
         "androidx.core_core-ktx",
         "androidx.test.ext.junit",
         "androidx.test.rules",
-        "androidx.test.ext.junit",
         "hamcrest-library",
         "kotlinx_coroutines_test",
         "mockito-target-inline-minus-junit4",
+        "platform-screenshot-diff-core",
+        "platform-test-annotations",
         "testables",
         "truth",
     ],
@@ -55,6 +56,7 @@
         "android.test.mock.stubs.system",
     ],
     certificate: "platform",
+    test_config: "AndroidTest.xml",
     test_suites: [
         "device-tests",
         "automotive-tests",
diff --git a/tests/testables/tests/AndroidManifest.xml b/tests/testables/tests/AndroidManifest.xml
index 2bfb04f..6cba598 100644
--- a/tests/testables/tests/AndroidManifest.xml
+++ b/tests/testables/tests/AndroidManifest.xml
@@ -23,6 +23,10 @@
 
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
+        <activity
+            android:name="platform.test.screenshot.ScreenshotActivity"
+            android:exported="true">
+        </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/testables/tests/AndroidTest.xml b/tests/testables/tests/AndroidTest.xml
new file mode 100644
index 0000000..85f6e62
--- /dev/null
+++ b/tests/testables/tests/AndroidTest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs Tests for Testables.">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="TestablesTests.apk" />
+        <option name="install-arg" value="-t" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+        <option name="force-root" value="true" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <option name="screen-always-on" value="on" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+        <option name="run-command" value="wm dismiss-keyguard" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="framework-base-presubmit" />
+    <option name="test-tag" value="TestableTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.testables" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="test-filter-dir" value="/data/data/com.android.testables" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys" value="/data/user/0/com.android.testables/files"/>
+        <option name="collect-on-run-ended-only" value="true"/>
+        <option name="clean-up" value="true"/>
+    </metrics_collector>
+</configuration>
diff --git a/tests/testables/tests/goldens/recordFilmstrip_withAnimator.png b/tests/testables/tests/goldens/recordFilmstrip_withAnimator.png
new file mode 100644
index 0000000..9aed2e9
--- /dev/null
+++ b/tests/testables/tests/goldens/recordFilmstrip_withAnimator.png
Binary files differ
diff --git a/tests/testables/tests/goldens/recordFilmstrip_withSpring.png b/tests/testables/tests/goldens/recordFilmstrip_withSpring.png
new file mode 100644
index 0000000..1d0c0c3
--- /dev/null
+++ b/tests/testables/tests/goldens/recordFilmstrip_withSpring.png
Binary files differ
diff --git a/tests/testables/tests/goldens/recordMotion_withAnimator.json b/tests/testables/tests/goldens/recordTimeSeries_withAnimator.json
similarity index 97%
rename from tests/testables/tests/goldens/recordMotion_withAnimator.json
rename to tests/testables/tests/goldens/recordTimeSeries_withAnimator.json
index 87fece5..73eb6c7 100644
--- a/tests/testables/tests/goldens/recordMotion_withAnimator.json
+++ b/tests/testables/tests/goldens/recordTimeSeries_withAnimator.json
@@ -29,7 +29,7 @@
   ],
   "features": [
     {
-      "name": "value",
+      "name": "alpha",
       "type": "float",
       "data_points": [
         1,
diff --git a/tests/testables/tests/goldens/recordMotion_withSpring.json b/tests/testables/tests/goldens/recordTimeSeries_withSpring.json
similarity index 96%
rename from tests/testables/tests/goldens/recordMotion_withSpring.json
rename to tests/testables/tests/goldens/recordTimeSeries_withSpring.json
index e9fb5b4..2b97bad 100644
--- a/tests/testables/tests/goldens/recordMotion_withSpring.json
+++ b/tests/testables/tests/goldens/recordTimeSeries_withSpring.json
@@ -21,7 +21,7 @@
   ],
   "features": [
     {
-      "name": "value",
+      "name": "alpha",
       "type": "float",
       "data_points": [
         1,
diff --git a/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt b/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
index fbef489..993c3fe 100644
--- a/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
+++ b/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
@@ -16,10 +16,15 @@
 
 package android.animation
 
-import android.util.FloatProperty
+import android.graphics.Color
+import android.platform.test.annotations.MotionTest
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.test.ext.junit.rules.ActivityScenarioRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.internal.dynamicanimation.animation.DynamicAnimation
 import com.android.internal.dynamicanimation.animation.SpringAnimation
 import com.android.internal.dynamicanimation.animation.SpringForce
 import kotlinx.coroutines.test.TestScope
@@ -28,102 +33,169 @@
 import org.junit.runner.RunWith
 import platform.test.motion.MotionTestRule
 import platform.test.motion.RecordedMotion
-import platform.test.motion.golden.FeatureCapture
-import platform.test.motion.golden.asDataPoint
 import platform.test.motion.testing.createGoldenPathManager
+import platform.test.motion.view.ViewFeatureCaptures
+import platform.test.screenshot.DeviceEmulationRule
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.DisplaySpec
+import platform.test.screenshot.ScreenshotActivity
+import platform.test.screenshot.ScreenshotTestRule
 
 @SmallTest
+@MotionTest
 @RunWith(AndroidJUnit4::class)
 class AnimatorTestRuleToolkitTest {
     companion object {
         private val GOLDEN_PATH_MANAGER =
             createGoldenPathManager("frameworks/base/tests/testables/tests/goldens")
 
-        private val TEST_PROPERTY =
-            object : FloatProperty<TestState>("value") {
-                override fun get(state: TestState): Float {
-                    return state.animatedValue
-                }
-
-                override fun setValue(state: TestState, value: Float) {
-                    state.animatedValue = value
-                }
-            }
+        private val EMULATION_SPEC =
+            DeviceEmulationSpec(DisplaySpec("phone", width = 320, height = 690, densityDpi = 160))
     }
 
-    @get:Rule(order = 0) val animatorTestRule = AnimatorTestRule(this)
-    @get:Rule(order = 1)
+    @get:Rule(order = 0) val deviceEmulationRule = DeviceEmulationRule(EMULATION_SPEC)
+    @get:Rule(order = 1) val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
+    @get:Rule(order = 2) val animatorTestRule = AnimatorTestRule(this)
+    @get:Rule(order = 3) val screenshotRule = ScreenshotTestRule(GOLDEN_PATH_MANAGER)
+    @get:Rule(order = 4)
     val motionRule =
-        MotionTestRule(AnimatorTestRuleToolkit(animatorTestRule, TestScope()), GOLDEN_PATH_MANAGER)
+        MotionTestRule(
+            AnimatorTestRuleToolkit(animatorTestRule, TestScope()) { activityRule.scenario },
+            GOLDEN_PATH_MANAGER,
+            bitmapDiffer = screenshotRule,
+        )
 
     @Test
-    fun recordMotion_withAnimator() {
-        val state = TestState()
-        AnimatorSet().apply {
-            duration = 500
-            play(
-                ValueAnimator.ofFloat(state.animatedValue, 0f).apply {
-                    addUpdateListener { state.animatedValue = it.animatedValue as Float }
-                }
+    fun recordFilmstrip_withAnimator() {
+        val animatedBox = createScene()
+        createAnimator(animatedBox).apply { getInstrumentation().runOnMainSync { start() } }
+
+        val recordedMotion =
+            record(
+                animatedBox,
+                MotionControl { awaitFrames(count = 26) },
+                sampleIntervalMs = 20L,
+                recordScreenshots = true,
             )
+
+        motionRule.assertThat(recordedMotion).filmstripMatchesGolden("recordFilmstrip_withAnimator")
+    }
+
+    @Test
+    fun recordTimeSeries_withAnimator() {
+        val animatedBox = createScene()
+        createAnimator(animatedBox).apply { getInstrumentation().runOnMainSync { start() } }
+
+        val recordedMotion =
+            record(
+                animatedBox,
+                MotionControl { awaitFrames(count = 26) },
+                sampleIntervalMs = 20L,
+                recordScreenshots = false,
+            )
+
+        motionRule
+            .assertThat(recordedMotion)
+            .timeSeriesMatchesGolden("recordTimeSeries_withAnimator")
+    }
+
+    @Test
+    fun recordFilmstrip_withSpring() {
+        val animatedBox = createScene()
+        var isDone = false
+        createSpring(animatedBox).apply {
+            addEndListener { _, _, _, _ -> isDone = true }
             getInstrumentation().runOnMainSync { start() }
         }
 
         val recordedMotion =
-            record(state, MotionControl { awaitFrames(count = 26) }, sampleIntervalMs = 20L)
+            record(
+                animatedBox,
+                MotionControl { awaitCondition { isDone } },
+                sampleIntervalMs = 16L,
+                recordScreenshots = true,
+            )
 
-        motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden("recordMotion_withAnimator")
+        motionRule.assertThat(recordedMotion).filmstripMatchesGolden("recordFilmstrip_withSpring")
     }
 
     @Test
-    fun recordMotion_withSpring() {
-        val state = TestState()
+    fun recordTimeSeries_withSpring() {
+        val animatedBox = createScene()
         var isDone = false
-        SpringAnimation(state, TEST_PROPERTY).apply {
+        createSpring(animatedBox).apply {
+            addEndListener { _, _, _, _ -> isDone = true }
+            getInstrumentation().runOnMainSync { start() }
+        }
+
+        val recordedMotion =
+            record(
+                animatedBox,
+                MotionControl { awaitCondition { isDone } },
+                sampleIntervalMs = 16L,
+                recordScreenshots = false,
+            )
+
+        motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden("recordTimeSeries_withSpring")
+    }
+
+    private fun createScene(): ViewGroup {
+        lateinit var sceneRoot: ViewGroup
+        activityRule.scenario.onActivity { activity ->
+            sceneRoot = FrameLayout(activity).apply { setBackgroundColor(Color.BLACK) }
+            activity.setContentView(sceneRoot)
+        }
+        getInstrumentation().waitForIdleSync()
+        return sceneRoot
+    }
+
+    private fun createAnimator(animatedBox: ViewGroup): AnimatorSet {
+        return AnimatorSet().apply {
+            duration = 500
+            play(
+                ValueAnimator.ofFloat(animatedBox.alpha, 0f).apply {
+                    addUpdateListener { animatedBox.alpha = it.animatedValue as Float }
+                }
+            )
+        }
+    }
+
+    private fun createSpring(animatedBox: ViewGroup): SpringAnimation {
+        return SpringAnimation(animatedBox, DynamicAnimation.ALPHA).apply {
             spring =
                 SpringForce(0f).apply {
                     stiffness = 500f
                     dampingRatio = 0.95f
                 }
 
-            setStartValue(1f)
+            setStartValue(animatedBox.alpha)
             setMinValue(0f)
             setMaxValue(1f)
             minimumVisibleChange = 0.01f
-
-            addEndListener { _, _, _, _ -> isDone = true }
-            getInstrumentation().runOnMainSync { start() }
         }
-
-        val recordedMotion =
-            record(state, MotionControl { awaitCondition { isDone } }, sampleIntervalMs = 16L)
-
-        motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden("recordMotion_withSpring")
     }
 
     private fun record(
-        state: TestState,
+        container: ViewGroup,
         motionControl: MotionControl,
         sampleIntervalMs: Long,
+        recordScreenshots: Boolean,
     ): RecordedMotion {
-        var recordedMotion: RecordedMotion? = null
-        getInstrumentation().runOnMainSync {
-            recordedMotion =
-                motionRule.recordMotion(
-                    AnimatorRuleRecordingSpec(
-                        state,
-                        motionControl,
-                        sampleIntervalMs,
-                    ) {
-                        feature(
-                            FeatureCapture("value") { state -> state.animatedValue.asDataPoint() },
-                            "value",
-                        )
-                    }
-                )
-        }
-        return recordedMotion!!
+        val visualCapture =
+            if (recordScreenshots) {
+                ::captureView
+            } else {
+                null
+            }
+        return motionRule.recordMotion(
+            AnimatorRuleRecordingSpec(
+                container,
+                motionControl,
+                sampleIntervalMs,
+                visualCapture,
+            ) {
+                feature(ViewFeatureCaptures.alpha, "alpha")
+            }
+        )
     }
-
-    data class TestState(var animatedValue: Float = 1f)
 }
diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java
index a826646..1bcfaf6 100644
--- a/tests/utils/testutils/java/android/os/test/TestLooper.java
+++ b/tests/utils/testutils/java/android/os/test/TestLooper.java
@@ -93,13 +93,25 @@
         try {
             mLooper = LOOPER_CONSTRUCTOR.newInstance(false);
 
-            ThreadLocal<Looper> threadLocalLooper = (ThreadLocal<Looper>) THREAD_LOCAL_LOOPER_FIELD
-                    .get(null);
+            ThreadLocal<Looper> threadLocalLooper =
+                    (ThreadLocal<Looper>) THREAD_LOCAL_LOOPER_FIELD.get(null);
             threadLocalLooper.set(mLooper);
         } catch (IllegalAccessException | InstantiationException | InvocationTargetException 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;
     }
 
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index e045f10..4c7b25a 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -223,7 +223,6 @@
         doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider();
         doReturn(mFeatureFlags).when(mVcnContext).getFeatureFlags();
         doReturn(true).when(mVcnContext).isFlagSafeModeTimeoutConfigEnabled();
-        doReturn(true).when(mVcnContext).isFlagIpSecTransformStateEnabled();
 
         doReturn(mUnderlyingNetworkController)
                 .when(mDeps)
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
index bc7ff47..441b780 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -20,7 +20,6 @@
 import static com.android.server.vcn.routeselection.UnderlyingNetworkControllerTest.getLinkPropertiesWithName;
 
 import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
@@ -127,8 +126,6 @@
                                 false /* isInTestMode */));
         doNothing().when(mVcnContext).ensureRunningOnLooperThread();
 
-        doReturn(true).when(mVcnContext).isFlagIpSecTransformStateEnabled();
-
         setupSystemService(
                 mContext,
                 mConnectivityManager,
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
index 6f31d8d..e540932 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
@@ -226,7 +226,6 @@
     private void resetVcnContext(VcnContext vcnContext) {
         reset(vcnContext);
         doNothing().when(vcnContext).ensureRunningOnLooperThread();
-        doReturn(true).when(vcnContext).isFlagIpSecTransformStateEnabled();
     }
 
     // Package private for use in NetworkPriorityClassifierTest
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
index 2285880..108942e 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -368,7 +368,7 @@
     }
 
     interface ProtologViewerConfigBuilder {
-        fun build(statements: Map<LogCall, Long>): ByteArray
+        fun build(groups: Collection<LogGroup>, statements: Map<LogCall, Long>): ByteArray
     }
 
     private fun viewerConf(command: CommandOptions) {
@@ -416,7 +416,7 @@
         }
 
         val outFile = injector.fileOutputStream(command.viewerConfigFileNameArg)
-        outFile.write(configBuilder.build(logCallRegistry.getStatements()))
+        outFile.write(configBuilder.build(groups.values, logCallRegistry.getStatements()))
         outFile.close()
     }
 
diff --git a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigJsonBuilder.kt b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigJsonBuilder.kt
index 7714db2..16a3d7c 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigJsonBuilder.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigJsonBuilder.kt
@@ -21,8 +21,7 @@
 import java.io.StringWriter
 
 class ViewerConfigJsonBuilder : ProtoLogTool.ProtologViewerConfigBuilder {
-    override fun build(statements: Map<ProtoLogTool.LogCall, Long>): ByteArray {
-        val groups = statements.map { it.key.logGroup }.toSet()
+    override fun build(groups: Collection<LogGroup>, statements: Map<ProtoLogTool.LogCall, Long>): ByteArray {
         val stringWriter = StringWriter()
         val writer = JsonWriter(stringWriter)
         writer.setIndent("  ")
diff --git a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt
index 245e802..de85411 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt
@@ -28,10 +28,14 @@
      * @return a byte array of a ProtoLogViewerConfig proto message encoding all the viewer
      * configurations mapping protolog hashes to message information and log group information.
      */
-    override fun build(statements: Map<ProtoLogTool.LogCall, Long>): ByteArray {
+    override fun build(groups: Collection<LogGroup>, statements: Map<ProtoLogTool.LogCall, Long>): ByteArray {
         val configBuilder = ProtoLogViewerConfig.newBuilder()
 
-        val groups = statements.map { it.key.logGroup }.toSet()
+        // TODO(b/373754057): We are passing all the groups now, because some groups might only be
+        //  used by Kotlin code that is not processed, but for group that get enabled to log to
+        //  logcat we try and load the viewer configurations for this group, so the group must exist
+        //  in the viewer config. Once Kotlin is pre-processed or this logic changes we should only
+        //  use the groups that are actually used as an optimization.
         val groupIds = mutableMapOf<LogGroup, Int>()
         groups.forEach {
             groupIds.putIfAbsent(it, groupIds.size + 1)
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigJsonBuilderTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigJsonBuilderTest.kt
index d27ae88..1a20d4c 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigJsonBuilderTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigJsonBuilderTest.kt
@@ -36,6 +36,8 @@
         private val GROUP_DISABLED = LogGroup("DEBUG_GROUP", false, true, TAG2)
         private val GROUP_TEXT_DISABLED = LogGroup("DEBUG_GROUP", true, false, TAG2)
         private const val PATH = "/tmp/test.java"
+
+        private val GROUPS = listOf(GROUP1, GROUP2, GROUP3)
     }
 
     private val configBuilder = ViewerConfigJsonBuilder()
@@ -53,7 +55,7 @@
                 LogCall(TEST3.messageString, LogLevel.ERROR, GROUP3, PATH)))
 
         val parsedConfig = parseConfig(
-            configBuilder.build(logCallRegistry.getStatements()).toString(Charsets.UTF_8))
+            configBuilder.build(GROUPS, logCallRegistry.getStatements()).toString(Charsets.UTF_8))
         assertEquals(3, parsedConfig.size)
         assertEquals(TEST1, parsedConfig[CodeUtils.hash(PATH,
 	           TEST1.messageString, LogLevel.INFO, GROUP1)])
@@ -72,7 +74,7 @@
                 LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH)))
 
         val parsedConfig = parseConfig(
-            configBuilder.build(logCallRegistry.getStatements()).toString(Charsets.UTF_8))
+            configBuilder.build(GROUPS, logCallRegistry.getStatements()).toString(Charsets.UTF_8))
         assertEquals(1, parsedConfig.size)
         assertEquals(TEST1, parsedConfig[CodeUtils.hash(PATH, TEST1.messageString,
             LogLevel.INFO, GROUP1)])
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigProtoBuilderTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigProtoBuilderTest.kt
new file mode 100644
index 0000000..74a8de7
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigProtoBuilderTest.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.protolog.tool
+
+import com.android.internal.protolog.common.LogLevel
+import com.android.protolog.tool.ProtoLogTool.LogCall
+import com.google.common.truth.Truth
+import org.junit.Test
+import perfetto.protos.PerfettoTrace.ProtoLogViewerConfig
+
+class ViewerConfigProtoBuilderTest {
+    companion object {
+        private val TAG1 = "WM_TEST"
+        private val TAG2 = "WM_DEBUG"
+
+        private val TEST1 = ViewerConfigParser.ConfigEntry("test1", LogLevel.INFO.name,
+            TAG1
+        )
+        private val TEST2 = ViewerConfigParser.ConfigEntry("test2", LogLevel.DEBUG.name,
+            TAG2
+        )
+
+        private val GROUP1 = LogGroup("TEST_GROUP", true, true, TAG1)
+        private val GROUP2 = LogGroup("DEBUG_GROUP", true, true, TAG2)
+        private val GROUP3 = LogGroup("UNUSED_GROUP", true, true, TAG1)
+
+        private val GROUPS = listOf(
+            GROUP1,
+            GROUP2,
+            GROUP3
+        )
+
+        private const val PATH = "/tmp/test.java"
+    }
+
+    @Test
+    fun includesUnusedProtoLogGroups() {
+        // Added because of b/373754057. This test might need to be removed in the future.
+
+        val configBuilder = ViewerConfigProtoBuilder()
+
+        val logCallRegistry = ProtoLogTool.LogCallRegistry()
+        logCallRegistry.addLogCalls(listOf(
+            LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH),
+            LogCall(TEST2.messageString, LogLevel.INFO, GROUP2, PATH),
+        ))
+
+        val rawProto = configBuilder.build(GROUPS, logCallRegistry.getStatements())
+
+        val viewerConfig = ProtoLogViewerConfig.parseFrom(rawProto)
+        Truth.assertThat(viewerConfig.groupsCount).isEqualTo(GROUPS.size)
+        Truth.assertThat(viewerConfig.messagesCount).isLessThan(GROUPS.size)
+    }
+}
\ No newline at end of file
diff --git a/tools/systemfeatures/Android.bp b/tools/systemfeatures/Android.bp
index 590f719..e6d0a3d 100644
--- a/tools/systemfeatures/Android.bp
+++ b/tools/systemfeatures/Android.bp
@@ -58,6 +58,7 @@
         "junit",
         "objenesis",
         "mockito",
+        "systemfeatures-gen-lib",
         "truth",
     ],
 }
diff --git a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
index 196b5e7..1abe77f 100644
--- a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
+++ b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
@@ -71,7 +71,7 @@
         println("Usage: SystemFeaturesGenerator <outputClassName> [options]")
         println(" Options:")
         println("  --readonly=true|false    Whether to encode features as build-time constants")
-        println("  --feature=\$NAME:\$VER   A feature+version pair, where \$VER can be:")
+        println("  --feature=\$NAME:\$VER     A feature+version pair, where \$VER can be:")
         println("                             * blank/empty == undefined (variable API)")
         println("                             * valid int   == enabled   (constant API)")
         println("                             * UNAVAILABLE == disabled  (constant API)")
@@ -89,6 +89,17 @@
     /** Main entrypoint for build-time system feature codegen. */
     @JvmStatic
     fun main(args: Array<String>) {
+        generate(args, System.out)
+    }
+
+    /**
+     * Simple API entrypoint for build-time system feature codegen.
+     *
+     * Note: Typically this would be implemented in terms of a proper Builder-type input argument,
+     * but it's primarily used for testing as opposed to direct production usage.
+     */
+    @JvmStatic
+    fun generate(args: Array<String>, output: Appendable) {
         if (args.size < 1) {
             usage()
             return
@@ -155,7 +166,7 @@
             .addFileComment("This file is auto-generated. DO NOT MODIFY.\n")
             .addFileComment("Args: ${args.joinToString(" \\\n           ")}")
             .build()
-            .writeTo(System.out)
+            .writeTo(output)
     }
 
     /*
@@ -171,12 +182,27 @@
         return when (featureArgs.getOrNull(1)) {
             null, "" -> FeatureInfo(name, null, readonly = false)
             "UNAVAILABLE" -> FeatureInfo(name, null, readonly = true)
-            else -> FeatureInfo(name, featureArgs[1].toIntOrNull(), readonly = true)
+            else -> {
+                val featureVersion =
+                    featureArgs[1].toIntOrNull()
+                        ?: throw IllegalArgumentException(
+                            "Invalid feature version input for $name: ${featureArgs[1]}"
+                        )
+                FeatureInfo(name, featureArgs[1].toInt(), readonly = true)
+            }
         }
     }
 
     private fun parseFeatureName(name: String): String =
-        if (name.startsWith("FEATURE_")) name else "FEATURE_$name"
+        when {
+            name.startsWith("android") ->
+                throw IllegalArgumentException(
+                    "Invalid feature name input: \"android\"-namespaced features must be " +
+                    "provided as PackageManager.FEATURE_* suffixes, not raw feature strings."
+                )
+            name.startsWith("FEATURE_") -> name
+            else -> "FEATURE_$name"
+        }
 
     /*
      * Adds per-feature query methods to the class with the form:
diff --git a/tools/systemfeatures/tests/src/SystemFeaturesGeneratorApiTest.java b/tools/systemfeatures/tests/src/SystemFeaturesGeneratorApiTest.java
new file mode 100644
index 0000000..f8c585d
--- /dev/null
+++ b/tools/systemfeatures/tests/src/SystemFeaturesGeneratorApiTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemfeatures;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.io.IOException;
+
+// Note: This is a very simple argument test to validate certain behaviors for
+// invalid arguments. Correctness and validity is largely exercised by
+// SystemFeaturesGeneratorTest.
+@RunWith(JUnit4.class)
+public class SystemFeaturesGeneratorApiTest {
+
+    @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock private Appendable mOut;
+
+    @Test
+    public void testEmpty() throws IOException {
+        final String[] args = new String[] {};
+        // This should just print the commandline and return.
+        SystemFeaturesGenerator.generate(args, mOut);
+        verify(mOut, never()).append(any());
+    }
+
+    @Test
+    public void testBasic() throws IOException {
+        final String[] args = new String[] {
+            "com.foo.Features",
+            "--feature=TELEVISION:0",
+        };
+        SystemFeaturesGenerator.generate(args, mOut);
+        verify(mOut, atLeastOnce()).append(any());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidFeatureVersion() throws IOException {
+        final String[] args = new String[] {
+            "com.foo.Features",
+            "--feature=TELEVISION:blarg",
+        };
+        SystemFeaturesGenerator.generate(args, mOut);
+        verify(mOut, never()).append(any());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidFeatureNameFromAndroidNamespace() throws IOException {
+        final String[] args = new String[] {
+            "com.foo.Features",
+            "--feature=android.hardware.doesntexist:0",
+        };
+        SystemFeaturesGenerator.generate(args, mOut);
+        verify(mOut, never()).append(any());
+    }
+}