Merge "Formalize DisconnectCause API" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index cd991c7..1c6df75a 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -36,6 +36,7 @@
     ":android.hardware.radio.flags-aconfig-java{.generated_srcjars}",
     ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}",
     ":android.location.flags-aconfig-java{.generated_srcjars}",
+    ":android.media.codec-aconfig-java{.generated_srcjars}",
     ":android.media.tv.flags-aconfig-java{.generated_srcjars}",
     ":android.multiuser.flags-aconfig-java{.generated_srcjars}",
     ":android.net.platform.flags-aconfig-java{.generated_srcjars}",
@@ -55,6 +56,7 @@
     ":android.service.notification.flags-aconfig-java{.generated_srcjars}",
     ":android.service.voice.flags-aconfig-java{.generated_srcjars}",
     ":android.speech.flags-aconfig-java{.generated_srcjars}",
+    ":android.systemserver.flags-aconfig-java{.generated_srcjars}",
     ":android.tracing.flags-aconfig-java{.generated_srcjars}",
     ":android.view.accessibility.flags-aconfig-java{.generated_srcjars}",
     ":android.view.contentcapture.flags-aconfig-java{.generated_srcjars}",
@@ -1158,3 +1160,16 @@
     host_supported: true,
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// System Server
+aconfig_declarations {
+    name: "android.systemserver.flags-aconfig",
+    package: "android.server",
+    srcs: ["services/java/com/android/server/flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.systemserver.flags-aconfig-java",
+    aconfig_declarations: "android.systemserver.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 324d8ca..7284f47 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -23,7 +23,6 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.util.TimeUtils.formatDuration;
 
 import android.annotation.BytesLong;
@@ -50,9 +49,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
-import android.os.Process;
 import android.os.Trace;
-import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.Log;
 
@@ -206,6 +203,8 @@
     /* Minimum flex for a periodic job, in milliseconds. */
     private static final long MIN_FLEX_MILLIS = 5 * 60 * 1000L; // 5 minutes
 
+    private static final long MIN_ALLOWED_TIME_WINDOW_MILLIS = MIN_PERIOD_MILLIS;
+
     /**
      * Minimum backoff interval for a job, in milliseconds
      * @hide
@@ -1881,11 +1880,12 @@
         }
 
         /**
-         * Set deadline which is the maximum scheduling latency. The job will be run by this
-         * deadline even if other requirements (including a delay set through
-         * {@link #setMinimumLatency(long)}) are not met.
+         * Set a deadline after which all other functional requested constraints will be ignored.
+         * After the deadline has passed, the job can run even if other requirements (including
+         * a delay set through {@link #setMinimumLatency(long)}) are not met.
          * {@link JobParameters#isOverrideDeadlineExpired()} will return {@code true} if the job's
-         * deadline has passed.
+         * deadline has passed. The job's execution may be delayed beyond the set deadline by
+         * other factors such as Doze mode and system health signals.
          *
          * <p>
          * Because it doesn't make sense setting this property on a periodic job, doing so will
@@ -1894,30 +1894,23 @@
          *
          * <p class="note">
          * Since a job will run once the deadline has passed regardless of the status of other
-         * constraints, setting a deadline of 0 with other constraints makes those constraints
-         * meaningless when it comes to execution decisions. Avoid doing this.
-         * </p>
-         *
-         * <p>
-         * Short deadlines hinder the system's ability to optimize scheduling behavior and may
-         * result in running jobs at inopportune times. Therefore, starting in Android version
-         * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, minimum time windows will be
-         * enforced to help make it easier to better optimize job execution. Time windows are
+         * constraints, setting a deadline of 0 (or a {@link #setMinimumLatency(long) delay} equal
+         * to the deadline) with other constraints makes those constraints
+         * meaningless when it comes to execution decisions. Since doing so is indicative of an
+         * error in the logic, starting in Android version
+         * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, jobs with extremely short
+         * time windows will fail to build. Time windows are
          * defined as the time between a job's {@link #setMinimumLatency(long) minimum latency}
          * and its deadline. If the minimum latency is not set, it is assumed to be 0.
-         * The following minimums will be enforced:
-         * <ul>
-         *     <li>
-         *         Jobs with {@link #PRIORITY_DEFAULT} or higher priorities have a minimum time
-         *         window of one hour.
-         *     </li>
-         *     <li>Jobs with {@link #PRIORITY_LOW} have a minimum time window of 6 hours.</li>
-         *     <li>Jobs with {@link #PRIORITY_MIN} have a minimum time window of 12 hours.</li>
-         * </ul>
          *
          * Work that must happen immediately should use {@link #setExpedited(boolean)} or
          * {@link #setUserInitiated(boolean)} in the appropriate manner.
          *
+         * <p>
+         * This API aimed to guarantee execution of the job by the deadline only on Android version
+         * {@link android.os.Build.VERSION_CODES#LOLLIPOP}. That aim and guarantee has not existed
+         * since {@link android.os.Build.VERSION_CODES#M}.
+         *
          * @see JobInfo#getMaxExecutionDelayMillis()
          */
         public Builder setOverrideDeadline(long maxExecutionDelayMillis) {
@@ -2347,35 +2340,36 @@
                 throw new IllegalArgumentException("Invalid priority level provided: " + mPriority);
         }
 
-        if (enforceMinimumTimeWindows
-                && Flags.enforceMinimumTimeWindows()
-                // TODO(312197030): remove exemption for the system
-                && !UserHandle.isCore(Process.myUid())
-                && hasLateConstraint && !isPeriodic) {
-            final long windowStart = hasEarlyConstraint ? minLatencyMillis : 0;
-            if (mPriority >= PRIORITY_DEFAULT) {
-                if (maxExecutionDelayMillis - windowStart < HOUR_IN_MILLIS) {
-                    throw new IllegalArgumentException(
-                            getPriorityString(mPriority)
-                                    + " cannot have a time window less than 1 hour."
-                                    + " Delay=" + windowStart
-                                    + ", deadline=" + maxExecutionDelayMillis);
-                }
-            } else if (mPriority >= PRIORITY_LOW) {
-                if (maxExecutionDelayMillis - windowStart < 6 * HOUR_IN_MILLIS) {
-                    throw new IllegalArgumentException(
-                            getPriorityString(mPriority)
-                                    + " cannot have a time window less than 6 hours."
-                                    + " Delay=" + windowStart
-                                    + ", deadline=" + maxExecutionDelayMillis);
-                }
+        final boolean hasFunctionalConstraint = networkRequest != null
+                || constraintFlags != 0
+                || (triggerContentUris != null && triggerContentUris.length > 0);
+        if (hasLateConstraint && !isPeriodic) {
+            if (!hasFunctionalConstraint) {
+                Log.w(TAG, "Job '" + service.flattenToShortString() + "#" + jobId + "'"
+                        + " has a deadline with no functional constraints."
+                        + " The deadline won't improve job execution latency."
+                        + " Consider removing the deadline.");
             } else {
-                if (maxExecutionDelayMillis - windowStart < 12 * HOUR_IN_MILLIS) {
-                    throw new IllegalArgumentException(
-                            getPriorityString(mPriority)
-                                    + " cannot have a time window less than 12 hours."
-                                    + " Delay=" + windowStart
-                                    + ", deadline=" + maxExecutionDelayMillis);
+                final long windowStart = hasEarlyConstraint ? minLatencyMillis : 0;
+                if (maxExecutionDelayMillis - windowStart < MIN_ALLOWED_TIME_WINDOW_MILLIS) {
+                    if (enforceMinimumTimeWindows
+                            && Flags.enforceMinimumTimeWindows()) {
+                        throw new IllegalArgumentException("Jobs with a deadline and"
+                                + " functional constraints cannot have a time window less than "
+                                + MIN_ALLOWED_TIME_WINDOW_MILLIS + " ms."
+                                + " Job '" + service.flattenToShortString() + "#" + jobId + "'"
+                                + " has delay=" + windowStart
+                                + ", deadline=" + maxExecutionDelayMillis);
+                    } else {
+                        Log.w(TAG, "Job '" + service.flattenToShortString() + "#" + jobId + "'"
+                                + " has a deadline with functional constraints and an extremely"
+                                + " short time window of "
+                                + (maxExecutionDelayMillis - windowStart) + " ms"
+                                + " (delay=" + windowStart
+                                + ", deadline=" + maxExecutionDelayMillis + ")."
+                                + " The functional constraints are not likely to be satisfied when"
+                                + " the job runs.");
+                    }
                 }
             }
         }
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 cea16d6..e6ee975 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -318,7 +318,8 @@
     private final List<JobRestriction> mJobRestrictions;
 
     @GuardedBy("mLock")
-    private final BatteryStateTracker mBatteryStateTracker;
+    @VisibleForTesting
+    final BatteryStateTracker mBatteryStateTracker;
 
     @GuardedBy("mLock")
     private final SparseArray<String> mCloudMediaProviderPackages = new SparseArray<>();
@@ -4261,20 +4262,34 @@
                 .sendToTarget();
     }
 
-    private final class BatteryStateTracker extends BroadcastReceiver {
-        /**
-         * Track whether we're "charging", where charging means that we're ready to commit to
-         * doing work.
-         */
-        private boolean mCharging;
+    @VisibleForTesting
+    final class BatteryStateTracker extends BroadcastReceiver
+            implements BatteryManagerInternal.ChargingPolicyChangeListener {
+        private final BatteryManagerInternal mBatteryManagerInternal;
+
+        /** Last reported battery level. */
+        private int mBatteryLevel;
         /** Keep track of whether the battery is charged enough that we want to do work. */
         private boolean mBatteryNotLow;
+        /**
+         * Charging status based on {@link BatteryManager#ACTION_CHARGING} and
+         * {@link BatteryManager#ACTION_DISCHARGING}.
+         */
+        private boolean mCharging;
+        /**
+         * The most recently acquired value of
+         * {@link BatteryManager#BATTERY_PROPERTY_CHARGING_POLICY}.
+         */
+        private int mChargingPolicy;
+        /** Track whether there is power connected. It doesn't mean the device is charging. */
+        private boolean mPowerConnected;
         /** Sequence number of last broadcast. */
         private int mLastBatterySeq = -1;
 
         private BroadcastReceiver mMonitor;
 
         BatteryStateTracker() {
+            mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class);
         }
 
         public void startTracking() {
@@ -4286,13 +4301,18 @@
             // Charging/not charging.
             filter.addAction(BatteryManager.ACTION_CHARGING);
             filter.addAction(BatteryManager.ACTION_DISCHARGING);
+            filter.addAction(Intent.ACTION_BATTERY_LEVEL_CHANGED);
+            filter.addAction(Intent.ACTION_POWER_CONNECTED);
+            filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
             getTestableContext().registerReceiver(this, filter);
 
+            mBatteryManagerInternal.registerChargingPolicyChangeListener(this);
+
             // Initialise tracker state.
-            BatteryManagerInternal batteryManagerInternal =
-                    LocalServices.getService(BatteryManagerInternal.class);
-            mBatteryNotLow = !batteryManagerInternal.getBatteryLevelLow();
-            mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
+            mBatteryLevel = mBatteryManagerInternal.getBatteryLevel();
+            mBatteryNotLow = !mBatteryManagerInternal.getBatteryLevelLow();
+            mCharging = mBatteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
+            mChargingPolicy = mBatteryManagerInternal.getChargingPolicy();
         }
 
         public void setMonitorBatteryLocked(boolean enabled) {
@@ -4315,7 +4335,7 @@
         }
 
         public boolean isCharging() {
-            return mCharging;
+            return isConsideredCharging();
         }
 
         public boolean isBatteryNotLow() {
@@ -4326,17 +4346,42 @@
             return mMonitor != null;
         }
 
+        public boolean isPowerConnected() {
+            return mPowerConnected;
+        }
+
         public int getSeq() {
             return mLastBatterySeq;
         }
 
         @Override
+        public void onChargingPolicyChanged(int newPolicy) {
+            synchronized (mLock) {
+                if (mChargingPolicy == newPolicy) {
+                    return;
+                }
+                if (DEBUG) {
+                    Slog.i(TAG,
+                            "Charging policy changed from " + mChargingPolicy + " to " + newPolicy);
+                }
+
+                final boolean wasConsideredCharging = isConsideredCharging();
+                mChargingPolicy = newPolicy;
+
+                if (isConsideredCharging() != wasConsideredCharging) {
+                    for (int c = mControllers.size() - 1; c >= 0; --c) {
+                        mControllers.get(c).onBatteryStateChangedLocked();
+                    }
+                }
+            }
+        }
+
+        @Override
         public void onReceive(Context context, Intent intent) {
             onReceiveInternal(intent);
         }
 
-        @VisibleForTesting
-        public void onReceiveInternal(Intent intent) {
+        private void onReceiveInternal(Intent intent) {
             synchronized (mLock) {
                 final String action = intent.getAction();
                 boolean changed = false;
@@ -4356,21 +4401,49 @@
                         mBatteryNotLow = true;
                         changed = true;
                     }
+                } else if (Intent.ACTION_BATTERY_LEVEL_CHANGED.equals(action)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Battery level changed @ "
+                                + sElapsedRealtimeClock.millis());
+                    }
+                    final boolean wasConsideredCharging = isConsideredCharging();
+                    mBatteryLevel = mBatteryManagerInternal.getBatteryLevel();
+                    changed = isConsideredCharging() != wasConsideredCharging;
+                } else if (Intent.ACTION_POWER_CONNECTED.equals(action)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Power connected @ " + sElapsedRealtimeClock.millis());
+                    }
+                    if (mPowerConnected) {
+                        return;
+                    }
+                    mPowerConnected = true;
+                    changed = true;
+                } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Power disconnected @ " + sElapsedRealtimeClock.millis());
+                    }
+                    if (!mPowerConnected) {
+                        return;
+                    }
+                    mPowerConnected = false;
+                    changed = true;
                 } else if (BatteryManager.ACTION_CHARGING.equals(action)) {
                     if (DEBUG) {
                         Slog.d(TAG, "Battery charging @ " + sElapsedRealtimeClock.millis());
                     }
                     if (!mCharging) {
+                        final boolean wasConsideredCharging = isConsideredCharging();
                         mCharging = true;
-                        changed = true;
+                        changed = isConsideredCharging() != wasConsideredCharging;
                     }
                 } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
                     if (DEBUG) {
                         Slog.d(TAG, "Battery discharging @ " + sElapsedRealtimeClock.millis());
                     }
                     if (mCharging) {
+                        final boolean wasConsideredCharging = isConsideredCharging();
                         mCharging = false;
-                        changed = true;
+                        changed = isConsideredCharging() != wasConsideredCharging;
                     }
                 }
                 mLastBatterySeq =
@@ -4382,6 +4455,30 @@
                 }
             }
         }
+
+        private boolean isConsideredCharging() {
+            if (mCharging) {
+                return true;
+            }
+            // BatteryService (or Health HAL or whatever central location makes sense)
+            // should ideally hold this logic so that everyone has a consistent
+            // idea of when the device is charging (or an otherwise stable charging/plugged state).
+            // TODO(304512874): move this determination to BatteryService
+            if (!mPowerConnected) {
+                return false;
+            }
+
+            if (mChargingPolicy == Integer.MIN_VALUE) {
+                // Property not supported on this device.
+                return false;
+            }
+            // Adaptive charging policies don't expose their target battery level, but 80% is a
+            // commonly used threshold for battery health, so assume that's what's being used by
+            // the policies and use 70%+ as the threshold here for charging in case some
+            // implementations choose to discharge the device slightly before recharging back up
+            // to the target level.
+            return mBatteryLevel >= 70 && BatteryManager.isAdaptiveChargingPolicy(mChargingPolicy);
+        }
     }
 
     final class LocalService implements JobSchedulerInternal {
@@ -5450,6 +5547,13 @@
         }
     }
 
+    /** Return {@code true} if the device is connected to power. */
+    public boolean isPowerConnected() {
+        synchronized (mLock) {
+            return mBatteryStateTracker.isPowerConnected();
+        }
+    }
+
     int getStorageSeq() {
         synchronized (mLock) {
             return mStorageController.getTracker().getSeq();
@@ -5778,8 +5882,14 @@
             mQuotaTracker.dump(pw);
             pw.println();
 
+            pw.print("Power connected: ");
+            pw.println(mBatteryStateTracker.isPowerConnected());
             pw.print("Battery charging: ");
-            pw.println(mBatteryStateTracker.isCharging());
+            pw.println(mBatteryStateTracker.mCharging);
+            pw.print("Considered charging: ");
+            pw.println(mBatteryStateTracker.isConsideredCharging());
+            pw.print("Battery level: ");
+            pw.println(mBatteryStateTracker.mBatteryLevel);
             pw.print("Battery not low: ");
             pw.println(mBatteryStateTracker.isBatteryNotLow());
             if (mBatteryStateTracker.isMonitoring()) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
index ddbc2ec..e9f9b14 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
@@ -20,12 +20,6 @@
 
 import android.annotation.NonNull;
 import android.app.job.JobInfo;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.BatteryManager;
-import android.os.BatteryManagerInternal;
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
@@ -36,7 +30,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.AppSchedulingModuleThread;
-import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateControllerProto;
 
@@ -60,8 +53,6 @@
     @GuardedBy("mLock")
     private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>();
 
-    private final PowerTracker mPowerTracker;
-
     private final FlexibilityController mFlexibilityController;
     /**
      * Helper set to avoid too much GC churn from frequent calls to
@@ -77,16 +68,10 @@
     public BatteryController(JobSchedulerService service,
             FlexibilityController flexibilityController) {
         super(service);
-        mPowerTracker = new PowerTracker();
         mFlexibilityController = flexibilityController;
     }
 
     @Override
-    public void startTrackingLocked() {
-        mPowerTracker.startTracking();
-    }
-
-    @Override
     public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
         if (taskStatus.hasPowerConstraint()) {
             final long nowElapsed = sElapsedRealtimeClock.millis();
@@ -95,7 +80,7 @@
             if (taskStatus.hasChargingConstraint()) {
                 if (hasTopExemptionLocked(taskStatus)) {
                     taskStatus.setChargingConstraintSatisfied(nowElapsed,
-                            mPowerTracker.isPowerConnected());
+                            mService.isPowerConnected());
                 } else {
                     taskStatus.setChargingConstraintSatisfied(nowElapsed,
                             mService.isBatteryCharging() && mService.isBatteryNotLow());
@@ -178,7 +163,7 @@
 
     @GuardedBy("mLock")
     private void maybeReportNewChargingStateLocked() {
-        final boolean powerConnected = mPowerTracker.isPowerConnected();
+        final boolean powerConnected = mService.isPowerConnected();
         final boolean stablePower = mService.isBatteryCharging() && mService.isBatteryNotLow();
         final boolean batteryNotLow = mService.isBatteryNotLow();
         if (DEBUG) {
@@ -239,62 +224,6 @@
         mChangedJobs.clear();
     }
 
-    private final class PowerTracker extends BroadcastReceiver {
-        /**
-         * Track whether there is power connected. It doesn't mean the device is charging.
-         * Use {@link JobSchedulerService#isBatteryCharging()} to determine if the device is
-         * charging.
-         */
-        private boolean mPowerConnected;
-
-        PowerTracker() {
-        }
-
-        void startTracking() {
-            IntentFilter filter = new IntentFilter();
-
-            filter.addAction(Intent.ACTION_POWER_CONNECTED);
-            filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
-            mContext.registerReceiver(this, filter);
-
-            // Initialize tracker state.
-            BatteryManagerInternal batteryManagerInternal =
-                    LocalServices.getService(BatteryManagerInternal.class);
-            mPowerConnected = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
-        }
-
-        boolean isPowerConnected() {
-            return mPowerConnected;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            synchronized (mLock) {
-                final String action = intent.getAction();
-
-                if (Intent.ACTION_POWER_CONNECTED.equals(action)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Power connected @ " + sElapsedRealtimeClock.millis());
-                    }
-                    if (mPowerConnected) {
-                        return;
-                    }
-                    mPowerConnected = true;
-                } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Power disconnected @ " + sElapsedRealtimeClock.millis());
-                    }
-                    if (!mPowerConnected) {
-                        return;
-                    }
-                    mPowerConnected = false;
-                }
-
-                maybeReportNewChargingStateLocked();
-            }
-        }
-    }
-
     @VisibleForTesting
     ArraySet<JobStatus> getTrackedJobs() {
         return mTrackedTasks;
@@ -308,7 +237,6 @@
     @Override
     public void dumpControllerStateLocked(IndentingPrintWriter pw,
             Predicate<JobStatus> predicate) {
-        pw.println("Power connected: " + mPowerTracker.isPowerConnected());
         pw.println("Stable power: " + (mService.isBatteryCharging() && mService.isBatteryNotLow()));
         pw.println("Not low: " + mService.isBatteryNotLow());
 
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 2ea980d..a3a686f 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
@@ -2053,6 +2053,11 @@
             case CONSTRAINT_WITHIN_QUOTA:
                 return JobParameters.STOP_REASON_QUOTA;
 
+            // This can change from true to false, but should never change when a job is already
+            // running, so there's no reason to log a message or create a new stop reason.
+            case CONSTRAINT_FLEXIBLE:
+                return JobParameters.STOP_REASON_UNDEFINED;
+
             // These should never be stop reasons since they can never go from true to false.
             case CONSTRAINT_CONTENT_TRIGGER:
             case CONSTRAINT_DEADLINE:
diff --git a/api/Android.bp b/api/Android.bp
index 27c372a..a148cbd 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -200,7 +200,7 @@
     out: ["current.srcjar"],
     cmd: "$(location merge_zips) $(out) $(in)",
     srcs: [
-        ":api-stubs-docs-non-updatable",
+        ":api-stubs-docs-non-updatable{.exportable}",
         ":all-modules-public-stubs-source",
     ],
     visibility: ["//visibility:private"], // Used by make module in //development, mind
diff --git a/core/api/current.txt b/core/api/current.txt
index 283e429..bca15bd 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -765,7 +765,7 @@
     field public static final int endY = 16844051; // 0x1010513
     field @Deprecated public static final int endYear = 16843133; // 0x101017d
     field public static final int enforceNavigationBarContrast = 16844293; // 0x1010605
-    field public static final int enforceStatusBarContrast = 16844292; // 0x1010604
+    field @Deprecated public static final int enforceStatusBarContrast = 16844292; // 0x1010604
     field public static final int enterFadeDuration = 16843532; // 0x101030c
     field public static final int entries = 16842930; // 0x10100b2
     field public static final int entryValues = 16843256; // 0x10101f8
@@ -1196,8 +1196,8 @@
     field public static final int multiprocess = 16842771; // 0x1010013
     field public static final int name = 16842755; // 0x1010003
     field public static final int nativeHeapZeroInitialized = 16844325; // 0x1010625
-    field public static final int navigationBarColor = 16843858; // 0x1010452
-    field public static final int navigationBarDividerColor = 16844141; // 0x101056d
+    field @Deprecated public static final int navigationBarColor = 16843858; // 0x1010452
+    field @Deprecated public static final int navigationBarDividerColor = 16844141; // 0x101056d
     field public static final int navigationContentDescription = 16843969; // 0x10104c1
     field public static final int navigationIcon = 16843968; // 0x10104c0
     field public static final int navigationMode = 16843471; // 0x10102cf
@@ -1568,7 +1568,7 @@
     field public static final int state_single = 16842915; // 0x10100a3
     field public static final int state_window_focused = 16842909; // 0x101009d
     field public static final int staticWallpaperPreview = 16843569; // 0x1010331
-    field public static final int statusBarColor = 16843857; // 0x1010451
+    field @Deprecated public static final int statusBarColor = 16843857; // 0x1010451
     field public static final int stepSize = 16843078; // 0x1010146
     field public static final int stopWithTask = 16843626; // 0x101036a
     field public static final int streamType = 16843273; // 0x1010209
@@ -1890,6 +1890,7 @@
     field public static final int windowNoDisplay = 16843294; // 0x101021e
     field public static final int windowNoMoveAnimation = 16844421; // 0x1010685
     field public static final int windowNoTitle = 16842838; // 0x1010056
+    field @FlaggedApi("com.android.window.flags.enforce_edge_to_edge") public static final int windowOptOutEdgeToEdgeEnforcement;
     field @Deprecated public static final int windowOverscan = 16843727; // 0x10103cf
     field public static final int windowReenterTransition = 16843951; // 0x10104af
     field public static final int windowReturnTransition = 16843950; // 0x10104ae
@@ -7894,6 +7895,7 @@
     field public static final String AUTO_TIME_POLICY = "autoTime";
     field public static final String BACKUP_SERVICE_POLICY = "backupService";
     field public static final String CAMERA_DISABLED_POLICY = "cameraDisabled";
+    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 public static final String PACKAGES_SUSPENDED_POLICY = "packagesSuspended";
@@ -7944,6 +7946,7 @@
     method public boolean getBluetoothContactSharingDisabled(@NonNull android.content.ComponentName);
     method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CAMERA, conditional=true) public boolean getCameraDisabled(@Nullable android.content.ComponentName);
     method @Deprecated @Nullable public String getCertInstallerPackage(@NonNull android.content.ComponentName) throws java.lang.SecurityException;
+    method @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, conditional=true) public int getContentProtectionPolicy(@Nullable android.content.ComponentName);
     method @Nullable public android.app.admin.PackagePolicy getCredentialManagerPolicy();
     method @Deprecated @Nullable public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName);
     method @Deprecated public boolean getCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName);
@@ -8100,6 +8103,7 @@
     method @Deprecated public void setCertInstallerPackage(@NonNull android.content.ComponentName, @Nullable String) throws java.lang.SecurityException;
     method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE, conditional=true) public void setCommonCriteriaModeEnabled(@Nullable android.content.ComponentName, boolean);
     method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_WIFI, conditional=true) public void setConfiguredNetworksLockdownState(@Nullable android.content.ComponentName, boolean);
+    method @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, conditional=true) public void setContentProtectionPolicy(@Nullable android.content.ComponentName, int);
     method public void setCredentialManagerPolicy(@Nullable android.app.admin.PackagePolicy);
     method @Deprecated public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>);
     method @Deprecated public void setCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName, boolean);
@@ -8525,6 +8529,7 @@
     field public static final int TAG_ADB_SHELL_CMD = 210002; // 0x33452
     field public static final int TAG_ADB_SHELL_INTERACTIVE = 210001; // 0x33451
     field public static final int TAG_APP_PROCESS_START = 210005; // 0x33455
+    field @FlaggedApi("android.app.admin.flags.backup_service_security_log_event_enabled") public static final int TAG_BACKUP_SERVICE_TOGGLED = 210044; // 0x3347c
     field public static final int TAG_BLUETOOTH_CONNECTION = 210039; // 0x33477
     field public static final int TAG_BLUETOOTH_DISCONNECTION = 210040; // 0x33478
     field public static final int TAG_CAMERA_POLICY_SET = 210034; // 0x33472
@@ -9357,9 +9362,12 @@
     method public long getDataBytes();
     method public long getExternalCacheBytes();
     method public void writeToParcel(android.os.Parcel, int);
-    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_APK = 0; // 0x0
-    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_DM = 1; // 0x1
-    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_LIB = 2; // 0x2
+    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_APK = 3; // 0x3
+    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_CURRENT_PROFILE = 2; // 0x2
+    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_DEXOPT_ARTIFACT = 0; // 0x0
+    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_DM = 4; // 0x4
+    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_REFERENCE_PROFILE = 1; // 0x1
+    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_LIB = 5; // 0x5
     field @NonNull public static final android.os.Parcelable.Creator<android.app.usage.StorageStats> CREATOR;
   }
 
@@ -12407,7 +12415,7 @@
     method public void registerCallback(android.content.pm.LauncherApps.Callback, android.os.Handler);
     method public void registerPackageInstallerSessionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.content.pm.PackageInstaller.SessionCallback);
     method public android.content.pm.LauncherActivityInfo resolveActivity(android.content.Intent, android.os.UserHandle);
-    method @FlaggedApi("android.content.pm.archiving") public void setArchiveCompatibilityOptions(boolean, boolean);
+    method @FlaggedApi("android.content.pm.archiving") public void setArchiveCompatibility(@NonNull android.content.pm.LauncherApps.ArchiveCompatibilityParams);
     method public boolean shouldHideFromSuggestions(@NonNull String, @NonNull android.os.UserHandle);
     method public void startAppDetailsActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
     method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
@@ -12421,6 +12429,12 @@
     field public static final String EXTRA_PIN_ITEM_REQUEST = "android.content.pm.extra.PIN_ITEM_REQUEST";
   }
 
+  @FlaggedApi("android.content.pm.archiving") public static class LauncherApps.ArchiveCompatibilityParams {
+    ctor public LauncherApps.ArchiveCompatibilityParams();
+    method public void setEnableIconOverlay(boolean);
+    method public void setEnableUnarchivalConfirmation(boolean);
+  }
+
   public abstract static class LauncherApps.Callback {
     ctor public LauncherApps.Callback();
     method public abstract void onPackageAdded(String, android.os.UserHandle);
@@ -16516,8 +16530,8 @@
     field public static final int START_HYPHEN_EDIT_NO_EDIT = 0; // 0x0
     field public static final int STRIKE_THRU_TEXT_FLAG = 16; // 0x10
     field public static final int SUBPIXEL_TEXT_FLAG = 128; // 0x80
-    field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_LEFT_EDGE = 8192; // 0x2000
-    field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 16384; // 0x4000
+    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
   }
 
@@ -17994,7 +18008,7 @@
     field public static final int HYPHENATION_FREQUENCY_FULL = 2; // 0x2
     field public static final int HYPHENATION_FREQUENCY_NONE = 0; // 0x0
     field public static final int HYPHENATION_FREQUENCY_NORMAL = 1; // 0x1
-    field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
+    field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
     field public static final int JUSTIFICATION_MODE_INTER_WORD = 1; // 0x1
     field public static final int JUSTIFICATION_MODE_NONE = 0; // 0x0
   }
@@ -24697,6 +24711,7 @@
   }
 
   public class Ringtone {
+    method protected void finalize();
     method public android.media.AudioAttributes getAudioAttributes();
     method @Deprecated public int getStreamType();
     method public String getTitle(android.content.Context);
@@ -26623,6 +26638,8 @@
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.BroadcastInfoRequest> CREATOR;
     field public static final int REQUEST_OPTION_AUTO_UPDATE = 1; // 0x1
+    field @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public static final int REQUEST_OPTION_ONESHOT = 3; // 0x3
+    field @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public static final int REQUEST_OPTION_ONEWAY = 2; // 0x2
     field public static final int REQUEST_OPTION_REPEAT = 0; // 0x0
   }
 
@@ -27296,6 +27313,24 @@
     field public static final int RECORDING_ERROR_INSUFFICIENT_SPACE = 1; // 0x1
     field public static final int RECORDING_ERROR_RESOURCE_BUSY = 2; // 0x2
     field public static final int RECORDING_ERROR_UNKNOWN = 0; // 0x0
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_AD_RESPONSE = "ad_response";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE = "broadcast_info_response";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_CHANNEL_URI = "channel_uri";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_TRACKS = "tracks";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_TRACK_ID = "track_id";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_TRACK_TYPE = "track_type";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_TV_MESSAGE_TYPE = "tv_message_type";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON = "video_unavailable_reason";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_AD_BUFFER_CONSUMED = "ad_buffer_consumed";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_AD_RESPONSE = "ad_response";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_BROADCAST_INFO_RESPONSE = "broadcast_info_response";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_TRACKS_CHANGED = "tracks_changed";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_TRACK_SELECTED = "track_selected";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_TUNED = "tuned";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_TV_MESSAGE = "tv_message";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_VIDEO_AVAILABLE = "video_available";
+    field @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public static final String SESSION_DATA_TYPE_VIDEO_UNAVAILABLE = "video_unavailable";
     field public static final int SIGNAL_STRENGTH_LOST = 1; // 0x1
     field public static final int SIGNAL_STRENGTH_STRONG = 3; // 0x3
     field public static final int SIGNAL_STRENGTH_WEAK = 2; // 0x2
@@ -27411,6 +27446,7 @@
     method public void notifyTuned(@NonNull android.net.Uri);
     method public void notifyTvMessage(int, @NonNull android.os.Bundle);
     method public void notifyVideoAvailable();
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void notifyVideoFreezeUpdated(boolean);
     method public void notifyVideoUnavailable(int);
     method public void onAdBufferReady(@NonNull android.media.tv.AdBuffer);
     method public void onAppPrivateCommand(@NonNull String, android.os.Bundle);
@@ -27447,8 +27483,10 @@
     method public boolean onTrackballEvent(android.view.MotionEvent);
     method public abstract boolean onTune(android.net.Uri);
     method public boolean onTune(android.net.Uri, android.os.Bundle);
+    method @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public void onTvAdSessionData(@NonNull String, @NonNull android.os.Bundle);
     method public void onTvMessage(int, @NonNull android.os.Bundle);
     method public void onUnblockContent(android.media.tv.TvContentRating);
+    method @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public void sendTvInputSessionData(@NonNull String, @NonNull android.os.Bundle);
     method public void setOverlayViewEnabled(boolean);
   }
 
@@ -27579,6 +27617,7 @@
     method public void setStreamVolume(@FloatRange(from=0.0, to=1.0) float);
     method public void setTimeShiftPositionCallback(@Nullable android.media.tv.TvView.TimeShiftPositionCallback);
     method public void setTvMessageEnabled(int, boolean);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void setVideoFrozen(boolean);
     method public void setZOrderMediaOverlay(boolean);
     method public void setZOrderOnTop(boolean);
     method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void stopPlayback(int);
@@ -27622,6 +27661,7 @@
     method public void onTuned(@NonNull String, @NonNull android.net.Uri);
     method public void onTvMessage(@NonNull String, int, @NonNull android.os.Bundle);
     method public void onVideoAvailable(String);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onVideoFreezeUpdated(@NonNull String, boolean);
     method public void onVideoSizeChanged(String, int, int);
     method public void onVideoUnavailable(String, int);
   }
@@ -27632,6 +27672,27 @@
 
   @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public class TvAdManager {
     method @NonNull public java.util.List<android.media.tv.ad.TvAdServiceInfo> getTvAdServiceList();
+    method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdManager.TvAdServiceCallback);
+    method public void sendAppLinkCommand(@NonNull String, @NonNull android.os.Bundle);
+    method public void unregisterCallback(@NonNull android.media.tv.ad.TvAdManager.TvAdServiceCallback);
+    field public static final String ACTION_APP_LINK_COMMAND = "android.media.tv.ad.action.APP_LINK_COMMAND";
+    field public static final String APP_LINK_KEY_BACK_URI = "back_uri";
+    field public static final String APP_LINK_KEY_CLASS_NAME = "class_name";
+    field public static final String APP_LINK_KEY_COMMAND_TYPE = "command_type";
+    field public static final String APP_LINK_KEY_PACKAGE_NAME = "package_name";
+    field public static final String APP_LINK_KEY_SERVICE_ID = "service_id";
+    field public static final String INTENT_KEY_AD_SERVICE_ID = "ad_service_id";
+    field public static final String INTENT_KEY_CHANNEL_URI = "channel_uri";
+    field public static final String INTENT_KEY_COMMAND_TYPE = "command_type";
+    field public static final String INTENT_KEY_TV_INPUT_ID = "tv_input_id";
+    field public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer";
+    field public static final String SESSION_DATA_KEY_AD_REQUEST = "ad_request";
+    field public static final String SESSION_DATA_KEY_BROADCAST_INFO_REQUEST = "broadcast_info_request";
+    field public static final String SESSION_DATA_KEY_REQUEST_ID = "request_id";
+    field public static final String SESSION_DATA_TYPE_AD_BUFFER_READY = "ad_buffer_ready";
+    field public static final String SESSION_DATA_TYPE_AD_REQUEST = "ad_request";
+    field public static final String SESSION_DATA_TYPE_BROADCAST_INFO_REQUEST = "broadcast_info_request";
+    field public static final String SESSION_DATA_TYPE_REMOVE_BROADCAST_INFO_REQUEST = "remove_broadcast_info_request";
   }
 
   public abstract static class TvAdManager.TvAdServiceCallback {
@@ -27643,6 +27704,7 @@
 
   @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public abstract class TvAdService extends android.app.Service {
     ctor public TvAdService();
+    method public void onAppLinkCommand(@NonNull android.os.Bundle);
     method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
     method @Nullable public abstract android.media.tv.ad.TvAdService.Session onCreateSession(@NonNull String, @NonNull String);
     field public static final String SERVICE_INTERFACE = "android.media.tv.ad.TvAdService";
@@ -27651,17 +27713,26 @@
 
   public abstract static class TvAdService.Session implements android.view.KeyEvent.Callback {
     ctor public TvAdService.Session(@NonNull android.content.Context);
+    method public boolean isMediaViewEnabled();
     method @CallSuper public void layoutSurface(int, int, int, int);
+    method @Nullable public android.view.View onCreateMediaView();
     method public boolean onGenericMotionEvent(@NonNull android.view.MotionEvent);
     method public boolean onKeyDown(int, @Nullable android.view.KeyEvent);
     method public boolean onKeyLongPress(int, @Nullable android.view.KeyEvent);
     method public boolean onKeyMultiple(int, int, @Nullable android.view.KeyEvent);
     method public boolean onKeyUp(int, @Nullable android.view.KeyEvent);
+    method public void onMediaViewSizeChanged(@Px int, @Px int);
     method public abstract void onRelease();
+    method public void onResetAdService();
     method public abstract boolean onSetSurface(@Nullable android.view.Surface);
+    method public void onStartAdService();
+    method public void onStopAdService();
     method public void onSurfaceChanged(int, int, int);
     method public boolean onTouchEvent(@NonNull android.view.MotionEvent);
     method public boolean onTrackballEvent(@NonNull android.view.MotionEvent);
+    method public void onTvInputSessionData(@NonNull String, @NonNull android.os.Bundle);
+    method public void sendTvAdSessionData(@NonNull String, @NonNull android.os.Bundle);
+    method @CallSuper public void setMediaViewEnabled(boolean);
   }
 
   @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public final class TvAdServiceInfo implements android.os.Parcelable {
@@ -27678,12 +27749,26 @@
     ctor public TvAdView(@NonNull android.content.Context);
     ctor public TvAdView(@NonNull android.content.Context, @Nullable android.util.AttributeSet);
     ctor public TvAdView(@NonNull android.content.Context, @Nullable android.util.AttributeSet, int);
+    method public void clearOnUnhandledInputEventListener();
+    method public boolean dispatchUnhandledInputEvent(@NonNull android.view.InputEvent);
+    method @Nullable public android.media.tv.ad.TvAdView.OnUnhandledInputEventListener getOnUnhandledInputEventListener();
     method public void onAttachedToWindow();
     method public void onDetachedFromWindow();
     method public void onLayout(boolean, int, int, int, int);
     method public void onMeasure(int, int);
+    method public boolean onUnhandledInputEvent(@NonNull android.view.InputEvent);
     method public void onVisibilityChanged(@NonNull android.view.View, int);
     method public void prepareAdService(@NonNull String, @NonNull String);
+    method public void reset();
+    method public void resetAdService();
+    method public void setOnUnhandledInputEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdView.OnUnhandledInputEventListener);
+    method public boolean setTvView(@Nullable android.media.tv.TvView);
+    method public void startAdService();
+    method public void stopAdService();
+  }
+
+  public static interface TvAdView.OnUnhandledInputEventListener {
+    method public boolean onUnhandledInputEvent(@NonNull android.view.InputEvent);
   }
 
 }
@@ -27796,6 +27881,7 @@
     method public void onAdResponse(@NonNull android.media.tv.AdResponse);
     method public void onAvailableSpeeds(@NonNull float[]);
     method public void onBroadcastInfoResponse(@NonNull android.media.tv.BroadcastInfoResponse);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onCertificate(@NonNull String, int, @NonNull android.net.http.SslCertificate);
     method public void onContentAllowed();
     method public void onContentBlocked(@NonNull android.media.tv.TvContentRating);
     method public void onCreateBiInteractiveAppRequest(@NonNull android.net.Uri, @Nullable android.os.Bundle);
@@ -27845,11 +27931,13 @@
     method public void onTvRecordingInfo(@Nullable android.media.tv.TvRecordingInfo);
     method public void onTvRecordingInfoList(@NonNull java.util.List<android.media.tv.TvRecordingInfo>);
     method public void onVideoAvailable();
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onVideoFreezeUpdated(boolean);
     method public void onVideoUnavailable(int);
     method @CallSuper public void removeBroadcastInfo(int);
     method @CallSuper public void requestAd(@NonNull android.media.tv.AdRequest);
     method @CallSuper public void requestAvailableSpeeds();
     method @CallSuper public void requestBroadcastInfo(@NonNull android.media.tv.BroadcastInfoRequest);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") @CallSuper public void requestCertificate(@NonNull String, int);
     method @CallSuper public void requestCurrentChannelLcn();
     method @CallSuper public void requestCurrentChannelUri();
     method @CallSuper public void requestCurrentTvInputId();
@@ -27858,6 +27946,7 @@
     method @CallSuper public void requestScheduleRecording(@NonNull String, @NonNull String, @NonNull android.net.Uri, long, long, int, @NonNull android.os.Bundle);
     method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") @CallSuper public void requestSelectedTrackInfo();
     method @CallSuper public void requestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") @CallSuper public void requestSigning(@NonNull String, @NonNull String, @NonNull String, int, @NonNull byte[]);
     method @CallSuper public void requestStartRecording(@NonNull String, @Nullable android.net.Uri);
     method @CallSuper public void requestStopRecording(@NonNull String);
     method @CallSuper public void requestStreamVolume();
@@ -27907,6 +27996,7 @@
     method public void notifyTimeShiftStartPositionChanged(@NonNull String, long);
     method public void notifyTimeShiftStatusChanged(@NonNull String, int);
     method public void notifyTvMessage(@NonNull int, @NonNull android.os.Bundle);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void notifyVideoFreezeUpdated(boolean);
     method public void onAttachedToWindow();
     method public void onDetachedFromWindow();
     method public void onLayout(boolean, int, int, int, int);
@@ -27917,6 +28007,7 @@
     method public void reset();
     method public void resetInteractiveApp();
     method public void sendAvailableSpeeds(@NonNull float[]);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void sendCertificate(@NonNull String, int, @NonNull android.net.http.SslCertificate);
     method public void sendCurrentChannelLcn(int);
     method public void sendCurrentChannelUri(@Nullable android.net.Uri);
     method public void sendCurrentTvInputId(@Nullable String);
@@ -27951,6 +28042,7 @@
     method public void onBiInteractiveAppCreated(@NonNull String, @NonNull android.net.Uri, @Nullable String);
     method public void onPlaybackCommandRequest(@NonNull String, @NonNull String, @NonNull android.os.Bundle);
     method public void onRequestAvailableSpeeds(@NonNull String);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onRequestCertificate(@NonNull String, @NonNull String, int);
     method public void onRequestCurrentChannelLcn(@NonNull String);
     method public void onRequestCurrentChannelUri(@NonNull String);
     method public void onRequestCurrentTvInputId(@NonNull String);
@@ -27959,6 +28051,7 @@
     method public void onRequestScheduleRecording(@NonNull String, @NonNull String, @NonNull String, @NonNull android.net.Uri, long, long, int, @NonNull android.os.Bundle);
     method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onRequestSelectedTrackInfo(@NonNull String);
     method public void onRequestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onRequestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int, @NonNull byte[]);
     method public void onRequestStartRecording(@NonNull String, @NonNull String, @Nullable android.net.Uri);
     method public void onRequestStopRecording(@NonNull String, @NonNull String);
     method public void onRequestStreamVolume(@NonNull String);
@@ -46211,6 +46304,7 @@
     method @NonNull public android.telephony.euicc.EuiccManager createForCardId(int);
     method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void deleteSubscription(int, android.app.PendingIntent);
     method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void downloadSubscription(android.telephony.euicc.DownloadableSubscription, boolean, android.app.PendingIntent);
+    method @FlaggedApi("com.android.internal.telephony.flags.esim_available_memory") @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE", "carrier privileges"}) public long getAvailableMemoryInBytes();
     method @Nullable public String getEid();
     method @Nullable public android.telephony.euicc.EuiccInfo getEuiccInfo();
     method public boolean isEnabled();
@@ -46243,6 +46337,7 @@
     field public static final int ERROR_SIM_MISSING = 10008; // 0x2718
     field public static final int ERROR_TIME_OUT = 10005; // 0x2715
     field public static final int ERROR_UNSUPPORTED_VERSION = 10007; // 0x2717
+    field @FlaggedApi("com.android.internal.telephony.flags.esim_available_memory") public static final long EUICC_MEMORY_FIELD_UNAVAILABLE = -1L; // 0xffffffffffffffffL
     field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DETAILED_CODE";
     field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION";
     field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_ERROR_CODE = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_ERROR_CODE";
@@ -47216,7 +47311,7 @@
     method public int getLineForOffset(int);
     method public int getLineForVertical(int);
     method public float getLineLeft(int);
-    method @FlaggedApi("com.android.text.flags.inter_character_justification") @IntRange(from=0) public int getLineLetterSpacingUnitCount(@IntRange(from=0) int, boolean);
+    method @FlaggedApi("com.android.text.flags.letter_spacing_justification") @IntRange(from=0) public int getLineLetterSpacingUnitCount(@IntRange(from=0) int, boolean);
     method public float getLineMax(int);
     method public float getLineRight(int);
     method @FlaggedApi("com.android.text.flags.use_bounds_for_width") public final float getLineSpacingAmount();
@@ -47267,7 +47362,7 @@
     field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_ANY_OVERLAP;
     field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_ALL;
     field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_CENTER;
-    field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
+    field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
     field public static final int JUSTIFICATION_MODE_INTER_WORD = 1; // 0x1
     field public static final int JUSTIFICATION_MODE_NONE = 0; // 0x0
   }
@@ -53531,8 +53626,8 @@
     method @NonNull public abstract android.view.LayoutInflater getLayoutInflater();
     method protected final int getLocalFeatures();
     method public android.media.session.MediaController getMediaController();
-    method @ColorInt public abstract int getNavigationBarColor();
-    method @ColorInt public int getNavigationBarDividerColor();
+    method @Deprecated @ColorInt public abstract int getNavigationBarColor();
+    method @Deprecated @ColorInt public int getNavigationBarDividerColor();
     method @NonNull public android.window.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
     method public android.transition.Transition getReenterTransition();
     method public android.transition.Transition getReturnTransition();
@@ -53542,7 +53637,7 @@
     method public android.transition.Transition getSharedElementReenterTransition();
     method public android.transition.Transition getSharedElementReturnTransition();
     method public boolean getSharedElementsUseOverlay();
-    method @ColorInt public abstract int getStatusBarColor();
+    method @Deprecated @ColorInt public abstract int getStatusBarColor();
     method @NonNull public java.util.List<android.graphics.Rect> getSystemGestureExclusionRects();
     method public long getTransitionBackgroundFadeDuration();
     method public android.transition.TransitionManager getTransitionManager();
@@ -53558,7 +53653,7 @@
     method public abstract boolean isFloating();
     method public boolean isNavigationBarContrastEnforced();
     method public abstract boolean isShortcutKey(int, android.view.KeyEvent);
-    method public boolean isStatusBarContrastEnforced();
+    method @Deprecated public boolean isStatusBarContrastEnforced();
     method public boolean isWideColorGamut();
     method public final void makeActive();
     method protected abstract void onActive();
@@ -53590,7 +53685,7 @@
     method public abstract void setContentView(android.view.View);
     method public abstract void setContentView(android.view.View, android.view.ViewGroup.LayoutParams);
     method public abstract void setDecorCaptionShade(int);
-    method public void setDecorFitsSystemWindows(boolean);
+    method @Deprecated public void setDecorFitsSystemWindows(boolean);
     method protected void setDefaultWindowFormat(int);
     method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public void setDesiredHdrHeadroom(@FloatRange(from=0.0f, to=10000.0) float);
     method public void setDimAmount(float);
@@ -53612,9 +53707,9 @@
     method public void setLocalFocus(boolean, boolean);
     method public void setLogo(@DrawableRes int);
     method public void setMediaController(android.media.session.MediaController);
-    method public abstract void setNavigationBarColor(@ColorInt int);
+    method @Deprecated public abstract void setNavigationBarColor(@ColorInt int);
     method public void setNavigationBarContrastEnforced(boolean);
-    method public void setNavigationBarDividerColor(@ColorInt int);
+    method @Deprecated public void setNavigationBarDividerColor(@ColorInt int);
     method public void setPreferMinimalPostProcessing(boolean);
     method public void setReenterTransition(android.transition.Transition);
     method public abstract void setResizingCaptionDrawable(android.graphics.drawable.Drawable);
@@ -53626,8 +53721,8 @@
     method public void setSharedElementReturnTransition(android.transition.Transition);
     method public void setSharedElementsUseOverlay(boolean);
     method public void setSoftInputMode(int);
-    method public abstract void setStatusBarColor(@ColorInt int);
-    method public void setStatusBarContrastEnforced(boolean);
+    method @Deprecated public abstract void setStatusBarColor(@ColorInt int);
+    method @Deprecated public void setStatusBarContrastEnforced(boolean);
     method public void setSustainedPerformanceMode(boolean);
     method public void setSystemGestureExclusionRects(@NonNull java.util.List<android.graphics.Rect>);
     method public abstract void setTitle(CharSequence);
diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt
index 162f54c..e901f00 100644
--- a/core/api/lint-baseline.txt
+++ b/core/api/lint-baseline.txt
@@ -389,6 +389,12 @@
     Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
 
 
+KotlinOperator: android.graphics.Matrix44#get(int, int):
+    Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
+KotlinOperator: android.graphics.Matrix44#set(int, int, float):
+    Method can be invoked with an indexing operator from Kotlin: `set` (this is usually desirable; just make sure it makes sense for this type of object)
+
+
 RequiresPermission: android.accounts.AccountManager#getAccountsByTypeAndFeatures(String, String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler):
     Method 'getAccountsByTypeAndFeatures' documentation mentions permissions without declaring @RequiresPermission
 RequiresPermission: android.accounts.AccountManager#hasFeatures(android.accounts.Account, String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler):
@@ -477,6 +483,8 @@
     Method 'clearResetPasswordToken' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#generateKeyPair(android.content.ComponentName, String, android.security.keystore.KeyGenParameterSpec, int):
     Method 'generateKeyPair' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.app.admin.DevicePolicyManager#getContentProtectionPolicy(android.content.ComponentName):
+    Method 'getContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#getCrossProfileWidgetProviders(android.content.ComponentName):
     Method 'getCrossProfileWidgetProviders' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#getLockTaskFeatures(android.content.ComponentName):
@@ -513,6 +521,8 @@
     Method 'removeCrossProfileWidgetProvider' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#setAlwaysOnVpnPackage(android.content.ComponentName, String, boolean):
     Method 'setAlwaysOnVpnPackage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.app.admin.DevicePolicyManager#setContentProtectionPolicy(android.content.ComponentName, int):
+    Method 'setContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#setLockTaskFeatures(android.content.ComponentName, int):
     Method 'setLockTaskFeatures' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#setLockTaskPackages(android.content.ComponentName, String[]):
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 783bebd..1273da7 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -350,7 +350,9 @@
 package android.os {
 
   public class ArtModuleServiceManager {
+    method @FlaggedApi("android.content.pm.use_art_service_v2") @NonNull public android.os.ArtModuleServiceManager.ServiceRegisterer getArtdPreRebootServiceRegisterer();
     method @NonNull public android.os.ArtModuleServiceManager.ServiceRegisterer getArtdServiceRegisterer();
+    method @FlaggedApi("android.content.pm.use_art_service_v2") @NonNull public android.os.ArtModuleServiceManager.ServiceRegisterer getDexoptChrootSetupServiceRegisterer();
   }
 
   public static final class ArtModuleServiceManager.ServiceRegisterer {
diff --git a/core/api/module-lib-lint-baseline.txt b/core/api/module-lib-lint-baseline.txt
index a2179bc..42c4efc 100644
--- a/core/api/module-lib-lint-baseline.txt
+++ b/core/api/module-lib-lint-baseline.txt
@@ -611,6 +611,8 @@
     Method 'generateKeyPair' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#getApplicationExemptions(String):
     Method 'getApplicationExemptions' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.app.admin.DevicePolicyManager#getContentProtectionPolicy(android.content.ComponentName):
+    Method 'getContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#getCrossProfileWidgetProviders(android.content.ComponentName):
     Method 'getCrossProfileWidgetProviders' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#getLockTaskFeatures(android.content.ComponentName):
@@ -657,6 +659,8 @@
     Method 'setAlwaysOnVpnPackage' documentation mentions permissions without declaring @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#setApplicationExemptions(String, java.util.Set<java.lang.Integer>):
     Method 'setApplicationExemptions' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.app.admin.DevicePolicyManager#setContentProtectionPolicy(android.content.ComponentName, int):
+    Method 'setContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#setDeviceProvisioningConfigApplied():
     Method 'setDeviceProvisioningConfigApplied' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#setLockTaskFeatures(android.content.ComponentName, int):
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 934b193..8057e18 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -11,6 +11,7 @@
     field public static final String ACCESS_DRM_CERTIFICATES = "android.permission.ACCESS_DRM_CERTIFICATES";
     field @Deprecated public static final String ACCESS_FM_RADIO = "android.permission.ACCESS_FM_RADIO";
     field public static final String ACCESS_FPS_COUNTER = "android.permission.ACCESS_FPS_COUNTER";
+    field @FlaggedApi("android.multiuser.flags.enable_permission_to_access_hidden_profiles") public static final String ACCESS_HIDDEN_PROFILES_FULL = "android.permission.ACCESS_HIDDEN_PROFILES_FULL";
     field public static final String ACCESS_INSTANT_APPS = "android.permission.ACCESS_INSTANT_APPS";
     field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ACCESS_LAST_KNOWN_CELL_ID = "android.permission.ACCESS_LAST_KNOWN_CELL_ID";
     field public static final String ACCESS_LOCUS_ID_USAGE_STATS = "android.permission.ACCESS_LOCUS_ID_USAGE_STATS";
@@ -3153,7 +3154,9 @@
   public class WearableSensingManager {
     method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideData(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideDataStream(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideWearableConnection(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     field public static final int STATUS_ACCESS_DENIED = 5; // 0x5
+    field @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") public static final int STATUS_CHANNEL_ERROR = 7; // 0x7
     field public static final int STATUS_SERVICE_UNAVAILABLE = 3; // 0x3
     field public static final int STATUS_SUCCESS = 1; // 0x1
     field public static final int STATUS_UNKNOWN = 0; // 0x0
@@ -3944,7 +3947,9 @@
     method @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public void addFile(int, @NonNull String, long, @NonNull byte[], @Nullable byte[]);
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void commitTransferred(@NonNull android.content.IntentSender);
     method @Nullable @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public android.content.pm.DataLoaderParams getDataLoaderParams();
+    method @FlaggedApi("android.content.pm.set_pre_verified_domains") @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public java.util.Set<java.lang.String> getPreVerifiedDomains();
     method @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public void removeFile(int, @NonNull String);
+    method @FlaggedApi("android.content.pm.set_pre_verified_domains") @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public void setPreVerifiedDomains(@NonNull java.util.Set<java.lang.String>);
   }
 
   public static class PackageInstaller.SessionInfo implements android.os.Parcelable {
@@ -4247,7 +4252,6 @@
   public final class UserProperties implements android.os.Parcelable {
     method public int describeContents();
     method public int getCrossProfileContentSharingStrategy();
-    method @FlaggedApi("android.multiuser.support_hiding_profiles") @NonNull public int getProfileApiVisibility();
     method public int getShowInQuietMode();
     method public int getShowInSharingSurfaces();
     method public boolean isCredentialShareableWithParent();
@@ -4257,9 +4261,6 @@
     field public static final int CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT = 1; // 0x1
     field public static final int CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION = 0; // 0x0
     field public static final int CROSS_PROFILE_CONTENT_SHARING_UNKNOWN = -1; // 0xffffffff
-    field @FlaggedApi("android.multiuser.support_hiding_profiles") public static final int PROFILE_API_VISIBILITY_HIDDEN = 1; // 0x1
-    field @FlaggedApi("android.multiuser.support_hiding_profiles") public static final int PROFILE_API_VISIBILITY_UNKNOWN = -1; // 0xffffffff
-    field @FlaggedApi("android.multiuser.support_hiding_profiles") public static final int PROFILE_API_VISIBILITY_VISIBLE = 0; // 0x0
     field public static final int SHOW_IN_QUIET_MODE_DEFAULT = 2; // 0x2
     field public static final int SHOW_IN_QUIET_MODE_HIDDEN = 1; // 0x1
     field public static final int SHOW_IN_QUIET_MODE_PAUSED = 0; // 0x0
@@ -4504,6 +4505,7 @@
     method @NonNull public android.credentials.selection.RequestToken getRequestToken();
     method @NonNull public String getType();
     method public boolean hasPermissionToOverrideDefault();
+    method public boolean isShowAllOptionsRequested();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.credentials.selection.RequestInfo> CREATOR;
     field @NonNull public static final String TYPE_CREATE = "android.credentials.selection.TYPE_CREATE";
@@ -10389,6 +10391,7 @@
   public final class ConfigUpdate {
     field public static final String ACTION_UPDATE_CARRIER_ID_DB = "android.os.action.UPDATE_CARRIER_ID_DB";
     field public static final String ACTION_UPDATE_CARRIER_PROVISIONING_URLS = "android.intent.action.UPDATE_CARRIER_PROVISIONING_URLS";
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final String ACTION_UPDATE_CONFIG = "android.os.action.UPDATE_CONFIG";
     field public static final String ACTION_UPDATE_CONVERSATION_ACTIONS = "android.intent.action.UPDATE_CONVERSATION_ACTIONS";
     field public static final String ACTION_UPDATE_CT_LOGS = "android.intent.action.UPDATE_CT_LOGS";
     field public static final String ACTION_UPDATE_EMERGENCY_NUMBER_DB = "android.os.action.UPDATE_EMERGENCY_NUMBER_DB";
@@ -10398,6 +10401,7 @@
     field public static final String ACTION_UPDATE_PINS = "android.intent.action.UPDATE_PINS";
     field public static final String ACTION_UPDATE_SMART_SELECTION = "android.intent.action.UPDATE_SMART_SELECTION";
     field public static final String ACTION_UPDATE_SMS_SHORT_CODES = "android.intent.action.UPDATE_SMS_SHORT_CODES";
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final String EXTRA_DOMAIN = "android.os.extra.DOMAIN";
     field public static final String EXTRA_REQUIRED_HASH = "android.os.extra.REQUIRED_HASH";
     field public static final String EXTRA_VERSION = "android.os.extra.VERSION";
   }
@@ -11168,6 +11172,7 @@
     method @WorkerThread public void allocateBytes(java.io.FileDescriptor, long, @RequiresPermission int) throws java.io.IOException;
     method @WorkerThread public long getAllocatableBytes(@NonNull java.util.UUID, @RequiresPermission int) throws java.io.IOException;
     method @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public int getExternalStorageMountMode(int, @NonNull String);
+    method @FlaggedApi("android.os.storage_lifetime_api") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getInternalStorageRemainingLifetime();
     method public static boolean hasIsolatedStorage();
     method public void updateExternalStorageFileQuotaType(@NonNull java.io.File, int) throws java.io.IOException;
     field @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public static final int FLAG_ALLOCATE_AGGRESSIVE = 1; // 0x1
@@ -12398,6 +12403,7 @@
     method @Deprecated public int onDownloadSubscription(int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean);
     method @Deprecated public abstract int onEraseSubscriptions(int);
     method public int onEraseSubscriptions(int, int);
+    method @FlaggedApi("com.android.internal.telephony.flags.esim_available_memory") public long onGetAvailableMemoryInBytes(int);
     method public abstract android.service.euicc.GetDefaultDownloadableSubscriptionListResult onGetDefaultDownloadableSubscriptionList(int, boolean);
     method public abstract android.service.euicc.GetDownloadableSubscriptionMetadataResult onGetDownloadableSubscriptionMetadata(int, android.telephony.euicc.DownloadableSubscription, boolean);
     method @NonNull public android.service.euicc.GetDownloadableSubscriptionMetadataResult onGetDownloadableSubscriptionMetadata(int, int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean);
@@ -13427,6 +13433,7 @@
     method @BinderThread public abstract void onDataProvided(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @BinderThread public abstract void onDataStreamProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @BinderThread public abstract void onQueryServiceStatus(@NonNull java.util.Set<java.lang.Integer>, @NonNull String, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionServiceStatus>);
+    method @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") @BinderThread public void onSecureWearableConnectionProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @BinderThread public abstract void onStartDetection(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull String, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionServiceStatus>, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionResult>);
     method public abstract void onStopDetection(@NonNull String);
     field public static final String SERVICE_INTERFACE = "android.service.wearable.WearableSensingService";
@@ -14758,6 +14765,7 @@
     method public boolean areUiccApplicationsEnabled();
     method @Nullable public java.util.List<android.telephony.UiccAccessRule> getAccessRules();
     method public int getProfileClass();
+    method @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") public int getTransferStatus();
     method public boolean isGroupDisabled();
   }
 
@@ -14780,6 +14788,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultVoiceSubscriptionId(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPreferredDataSubscriptionId(int, boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setSubscriptionEnabled(int, boolean);
+    method @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setTransferStatus(int, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUiccApplicationsEnabled(int, boolean);
     field @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_PLANS) public static final String ACTION_SUBSCRIPTION_PLANS_CHANGED = "android.telephony.action.SUBSCRIPTION_PLANS_CHANGED";
     field @NonNull public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI;
@@ -14789,6 +14798,9 @@
     field public static final int PROFILE_CLASS_PROVISIONING = 1; // 0x1
     field public static final int PROFILE_CLASS_TESTING = 0; // 0x0
     field public static final int PROFILE_CLASS_UNSET = -1; // 0xffffffff
+    field @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") public static final int TRANSFER_STATUS_CONVERTED = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") public static final int TRANSFER_STATUS_NONE = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") public static final int TRANSFER_STATUS_TRANSFERRED_OUT = 1; // 0x1
     field @NonNull public static final android.net.Uri VT_ENABLED_CONTENT_URI;
     field @NonNull public static final android.net.Uri WFC_ENABLED_CONTENT_URI;
     field @NonNull public static final android.net.Uri WFC_MODE_CONTENT_URI;
@@ -15654,7 +15666,9 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public int getOtaStatus();
     method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public java.util.List<java.lang.String> getSupportedCountries();
     method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public java.util.List<java.lang.String> getUnsupportedCountries();
+    method @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public boolean isPsimConversionSupported(int);
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public boolean isSupportedCountry(@NonNull String);
+    method @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setPsimConversionSupportedCarriers(@NonNull java.util.Set<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setSupportedCountries(@NonNull java.util.List<java.lang.String>);
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setUnsupportedCountries(@NonNull java.util.List<java.lang.String>);
     field @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public static final String ACTION_CONVERT_TO_EMBEDDED_SUBSCRIPTION = "android.telephony.euicc.action.CONVERT_TO_EMBEDDED_SUBSCRIPTION";
@@ -17378,6 +17392,19 @@
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaPosition> CREATOR;
   }
 
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public class EnableRequestAttributes {
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public boolean isDemoMode();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public boolean isEmergencyMode();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public boolean isEnabled();
+  }
+
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final class EnableRequestAttributes.Builder {
+    ctor @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public EnableRequestAttributes.Builder(boolean);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public android.telephony.satellite.EnableRequestAttributes build();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public android.telephony.satellite.EnableRequestAttributes.Builder setDemoMode(boolean);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public android.telephony.satellite.EnableRequestAttributes.Builder setEmergencyMode(boolean);
+  }
+
   @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class NtnSignalStrength implements android.os.Parcelable {
     ctor @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public NtnSignalStrength(@Nullable android.telephony.satellite.NtnSignalStrength);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents();
@@ -17443,10 +17470,11 @@
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void removeAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestAttachEnabledForCarrier(int, boolean, @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 requestCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestEnabled(boolean, boolean, @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 requestEnabled(@NonNull android.telephony.satellite.EnableRequestAttributes, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsAttachEnabledForCarrier(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsCommunicationAllowedForCurrentLocation(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsDemoModeEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsEmergencyModeEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void requestIsSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
@@ -17508,6 +17536,7 @@
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; // 0x6
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_BUSY = 22; // 0x16
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_ERROR = 4; // 0x4
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_TIMEOUT = 24; // 0x18
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NETWORK_ERROR = 5; // 0x5
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NETWORK_TIMEOUT = 17; // 0x11
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_AUTHORIZED = 19; // 0x13
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 6c83fd0..8a485d2 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -525,6 +525,10 @@
     Avoid field names that are Kotlin hard keywords ("when"); see https://android.github.io/kotlin-guides/interop.html#no-hard-keywords
 
 
+KotlinOperator: android.hardware.camera2.extension.CharacteristicsMap#get(String):
+    Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
+
+
 ListenerLast: android.telephony.satellite.SatelliteManager#stopSatelliteTransmissionUpdates(android.telephony.satellite.SatelliteTransmissionUpdateCallback, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>) parameter #1:
     Listeners should always be at end of argument list (method `stopSatelliteTransmissionUpdates`)
 ListenerLast: android.telephony.satellite.SatelliteManager#stopSatelliteTransmissionUpdates(android.telephony.satellite.SatelliteTransmissionUpdateCallback, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>) parameter #2:
@@ -685,6 +689,8 @@
     Method 'generateKeyPair' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#getApplicationExemptions(String):
     Method 'getApplicationExemptions' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.app.admin.DevicePolicyManager#getContentProtectionPolicy(android.content.ComponentName):
+    Method 'getContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#getCrossProfileWidgetProviders(android.content.ComponentName):
     Method 'getCrossProfileWidgetProviders' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#getLockTaskFeatures(android.content.ComponentName):
@@ -731,6 +737,8 @@
     Method 'setAlwaysOnVpnPackage' documentation mentions permissions without declaring @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#setApplicationExemptions(String, java.util.Set<java.lang.Integer>):
     Method 'setApplicationExemptions' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.app.admin.DevicePolicyManager#setContentProtectionPolicy(android.content.ComponentName, int):
+    Method 'setContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#setDeviceProvisioningConfigApplied():
     Method 'setDeviceProvisioningConfigApplied' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#setLockTaskFeatures(android.content.ComponentName, int):
@@ -2305,14 +2313,14 @@
     New API must be flagged with @FlaggedApi: constructor android.telephony.satellite.SatelliteManager.SatelliteException(int)
 UnflaggedApi: android.telephony.satellite.SatelliteManager.SatelliteException#getErrorCode():
     New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteManager.SatelliteException.getErrorCode()
-UnflaggedApi: android.telephony.satellite.SatelliteProvisionStateCallback:
-    New API must be flagged with @FlaggedApi: class android.telephony.satellite.SatelliteProvisionStateCallback
-UnflaggedApi: android.telephony.satellite.SatelliteProvisionStateCallback#onSatelliteProvisionStateChanged(boolean):
-    New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteProvisionStateCallback.onSatelliteProvisionStateChanged(boolean)
 UnflaggedApi: android.telephony.satellite.SatelliteModemStateCallback:
     New API must be flagged with @FlaggedApi: class android.telephony.satellite.SatelliteModemStateCallback
 UnflaggedApi: android.telephony.satellite.SatelliteModemStateCallback#onSatelliteModemStateChanged(int):
     New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteModemStateCallback.onSatelliteModemStateChanged(int)
+UnflaggedApi: android.telephony.satellite.SatelliteProvisionStateCallback:
+    New API must be flagged with @FlaggedApi: class android.telephony.satellite.SatelliteProvisionStateCallback
+UnflaggedApi: android.telephony.satellite.SatelliteProvisionStateCallback#onSatelliteProvisionStateChanged(boolean):
+    New API must be flagged with @FlaggedApi: method android.telephony.satellite.SatelliteProvisionStateCallback.onSatelliteProvisionStateChanged(boolean)
 UnflaggedApi: android.telephony.satellite.SatelliteTransmissionUpdateCallback:
     New API must be flagged with @FlaggedApi: class android.telephony.satellite.SatelliteTransmissionUpdateCallback
 UnflaggedApi: android.telephony.satellite.SatelliteTransmissionUpdateCallback#onReceiveDatagramStateChanged(int, int, int):
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a21d7c4..bbd6bde 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -624,6 +624,7 @@
     field public static final int OPERATION_SET_APPLICATION_HIDDEN = 15; // 0xf
     field public static final int OPERATION_SET_APPLICATION_RESTRICTIONS = 16; // 0x10
     field public static final int OPERATION_SET_CAMERA_DISABLED = 31; // 0x1f
+    field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final int OPERATION_SET_CONTENT_PROTECTION_POLICY = 41; // 0x29
     field public static final int OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY = 32; // 0x20
     field public static final int OPERATION_SET_GLOBAL_PRIVATE_DNS = 33; // 0x21
     field public static final int OPERATION_SET_KEEP_UNINSTALLED_PACKAGES = 17; // 0x11
@@ -1347,8 +1348,8 @@
   }
 
   @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class RequestInfo implements android.os.Parcelable {
-    method @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") @NonNull public static android.credentials.selection.RequestInfo newCreateRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.CreateCredentialRequest, @NonNull String, boolean, @NonNull java.util.List<java.lang.String>);
-    method @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") @NonNull public static android.credentials.selection.RequestInfo newGetRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.GetCredentialRequest, @NonNull String, boolean);
+    method @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") @NonNull public static android.credentials.selection.RequestInfo newCreateRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.CreateCredentialRequest, @NonNull String, boolean, @NonNull java.util.List<java.lang.String>, boolean);
+    method @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") @NonNull public static android.credentials.selection.RequestInfo newGetRequestInfo(@NonNull android.os.IBinder, @NonNull android.credentials.GetCredentialRequest, @NonNull String, boolean, boolean);
   }
 
   @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class RequestToken {
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 5e904ef..b938f0f 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -673,6 +673,8 @@
     Method 'generateKeyPair' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#getApplicationExemptions(String):
     Method 'getApplicationExemptions' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.app.admin.DevicePolicyManager#getContentProtectionPolicy(android.content.ComponentName):
+    Method 'getContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#getCrossProfileWidgetProviders(android.content.ComponentName):
     Method 'getCrossProfileWidgetProviders' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#getLockTaskFeatures(android.content.ComponentName):
@@ -721,6 +723,8 @@
     Method 'setAlwaysOnVpnPackage' documentation mentions permissions without declaring @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#setApplicationExemptions(String, java.util.Set<java.lang.Integer>):
     Method 'setApplicationExemptions' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.app.admin.DevicePolicyManager#setContentProtectionPolicy(android.content.ComponentName, int):
+    Method 'setContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#setDeviceOwner(android.content.ComponentName, int):
     Method 'setDeviceOwner' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#setDeviceProvisioningConfigApplied():
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 0dbce97..5074ed7 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -4588,19 +4588,19 @@
          */
         private @Nullable NoteOpEvent getLastRejectEvent(@UidState int fromUidState,
                 @UidState int toUidState, @OpFlags int flags) {
-            NoteOpEvent lastAccessEvent = null;
+            NoteOpEvent lastRejectEvent = null;
             for (AttributedOpEntry attributionEntry : mAttributedOpEntries.values()) {
-                NoteOpEvent lastAttributionAccessEvent = attributionEntry.getLastRejectEvent(
+                NoteOpEvent lastAttributionRejectEvent = attributionEntry.getLastRejectEvent(
                         fromUidState, toUidState, flags);
 
-                if (lastAccessEvent == null || (lastAttributionAccessEvent != null
-                        && lastAttributionAccessEvent.getNoteTime()
-                        > lastAccessEvent.getNoteTime())) {
-                    lastAccessEvent = lastAttributionAccessEvent;
+                if (lastRejectEvent == null || (lastAttributionRejectEvent != null
+                        && lastAttributionRejectEvent.getNoteTime()
+                        > lastRejectEvent.getNoteTime())) {
+                    lastRejectEvent = lastAttributionRejectEvent;
                 }
             }
 
-            return lastAccessEvent;
+            return lastRejectEvent;
         }
 
         /**
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 0a34d36..aa9de81 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -23,6 +23,7 @@
 import static android.app.admin.DevicePolicyResources.UNDEFINED;
 import static android.graphics.drawable.Icon.TYPE_URI;
 import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
+import static android.app.Flags.evenlyDividedCallStyleActionLayout;
 
 import static java.util.Objects.requireNonNull;
 
@@ -3540,15 +3541,12 @@
      * Sets the token used for background operations for the pending intents associated with this
      * notification.
      *
-     * This token is automatically set during deserialization for you, you usually won't need to
-     * call this unless you want to change the existing token, if any.
-     *
      * @hide
      */
-    public void clearAllowlistToken() {
-        mAllowlistToken = null;
+    public void overrideAllowlistToken(IBinder token) {
+        mAllowlistToken = token;
         if (publicVersion != null) {
-            publicVersion.clearAllowlistToken();
+            publicVersion.overrideAllowlistToken(token);
         }
     }
 
@@ -5957,7 +5955,7 @@
                 // there is enough space to do so (and fall back to the left edge if not).
                 big.setInt(R.id.actions, "setCollapsibleIndentDimen",
                         R.dimen.call_notification_collapsible_indent);
-                if (CallStyle.USE_NEW_ACTION_LAYOUT) {
+                if (evenlyDividedCallStyleActionLayout()) {
                     if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
                         Log.d(TAG, "setting evenly divided mode on action list");
                     }
@@ -6439,7 +6437,7 @@
                     title = ContrastColorUtil.ensureColorSpanContrast(title, buttonFillColor);
                 }
                 final CharSequence label = ensureColorSpanContrast(title, p);
-                if (p.mCallStyleActions && CallStyle.USE_NEW_ACTION_LAYOUT) {
+                if (p.mCallStyleActions && evenlyDividedCallStyleActionLayout()) {
                     if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
                         Log.d(TAG, "new action layout enabled, gluing instead of setting text");
                     }
@@ -6463,7 +6461,7 @@
                 button.setColorStateList(R.id.action0, "setButtonBackground",
                         ColorStateList.valueOf(buttonFillColor));
                 if (p.mCallStyleActions) {
-                    if (CallStyle.USE_NEW_ACTION_LAYOUT) {
+                    if (evenlyDividedCallStyleActionLayout()) {
                         if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
                             Log.d(TAG, "new action layout enabled, gluing instead of setting icon");
                         }
@@ -9600,11 +9598,6 @@
         /**
          * @hide
          */
-        public static final boolean USE_NEW_ACTION_LAYOUT = false;
-
-        /**
-         * @hide
-         */
         public static final boolean DEBUG_NEW_ACTION_LAYOUT = true;
 
         /**
diff --git a/core/java/android/app/QueuedWork.java b/core/java/android/app/QueuedWork.java
index 6a114f9..60b61f3 100644
--- a/core/java/android/app/QueuedWork.java
+++ b/core/java/android/app/QueuedWork.java
@@ -27,6 +27,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ExponentiallyBucketedHistogram;
 
 import java.util.LinkedList;
@@ -114,6 +115,20 @@
     }
 
     /**
+     * Tear down the handler.
+     */
+    @VisibleForTesting
+    public static void resetHandler() {
+        synchronized (sLock) {
+            if (sHandler == null) {
+                return;
+            }
+            sHandler.getLooper().quitSafely();
+            sHandler = null;
+        }
+    }
+
+    /**
      * Remove all Messages from the Handler with the given code.
      *
      * This method intentionally avoids creating the Handler if it doesn't
diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java
index b0bec78..d7aafa0 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -22,7 +22,6 @@
 import android.app.admin.flags.Flags;
 import android.os.UserManager;
 
-
 import java.util.Objects;
 
 /**
@@ -163,6 +162,12 @@
     public static final String CROSS_PROFILE_WIDGET_PROVIDER_POLICY = "crossProfileWidgetProvider";
 
     /**
+     * String identifier for {@link DevicePolicyManager#setContentProtectionPolicy}.
+     */
+    @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+    public static final String CONTENT_PROTECTION_POLICY = "contentProtection";
+
+    /**
      * String identifier for {@link DevicePolicyManager#setUsbDataSignalingEnabled}.
      */
     @FlaggedApi(Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED)
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 86d0125..1ef4346 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -24,6 +24,7 @@
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CAMERA;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CERTIFICATES;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_DEFAULT_SMS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_FACTORY_RESET;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_INPUT_METHODS;
@@ -53,7 +54,6 @@
 import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
 import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
-import static android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED;
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
@@ -3825,6 +3825,10 @@
     /** @hide */
     @TestApi
     public static final int OPERATION_UNINSTALL_CA_CERT = 40;
+    /** @hide */
+    @TestApi
+    @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+    public static final int OPERATION_SET_CONTENT_PROTECTION_POLICY = 41;
 
     private static final String PREFIX_OPERATION = "OPERATION_";
 
@@ -3869,7 +3873,8 @@
             OPERATION_SET_PERMISSION_GRANT_STATE,
             OPERATION_SET_PERMISSION_POLICY,
             OPERATION_SET_RESTRICTIONS_PROVIDER,
-            OPERATION_UNINSTALL_CA_CERT
+            OPERATION_UNINSTALL_CA_CERT,
+            OPERATION_SET_CONTENT_PROTECTION_POLICY
     })
     @Retention(RetentionPolicy.SOURCE)
     public static @interface DevicePolicyOperation {
@@ -4095,15 +4100,15 @@
     }
 
     /** Indicates that content protection is not controlled by policy, allowing user to choose. */
-    @FlaggedApi(FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+    @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
     public static final int CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY = 0;
 
-    /** Indicates that content protection is controlled and disabled by a policy. */
-    @FlaggedApi(FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+    /** Indicates that content protection is controlled and disabled by a policy (default). */
+    @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
     public static final int CONTENT_PROTECTION_DISABLED = 1;
 
     /** Indicates that content protection is controlled and enabled by a policy. */
-    @FlaggedApi(FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+    @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
     public static final int CONTENT_PROTECTION_ENABLED = 2;
 
     /** @hide */
@@ -4118,6 +4123,86 @@
     public @interface ContentProtectionPolicy {}
 
     /**
+     * Sets the content protection policy which controls scanning for deceptive apps.
+     * <p>
+     * This function can only be called by the device owner, a profile owner of an affiliated user
+     * or profile, or the profile owner when no device owner is set or holders of the permission
+     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CONTENT_PROTECTION}. See
+     * {@link #isAffiliatedUser}.
+     * Any policy set via this method will be cleared if the user becomes unaffiliated.
+     * <p>
+     * After the content protection policy has been set,
+     * {@link PolicyUpdateReceiver#onPolicySetResult(Context, String, Bundle, TargetUser,
+     * PolicyUpdateResult)} will notify the admin on whether the policy was successfully set or not.
+     * This callback will contain:
+     * <ul>
+     * <li> The policy identifier {@link DevicePolicyIdentifiers#CONTENT_PROTECTION_POLICY}
+     * <li> The {@link TargetUser} that this policy relates to
+     * <li> The {@link PolicyUpdateResult}, which will be
+     * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the
+     * reason the policy failed to be set
+     * (e.g. {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY})
+     * </ul>
+     * If there has been a change to the policy,
+     * {@link PolicyUpdateReceiver#onPolicyChanged(Context, String, Bundle, TargetUser,
+     * PolicyUpdateResult)} will notify the admin of this change. This callback will contain the
+     * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult}
+     * will contain the reason why the policy changed.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
+     *               caller is not a device admin.
+     * @param policy The content protection policy to set. One of {@link
+     *               #CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY},
+     *               {@link #CONTENT_PROTECTION_DISABLED} or {@link #CONTENT_PROTECTION_ENABLED}.
+     * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an
+     * affiliated user or profile, or the profile owner when no device owner is set or holder of the
+     * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CONTENT_PROTECTION}.
+     * @see #isAffiliatedUser
+     */
+    @RequiresPermission(value = MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, conditional = true)
+    @SupportsCoexistence
+    @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+    public void setContentProtectionPolicy(
+            @Nullable ComponentName admin, @ContentProtectionPolicy int policy) {
+        throwIfParentInstance("setContentProtectionPolicy");
+        if (mService != null) {
+            try {
+                mService.setContentProtectionPolicy(admin, mContext.getPackageName(), policy);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Returns the current content protection policy.
+     * <p>
+     * The returned policy will be the current resolved policy rather than the policy set by the
+     * calling admin.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
+     *              caller is not a device admin.
+     * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an
+     * affiliated user or profile, or the profile owner when no device owner is set or holder of the
+     * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CONTENT_PROTECTION}.
+     * @see #isAffiliatedUser
+     * @see #setContentProtectionPolicy
+     */
+    @RequiresPermission(value = MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, conditional = true)
+    @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+    public @ContentProtectionPolicy int getContentProtectionPolicy(@Nullable ComponentName admin) {
+        throwIfParentInstance("getContentProtectionPolicy");
+        if (mService != null) {
+            try {
+                return mService.getContentProtectionPolicy(admin, mContext.getPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return CONTENT_PROTECTION_DISABLED;
+    }
+
+    /**
      * This object is a single place to tack on invalidation and disable calls.  All
      * binder caches in this class derive from this Config, so all can be invalidated or
      * disabled through this Config.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 575fa4c..efcf563 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -610,4 +610,7 @@
     String getFinancedDeviceKioskRoleHolder(String callerPackageName);
 
     void calculateHasIncompatibleAccounts();
+
+    void setContentProtectionPolicy(in ComponentName who, String callerPackageName, int policy);
+    int getContentProtectionPolicy(in ComponentName who, String callerPackageName);
 }
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index ca2e97e..ed1b8ca 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -17,12 +17,14 @@
 package android.app.admin;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.os.Build;
@@ -99,6 +101,7 @@
             TAG_PACKAGE_INSTALLED,
             TAG_PACKAGE_UPDATED,
             TAG_PACKAGE_UNINSTALLED,
+            TAG_BACKUP_SERVICE_TOGGLED,
     })
     public @interface SecurityLogTag {}
 
@@ -599,6 +602,18 @@
     public static final int TAG_PACKAGE_UNINSTALLED = SecurityLogTags.SECURITY_PACKAGE_UNINSTALLED;
 
     /**
+     * Indicates that an admin has enabled or disabled backup service. The log entry contains the
+     * following information about the event encapsulated in an {@link Object} array, accessible
+     * via {@link SecurityEvent#getData()}:
+     * <li> [0] admin package name ({@code String})
+     * <li> [1] admin user ID ({@code Integer})
+     * <li> [2] backup service state ({@code Integer}, 1 for enabled, 0 for disabled)
+     * @see DevicePolicyManager#setBackupServiceEnabled(ComponentName, boolean)
+     */
+    @FlaggedApi(Flags.FLAG_BACKUP_SERVICE_SECURITY_LOG_EVENT_ENABLED)
+    public static final int TAG_BACKUP_SERVICE_TOGGLED =
+            SecurityLogTags.SECURITY_BACKUP_SERVICE_TOGGLED;
+    /**
      * Event severity level indicating that the event corresponds to normal workflow.
      */
     public static final int LEVEL_INFO = 1;
diff --git a/core/java/android/app/admin/SecurityLogTags.logtags b/core/java/android/app/admin/SecurityLogTags.logtags
index e4af8dd..7b3aa7b 100644
--- a/core/java/android/app/admin/SecurityLogTags.logtags
+++ b/core/java/android/app/admin/SecurityLogTags.logtags
@@ -47,4 +47,5 @@
 210040 security_bluetooth_disconnection         (addr|3),(reason|3)
 210041 security_package_installed               (package_name|3),(version_code|1),(user_id|1)
 210042 security_package_updated                 (package_name|3),(version_code|1),(user_id|1)
-210043 security_package_uninstalled             (package_name|3),(version_code|1),(user_id|1)
\ No newline at end of file
+210043 security_package_uninstalled             (package_name|3),(version_code|1),(user_id|1)
+210044 security_backup_service_toggled          (package|3),(admin_user|1),(enabled|1)
\ No newline at end of file
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index b3ecd92..561eb00 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -62,3 +62,10 @@
     description: "Exempt the default sms app of the context user for suspension when calling setPersonalAppsSuspended"
     bug: "309183330"
 }
+
+flag {
+  name: "backup_service_security_log_event_enabled"
+  namespace: "enterprise"
+  description: "Emit a security log event when DPM.setBackupServiceEnabled is called"
+  bug: "304999634"
+}
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java
index ea31ef3..112c5fd 100644
--- a/core/java/android/app/backup/BackupRestoreEventLogger.java
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.java
@@ -26,6 +26,8 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 
+import com.android.server.backup.Flags;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.nio.charset.StandardCharsets;
@@ -56,7 +58,7 @@
      *
      * @hide
      */
-    public static final int DATA_TYPES_ALLOWED = 15;
+    public static final int DATA_TYPES_ALLOWED = 150;
 
     /**
      * Denotes that the annotated element identifies a data type as required by the logging methods
@@ -299,7 +301,7 @@
         }
 
         if (!mResults.containsKey(dataType)) {
-            if (mResults.keySet().size() == DATA_TYPES_ALLOWED) {
+            if (mResults.keySet().size() == getDataTypesAllowed()) {
                 // This is a new data type and we're already at capacity.
                 Slog.d(TAG, "Logger is full, ignoring new data type");
                 return null;
@@ -315,6 +317,14 @@
         return mHashDigest.digest(metaData.getBytes(StandardCharsets.UTF_8));
     }
 
+    private int getDataTypesAllowed(){
+        if (Flags.enableIncreaseDatatypesForAgentLogging()) {
+            return DATA_TYPES_ALLOWED;
+        } else {
+            return 15;
+        }
+    }
+
     /**
      * Encapsulate logging results for a single data type.
      */
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index c40b23e..274d02a 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -56,4 +56,14 @@
   namespace: "systemui"
   description: "This flag enables the API to allow setting VibrationEffect for NotificationChannels"
   bug: "241732519"
+}
+
+flag {
+  name: "evenly_divided_call_style_action_layout"
+  namespace: "systemui"
+  description: "Evenly divides horizontal space for action buttons in CallStyle notifications."
+  bug: "268733030"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
 }
\ No newline at end of file
diff --git a/core/java/android/app/usage/StorageStats.java b/core/java/android/app/usage/StorageStats.java
index 87d97d5..7bfaef4 100644
--- a/core/java/android/app/usage/StorageStats.java
+++ b/core/java/android/app/usage/StorageStats.java
@@ -40,28 +40,79 @@
     /** @hide */ public long apkBytes;
     /** @hide */ public long libBytes;
     /** @hide */ public long dmBytes;
+    /** @hide */ public long dexoptBytes;
+    /** @hide */ public long curProfBytes;
+    /** @hide */ public long refProfBytes;
     /** @hide */ public long externalCacheBytes;
 
-    /** Represents all .apk files in application code path.
+    /**
+     * Represents all nonstale dexopt and runtime artifacts of application.
+     * This includes AOT-compiled code and other data that can speed up app execution.
+     * For more detailed information, read the
+     * <a href="https://source.android.com/docs/core/runtime/jit-compiler#flow">JIT compiler</a>
+     * guide.
+     *
+     * Dexopt artifacts become stale when one of their dependencies
+     * has changed. They may be cleaned up or replaced by ART Services at any time.
+     *
+     * For a preload app, this type includes dexopt artifacts on readonly partitions
+     * if they are up-to-date.
+     *
+     * Can be used as an input to {@link #getAppBytesByDataType(int)}
+     * to get the sum of sizes for files of this type. The sum might include the size of data
+     * that is part of appBytes, dataBytes or cacheBytes.
+     */
+    @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+    public static final int APP_DATA_TYPE_FILE_TYPE_DEXOPT_ARTIFACT = 0;
+
+    /**
+     * Represents reference profile of application.
+     *
+     * Reference profiles are the ones used during the last profile-guided dexopt.
+     * If the last dexopt wasn't profile-guided, then these profiles were not used.
+     *
+     * Can be used as an input to {@link #getAppBytesByDataType(int)}
+     * to get the size of files of this type.
+     */
+    @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+    public static final int APP_DATA_TYPE_FILE_TYPE_REFERENCE_PROFILE = 1;
+
+    /**
+     * Represents current profile of application.
+     *
+     * Current profiles may or may not be used during the next profile-guided dexopt.
+     *
+     * Can be used as an input to {@link #getAppBytesByDataType(int)}
+     * to get the size of files of this type. This size fluctuates regularly,
+     * it goes up when the user uses more and more classes/methods and comes down when
+     * a deamon merges this into the ref profile and does profile-guided dexopt.
+     */
+    @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+    public static final int APP_DATA_TYPE_FILE_TYPE_CURRENT_PROFILE = 2;
+
+    /**
+     * Represents all .apk files in application code path.
      * Can be used as an input to {@link #getAppBytesByDataType(int)}
      * to get the sum of sizes for files of this type.
      */
     @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
-    public static final int APP_DATA_TYPE_FILE_TYPE_APK = 0;
+    public static final int APP_DATA_TYPE_FILE_TYPE_APK = 3;
 
-    /** Represents all .dm files in application code path.
+    /**
+     * Represents all .dm files in application code path.
      * Can be used as an input to {@link #getAppBytesByDataType(int)}
      * to get the sum of sizes for files of this type.
      */
     @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
-    public static final int APP_DATA_TYPE_FILE_TYPE_DM = 1;
+    public static final int APP_DATA_TYPE_FILE_TYPE_DM = 4;
 
-    /** Represents lib/ in application code path.
+    /**
+     * Represents lib/ in application code path.
      * Can be used as an input to {@link #getAppBytesByDataType(int)}
      * to get the size of lib/ directory.
      */
     @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
-    public static final int APP_DATA_TYPE_LIB = 2;
+    public static final int APP_DATA_TYPE_LIB = 5;
 
     /**
      * Keep in sync with the file types defined above.
@@ -69,6 +120,9 @@
      */
     @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
     @IntDef(flag = false, value = {
+        APP_DATA_TYPE_FILE_TYPE_DEXOPT_ARTIFACT,
+        APP_DATA_TYPE_FILE_TYPE_REFERENCE_PROFILE,
+        APP_DATA_TYPE_FILE_TYPE_CURRENT_PROFILE,
         APP_DATA_TYPE_FILE_TYPE_APK,
         APP_DATA_TYPE_FILE_TYPE_DM,
         APP_DATA_TYPE_LIB,
@@ -103,6 +157,9 @@
     @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
     public long getAppBytesByDataType(@AppDataType int dataType) {
         switch (dataType) {
+          case APP_DATA_TYPE_FILE_TYPE_DEXOPT_ARTIFACT: return dexoptBytes;
+          case APP_DATA_TYPE_FILE_TYPE_REFERENCE_PROFILE: return refProfBytes;
+          case APP_DATA_TYPE_FILE_TYPE_CURRENT_PROFILE: return curProfBytes;
           case APP_DATA_TYPE_FILE_TYPE_APK: return apkBytes;
           case APP_DATA_TYPE_LIB: return libBytes;
           case APP_DATA_TYPE_FILE_TYPE_DM: return dmBytes;
@@ -161,6 +218,9 @@
         this.codeBytes = in.readLong();
         this.dataBytes = in.readLong();
         this.cacheBytes = in.readLong();
+        this.dexoptBytes = in.readLong();
+        this.refProfBytes = in.readLong();
+        this.curProfBytes = in.readLong();
         this.apkBytes = in.readLong();
         this.libBytes = in.readLong();
         this.dmBytes = in.readLong();
@@ -177,6 +237,9 @@
         dest.writeLong(codeBytes);
         dest.writeLong(dataBytes);
         dest.writeLong(cacheBytes);
+        dest.writeLong(dexoptBytes);
+        dest.writeLong(refProfBytes);
+        dest.writeLong(curProfBytes);
         dest.writeLong(apkBytes);
         dest.writeLong(libBytes);
         dest.writeLong(dmBytes);
diff --git a/core/java/android/app/wearable/IWearableSensingManager.aidl b/core/java/android/app/wearable/IWearableSensingManager.aidl
index ff37bd8..9d55ce28 100644
--- a/core/java/android/app/wearable/IWearableSensingManager.aidl
+++ b/core/java/android/app/wearable/IWearableSensingManager.aidl
@@ -28,6 +28,8 @@
  */
 interface IWearableSensingManager {
      @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
+     void provideWearableConnection(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
+     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
      void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
      @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
      void provideData(in PersistableBundle data, in SharedMemory sharedMemory, in RemoteCallback callback);
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
index eca0039..401d0b7 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -26,6 +26,7 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.app.ambientcontext.AmbientContextEvent;
+import android.companion.CompanionDeviceManager;
 import android.content.Context;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
@@ -36,6 +37,8 @@
 import android.service.wearable.WearableSensingService;
 import android.system.OsConstants;
 
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.Executor;
@@ -107,6 +110,14 @@
     @FlaggedApi(Flags.FLAG_ENABLE_UNSUPPORTED_OPERATION_STATUS_CODE)
     public static final int STATUS_UNSUPPORTED_OPERATION = 6;
 
+    /**
+     * The value of the status code that indicates an error occurred in the encrypted channel backed
+     * by the provided connection. See {@link #provideWearableConnection(ParcelFileDescriptor,
+     * Executor, Consumer)}.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API)
+    public static final int STATUS_CHANNEL_ERROR = 7;
+
     /** @hide */
     @IntDef(prefix = { "STATUS_" }, value = {
             STATUS_UNKNOWN,
@@ -115,7 +126,8 @@
             STATUS_SERVICE_UNAVAILABLE,
             STATUS_WEARABLE_UNAVAILABLE,
             STATUS_ACCESS_DENIED,
-            STATUS_UNSUPPORTED_OPERATION
+            STATUS_UNSUPPORTED_OPERATION,
+            STATUS_CHANNEL_ERROR
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface StatusCode {}
@@ -132,6 +144,60 @@
     }
 
     /**
+     * Provides a remote wearable device connection to the WearableSensingService and sends the
+     * resulting status to the {@code statusConsumer} after the call.
+     *
+     * <p>This is used by applications that will also provide an implementation of the isolated
+     * WearableSensingService.
+     *
+     * <p>The provided {@code wearableConnection} is expected to be a connection to a remotely
+     * connected wearable device. This {@code wearableConnection} will be attached to
+     * CompanionDeviceManager via {@link CompanionDeviceManager#attachSystemDataTransport(int,
+     * InputStream, OutputStream)}, which will create an encrypted channel using {@code
+     * wearableConnection} as the raw underlying connection. The wearable device is expected to
+     * attach its side of the raw connection to its CompanionDeviceManager via the same method so
+     * that the two CompanionDeviceManagers on the two devices can perform attestation and set up
+     * the encrypted channel. Attestation requirements are listed in
+     * com.android.server.security.AttestationVerificationPeerDeviceVerifier
+     *
+     * <p>A proxy to the encrypted channel will be provided to the WearableSensingService, which is
+     * referred to as the secureWearableConnection in WearableSensingService. Any data written to
+     * secureWearableConnection will be encrypted by CompanionDeviceManager and sent over the raw
+     * {@code wearableConnection} to the remote wearable device, which is expected to use its
+     * CompanionDeviceManager to decrypt the data. Encrypted data arriving at the raw {@code
+     * wearableConnection} will be decrypted by CompanionDeviceManager and be readable as plain text
+     * from secureWearableConnection. The raw {@code wearableConnection} provided to this method
+     * will not be directly available to the WearableSensingService.
+     *
+     * <p>If an error occurred in the encrypted channel (such as the underlying stream closed), the
+     * system will send a status code of {@link STATUS_CHANNEL_ERROR} to the {@code statusConsumer}
+     * and kill the WearableSensingService process.
+     *
+     * <p>Before providing the secureWearableConnection, the system will restart the
+     * WearableSensingService process. Other method calls into WearableSensingService may be dropped
+     * during the restart. The caller is responsible for ensuring other method calls are queued
+     * until a success status is returned from the {@code statusConsumer}.
+     *
+     * @param wearableConnection The connection to provide
+     * @param executor Executor on which to run the consumer callback
+     * @param statusConsumer A consumer that handles the status codes for providing the connection
+     *     and errors in the encrypted channel.
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
+    @FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API)
+    public void provideWearableConnection(
+            @NonNull ParcelFileDescriptor wearableConnection,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull @StatusCode Consumer<Integer> statusConsumer) {
+        try {
+            RemoteCallback callback = createStatusCallback(executor, statusConsumer);
+            mService.provideWearableConnection(wearableConnection, callback);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Provides a data stream to the WearableSensingService that's backed by the
      * parcelFileDescriptor, and sends the result to the {@link Consumer} right after the call.
      * This is used by applications that will also provide an implementation of
@@ -149,15 +215,7 @@
             @NonNull @CallbackExecutor Executor executor,
             @NonNull @StatusCode Consumer<Integer> statusConsumer) {
         try {
-            RemoteCallback callback = new RemoteCallback(result -> {
-                int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
-                final long identity = Binder.clearCallingIdentity();
-                try {
-                    executor.execute(() -> statusConsumer.accept(status));
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
-                }
-            });
+            RemoteCallback callback = createStatusCallback(executor, statusConsumer);
             mService.provideDataStream(parcelFileDescriptor, callback);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -191,19 +249,24 @@
             @NonNull @CallbackExecutor Executor executor,
             @NonNull @StatusCode Consumer<Integer> statusConsumer) {
         try {
-            RemoteCallback callback = new RemoteCallback(result -> {
-                int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
-                final long identity = Binder.clearCallingIdentity();
-                try {
-                    executor.execute(() -> statusConsumer.accept(status));
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
-                }
-            });
+            RemoteCallback callback = createStatusCallback(executor, statusConsumer);
             mService.provideData(data, sharedMemory, callback);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
+    private static RemoteCallback createStatusCallback(
+            Executor executor, Consumer<Integer> statusConsumer) {
+        return new RemoteCallback(
+                result -> {
+                    int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
+                    final long identity = Binder.clearCallingIdentity();
+                    try {
+                        executor.execute(() -> statusConsumer.accept(status));
+                    } finally {
+                        Binder.restoreCallingIdentity(identity);
+                    }
+                });
+    }
 }
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index 084cba3..822f02f 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -19,6 +19,9 @@
   namespace: "app_widgets"
   description: "Move state file IO to non-critical path"
   bug: "312949280"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
 }
 
 flag {
diff --git a/core/java/android/content/pm/IBackgroundInstallControlService.aidl b/core/java/android/content/pm/IBackgroundInstallControlService.aidl
index c8e7cae..2e7f19e 100644
--- a/core/java/android/content/pm/IBackgroundInstallControlService.aidl
+++ b/core/java/android/content/pm/IBackgroundInstallControlService.aidl
@@ -17,10 +17,15 @@
 package android.content.pm;
 
 import android.content.pm.ParceledListSlice;
+import android.os.IRemoteCallback;
 
 /**
  * {@hide}
  */
 interface IBackgroundInstallControlService {
     ParceledListSlice getBackgroundInstalledPackages(long flags, int userId);
+
+    void registerBackgroundInstallCallback(IRemoteCallback callback);
+
+    void unregisterBackgroundInstallCallback(IRemoteCallback callback);
 }
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index ea69a2b..9f31aec 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -21,6 +21,7 @@
 import android.content.pm.IOnChecksumsReadyListener;
 import android.content.pm.IPackageInstallObserver2;
 import android.content.pm.PackageInstaller;
+import android.content.pm.verify.domain.DomainSet;
 import android.content.IntentSender;
 import android.os.ParcelFileDescriptor;
 
@@ -73,4 +74,7 @@
     ParcelFileDescriptor getAppMetadataFd();
     ParcelFileDescriptor openWriteAppMetadata();
     void removeAppMetadata();
+
+    void setPreVerifiedDomains(in DomainSet preVerifiedDomains);
+    DomainSet getPreVerifiedDomains();
 }
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 08f1853..bff90f1 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -846,4 +846,6 @@
 
     @EnforcePermission("GET_APP_METADATA")
     int getAppMetadataSource(String packageName, int userId);
+
+    ComponentName getDomainVerificationAgent();
 }
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 50be983..7c264f6 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -1802,25 +1802,16 @@
     }
 
     /**
-     * Enable or disable different archive compatibility options of the launcher.
+     * Disable different archive compatibility options of the launcher for the caller of this
+     * method.
      *
-     * @param enableIconOverlay Provides a cloud overlay for archived apps to ensure users are aware
-     * that a certain app is archived. True by default.
-     * Launchers might want to disable this operation if they want to provide custom user experience
-     * to differentiate archived apps.
-     * @param enableUnarchivalConfirmation If true, the user is shown a confirmation dialog when
-     * they click an archived app, which explains that the app will be downloaded and restored in
-     * the background. True by default.
-     * Launchers might want to disable this operation if they provide sufficient, alternative user
-     * guidance to highlight that an unarchival is starting and ongoing once an archived app is
-     * tapped. E.g., this could be achieved by showing the unarchival progress around the icon.
+     * @see ArchiveCompatibilityParams for individual options.
      */
     @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
-    public void setArchiveCompatibilityOptions(boolean enableIconOverlay,
-            boolean enableUnarchivalConfirmation) {
+    public void setArchiveCompatibility(@NonNull ArchiveCompatibilityParams params) {
         try {
-            mService.setArchiveCompatibilityOptions(enableIconOverlay,
-                    enableUnarchivalConfirmation);
+            mService.setArchiveCompatibilityOptions(params.isEnableIconOverlay(),
+                    params.isEnableUnarchivalConfirmation());
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -1982,6 +1973,50 @@
         }
     };
 
+    /**
+     * Used to enable Archiving compatibility options with {@link #setArchiveCompatibility}.
+     */
+    @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
+    public static class ArchiveCompatibilityParams {
+        private boolean mEnableIconOverlay = true;
+
+        private boolean mEnableUnarchivalConfirmation = true;
+
+        /** @hide */
+        public boolean isEnableIconOverlay() {
+            return mEnableIconOverlay;
+        }
+
+        /** @hide */
+        public boolean isEnableUnarchivalConfirmation() {
+            return mEnableUnarchivalConfirmation;
+        }
+
+        /**
+         * If true, provides a cloud overlay for archived apps to ensure users are aware that a
+         * certain app is archived. True by default.
+         *
+         * <p> Launchers might want to disable this operation if they want to provide custom user
+         * experience to differentiate archived apps.
+         */
+        public void setEnableIconOverlay(boolean enableIconOverlay) {
+            this.mEnableIconOverlay = enableIconOverlay;
+        }
+
+        /**
+         * If true, the user is shown a confirmation dialog when they click an archived app, which
+         * explains that the app will be downloaded and restored in the background. True by default.
+         *
+         * <p> Launchers might want to disable this operation if they provide sufficient,
+         * alternative user guidance to highlight that an unarchival is starting and ongoing once an
+         * archived app is tapped. E.g., this could be achieved by showing the unarchival progress
+         * around the icon.
+         */
+        public void setEnableUnarchivalConfirmation(boolean enableUnarchivalConfirmation) {
+            this.mEnableUnarchivalConfirmation = enableUnarchivalConfirmation;
+        }
+    }
+
     private static class CallbackMessageHandler extends Handler {
         private static final int MSG_ADDED = 1;
         private static final int MSG_REMOVED = 2;
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 5df23c0..c2ff9f6 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -61,6 +61,7 @@
 import android.content.pm.parsing.PackageLite;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
+import android.content.pm.verify.domain.DomainSet;
 import android.graphics.Bitmap;
 import android.icu.util.ULocale;
 import android.net.Uri;
@@ -2296,6 +2297,66 @@
                 throw e.rethrowFromSystemServer();
             }
         }
+
+        /**
+         * Sets the pre-verified domains for the app to be installed. By setting pre-verified
+         * domains, the installer allows the app to be opened by the app links of these domains
+         * immediately after it is installed.
+         *
+         * <p>The specified pre-verified domains should be a subset of the hostnames declared with
+         * {@code android:host} and {@code android:autoVerify=true} in the intent filters of the
+         * AndroidManifest.xml of the app. If some of the specified domains are not declared in
+         * the manifest, they will be ignored.</p>
+         * <p>If this API is called multiple times on the same {@link #Session}, the last call
+         * overrides the previous ones.</p>
+         * <p>The instant app installer is the only entity that may call this API.
+         * </p>
+         *
+         * @param preVerifiedDomains domains that are already pre-verified by the installer.
+         *
+         * @throws IllegalArgumentException if the number or the total size of the pre-verified
+         *                                  domains exceeds the maximum allowed, or if the domain
+         *                                  names contain invalid characters.
+         * @throws SecurityException if called from an installer that is not the instant app
+         *                           installer of the device, or if called after the session has
+         *                           been committed or abandoned.
+         *
+         * @hide
+         */
+        @SystemApi
+        @FlaggedApi(Flags.FLAG_SET_PRE_VERIFIED_DOMAINS)
+        @RequiresPermission(Manifest.permission.ACCESS_INSTANT_APPS)
+        public void setPreVerifiedDomains(@NonNull Set<String> preVerifiedDomains) {
+            Preconditions.checkArgument(preVerifiedDomains != null && !preVerifiedDomains.isEmpty(),
+                    "Provided pre-verified domains cannot be null or empty.");
+            try {
+                mSession.setPreVerifiedDomains(new DomainSet(preVerifiedDomains));
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Retrieve the pre-verified domains set in a session.
+         * See {@link #setPreVerifiedDomains(Set)} for the definition of pre-verified domains.
+         *
+         * @throws SecurityException if called from an installer that is not the owner of the
+         *                           session, or if called after the session has been committed or
+         *                           abandoned.
+         * @hide
+         */
+        @SystemApi
+        @FlaggedApi(Flags.FLAG_SET_PRE_VERIFIED_DOMAINS)
+        @RequiresPermission(Manifest.permission.ACCESS_INSTANT_APPS)
+        @NonNull
+        public Set<String> getPreVerifiedDomains() {
+            try {
+                DomainSet domainSet = mSession.getPreVerifiedDomains();
+                return domainSet != null ? domainSet.getDomains() : Collections.emptySet();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/content/pm/PackageStats.java b/core/java/android/content/pm/PackageStats.java
index b919c4b..db3050f 100644
--- a/core/java/android/content/pm/PackageStats.java
+++ b/core/java/android/content/pm/PackageStats.java
@@ -67,6 +67,18 @@
     /** @hide */
     public long dmSize;
 
+    /** Size of dexopt artifacts of the application. */
+    /** @hide */
+    public long dexoptSize;
+
+    /** Size of the current profile of the application. */
+    /** @hide */
+    public long curProfSize;
+
+    /** Size of the reference profile of the application. */
+    /** @hide */
+    public long refProfSize;
+
     /**
      * Size of the secure container on external storage holding the
      * application's code.
@@ -132,6 +144,18 @@
             sb.append(" dm=");
             sb.append(dmSize);
         }
+        if (dexoptSize != 0) {
+            sb.append(" dexopt=");
+            sb.append(dexoptSize);
+        }
+        if (curProfSize != 0) {
+            sb.append(" curProf=");
+            sb.append(curProfSize);
+        }
+        if (refProfSize != 0) {
+            sb.append(" refProf=");
+            sb.append(refProfSize);
+        }
         if (externalCodeSize != 0) {
             sb.append(" extCode=");
             sb.append(externalCodeSize);
@@ -176,6 +200,9 @@
         apkSize = source.readLong();
         libSize = source.readLong();
         dmSize = source.readLong();
+        dexoptSize = source.readLong();
+        curProfSize = source.readLong();
+        refProfSize = source.readLong();
         externalCodeSize = source.readLong();
         externalDataSize = source.readLong();
         externalCacheSize = source.readLong();
@@ -192,6 +219,9 @@
         apkSize = pStats.apkSize;
         libSize = pStats.libSize;
         dmSize = pStats.dmSize;
+        dexoptSize = pStats.dexoptSize;
+        curProfSize = pStats.curProfSize;
+        refProfSize = pStats.refProfSize;
         externalCodeSize = pStats.externalCodeSize;
         externalDataSize = pStats.externalDataSize;
         externalCacheSize = pStats.externalCacheSize;
@@ -212,6 +242,9 @@
         dest.writeLong(apkSize);
         dest.writeLong(libSize);
         dest.writeLong(dmSize);
+        dest.writeLong(dexoptSize);
+        dest.writeLong(curProfSize);
+        dest.writeLong(refProfSize);
         dest.writeLong(externalCodeSize);
         dest.writeLong(externalDataSize);
         dest.writeLong(externalCacheSize);
@@ -234,6 +267,9 @@
                 && apkSize == otherStats.apkSize
                 && libSize == otherStats.libSize
                 && dmSize == otherStats.dmSize
+                && dexoptSize == otherStats.dexoptSize
+                && curProfSize == otherStats.curProfSize
+                && refProfSize == otherStats.refProfSize
                 && externalCodeSize == otherStats.externalCodeSize
                 && externalDataSize == otherStats.externalDataSize
                 && externalCacheSize == otherStats.externalCacheSize
@@ -244,7 +280,8 @@
     @Override
     public int hashCode() {
         return Objects.hash(packageName, userHandle, codeSize, dataSize,
-                apkSize, libSize, dmSize, cacheSize, externalCodeSize,
+                apkSize, libSize, dmSize, dexoptSize, curProfSize,
+                refProfSize, cacheSize, externalCodeSize,
                 externalDataSize, externalCacheSize, externalMediaSize,
                 externalObbSize);
     }
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index f54b2ac..d347a0e 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -16,9 +16,6 @@
 
 package android.content.pm;
 
-import static android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES;
-
-import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -476,22 +473,26 @@
     )
     public @interface ProfileApiVisibility {
     }
-    /*
-    * The api visibility value for this profile user is undefined or unknown.
+
+    /**
+     * The api visibility value for this profile user is undefined or unknown.
+     *
+     * @hide
      */
-    @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
     public static final int PROFILE_API_VISIBILITY_UNKNOWN = -1;
 
     /**
      * Indicates that information about this profile user should be shown in API surfaces.
+     *
+     * @hide
      */
-    @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
     public static final int PROFILE_API_VISIBILITY_VISIBLE = 0;
 
     /**
      * Indicates that information about this profile should be not be visible in API surfaces.
+     *
+     * @hide
      */
-    @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
     public static final int PROFILE_API_VISIBILITY_HIDDEN = 1;
 
 
@@ -555,9 +556,7 @@
         setShowInQuietMode(orig.getShowInQuietMode());
         setShowInSharingSurfaces(orig.getShowInSharingSurfaces());
         setCrossProfileContentSharingStrategy(orig.getCrossProfileContentSharingStrategy());
-        if (android.multiuser.Flags.supportHidingProfiles()) {
-            setProfileApiVisibility(orig.getProfileApiVisibility());
-        }
+        setProfileApiVisibility(orig.getProfileApiVisibility());
     }
 
     /**
@@ -1002,9 +1001,10 @@
     /**
      * Returns the visibility of the profile user in API surfaces. Any information linked to the
      * profile (userId, package names) should be hidden API surfaces if a user is marked as hidden.
+     *
+     * @hide
      */
     @NonNull
-    @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
     public @ProfileApiVisibility int getProfileApiVisibility() {
         if (isPresent(INDEX_PROFILE_API_VISIBILITY)) return mProfileApiVisibility;
         if (mDefaultProperties != null) return mDefaultProperties.mProfileApiVisibility;
@@ -1012,7 +1012,6 @@
     }
     /** @hide */
     @NonNull
-    @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
     public void setProfileApiVisibility(@ProfileApiVisibility int profileApiVisibility) {
         this.mProfileApiVisibility = profileApiVisibility;
         setPresent(INDEX_PROFILE_API_VISIBILITY);
@@ -1053,9 +1052,6 @@
 
     @Override
     public String toString() {
-        String profileApiVisibility =
-                android.multiuser.Flags.supportHidingProfiles() ? ", mProfileApiVisibility="
-                        + getProfileApiVisibility() : "";
         // Please print in increasing order of PropertyIndex.
         return "UserProperties{"
                 + "mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent)
@@ -1079,7 +1075,7 @@
                 + ", mDeleteAppWithParent=" + getDeleteAppWithParent()
                 + ", mAlwaysVisible=" + getAlwaysVisible()
                 + ", mCrossProfileContentSharingStrategy=" + getCrossProfileContentSharingStrategy()
-                + ", mProfileApiVisibility=" + profileApiVisibility
+                + ", mProfileApiVisibility=" + getProfileApiVisibility()
                 + ", mItemsRestrictedOnHomeScreen=" + areItemsRestrictedOnHomeScreen()
                 + "}";
     }
@@ -1114,9 +1110,7 @@
         pw.println(prefix + "    mAlwaysVisible=" + getAlwaysVisible());
         pw.println(prefix + "    mCrossProfileContentSharingStrategy="
                 + getCrossProfileContentSharingStrategy());
-        if (android.multiuser.Flags.supportHidingProfiles()) {
-            pw.println(prefix + "    mProfileApiVisibility=" + getProfileApiVisibility());
-        }
+        pw.println(prefix + "    mProfileApiVisibility=" + getProfileApiVisibility());
         pw.println(prefix + "    mItemsRestrictedOnHomeScreen=" + areItemsRestrictedOnHomeScreen());
     }
 
@@ -1203,9 +1197,7 @@
                     setCrossProfileContentSharingStrategy(parser.getAttributeInt(i));
                     break;
                 case ATTR_PROFILE_API_VISIBILITY:
-                    if (android.multiuser.Flags.supportHidingProfiles()) {
-                        setProfileApiVisibility(parser.getAttributeInt(i));
-                    }
+                    setProfileApiVisibility(parser.getAttributeInt(i));
                     break;
                 case ITEMS_RESTRICTED_ON_HOME_SCREEN:
                     setItemsRestrictedOnHomeScreen(parser.getAttributeBoolean(i));
@@ -1293,10 +1285,8 @@
                     mCrossProfileContentSharingStrategy);
         }
         if (isPresent(INDEX_PROFILE_API_VISIBILITY)) {
-            if (android.multiuser.Flags.supportHidingProfiles()) {
-                serializer.attributeInt(null, ATTR_PROFILE_API_VISIBILITY,
-                        mProfileApiVisibility);
-            }
+            serializer.attributeInt(null, ATTR_PROFILE_API_VISIBILITY,
+                    mProfileApiVisibility);
         }
         if (isPresent(INDEX_ITEMS_RESTRICTED_ON_HOME_SCREEN)) {
             serializer.attributeBoolean(null, ITEMS_RESTRICTED_ON_HOME_SCREEN,
@@ -1566,7 +1556,6 @@
          * @hide
          */
         @NonNull
-        @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
         public Builder setProfileApiVisibility(@ProfileApiVisibility int profileApiVisibility){
             mProfileApiVisibility = profileApiVisibility;
             return this;
@@ -1650,9 +1639,7 @@
         setDeleteAppWithParent(deleteAppWithParent);
         setAlwaysVisible(alwaysVisible);
         setCrossProfileContentSharingStrategy(crossProfileContentSharingStrategy);
-        if (android.multiuser.Flags.supportHidingProfiles()) {
-            setProfileApiVisibility(profileApiVisibility);
-        }
+        setProfileApiVisibility(profileApiVisibility);
         setItemsRestrictedOnHomeScreen(itemsRestrictedOnHomeScreen);
     }
 }
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index f31521d..5e9d8f0 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -206,3 +206,11 @@
     description: "Feature flag to enable pre-verified domains"
     bug: "307327678"
 }
+
+flag {
+    name: "min_target_sdk_24"
+    namespace: "responsible_apis"
+    description: "Feature flag to bump min target sdk to 24"
+    bug: "297603927"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 7ded747..4b890fa 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -109,18 +109,10 @@
 }
 
 flag {
-    name: "allow_private_profile_apis"
+    name: "enable_private_space_features"
     namespace: "profile_experiences"
-    description: "Enable only the API changes to support private space"
-    bug: "299069460"
-}
-
-flag {
-    name: "support_hiding_profiles"
-    namespace: "profile_experiences"
-    description: "Allow the use of a hide_profile property to hide some profiles behind a permission"
-    bug: "316362775"
-    is_fixed_read_only: true
+    description: "Enable the support for private space and all its sub-features"
+    bug: "286418785"
 }
 
 flag {
diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java
index 4990a27..74ce62c 100644
--- a/core/java/android/content/pm/parsing/ApkLite.java
+++ b/core/java/android/content/pm/parsing/ApkLite.java
@@ -145,6 +145,11 @@
     private final boolean mUpdatableSystem;
 
     /**
+     * Name of the emergency installer for the designated system app.
+     */
+    private final @Nullable String mEmergencyInstaller;
+
+    /**
      * Archival install info.
      */
     private final @Nullable ArchivedPackageParcel mArchivedPackage;
@@ -159,7 +164,8 @@
             String requiredSystemPropertyName, String requiredSystemPropertyValue,
             int minSdkVersion, int targetSdkVersion, int rollbackDataPolicy,
             Set<String> requiredSplitTypes, Set<String> splitTypes,
-            boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean updatableSystem) {
+            boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean updatableSystem,
+            String emergencyInstaller) {
         mPath = path;
         mPackageName = packageName;
         mSplitName = splitName;
@@ -194,6 +200,7 @@
         mHasDeviceAdminReceiver = hasDeviceAdminReceiver;
         mIsSdkLibrary = isSdkLibrary;
         mUpdatableSystem = updatableSystem;
+        mEmergencyInstaller = emergencyInstaller;
         mArchivedPackage = null;
     }
 
@@ -232,6 +239,7 @@
         mHasDeviceAdminReceiver = false;
         mIsSdkLibrary = false;
         mUpdatableSystem = true;
+        mEmergencyInstaller = null;
         mArchivedPackage = archivedPackage;
     }
 
@@ -550,6 +558,14 @@
     }
 
     /**
+     * Name of the emergency installer for the designated system app.
+     */
+    @DataClass.Generated.Member
+    public @Nullable String getEmergencyInstaller() {
+        return mEmergencyInstaller;
+    }
+
+    /**
      * Archival install info.
      */
     @DataClass.Generated.Member
@@ -558,10 +574,10 @@
     }
 
     @DataClass.Generated(
-            time = 1699587291575L,
+            time = 1706896661616L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java",
-            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final  int mVersionCodeMajor\nprivate final  int mVersionCode\nprivate final  int mRevisionCode\nprivate final  int mInstallLocation\nprivate final  int mMinSdkVersion\nprivate final  int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final  boolean mFeatureSplit\nprivate final  boolean mIsolatedSplits\nprivate final  boolean mSplitRequired\nprivate final  boolean mCoreApp\nprivate final  boolean mDebuggable\nprivate final  boolean mProfileableByShell\nprivate final  boolean mMultiArch\nprivate final  boolean mUse32bitAbi\nprivate final  boolean mExtractNativeLibs\nprivate final  boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final  boolean mOverlayIsStatic\nprivate final  int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final  int mRollbackDataPolicy\nprivate final  boolean mHasDeviceAdminReceiver\nprivate final  boolean mIsSdkLibrary\nprivate final  boolean mUpdatableSystem\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic  long getLongVersionCode()\nprivate  boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\[email protected](genConstructor=false, genConstDefs=false)")
+            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final  int mVersionCodeMajor\nprivate final  int mVersionCode\nprivate final  int mRevisionCode\nprivate final  int mInstallLocation\nprivate final  int mMinSdkVersion\nprivate final  int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final  boolean mFeatureSplit\nprivate final  boolean mIsolatedSplits\nprivate final  boolean mSplitRequired\nprivate final  boolean mCoreApp\nprivate final  boolean mDebuggable\nprivate final  boolean mProfileableByShell\nprivate final  boolean mMultiArch\nprivate final  boolean mUse32bitAbi\nprivate final  boolean mExtractNativeLibs\nprivate final  boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final  boolean mOverlayIsStatic\nprivate final  int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final  int mRollbackDataPolicy\nprivate final  boolean mHasDeviceAdminReceiver\nprivate final  boolean mIsSdkLibrary\nprivate final  boolean mUpdatableSystem\nprivate final @android.annotation.Nullable java.lang.String mEmergencyInstaller\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic  long getLongVersionCode()\nprivate  boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\[email protected](genConstructor=false, genConstDefs=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 69f9a7d..ffb69c0 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -435,6 +435,7 @@
         boolean isSplitRequired = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
                 "isSplitRequired", false);
         String configForSplit = parser.getAttributeValue(null, "configForSplit");
+        String emergencyInstaller = parser.getAttributeValue(null, "emergencyInstaller");
 
         int targetSdkVersion = DEFAULT_TARGET_SDK_VERSION;
         int minSdkVersion = DEFAULT_MIN_SDK_VERSION;
@@ -644,7 +645,7 @@
                         overlayIsStatic, overlayPriority, requiredSystemPropertyName,
                         requiredSystemPropertyValue, minSdkVersion, targetSdkVersion,
                         rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second,
-                        hasDeviceAdminReceiver, isSdkLibrary, updatableSystem));
+                        hasDeviceAdminReceiver, isSdkLibrary, updatableSystem, emergencyInstaller));
     }
 
     private static boolean isDeviceAdminReceiver(
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationState.java b/core/java/android/content/pm/verify/domain/DomainVerificationState.java
index 8e28042..6c4fe37 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationState.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationState.java
@@ -33,7 +33,8 @@
             STATE_DENIED,
             STATE_LEGACY_FAILURE,
             STATE_SYS_CONFIG,
-            STATE_FIRST_VERIFIER_DEFINED
+            STATE_PRE_VERIFIED,
+            STATE_FIRST_VERIFIER_DEFINED,
     })
     @interface State {
     }
@@ -92,6 +93,13 @@
     int STATE_SYS_CONFIG = 7;
 
     /**
+     * The application has temporarily been granted auto verification for a set of domains as
+     * specified by a trusted installer during the installation. This will treat the domain as
+     * verified, but it should be updated by the verification agent.
+     */
+    int STATE_PRE_VERIFIED = 8;
+
+    /**
      * @see DomainVerificationInfo#STATE_FIRST_VERIFIER_DEFINED
      */
     int STATE_FIRST_VERIFIER_DEFINED = 0b10000000000;
@@ -115,6 +123,8 @@
                 return "legacy_failure";
             case DomainVerificationState.STATE_SYS_CONFIG:
                 return "system_configured";
+            case DomainVerificationState.STATE_PRE_VERIFIED:
+                return "pre_verified";
             default:
                 return String.valueOf(state);
         }
@@ -135,6 +145,7 @@
             case STATE_DENIED:
             case STATE_LEGACY_FAILURE:
             case STATE_SYS_CONFIG:
+            case STATE_PRE_VERIFIED:
             default:
                 return false;
         }
@@ -151,6 +162,7 @@
             case DomainVerificationState.STATE_MIGRATED:
             case DomainVerificationState.STATE_RESTORED:
             case DomainVerificationState.STATE_SYS_CONFIG:
+            case DomainVerificationState.STATE_PRE_VERIFIED:
                 return true;
             case DomainVerificationState.STATE_NO_RESPONSE:
             case DomainVerificationState.STATE_DENIED:
@@ -173,6 +185,7 @@
             case DomainVerificationState.STATE_MIGRATED:
             case DomainVerificationState.STATE_RESTORED:
             case DomainVerificationState.STATE_LEGACY_FAILURE:
+            case DomainVerificationState.STATE_PRE_VERIFIED:
                 return true;
             case DomainVerificationState.STATE_APPROVED:
             case DomainVerificationState.STATE_DENIED:
@@ -194,6 +207,7 @@
             case STATE_RESTORED:
             case STATE_APPROVED:
             case STATE_DENIED:
+            case STATE_PRE_VERIFIED:
                 return true;
             case STATE_NO_RESPONSE:
             case STATE_LEGACY_FAILURE:
diff --git a/core/java/android/credentials/Constants.java b/core/java/android/credentials/Constants.java
new file mode 100644
index 0000000..ea30c44
--- /dev/null
+++ b/core/java/android/credentials/Constants.java
@@ -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 android.credentials;
+
+/**
+ * Constants for credential manager service that doesn't fit into other structures
+ *
+ * @hide
+ */
+public class Constants {
+    /**
+     * The request is success and user selected an entry
+     */
+    public static final int SUCCESS_CREDMAN_SELECTOR = 0;
+    /**
+     * The error code for ui getting cancelled by user
+     */
+    public static final int FAILURE_CREDMAN_SELECTOR = -1;
+}
diff --git a/core/java/android/credentials/GetCandidateCredentialsResponse.java b/core/java/android/credentials/GetCandidateCredentialsResponse.java
index 73361ad..6e53fd9 100644
--- a/core/java/android/credentials/GetCandidateCredentialsResponse.java
+++ b/core/java/android/credentials/GetCandidateCredentialsResponse.java
@@ -41,8 +41,6 @@
 
     private final PendingIntent mPendingIntent;
 
-    private final GetCredentialResponse mGetCredentialResponse;
-
     /**
      * @hide
      */
@@ -52,7 +50,6 @@
     ) {
         mCandidateProviderDataList = null;
         mPendingIntent = null;
-        mGetCredentialResponse = getCredentialResponse;
     }
 
     /**
@@ -68,7 +65,6 @@
                 /*valueName=*/ "candidateProviderDataList");
         mCandidateProviderDataList = new ArrayList<>(candidateProviderDataList);
         mPendingIntent = pendingIntent;
-        mGetCredentialResponse = null;
     }
 
     /**
@@ -85,15 +81,6 @@
      *
      * @hide
      */
-    public GetCredentialResponse getGetCredentialResponse() {
-        return mGetCredentialResponse;
-    }
-
-    /**
-     * Returns candidate provider data list.
-     *
-     * @hide
-     */
     public PendingIntent getPendingIntent() {
         return mPendingIntent;
     }
@@ -106,14 +93,12 @@
         AnnotationValidations.validate(NonNull.class, null, mCandidateProviderDataList);
 
         mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
-        mGetCredentialResponse = in.readTypedObject(GetCredentialResponse.CREATOR);
     }
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeTypedList(mCandidateProviderDataList);
         dest.writeTypedObject(mPendingIntent, flags);
-        dest.writeTypedObject(mGetCredentialResponse, flags);
     }
 
     @Override
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index 1ca11e6..ec46d2f 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -55,3 +55,10 @@
     description: "Enables Credential Manager to work with the Biometric Authenticate API"
     bug: "323211850"
 }
+
+flag {
+    namespace: "wear_frameworks"
+    name: "wear_credential_manager_enabled"
+    description: "Enables Credential Manager on Wear Platform"
+    bug: "301168341"
+}
diff --git a/core/java/android/credentials/selection/Constants.java b/core/java/android/credentials/selection/Constants.java
index 7e6c781..f7fec23 100644
--- a/core/java/android/credentials/selection/Constants.java
+++ b/core/java/android/credentials/selection/Constants.java
@@ -36,5 +36,11 @@
     public static final String EXTRA_REQ_FOR_ALL_OPTIONS =
             "android.credentials.selection.extra.REQ_FOR_ALL_OPTIONS";
 
+    /**
+     * The intent extra key for the final result receiver object
+     */
+    public static final String EXTRA_FINAL_RESPONSE_RECEIVER =
+            "android.credentials.selection.extra.FINAL_RESPONSE_RECEIVER";
+
     private Constants() {}
 }
diff --git a/core/java/android/credentials/selection/RequestInfo.java b/core/java/android/credentials/selection/RequestInfo.java
index 2fd322a..60bbae6 100644
--- a/core/java/android/credentials/selection/RequestInfo.java
+++ b/core/java/android/credentials/selection/RequestInfo.java
@@ -110,6 +110,8 @@
 
     private final boolean mHasPermissionToOverrideDefault;
 
+    private final boolean mIsShowAllOptionsRequested;
+
     /**
      * Creates new {@code RequestInfo} for a create-credential flow.
      *
@@ -121,10 +123,10 @@
     public static RequestInfo newCreateRequestInfo(
             @NonNull IBinder token, @NonNull CreateCredentialRequest createCredentialRequest,
             @NonNull String appPackageName, boolean hasPermissionToOverrideDefault,
-            @NonNull List<String> defaultProviderIds) {
+            @NonNull List<String> defaultProviderIds, boolean isShowAllOptionsRequested) {
         return new RequestInfo(
                 token, TYPE_CREATE, appPackageName, createCredentialRequest, null,
-                hasPermissionToOverrideDefault, defaultProviderIds);
+                hasPermissionToOverrideDefault, defaultProviderIds, isShowAllOptionsRequested);
     }
 
     /**
@@ -137,11 +139,12 @@
     @NonNull
     public static RequestInfo newGetRequestInfo(
             @NonNull IBinder token, @NonNull GetCredentialRequest getCredentialRequest,
-            @NonNull String appPackageName, boolean hasPermissionToOverrideDefault) {
+            @NonNull String appPackageName, boolean hasPermissionToOverrideDefault,
+            boolean isShowAllOptionsRequested) {
         return new RequestInfo(
                 token, TYPE_GET, appPackageName, null, getCredentialRequest,
                 hasPermissionToOverrideDefault,
-                /*defaultProviderIds=*/ new ArrayList<>());
+                /*defaultProviderIds=*/ new ArrayList<>(), isShowAllOptionsRequested);
     }
 
 
@@ -218,12 +221,31 @@
         return mGetCredentialRequest;
     }
 
+    /**
+     * Returns true if all options should be immediately displayed in the UI, and false otherwise.
+     *
+     * Normally this bit is set to false, upon which the selection UI should first display a
+     * condensed view of popular, deduplicated options that is determined based on signals like
+     * last-used timestamps, credential type priorities, and preferred providers configured from the
+     * user settings {@link #getDefaultProviderIds()}; at the same time, the UI should offer an
+     * option (button) that navigates the user to viewing all options from this condensed view.
+     *
+     * In some special occasions, e.g. when a request is initiated from the autofill drop-down
+     * suggestion, this bit will be set to true to indicate that the selection UI should immediately
+     * render the all option UI. This means that the request initiator has collected a user signal
+     * to confirm that the user wants to view all the available options at once.
+     */
+    public boolean isShowAllOptionsRequested() {
+        return mIsShowAllOptionsRequested;
+    }
+
     private RequestInfo(@NonNull IBinder token, @NonNull @RequestType String type,
             @NonNull String appPackageName,
             @Nullable CreateCredentialRequest createCredentialRequest,
             @Nullable GetCredentialRequest getCredentialRequest,
             boolean hasPermissionToOverrideDefault,
-            @NonNull List<String> defaultProviderIds) {
+            @NonNull List<String> defaultProviderIds,
+            boolean isShowAllOptionsRequested) {
         mToken = token;
         mType = type;
         mAppPackageName = appPackageName;
@@ -232,6 +254,7 @@
         mHasPermissionToOverrideDefault = hasPermissionToOverrideDefault;
         mDefaultProviderIds = defaultProviderIds == null ? new ArrayList<>() : defaultProviderIds;
         mRegistryProviderIds = new ArrayList<>();
+        mIsShowAllOptionsRequested = isShowAllOptionsRequested;
     }
 
     private RequestInfo(@NonNull Parcel in) {
@@ -254,6 +277,7 @@
         mHasPermissionToOverrideDefault = in.readBoolean();
         mDefaultProviderIds = in.createStringArrayList();
         mRegistryProviderIds = in.createStringArrayList();
+        mIsShowAllOptionsRequested = in.readBoolean();
     }
 
     @Override
@@ -266,6 +290,7 @@
         dest.writeBoolean(mHasPermissionToOverrideDefault);
         dest.writeStringList(mDefaultProviderIds);
         dest.writeStringList(mRegistryProviderIds);
+        dest.writeBoolean(mIsShowAllOptionsRequested);
     }
 
     @Override
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 54e34ec..62473c5 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -76,6 +76,12 @@
      */
     public static final int MAX_ACCESSIBILITY_SLOW_KEYS_THRESHOLD_MILLIS = 5000;
 
+    /**
+     * Default value for {@link Settings.Secure#STYLUS_POINTER_ICON_ENABLED}.
+     * @hide
+     */
+    public static final int DEFAULT_STYLUS_POINTER_ICON_ENABLED = 1;
+
     private InputSettings() {
     }
 
@@ -383,14 +389,19 @@
     }
 
     /**
-     * Whether a pointer icon will be shown over the location of a
-     * stylus pointer.
+     * Whether a pointer icon will be shown over the location of a stylus pointer.
+     *
      * @hide
      */
     public static boolean isStylusPointerIconEnabled(@NonNull Context context) {
+        if (InputProperties.force_enable_stylus_pointer_icon().orElse(false)) {
+            return true;
+        }
         return context.getResources()
-                       .getBoolean(com.android.internal.R.bool.config_enableStylusPointerIcon)
-               || InputProperties.force_enable_stylus_pointer_icon().orElse(false);
+                        .getBoolean(com.android.internal.R.bool.config_enableStylusPointerIcon)
+                && Settings.Secure.getIntForUser(context.getContentResolver(),
+                        Settings.Secure.STYLUS_POINTER_ICON_ENABLED,
+                        DEFAULT_STYLUS_POINTER_ICON_ENABLED, UserHandle.USER_CURRENT_OR_SELF) != 0;
     }
 
     /**
diff --git a/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java b/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
index d943c37..1cc910c 100644
--- a/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
+++ b/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
@@ -219,26 +219,22 @@
             if (!glyphData.hasBaseText()) {
                 return;
             }
-            if (glyphData.hasValidShiftText() && glyphData.hasValidAltGrText()) {
-                mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
-                        GRAVITY_BOTTOM | GRAVITY_LEFT, mBaseTextPaint));
+            boolean isCenter = !glyphData.hasValidAltGrText() && !glyphData.hasValidAltShiftText();
+            mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
+                    GRAVITY_BOTTOM | (isCenter ? GRAVITY_CENTER_HORIZONTAL : GRAVITY_LEFT),
+                    mBaseTextPaint));
+            if (glyphData.hasValidShiftText()) {
                 mGlyphDrawables.add(new GlyphDrawable(glyphData.getShiftText(), new RectF(),
-                        GRAVITY_TOP | GRAVITY_LEFT, mModifierTextPaint));
+                        GRAVITY_TOP | (isCenter ? GRAVITY_CENTER_HORIZONTAL : GRAVITY_LEFT),
+                        mModifierTextPaint));
+            }
+            if (glyphData.hasValidAltGrText()) {
                 mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrText(), new RectF(),
                         GRAVITY_BOTTOM | GRAVITY_RIGHT, mModifierTextPaint));
-            } else if (glyphData.hasValidShiftText()) {
-                mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
-                        GRAVITY_BOTTOM | GRAVITY_CENTER_HORIZONTAL, mBaseTextPaint));
-                mGlyphDrawables.add(new GlyphDrawable(glyphData.getShiftText(), new RectF(),
-                        GRAVITY_TOP | GRAVITY_CENTER_HORIZONTAL, mModifierTextPaint));
-            } else if (glyphData.hasValidAltGrText()) {
-                mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
-                        GRAVITY_BOTTOM | GRAVITY_LEFT, mBaseTextPaint));
-                mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrText(), new RectF(),
-                        GRAVITY_BOTTOM | GRAVITY_RIGHT, mModifierTextPaint));
-            } else {
-                mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
-                        GRAVITY_CENTER, mBaseTextPaint));
+            }
+            if (glyphData.hasValidAltShiftText()) {
+                mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrShiftText(), new RectF(),
+                        GRAVITY_TOP | GRAVITY_RIGHT, mModifierTextPaint));
             }
         }
 
diff --git a/core/java/android/hardware/input/PhysicalKeyLayout.java b/core/java/android/hardware/input/PhysicalKeyLayout.java
index 3454c39..844e02f 100644
--- a/core/java/android/hardware/input/PhysicalKeyLayout.java
+++ b/core/java/android/hardware/input/PhysicalKeyLayout.java
@@ -396,6 +396,7 @@
         private final String mBaseText;
         private final String mShiftText;
         private final String mAltGrText;
+        private final String mAltGrShiftText;
 
         public KeyGlyph(KeyCharacterMap kcm, int keyCode) {
             mBaseText = getKeyText(kcm, keyCode, KeyEvent.META_CAPS_LOCK_ON);
@@ -403,6 +404,9 @@
                     KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON);
             mAltGrText = getKeyText(kcm, keyCode,
                     KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON | KeyEvent.META_CAPS_LOCK_ON);
+            mAltGrShiftText = getKeyText(kcm, keyCode,
+                    KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON | KeyEvent.META_SHIFT_LEFT_ON
+                            | KeyEvent.META_SHIFT_ON);
         }
 
         public String getBaseText() {
@@ -417,6 +421,10 @@
             return mAltGrText;
         }
 
+        public String getAltGrShiftText() {
+            return mAltGrShiftText;
+        }
+
         public boolean hasBaseText() {
             return !TextUtils.isEmpty(mBaseText);
         }
@@ -428,5 +436,12 @@
         public boolean hasValidAltGrText() {
             return !TextUtils.isEmpty(mAltGrText) && !TextUtils.equals(mBaseText, mAltGrText);
         }
+
+        public boolean hasValidAltShiftText() {
+            return !TextUtils.isEmpty(mAltGrShiftText)
+                    && !TextUtils.equals(mBaseText, mAltGrShiftText)
+                    && !TextUtils.equals(mAltGrText, mAltGrShiftText)
+                    && !TextUtils.equals(mShiftText, mAltGrShiftText);
+        }
     }
 }
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 4c95e02..a968c6f 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -62,55 +62,55 @@
 @SystemApi
 public final class ProgramSelector implements Parcelable {
     /** Invalid program type.
-     * @deprecated use {@link ProgramIdentifier} instead
+     * @deprecated use {@link IdentifierType} instead
      */
     @Deprecated
     public static final int PROGRAM_TYPE_INVALID = 0;
     /** Analog AM radio (with or without RDS).
-     * @deprecated use {@link ProgramIdentifier} instead
+     * @deprecated use {@link IdentifierType} instead
      */
     @Deprecated
     public static final int PROGRAM_TYPE_AM = 1;
     /** analog FM radio (with or without RDS).
-     * @deprecated use {@link ProgramIdentifier} instead
+     * @deprecated use {@link IdentifierType} instead
      */
     @Deprecated
     public static final int PROGRAM_TYPE_FM = 2;
     /** AM HD Radio.
-     * @deprecated use {@link ProgramIdentifier} instead
+     * @deprecated use {@link Identifier} instead
      */
     @Deprecated
     public static final int PROGRAM_TYPE_AM_HD = 3;
     /** FM HD Radio.
-     * @deprecated use {@link ProgramIdentifier} instead
+     * @deprecated use {@link Identifier} instead
      */
     @Deprecated
     public static final int PROGRAM_TYPE_FM_HD = 4;
     /** Digital audio broadcasting.
-     * @deprecated use {@link ProgramIdentifier} instead
+     * @deprecated use {@link Identifier} instead
      */
     @Deprecated
     public static final int PROGRAM_TYPE_DAB = 5;
     /** Digital Radio Mondiale.
-     * @deprecated use {@link ProgramIdentifier} instead
+     * @deprecated use {@link Identifier} instead
      */
     @Deprecated
     public static final int PROGRAM_TYPE_DRMO = 6;
     /** SiriusXM Satellite Radio.
-     * @deprecated use {@link ProgramIdentifier} instead
+     * @deprecated use {@link Identifier} instead
      */
     @Deprecated
     public static final int PROGRAM_TYPE_SXM = 7;
     /** Vendor-specific, not synced across devices.
-     * @deprecated use {@link ProgramIdentifier} instead
+     * @deprecated use {@link Identifier} instead
      */
     @Deprecated
     public static final int PROGRAM_TYPE_VENDOR_START = 1000;
-    /** @deprecated use {@link ProgramIdentifier} instead */
+    /** @deprecated use {@link Identifier} instead */
     @Deprecated
     public static final int PROGRAM_TYPE_VENDOR_END = 1999;
     /**
-     * @deprecated use {@link ProgramIdentifier} instead
+     * @deprecated use {@link Identifier} instead
      * @removed mistakenly exposed previously
      */
     @Deprecated
@@ -271,7 +271,7 @@
      */
     public static final int IDENTIFIER_TYPE_HD_STATION_NAME = 10004;
     /**
-     * @see {@link IDENTIFIER_TYPE_DAB_SID_EXT}
+     * @see #IDENTIFIER_TYPE_DAB_SID_EXT
      *
      * @deprecated use {@link #IDENTIFIER_TYPE_DAB_DMB_SID_EXT} instead
      */
@@ -381,7 +381,7 @@
      */
     public static final int IDENTIFIER_TYPE_VENDOR_START = PROGRAM_TYPE_VENDOR_START;
     /**
-     * @see {@link IDENTIFIER_TYPE_VENDOR_START}
+     * @see #IDENTIFIER_TYPE_VENDOR_START
      */
     public static final int IDENTIFIER_TYPE_VENDOR_END = PROGRAM_TYPE_VENDOR_END;
     /**
@@ -771,7 +771,7 @@
          * Returns whether this Identifier's type is considered a category when filtering
          * ProgramLists for category entries.
          *
-         * @see {@link ProgramList.Filter#areCategoriesIncluded()}
+         * @see ProgramList.Filter#areCategoriesIncluded
          * @return False if this identifier's type is not tuneable (e.g. DAB ensemble or
          *         vendor-specified type). True otherwise.
          */
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 41f21ef..61cf8901 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -311,7 +311,7 @@
         }
 
         /** Unique module identifier provided by the native service.
-         * For use with {@link #openTuner(int, BandConfig, boolean, Callback, Handler)}.
+         * For use with {@link #openTuner(int, BandConfig, boolean, RadioTuner.Callback, Handler)}.
          * @return the radio module unique identifier.
          */
         public int getId() {
@@ -1561,7 +1561,7 @@
         /** Main channel expressed in units according to band type.
          * Currently all defined band types express channels as frequency in kHz
          * @return the program channel
-         * @deprecated Use {@link getSelector()} instead.
+         * @deprecated Use {@link ProgramInfo#getSelector} instead.
          */
         @Deprecated
         public int getChannel() {
@@ -1575,7 +1575,7 @@
 
         /** Sub channel ID. E.g 1 for HD radio HD1
          * @return the program sub channel
-         * @deprecated Use {@link getSelector()} instead.
+         * @deprecated Use {@link ProgramInfo#getSelector} instead.
          */
         @Deprecated
         public int getSubChannel() {
@@ -1604,7 +1604,7 @@
 
         /** {@code true} if the received program is digital (e.g HD radio)
          * @return {@code true} if digital, {@code false} otherwise.
-         * @deprecated Use {@link getLogicallyTunedTo()} instead.
+         * @deprecated Use {@link ProgramInfo#getLogicallyTunedTo()} instead.
          */
         @Deprecated
         public boolean isDigital() {
@@ -1913,7 +1913,8 @@
      * Removes previously registered announcement listener.
      *
      * @param listener announcement listener, previously registered with
-     *        {@link addAnnouncementListener}
+     *        {@link #addAnnouncementListener(Executor, Set, Announcement.OnListUpdatedListener)}
+     *        or {@link #addAnnouncementListener(Set, Announcement.OnListUpdatedListener)}
      */
     @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public void removeAnnouncementListener(@NonNull Announcement.OnListUpdatedListener listener) {
diff --git a/core/java/android/hardware/radio/RadioMetadata.java b/core/java/android/hardware/radio/RadioMetadata.java
index da6b9c2..67381ec 100644
--- a/core/java/android/hardware/radio/RadioMetadata.java
+++ b/core/java/android/hardware/radio/RadioMetadata.java
@@ -593,7 +593,7 @@
      * Helper for getting the String key used by {@link RadioMetadata} from the
      * corrsponding native integer key.
      *
-     * @param editorKey The key used by the editor
+     * @param nativeKey The key used by the editor
      * @return the key used by this class or null if no mapping exists
      * @hide
      */
@@ -743,11 +743,11 @@
          * Put a {@link RadioMetadata.Clock} into the meta data. Custom keys may be used, but if the
          * METADATA_KEYs defined in this class are used they may only be one of the following:
          * <ul>
-         * <li>{@link #MEADATA_KEY_CLOCK}</li>
+         * <li>{@link #METADATA_KEY_CLOCK}</li>
          * </ul>
          *
          * @param utcSecondsSinceEpoch Number of seconds since epoch for UTC + 0 timezone.
-         * @param timezoneOffsetInMinutes Offset of timezone from UTC + 0 in minutes.
+         * @param timezoneOffsetMinutes Offset of timezone from UTC + 0 in minutes.
          * @return the same Builder instance.
          */
         public Builder putClock(String key, long utcSecondsSinceEpoch, int timezoneOffsetMinutes) {
diff --git a/core/java/android/os/ArtModuleServiceManager.java b/core/java/android/os/ArtModuleServiceManager.java
index 0009e61..e0b631d 100644
--- a/core/java/android/os/ArtModuleServiceManager.java
+++ b/core/java/android/os/ArtModuleServiceManager.java
@@ -15,9 +15,11 @@
  */
 package android.os;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.content.pm.Flags;
 
 /**
  * Provides a way to register and obtain the system service binder objects managed by the ART
@@ -60,4 +62,18 @@
     public ServiceRegisterer getArtdServiceRegisterer() {
         return new ServiceRegisterer("artd");
     }
+
+    /** Returns {@link ServiceRegisterer} for the "artd_pre_reboot" service. */
+    @NonNull
+    @FlaggedApi(Flags.FLAG_USE_ART_SERVICE_V2)
+    public ServiceRegisterer getArtdPreRebootServiceRegisterer() {
+        return new ServiceRegisterer("artd_pre_reboot");
+    }
+
+    /** Returns {@link ServiceRegisterer} for the "dexopt_chroot_setup" service. */
+    @NonNull
+    @FlaggedApi(Flags.FLAG_USE_ART_SERVICE_V2)
+    public ServiceRegisterer getDexoptChrootSetupServiceRegisterer() {
+        return new ServiceRegisterer("dexopt_chroot_setup");
+    }
 }
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index b9bb059..f3efd89 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -238,6 +238,16 @@
     public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE =
                                             OsProtoEnums.CHARGING_POLICY_ADAPTIVE_LONGLIFE; // = 4
 
+    /**
+     * Returns true if the policy is some type of adaptive charging policy.
+     * @hide
+     */
+    public static boolean isAdaptiveChargingPolicy(int policy) {
+        return policy == CHARGING_POLICY_ADAPTIVE_AC
+                || policy == CHARGING_POLICY_ADAPTIVE_AON
+                || policy == CHARGING_POLICY_ADAPTIVE_LONGLIFE;
+    }
+
     // values for "battery part status" property
     /**
      * Battery part status is not supported.
diff --git a/core/java/android/os/BatteryManagerInternal.java b/core/java/android/os/BatteryManagerInternal.java
index 9bad0de..0ec8729 100644
--- a/core/java/android/os/BatteryManagerInternal.java
+++ b/core/java/android/os/BatteryManagerInternal.java
@@ -16,6 +16,8 @@
 
 package android.os;
 
+import android.annotation.NonNull;
+
 /**
  * Battery manager local system service interface.
  *
@@ -84,6 +86,26 @@
      */
     public abstract boolean getBatteryLevelLow();
 
+    public interface ChargingPolicyChangeListener {
+        void onChargingPolicyChanged(int newPolicy);
+    }
+
+    /**
+     * Register a listener for changes to {@link BatteryManager#BATTERY_PROPERTY_CHARGING_POLICY}.
+     * The charging policy can't be added to the BATTERY_CHANGED intent because it requires
+     * the BATTERY_STATS permission.
+     */
+    public abstract void registerChargingPolicyChangeListener(
+            @NonNull ChargingPolicyChangeListener chargingPolicyChangeListener);
+
+    /**
+     * Returns the value of {@link BatteryManager#BATTERY_PROPERTY_CHARGING_POLICY}.
+     * This will return {@link Integer#MIN_VALUE} if the device does not support the property.
+     *
+     * @see BatteryManager#getIntProperty(int)
+     */
+    public abstract int getChargingPolicy();
+
     /**
      * Returns a non-zero value if an unsupported charger is attached.
      *
diff --git a/core/java/android/os/ConfigUpdate.java b/core/java/android/os/ConfigUpdate.java
index 4908919..87cd4ee 100644
--- a/core/java/android/os/ConfigUpdate.java
+++ b/core/java/android/os/ConfigUpdate.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.FlaggedApi;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
@@ -131,6 +132,23 @@
             "android.os.action.UPDATE_EMERGENCY_NUMBER_DB";
 
     /**
+     * Broadcast intent action indicating that the updated config data is available.
+     * This broadcast intent action is to be sent by the config updater app, and will be received
+     * and handled by the platform.
+     * <p>Extra: {@link #EXTRA_VERSION} the numeric version of the database.
+     * <p>Extra: {@link #EXTRA_REQUIRED_HASH} hash of the database, which is encoded by base-16
+     * SHA512
+     * <p>Extra: {@link #EXTRA_DOMAIN} the string identifying the affected module
+     * <p>Input: {@link android.content.Intent#getData} the URI to download config data file
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_UPDATE_CONFIG = "android.os.action.UPDATE_CONFIG";
+
+    /**
      * An integer to indicate the numeric version of the new data. Devices should only install
      * if the update version is newer than the current one.
      *
@@ -147,6 +165,16 @@
     @SystemApi
     public static final String EXTRA_REQUIRED_HASH = "android.os.extra.REQUIRED_HASH";
 
+    /**
+     * String identifying the affected module.
+     * Devices apply the updated config data to the module specified in the string.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final String EXTRA_DOMAIN  = "android.os.extra.DOMAIN";
+
     private ConfigUpdate() {
     }
 }
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 08b32bf..c9c91fc 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -33,7 +33,6 @@
 import android.os.vibrator.RampSegment;
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationEffectSegment;
-import android.util.Log;
 import android.util.MathUtils;
 
 import com.android.internal.util.Preconditions;
@@ -54,7 +53,6 @@
  * <p>These effects may be any number of things, from single shot vibrations to complex waveforms.
  */
 public abstract class VibrationEffect implements Parcelable {
-    private static final String TAG = "VibrationEffect";
     // Stevens' coefficient to scale the perceived vibration intensity.
     private static final float SCALE_GAMMA = 0.65f;
     // If a vibration is playing for longer than 1s, it's probably not haptic feedback
@@ -397,32 +395,26 @@
             return null;
         }
 
-        try {
-            final ContentResolver cr = context.getContentResolver();
-            Uri uncanonicalUri = cr.uncanonicalize(uri);
-            if (uncanonicalUri == null) {
-                // If we already had an uncanonical URI, it's possible we'll get null back here. In
-                // this case, just use the URI as passed in since it wasn't canonicalized in the
-                // first place.
-                uncanonicalUri = uri;
-            }
+        final ContentResolver cr = context.getContentResolver();
+        Uri uncanonicalUri = cr.uncanonicalize(uri);
+        if (uncanonicalUri == null) {
+            // If we already had an uncanonical URI, it's possible we'll get null back here. In
+            // this case, just use the URI as passed in since it wasn't canonicalized in the first
+            // place.
+            uncanonicalUri = uri;
+        }
 
-            for (int i = 0; i < uris.length && i < RINGTONES.length; i++) {
-                if (uris[i] == null) {
-                    continue;
-                }
-                Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i]));
-                if (mappedUri == null) {
-                    continue;
-                }
-                if (mappedUri.equals(uncanonicalUri)) {
-                    return get(RINGTONES[i]);
-                }
+        for (int i = 0; i < uris.length && i < RINGTONES.length; i++) {
+            if (uris[i] == null) {
+                continue;
             }
-        } catch (Exception e) {
-            // Don't give unexpected exceptions to callers if the Uri's ContentProvider is
-            // misbehaving - it's very unlikely to be mapped in that case anyway.
-            Log.e(TAG, "Exception getting default vibration for Uri " + uri, e);
+            Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i]));
+            if (mappedUri == null) {
+                continue;
+            }
+            if (mappedUri.equals(uncanonicalUri)) {
+                return get(RINGTONES[i]);
+            }
         }
         return null;
     }
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 82518bf..abfa4e3 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -114,3 +114,19 @@
     is_fixed_read_only: true
     bug: "309792384"
 }
+
+flag {
+    name: "storage_lifetime_api"
+    namespace: "phoenix"
+    description: "Feature flag for adding storage component health APIs."
+    is_fixed_read_only: true
+    bug: "309792384"
+}
+
+flag {
+     namespace: "system_performance"
+     name: "telemetry_apis_framework_initialization"
+     description: "Control framework initialization APIs of telemetry APIs feature."
+     is_fixed_read_only: true
+     bug: "324241334"
+}
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 54ed73c..1ab48a22 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -175,4 +175,12 @@
     void setCloudMediaProvider(in String authority) = 96;
     String getCloudMediaProvider() = 97;
     long getInternalStorageBlockDeviceSize() = 98;
-}
\ No newline at end of file
+    /**
+     * Returns the remaining lifetime of the internal storage device, as an
+     * integer percentage. For example, 90 indicates that 90% of the storage
+     * 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/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 3a57e84..5a09541 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -28,6 +28,7 @@
 
 import android.annotation.BytesLong;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -58,6 +59,7 @@
 import android.os.Build;
 import android.os.Environment;
 import android.os.FileUtils;
+import android.os.Flags;
 import android.os.Handler;
 import android.os.IInstalld;
 import android.os.IVold;
@@ -2939,4 +2941,24 @@
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static final int CRYPT_TYPE_DEFAULT = 1;
+
+    /**
+     * Returns the remaining lifetime of the internal storage device, as an integer percentage. For
+     * example, 90 indicates that 90% of the storage device's useful lifetime remains. If no
+     * information is available, -1 is returned.
+     *
+     * @return Percentage of the remaining useful lifetime of the internal storage device.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_STORAGE_LIFETIME_API)
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public int getInternalStorageRemainingLifetime() {
+        try {
+            return mStorageManager.getInternalStorageRemainingLifetime();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 437668c..ea9375e 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -16,13 +16,6 @@
 
 flag {
     namespace: "haptics"
-    name: "haptics_customization_ringtone_v2_enabled"
-    description: "Enables the usage of the new RingtoneV2 class"
-    bug: "241918098"
-}
-
-flag {
-    namespace: "haptics"
     name: "enable_vibration_serialization_apis"
     description: "Enables the APIs for vibration serialization/deserialization."
     bug: "245129509"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ef2d5eb..ce7a026 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12312,6 +12312,16 @@
         public static final String PRIVATE_SPACE_AUTO_LOCK = "private_space_auto_lock";
 
         /**
+         * Toggle for enabling stylus pointer icon. Pointer icons for styluses will only be be shown
+         * when this is enabled. Enabling this alone won't enable the stylus pointer;
+         * config_enableStylusPointerIcon needs to be true as well.
+         *
+         * @hide
+         */
+        @Readable
+        public static final String STYLUS_POINTER_ICON_ENABLED = "stylus_pointer_icon_enabled";
+
+        /**
          * These entries are considered common between the personal and the managed profile,
          * since the managed profile doesn't get to change them.
          */
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 2841dc0..658cec8 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4943,6 +4943,13 @@
         public static final String COLUMN_IS_NTN = "is_ntn";
 
         /**
+         * TelephonyProvider column name for transferred status
+         *
+         * @hide
+         */
+        public static final String COLUMN_TRANSFER_STATUS = "transfer_status";
+
+        /**
          * TelephonyProvider column name to indicate the service capability bitmasks.
          *
          * @hide
@@ -5021,7 +5028,8 @@
                 COLUMN_SATELLITE_ENABLED,
                 COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
                 COLUMN_IS_NTN,
-                COLUMN_SERVICE_CAPABILITIES
+                COLUMN_SERVICE_CAPABILITIES,
+                COLUMN_TRANSFER_STATUS
         );
 
         /**
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 298bdb8..e6a84df 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -600,6 +600,14 @@
      */
     public static final String EXTRA_ERROR = "error";
 
+    /**
+     * Name of the key used to mark whether the fill response is for a webview.
+     *
+     * @hide
+     */
+    public static final String WEBVIEW_REQUESTED_CREDENTIAL_KEY = "webview_requested_credential";
+
+
     private final IAutoFillService mInterface = new IAutoFillService.Stub() {
         @Override
         public void onConnectedStateChanged(boolean connected) {
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index 1afe8d9..da8817a 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -26,8 +26,8 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.content.ClipData;
+import android.content.Intent;
 import android.content.IntentSender;
-import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArrayMap;
@@ -190,7 +190,7 @@
     @Nullable private final InlinePresentation mInlineTooltipPresentation;
     private final IntentSender mAuthentication;
 
-    @Nullable private final Bundle mAuthenticationExtras;
+    @Nullable private Intent mCredentialFillInIntent;
 
     @Nullable String mId;
 
@@ -229,7 +229,7 @@
         mInlinePresentation = inlinePresentation;
         mInlineTooltipPresentation = inlineTooltipPresentation;
         mAuthentication = authentication;
-        mAuthenticationExtras = null;
+        mCredentialFillInIntent = null;
         mId = id;
     }
 
@@ -252,7 +252,7 @@
         mInlinePresentation = dataset.mInlinePresentation;
         mInlineTooltipPresentation = dataset.mInlineTooltipPresentation;
         mAuthentication = dataset.mAuthentication;
-        mAuthenticationExtras = dataset.mAuthenticationExtras;
+        mCredentialFillInIntent = dataset.mCredentialFillInIntent;
         mId = dataset.mId;
         mAutofillDatatypes = dataset.mAutofillDatatypes;
     }
@@ -271,7 +271,7 @@
         mInlinePresentation = builder.mInlinePresentation;
         mInlineTooltipPresentation = builder.mInlineTooltipPresentation;
         mAuthentication = builder.mAuthentication;
-        mAuthenticationExtras = builder.mAuthenticationExtras;
+        mCredentialFillInIntent = builder.mCredentialFillInIntent;
         mId = builder.mId;
         mAutofillDatatypes = builder.mAutofillDatatypes;
     }
@@ -354,8 +354,14 @@
 
     /** @hide */
     @Hide
-    public @Nullable Bundle getAuthenticationExtras() {
-        return mAuthenticationExtras;
+    public @Nullable Intent getCredentialFillInIntent() {
+        return mCredentialFillInIntent;
+    }
+
+    /** @hide */
+    @Hide
+    public void setCredentialFillInIntent(Intent intent) {
+        mCredentialFillInIntent = intent;
     }
 
     /** @hide */
@@ -415,7 +421,7 @@
         if (mAuthentication != null) {
             builder.append(", hasAuthentication");
         }
-        if (mAuthenticationExtras != null) {
+        if (mCredentialFillInIntent != null) {
             builder.append(", hasAuthenticationExtras");
         }
         if (mAutofillDatatypes != null) {
@@ -472,7 +478,7 @@
         @Nullable private InlinePresentation mInlineTooltipPresentation;
         private IntentSender mAuthentication;
 
-        private Bundle mAuthenticationExtras;
+        private Intent mCredentialFillInIntent;
         private boolean mDestroyed;
         @Nullable private String mId;
 
@@ -655,9 +661,9 @@
          * @hide
          */
         @Hide
-        public @NonNull Builder setAuthenticationExtras(@Nullable Bundle authenticationExtra) {
+        public @NonNull Builder setCredentialFillInIntent(@Nullable Intent credentialFillInIntent) {
             throwIfDestroyed();
-            mAuthenticationExtras = authenticationExtra;
+            mCredentialFillInIntent = credentialFillInIntent;
             return this;
         }
 
@@ -1401,7 +1407,7 @@
         parcel.writeParcelable(mAuthentication, flags);
         parcel.writeString(mId);
         parcel.writeInt(mEligibleReason);
-        parcel.writeTypedObject(mAuthenticationExtras, flags);
+        parcel.writeTypedObject(mCredentialFillInIntent, flags);
     }
 
     public static final @NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() {
@@ -1437,7 +1443,7 @@
                     android.content.IntentSender.class);
             final String datasetId = parcel.readString();
             final int eligibleReason = parcel.readInt();
-            final Bundle authenticationExtras = parcel.readTypedObject(Bundle.CREATOR);
+            final Intent credentialFillInIntent = parcel.readTypedObject(Intent.CREATOR);
 
             // Always go through the builder to ensure the data ingested by
             // the system obeys the contract of the builder to avoid attacks
@@ -1482,7 +1488,7 @@
                         fieldDialogPresentation);
             }
             builder.setAuthentication(authentication);
-            builder.setAuthenticationExtras(authenticationExtras);
+            builder.setCredentialFillInIntent(credentialFillInIntent);
             builder.setId(datasetId);
             Dataset dataset = builder.build();
             dataset.mEligibleReason = eligibleReason;
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 09ec933..c43ba6c 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -56,6 +56,7 @@
  * <p>See the main {@link AutofillService} documentation for more details and examples.
  */
 public final class FillResponse implements Parcelable {
+    // common_typos_disable
 
     /**
      * Flag used to generate {@link FillEventHistory.Event events} of type
@@ -82,11 +83,17 @@
      */
     public static final int FLAG_DELAY_FILL = 0x4;
 
+    /**
+     * @hide
+     */
+    public static final int FLAG_CREDENTIAL_MANAGER_RESPONSE = 0x8;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "FLAG_" }, value = {
             FLAG_TRACK_CONTEXT_COMMITED,
             FLAG_DISABLE_ACTIVITY_ONLY,
-            FLAG_DELAY_FILL
+            FLAG_DELAY_FILL,
+            FLAG_CREDENTIAL_MANAGER_RESPONSE
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface FillResponseFlags {}
@@ -834,7 +841,9 @@
         public Builder setFlags(@FillResponseFlags int flags) {
             throwIfDestroyed();
             mFlags = Preconditions.checkFlagsArgument(flags,
-                    FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY | FLAG_DELAY_FILL);
+                    FLAG_TRACK_CONTEXT_COMMITED
+                            | FLAG_DISABLE_ACTIVITY_ONLY | FLAG_DELAY_FILL
+                            | FLAG_CREDENTIAL_MANAGER_RESPONSE);
             return this;
         }
 
diff --git a/core/java/android/service/wearable/IWearableSensingService.aidl b/core/java/android/service/wearable/IWearableSensingService.aidl
index 44a13c4..8fdb2c2 100644
--- a/core/java/android/service/wearable/IWearableSensingService.aidl
+++ b/core/java/android/service/wearable/IWearableSensingService.aidl
@@ -28,6 +28,7 @@
  * @hide
  */
 oneway interface IWearableSensingService {
+    void provideSecureWearableConnection(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
     void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
     void provideData(in PersistableBundle data, in SharedMemory sharedMemory, in RemoteCallback callback);
     void startDetection(in AmbientContextEventRequest request, in String packageName,
diff --git a/core/java/android/service/wearable/WearableSensingService.java b/core/java/android/service/wearable/WearableSensingService.java
index e7e44a5..d21115b 100644
--- a/core/java/android/service/wearable/WearableSensingService.java
+++ b/core/java/android/service/wearable/WearableSensingService.java
@@ -17,12 +17,14 @@
 package android.service.wearable;
 
 import android.annotation.BinderThread;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.app.Service;
 import android.app.ambientcontext.AmbientContextEvent;
 import android.app.ambientcontext.AmbientContextEventRequest;
+import android.app.wearable.Flags;
 import android.app.wearable.WearableSensingManager;
 import android.content.Intent;
 import android.os.Bundle;
@@ -39,6 +41,7 @@
 import java.util.HashSet;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /**
@@ -94,17 +97,20 @@
             return new IWearableSensingService.Stub() {
                 /** {@inheritDoc} */
                 @Override
+                public void provideSecureWearableConnection(
+                        ParcelFileDescriptor secureWearableConnection, RemoteCallback callback) {
+                    Objects.requireNonNull(secureWearableConnection);
+                    Consumer<Integer> consumer = createWearableStatusConsumer(callback);
+                    WearableSensingService.this.onSecureWearableConnectionProvided(
+                            secureWearableConnection, consumer);
+                }
+
+                /** {@inheritDoc} */
+                @Override
                 public void provideDataStream(
-                        ParcelFileDescriptor parcelFileDescriptor,
-                        RemoteCallback callback) {
+                        ParcelFileDescriptor parcelFileDescriptor, RemoteCallback callback) {
                     Objects.requireNonNull(parcelFileDescriptor);
-                    Consumer<Integer> consumer = response -> {
-                        Bundle bundle = new Bundle();
-                        bundle.putInt(
-                                STATUS_RESPONSE_BUNDLE_KEY,
-                                response);
-                        callback.sendResult(bundle);
-                    };
+                    Consumer<Integer> consumer = createWearableStatusConsumer(callback);
                     WearableSensingService.this.onDataStreamProvided(
                             parcelFileDescriptor, consumer);
                 }
@@ -116,38 +122,38 @@
                         SharedMemory sharedMemory,
                         RemoteCallback callback) {
                     Objects.requireNonNull(data);
-                    Consumer<Integer> consumer = response -> {
-                        Bundle bundle = new Bundle();
-                        bundle.putInt(
-                                STATUS_RESPONSE_BUNDLE_KEY,
-                                response);
-                        callback.sendResult(bundle);
-                    };
+                    Consumer<Integer> consumer = createWearableStatusConsumer(callback);
                     WearableSensingService.this.onDataProvided(data, sharedMemory, consumer);
                 }
 
                 /** {@inheritDoc} */
                 @Override
-                public void startDetection(@NonNull AmbientContextEventRequest request,
-                        String packageName, RemoteCallback detectionResultCallback,
+                public void startDetection(
+                        @NonNull AmbientContextEventRequest request,
+                        String packageName,
+                        RemoteCallback detectionResultCallback,
                         RemoteCallback statusCallback) {
                     Objects.requireNonNull(request);
                     Objects.requireNonNull(packageName);
                     Objects.requireNonNull(detectionResultCallback);
                     Objects.requireNonNull(statusCallback);
-                    Consumer<AmbientContextDetectionResult> detectionResultConsumer = result -> {
-                        Bundle bundle = new Bundle();
-                        bundle.putParcelable(
-                                AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY, result);
-                        detectionResultCallback.sendResult(bundle);
-                    };
-                    Consumer<AmbientContextDetectionServiceStatus> statusConsumer = status -> {
-                        Bundle bundle = new Bundle();
-                        bundle.putParcelable(
-                                AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY,
-                                status);
-                        statusCallback.sendResult(bundle);
-                    };
+                    Consumer<AmbientContextDetectionResult> detectionResultConsumer =
+                            result -> {
+                                Bundle bundle = new Bundle();
+                                bundle.putParcelable(
+                                        AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY,
+                                        result);
+                                detectionResultCallback.sendResult(bundle);
+                            };
+                    Consumer<AmbientContextDetectionServiceStatus> statusConsumer =
+                            status -> {
+                                Bundle bundle = new Bundle();
+                                bundle.putParcelable(
+                                        AmbientContextDetectionServiceStatus
+                                                .STATUS_RESPONSE_BUNDLE_KEY,
+                                        status);
+                                statusCallback.sendResult(bundle);
+                            };
                     WearableSensingService.this.onStartDetection(
                             request, packageName, statusConsumer, detectionResultConsumer);
                     Slog.d(TAG, "startDetection " + request);
@@ -162,23 +168,26 @@
 
                 /** {@inheritDoc} */
                 @Override
-                public void queryServiceStatus(@AmbientContextEvent.EventCode int[] eventTypes,
-                        String packageName, RemoteCallback callback) {
+                public void queryServiceStatus(
+                        @AmbientContextEvent.EventCode int[] eventTypes,
+                        String packageName,
+                        RemoteCallback callback) {
                     Objects.requireNonNull(eventTypes);
                     Objects.requireNonNull(packageName);
                     Objects.requireNonNull(callback);
-                    Consumer<AmbientContextDetectionServiceStatus> consumer = response -> {
-                        Bundle bundle = new Bundle();
-                        bundle.putParcelable(
-                                AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY,
-                                response);
-                        callback.sendResult(bundle);
-                    };
+                    Consumer<AmbientContextDetectionServiceStatus> consumer =
+                            response -> {
+                                Bundle bundle = new Bundle();
+                                bundle.putParcelable(
+                                        AmbientContextDetectionServiceStatus
+                                                .STATUS_RESPONSE_BUNDLE_KEY,
+                                        response);
+                                callback.sendResult(bundle);
+                            };
                     Integer[] events = intArrayToIntegerArray(eventTypes);
                     WearableSensingService.this.onQueryServiceStatus(
                             new HashSet<>(Arrays.asList(events)), packageName, consumer);
                 }
-
             };
         }
         Slog.w(TAG, "Incorrect service interface, returning null.");
@@ -186,6 +195,30 @@
     }
 
     /**
+     * Called when a secure connection to the wearable is available. See {@link
+     * WearableSensingManager#provideWearableConnection(ParcelFileDescriptor, Executor, Consumer)}
+     * for details about the secure connection.
+     *
+     * <p>When the {@code secureWearableConnection} is closed, the system will send a {@link
+     * WearableSensingManager#STATUS_CHANNEL_ERROR} status code to the status consumer provided by
+     * the caller of {@link WearableSensingManager#provideWearableConnection(ParcelFileDescriptor,
+     * Executor, Consumer)}.
+     *
+     * <p>The implementing class should override this method. It should return an appropriate status
+     * code via {@code statusConsumer} after receiving the {@code secureWearableConnection}.
+     *
+     * @param secureWearableConnection The secure connection to the wearable.
+     * @param statusConsumer The consumer for the service status.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API)
+    @BinderThread
+    public void onSecureWearableConnectionProvided(
+            @NonNull ParcelFileDescriptor secureWearableConnection,
+            @NonNull Consumer<Integer> statusConsumer) {
+        statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION);
+    }
+
+    /**
      * Called when a data stream to the wearable is provided. This data stream can be used to obtain
      * data from a wearable device. It is up to the implementation to maintain the data stream and
      * close the data stream when it is finished.
@@ -275,4 +308,13 @@
         }
         return intArray;
     }
+
+    @NonNull
+    private static Consumer<Integer> createWearableStatusConsumer(RemoteCallback statusCallback) {
+        return response -> {
+            Bundle bundle = new Bundle();
+            bundle.putInt(STATUS_RESPONSE_BUNDLE_KEY, response);
+            statusCallback.sendResult(bundle);
+        };
+    }
 }
diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java
index 0421d5a..32f05be 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/core/java/android/text/ClientFlags.java
@@ -54,4 +54,11 @@
     public static boolean fixLineHeightForLocale() {
         return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE);
     }
+
+    /**
+     * @see Flags#icuBidiMigration()
+     */
+    public static boolean icuBidiMigration() {
+        return TextFlags.isFeatureEnabled(Flags.FLAG_ICU_BIDI_MIGRATION);
+    }
 }
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 1ea80f1..8ddb42d 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -18,7 +18,7 @@
 
 import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
 import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
-import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
+import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION;
 
 import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
@@ -172,7 +172,7 @@
     /**
      * Value for justification mode indicating the text is justified by stretching letter spacing.
      */
-    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
     public static final int JUSTIFICATION_MODE_INTER_CHARACTER =
             LineBreaker.JUSTIFICATION_MODE_INTER_CHARACTER;
 
@@ -1831,7 +1831,7 @@
      * @return the number of cluster count in the line.
      */
     @IntRange(from = 0)
-    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
     public int getLineLetterSpacingUnitCount(@IntRange(from = 0) int line,
             boolean includeTrailingWhitespace) {
         final int start = getLineStart(line);
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index b268c2e..09f15c3 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -30,6 +30,7 @@
 import android.graphics.Rect;
 import android.graphics.text.LineBreakConfig;
 import android.graphics.text.MeasuredText;
+import android.icu.text.Bidi;
 import android.text.AutoGrowArray.ByteArray;
 import android.text.AutoGrowArray.FloatArray;
 import android.text.AutoGrowArray.IntArray;
@@ -115,6 +116,11 @@
     // This is empty if mLtrWithoutBidi is true.
     private @NonNull ByteArray mLevels = new ByteArray();
 
+    // The bidi level for runs.
+    private @NonNull ByteArray mRunLevels = new ByteArray();
+
+    private Bidi mBidi;
+
     // The whole width of the text.
     // See getWholeWidth comments.
     private @FloatRange(from = 0.0f) float mWholeWidth;
@@ -148,6 +154,7 @@
         reset();
         mLevels.clearWithReleasingLargeArray();
         mWidths.clearWithReleasingLargeArray();
+        mRunLevels.clearWithReleasingLargeArray();
         mFontMetrics.clearWithReleasingLargeArray();
         mSpanEndCache.clearWithReleasingLargeArray();
     }
@@ -160,10 +167,12 @@
         mCopiedBuffer = null;
         mWholeWidth = 0;
         mLevels.clear();
+        mRunLevels.clear();
         mWidths.clear();
         mFontMetrics.clear();
         mSpanEndCache.clear();
         mMeasuredText = null;
+        mBidi = null;
     }
 
     /**
@@ -193,6 +202,13 @@
      * @hide
      */
     public @Layout.Direction int getParagraphDir() {
+        if (ClientFlags.icuBidiMigration()) {
+            if (mBidi == null) {
+                return Layout.DIR_LEFT_TO_RIGHT;
+            }
+            return (mBidi.getParaLevel() & 0x01) == 0
+                    ? Layout.DIR_LEFT_TO_RIGHT : Layout.DIR_RIGHT_TO_LEFT;
+        }
         return mParaDir;
     }
 
@@ -204,6 +220,62 @@
      */
     public Directions getDirections(@IntRange(from = 0) int start,  // inclusive
                                     @IntRange(from = 0) int end) {  // exclusive
+        if (ClientFlags.icuBidiMigration()) {
+            // Easy case: mBidi == null means the text is all LTR and no bidi suppot is needed.
+            if (mBidi == null) {
+                return Layout.DIRS_ALL_LEFT_TO_RIGHT;
+            }
+
+            // Easy case: If the original text only contains single directionality run, the
+            // substring is only single run.
+            if (start == end) {
+                if ((mBidi.getParaLevel() & 0x01) == 0) {
+                    return Layout.DIRS_ALL_LEFT_TO_RIGHT;
+                } else {
+                    return Layout.DIRS_ALL_RIGHT_TO_LEFT;
+                }
+            }
+
+            // Okay, now we need to generate the line instance.
+            Bidi bidi = mBidi.createLineBidi(start, end);
+
+            // Easy case: If the line instance only contains single directionality run, no need
+            // to reorder visually.
+            if (bidi.getRunCount() == 1) {
+                if ((bidi.getParaLevel() & 0x01) == 1) {
+                    return Layout.DIRS_ALL_RIGHT_TO_LEFT;
+                } else {
+                    return Layout.DIRS_ALL_LEFT_TO_RIGHT;
+                }
+            }
+
+            // Reorder directionality run visually.
+            mRunLevels.resize(bidi.getRunCount());
+            byte[] levels = mRunLevels.getRawArray();
+            for (int i = 0; i < bidi.getRunCount(); ++i) {
+                levels[i] = (byte) bidi.getRunLevel(i);
+            }
+            int[] visualOrders = Bidi.reorderVisual(levels);
+
+            int[] dirs = new int[bidi.getRunCount() * 2];
+            for (int i = 0; i < bidi.getRunCount(); ++i) {
+                int vIndex;
+                if ((mBidi.getBaseLevel() & 0x01) == 1) {
+                    // For the historical reasons, if the base directionality is RTL, the Android
+                    // draws from the right, i.e. the visually reordered run needs to be reversed.
+                    vIndex = visualOrders[bidi.getRunCount() - i - 1];
+                } else {
+                    vIndex = visualOrders[i];
+                }
+
+                // Special packing of dire
+                dirs[i * 2] = bidi.getRunStart(vIndex);
+                dirs[i * 2 + 1] = bidi.getRunLevel(vIndex) << Layout.RUN_LEVEL_SHIFT
+                        | (bidi.getRunLimit(vIndex) - dirs[i * 2]);
+            }
+
+            return new Directions(dirs);
+        }
         if (mLtrWithoutBidi) {
             return Layout.DIRS_ALL_LEFT_TO_RIGHT;
         }
@@ -608,6 +680,37 @@
             }
         }
 
+        if (ClientFlags.icuBidiMigration()) {
+            if ((textDir == TextDirectionHeuristics.LTR
+                    || textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR
+                    || textDir == TextDirectionHeuristics.ANYRTL_LTR)
+                    && TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) {
+                mLevels.clear();
+                mLtrWithoutBidi = true;
+                return;
+            }
+            final int bidiRequest;
+            if (textDir == TextDirectionHeuristics.LTR) {
+                bidiRequest = Bidi.LTR;
+            } else if (textDir == TextDirectionHeuristics.RTL) {
+                bidiRequest = Bidi.RTL;
+            } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) {
+                bidiRequest = Bidi.LEVEL_DEFAULT_LTR;
+            } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) {
+                bidiRequest = Bidi.LEVEL_DEFAULT_RTL;
+            } else {
+                final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength);
+                bidiRequest = isRtl ? Bidi.RTL : Bidi.LTR;
+            }
+            mBidi = new Bidi(mCopiedBuffer, 0, null, 0, mCopiedBuffer.length, bidiRequest);
+            mLevels.resize(mTextLength);
+            byte[] rawArray = mLevels.getRawArray();
+            for (int i = 0; i < mTextLength; ++i) {
+                rawArray[i] = mBidi.getLevelAt(i);
+            }
+            mLtrWithoutBidi = false;
+            return;
+        }
         if ((textDir == TextDirectionHeuristics.LTR
                 || textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR
                 || textDir == TextDirectionHeuristics.ANYRTL_LTR)
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
index 2466386..770e5c9 100644
--- a/core/java/android/text/TextFlags.java
+++ b/core/java/android/text/TextFlags.java
@@ -59,6 +59,7 @@
             Flags.FLAG_PHRASE_STRICT_FALLBACK,
             Flags.FLAG_USE_BOUNDS_FOR_WIDTH,
             Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE,
+            Flags.FLAG_ICU_BIDI_MIGRATION,
     };
 
     /**
@@ -71,6 +72,7 @@
             Flags.phraseStrictFallback(),
             Flags.useBoundsForWidth(),
             Flags.fixLineHeightForLocale(),
+            Flags.icuBidiMigration(),
     };
 
     /**
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 224e5d8..bde9c77 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -305,7 +305,7 @@
             }
             mAddedWordSpacingInPx = (justifyWidth - width) / spaces;
             mAddedLetterSpacingInPx = 0;
-        } else {  // justificationMode == Layout.JUSTIFICATION_MODE_INTER_CHARACTER
+        } else {  // justificationMode == Layout.JUSTIFICATION_MODE_LETTER_SPACING
             LineInfo lineInfo = new LineInfo();
             float width = Math.abs(measure(end, false, null, null, lineInfo));
 
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index f3e0ea7..f37c4c2a 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -77,7 +77,7 @@
 }
 
 flag {
-  name: "inter_character_justification"
+  name: "letter_spacing_justification"
   namespace: "text"
   description: "A feature flag that implement inter character justification."
   bug: "283193133"
@@ -103,3 +103,10 @@
   description: "Fix that InputService#onUpdateSelection is not called when insert mode gesture is performed."
   bug: "300850862"
 }
+
+flag {
+  name: "icu_bidi_migration"
+  namespace: "text"
+  description: "A flag for replacing AndroidBidi with android.icu.text.Bidi."
+  bug: "317144801"
+}
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 891e2a2..d22d2a5 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -1544,7 +1544,7 @@
          * source, this method returns {@code false}.
          *
          * @param axis the {@link MotionEvent} axis whose value is used to get the scroll extent.
-         * @param source the {link InputDevice} source from which the {@link MotionEvent} that
+         * @param source the {@link InputDevice} source from which the {@link MotionEvent} that
          *      triggers the scroll came.
          * @return {@code true} if smooth scrolling should be used for the scroll, or {@code false}
          *      if smooth scrolling is not necessary, or if the provided axis and source combination
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 2366ff7..5c5817f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -15868,20 +15868,7 @@
         }
 
         if (onFilterTouchEventForSecurity(event)) {
-            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
-                result = true;
-            }
-            //noinspection SimplifiableIfStatement
-            ListenerInfo li = mListenerInfo;
-            if (li != null && li.mOnTouchListener != null
-                    && (mViewFlags & ENABLED_MASK) == ENABLED
-                    && li.mOnTouchListener.onTouch(this, event)) {
-                result = true;
-            }
-
-            if (!result && onTouchEvent(event)) {
-                result = true;
-            }
+            result = performOnTouchCallback(event);
         }
 
         if (!result && mInputEventConsistencyVerifier != null) {
@@ -15900,6 +15887,36 @@
         return result;
     }
 
+    /**
+     * Returns {@code true} if the {@link MotionEvent} from {@link #dispatchTouchEvent} was
+     * handled by this view.
+     */
+    private boolean performOnTouchCallback(MotionEvent event) {
+        boolean handled = false;
+        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
+            handled = true;
+        }
+        //noinspection SimplifiableIfStatement
+        ListenerInfo li = mListenerInfo;
+        if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED) {
+            try {
+                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "View.onTouchListener#onTouch");
+                handled = li.mOnTouchListener.onTouch(this, event);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            }
+        }
+        if (handled) {
+            return true;
+        }
+        try {
+            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "View#onTouchEvent");
+            return onTouchEvent(event);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
+    }
+
     boolean isAccessibilityFocusedViewOrHost() {
         return isAccessibilityFocused() || (getViewRootImpl() != null && getViewRootImpl()
                 .getAccessibilityFocusedHost() == this);
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 7bae7ec..4ba4ee3 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1497,13 +1497,21 @@
      * {@link View#SYSTEM_UI_LAYOUT_FLAGS} as well the
      * {@link WindowManager.LayoutParams#SOFT_INPUT_ADJUST_RESIZE} flag and fits content according
      * to these flags.
-     * </p>
+     *
      * <p>
      * If set to {@code false}, the framework will not fit the content view to the insets and will
      * just pass through the {@link WindowInsets} to the content view.
-     * </p>
+     *
+     * <p>
+     * If the app targets
+     * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
+     * the behavior will be like setting this to {@code false}, and cannot be changed.
+     *
      * @param decorFitsSystemWindows Whether the decor view should fit root-level content views for
      *                               insets.
+     * @deprecated Make space in the container views to prevent the critical elements from getting
+     *             obscured by {@link WindowInsets.Type#systemBars()} or
+     *             {@link WindowInsets.Type#displayCutout()} instead.
      */
     public void setDecorFitsSystemWindows(boolean decorFitsSystemWindows) {
     }
@@ -2597,7 +2605,9 @@
 
     /**
      * @return the color of the status bar.
+     * @deprecated This is no longer needed since the setter is deprecated.
      */
+    @Deprecated
     @ColorInt
     public abstract int getStatusBarColor();
 
@@ -2614,13 +2624,29 @@
      * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.
      * <p>
      * The transitionName for the view background will be "android:status:background".
-     * </p>
+     *
+     * <p>
+     * If the color is transparent and the window enforces the status bar contrast, the system
+     * will determine whether a scrim is necessary and draw one on behalf of the app to ensure
+     * that the status bar has enough contrast with the contents of this app, and set an appropriate
+     * effective bar background accordingly.
+     *
+     * <p>
+     * If the app targets
+     * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
+     * the color will be transparent and cannot be changed.
+     *
+     * @see #setNavigationBarContrastEnforced
+     * @deprecated Draw proper background behind {@link WindowInsets.Type#statusBars()}} instead.
      */
+    @Deprecated
     public abstract void setStatusBarColor(@ColorInt int color);
 
     /**
      * @return the color of the navigation bar.
+     * @deprecated This is no longer needed since the setter is deprecated.
      */
+    @Deprecated
     @ColorInt
     public abstract int getNavigationBarColor();
 
@@ -2637,9 +2663,24 @@
      * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}.
      * <p>
      * The transitionName for the view background will be "android:navigation:background".
-     * </p>
+     *
+     * <p>
+     * If the color is transparent and the window enforces the navigation bar contrast, the system
+     * will determine whether a scrim is necessary and draw one on behalf of the app to ensure that
+     * the navigation bar has enough contrast with the contents of this app, and set an appropriate
+     * effective bar background accordingly.
+     *
+     * <p>
+     * If the app targets
+     * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
+     * the color will be transparent and cannot be changed.
+     *
      * @attr ref android.R.styleable#Window_navigationBarColor
+     * @see #setNavigationBarContrastEnforced
+     * @deprecated Draw proper background behind {@link WindowInsets.Type#navigationBars()} or
+     *             {@link WindowInsets.Type#tappableElement()} instead.
      */
+    @Deprecated
     public abstract void setNavigationBarColor(@ColorInt int color);
 
     /**
@@ -2651,9 +2692,17 @@
      * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} and
      * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_NAVIGATION} must not be set.
      *
+     * <p>
+     * If the app targets
+     * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
+     * the color will be transparent and cannot be changed.
+     *
      * @param dividerColor The color of the thin line.
      * @attr ref android.R.styleable#Window_navigationBarDividerColor
+     * @deprecated Draw proper background behind {@link WindowInsets.Type#navigationBars()} or
+     *             {@link WindowInsets.Type#tappableElement()} instead.
      */
+    @Deprecated
     public void setNavigationBarDividerColor(@ColorInt int dividerColor) {
     }
 
@@ -2663,7 +2712,9 @@
      * @return The color of the navigation bar divider color.
      * @see #setNavigationBarColor(int)
      * @attr ref android.R.styleable#Window_navigationBarDividerColor
+     * @deprecated This is no longer needed since the setter is deprecated.
      */
+    @Deprecated
     public @ColorInt int getNavigationBarDividerColor() {
         return 0;
     }
@@ -2682,7 +2733,9 @@
      * @see android.R.attr#enforceStatusBarContrast
      * @see #isStatusBarContrastEnforced
      * @see #setStatusBarColor
+     * @deprecated Draw proper background behind {@link WindowInsets.Type#statusBars()}} instead.
      */
+    @Deprecated
     public void setStatusBarContrastEnforced(boolean ensureContrast) {
     }
 
@@ -2697,7 +2750,9 @@
      * @see android.R.attr#enforceStatusBarContrast
      * @see #setStatusBarContrastEnforced
      * @see #setStatusBarColor
+     * @deprecated This is not needed since the setter is deprecated.
      */
+    @Deprecated
     public boolean isStatusBarContrastEnforced() {
         return false;
     }
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index c49fce5..848261d 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -179,9 +179,29 @@
         }
     }
 
+    /**
+     * Sets {@link com.android.server.wm.WindowManagerService} for the system process.
+     * <p>
+     * It is needed to prevent possible deadlock. A possible scenario is:
+     * In system process, WMS holds {@link com.android.server.wm.WindowManagerGlobalLock} to call
+     * {@code WindowManagerGlobal} APIs and wait to lock {@code WindowManagerGlobal} itself
+     * (i.e. call {@link #getWindowManagerService()} in the global lock), while
+     * another component may lock {@code WindowManagerGlobal} and wait to lock
+     * {@link com.android.server.wm.WindowManagerGlobalLock}(i.e call {@link #addView} in the
+     * system process, which calls to {@link com.android.server.wm.WindowManagerService} API
+     * directly).
+     */
+    public static void setWindowManagerServiceForSystemProcess(@NonNull IWindowManager wms) {
+        sWindowManagerService = wms;
+    }
+
     @Nullable
     @UnsupportedAppUsage
     public static IWindowManager getWindowManagerService() {
+        if (sWindowManagerService != null) {
+            // Use WMS directly without locking WMGlobal to prevent deadlock.
+            return sWindowManagerService;
+        }
         synchronized (WindowManagerGlobal.class) {
             if (sWindowManagerService == null) {
                 sWindowManagerService = IWindowManager.Stub.asInterface(
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 559ccfea7..7ebabee 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -64,6 +64,7 @@
 import android.service.autofill.FillEventHistory;
 import android.service.autofill.Flags;
 import android.service.autofill.UserData;
+import android.service.credentials.CredentialProviderService;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -2382,7 +2383,18 @@
                 return;
             }
 
-            final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT);
+            final Parcelable result;
+            if (data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT) != null) {
+                result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT);
+            } else if (data.getParcelableExtra(
+                    CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE) != null
+                    && Flags.autofillCredmanIntegration()) {
+                result = data.getParcelableExtra(
+                        CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE);
+            } else {
+                result = null;
+            }
+
             final Bundle responseData = new Bundle();
             responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result);
             final Bundle newClientState = data.getBundleExtra(EXTRA_CLIENT_STATE);
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index 1dd99ba..6a4408b 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -1,13 +1,6 @@
 package: "android.view.flags"
 
 flag {
-     name: "enable_surface_native_alloc_registration"
-     namespace: "toolkit"
-     description: "Feature flag for registering surfaces with the VM for faster cleanup"
-     bug: "306193257"
-}
-
-flag {
     name: "enable_surface_native_alloc_registration_ro"
     namespace: "toolkit"
     description: "Feature flag for registering surfaces with the VM for faster"
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index 9ef6880..0cc9a0d 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -50,6 +50,8 @@
     static final int RESULT_CODE_UNREGISTER = 1;
     @NonNull
     private final ResultReceiver mResultReceiver;
+    @NonNull
+    private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
 
     public ImeOnBackInvokedDispatcher(Handler handler) {
         mResultReceiver = new ResultReceiver(handler) {
@@ -88,7 +90,7 @@
         // cause a memory leak because the app side already clears the reference correctly.
         final IOnBackInvokedCallback iCallback =
                 new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(
-                        callback, false /* useWeakRef */);
+                        callback, mProgressAnimator, false /* useWeakRef */);
         bundle.putBinder(RESULT_KEY_CALLBACK, iCallback.asBinder());
         bundle.putInt(RESULT_KEY_PRIORITY, priority);
         bundle.putInt(RESULT_KEY_ID, callback.hashCode());
@@ -179,6 +181,9 @@
             }
         }
         mImeCallbacks.clear();
+        // We should also stop running animations since all callbacks have been removed.
+        // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
+        Handler.getMain().post(mProgressAnimator::reset);
     }
 
     static class ImeOnBackInvokedCallback implements OnBackInvokedCallback {
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index baefe7b..5c911f4 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -246,7 +246,10 @@
                                     .ImeOnBackInvokedCallback
                                 ? ((ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback)
                                         callback).getIOnBackInvokedCallback()
-                                : new OnBackInvokedCallbackWrapper(callback, this);
+                                : new OnBackInvokedCallbackWrapper(
+                                        callback,
+                                        mProgressAnimator,
+                                        this);
                 callbackInfo = new OnBackInvokedCallbackInfo(
                         iCallback,
                         priority,
@@ -272,7 +275,7 @@
     }
 
     @NonNull
-    private static final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+    private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
     private boolean mIsDispatching = false;
 
     /**
@@ -339,18 +342,24 @@
          * forwarded and registered on the app's {@link WindowOnBackInvokedDispatcher}. */
         @Nullable
         private final WindowOnBackInvokedDispatcher mDispatcher;
+        @NonNull
+        private final BackProgressAnimator mProgressAnimator;
 
         OnBackInvokedCallbackWrapper(
                 @NonNull OnBackInvokedCallback callback,
+                @NonNull BackProgressAnimator progressAnimator,
                 WindowOnBackInvokedDispatcher dispatcher) {
             mCallbackRef = new CallbackRef(callback, true /* useWeakRef */);
+            mProgressAnimator = progressAnimator;
             mDispatcher = dispatcher;
         }
 
         OnBackInvokedCallbackWrapper(
                 @NonNull OnBackInvokedCallback callback,
+                @NonNull BackProgressAnimator progressAnimator,
                 boolean useWeakRef) {
             mCallbackRef = new CallbackRef(callback, useWeakRef);
+            mProgressAnimator = progressAnimator;
             mDispatcher = null;
         }
 
@@ -362,11 +371,11 @@
                 }
                 final OnBackAnimationCallback callback = getBackAnimationCallback();
                 if (callback != null) {
-                    mProgressAnimator.onBackStarted(backEvent, event ->
-                            callback.onBackProgressed(event));
                     callback.onBackStarted(new BackEvent(
                             backEvent.getTouchX(), backEvent.getTouchY(),
                             backEvent.getProgress(), backEvent.getSwipeEdge()));
+                    mProgressAnimator.onBackStarted(backEvent, event ->
+                            callback.onBackProgressed(event));
                 }
             });
         }
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 83acc47..d433ca3 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
@@ -227,6 +227,9 @@
     private String requiredAccountType;
     @Nullable
     @DataClass.ParcelWith(ForInternedString.class)
+    private String mEmergencyInstaller;
+    @Nullable
+    @DataClass.ParcelWith(ForInternedString.class)
     private String overlayTarget;
     @Nullable
     @DataClass.ParcelWith(ForInternedString.class)
@@ -1275,6 +1278,12 @@
         return restrictedAccountType;
     }
 
+    @Nullable
+    @Override
+    public String getEmergencyInstaller() {
+        return mEmergencyInstaller;
+    }
+
     @Override
     public int getRoundIconResourceId() {
         return roundIconRes;
@@ -2336,6 +2345,12 @@
     }
 
     @Override
+    public PackageImpl setEmergencyInstaller(@Nullable String emergencyInstaller) {
+        this.mEmergencyInstaller = emergencyInstaller;
+        return this;
+    }
+
+    @Override
     public PackageImpl setRoundIconResourceId(int value) {
         roundIconRes = value;
         return this;
@@ -3105,6 +3120,7 @@
         dest.writeString(this.mBaseApkPath);
         dest.writeString(this.restrictedAccountType);
         dest.writeString(this.requiredAccountType);
+        dest.writeString(this.mEmergencyInstaller);
         sForInternedString.parcel(this.overlayTarget, dest, flags);
         dest.writeString(this.overlayTargetOverlayableName);
         dest.writeString(this.overlayCategory);
@@ -3255,6 +3271,7 @@
         this.mBaseApkPath = in.readString();
         this.restrictedAccountType = in.readString();
         this.requiredAccountType = in.readString();
+        this.mEmergencyInstaller = in.readString();
         this.overlayTarget = sForInternedString.unparcel(in);
         this.overlayTargetOverlayableName = in.readString();
         this.overlayCategory = in.readString();
diff --git a/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java b/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java
index 7ef0b48..66cfb69 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java
@@ -75,6 +75,11 @@
 
     ParsedPackage setUpdatableSystem(boolean value);
 
+    /**
+     * Sets a system app that is allowed to update another system app
+     */
+    ParsedPackage setEmergencyInstaller(String emergencyInstaller);
+
     ParsedPackage markNotActivitiesAsNotExportedIfSingleUser();
 
     ParsedPackage setOdm(boolean odm);
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 6c09b7c..ef106e0 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
@@ -347,6 +347,11 @@
 
     ParsingPackage setUpdatableSystem(boolean value);
 
+    /**
+     * Sets a system app that is allowed to update another system app
+     */
+    ParsingPackage setEmergencyInstaller(String emergencyInstaller);
+
     ParsingPackage setLargeScreensSupported(int supportsLargeScreens);
 
     ParsingPackage setNormalScreensSupported(int supportsNormalScreens);
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 f483597..e0fdbc6 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -952,6 +952,8 @@
 
         final boolean updatableSystem = parser.getAttributeBooleanValue(null /*namespace*/,
                 "updatableSystem", true);
+        final String emergencyInstaller = parser.getAttributeValue(null /*namespace*/,
+                "emergencyInstaller");
 
         pkg.setInstallLocation(anInteger(PARSE_DEFAULT_INSTALL_LOCATION,
                         R.styleable.AndroidManifest_installLocation, sa))
@@ -959,7 +961,8 @@
                         R.styleable.AndroidManifest_targetSandboxVersion, sa))
                 /* Set the global "on SD card" flag */
                 .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0)
-                .setUpdatableSystem(updatableSystem);
+                .setUpdatableSystem(updatableSystem)
+                .setEmergencyInstaller(emergencyInstaller);
 
         boolean foundApp = false;
         final int depth = parser.getDepth();
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index b4f9ee3..5239245 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -120,7 +120,6 @@
 import android.window.ProxyOnBackInvokedDispatcher;
 
 import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.view.menu.ContextMenuBuilder;
 import com.android.internal.view.menu.IconMenuPresenter;
 import com.android.internal.view.menu.ListMenuPresenter;
@@ -369,8 +368,7 @@
 
     boolean mDecorFitsSystemWindows = true;
 
-    @VisibleForTesting
-    public final boolean mEdgeToEdgeEnforced;
+    private boolean mEdgeToEdgeEnforced;
 
     private final ProxyOnBackInvokedDispatcher mProxyOnBackInvokedDispatcher;
 
@@ -390,11 +388,6 @@
         mProxyOnBackInvokedDispatcher = new ProxyOnBackInvokedDispatcher(context);
         mAllowFloatingWindowsFillScreen = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_allowFloatingWindowsFillScreen);
-        mEdgeToEdgeEnforced = isEdgeToEdgeEnforced(context.getApplicationInfo(), true /* local */);
-        if (mEdgeToEdgeEnforced) {
-            getAttributes().privateFlags |= PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
-            mDecorFitsSystemWindows = false;
-        }
     }
 
     /**
@@ -436,15 +429,18 @@
      *
      * @param info The application to query.
      * @param local Whether this is called from the process of the given application.
+     * @param windowStyle The style of the window.
      * @return {@code true} if edge-to-edge is enforced. Otherwise, {@code false}.
      */
-    public static boolean isEdgeToEdgeEnforced(ApplicationInfo info, boolean local) {
-        return info.targetSdkVersion >= ENFORCE_EDGE_TO_EDGE_SDK_VERSION
-                || (Flags.enforceEdgeToEdge() && (local
-                        // Calling this doesn't require a permission.
-                        ? CompatChanges.isChangeEnabled(ENFORCE_EDGE_TO_EDGE)
-                        // Calling this requires permissions.
-                        : info.isChangeEnabled(ENFORCE_EDGE_TO_EDGE)));
+    public static boolean isEdgeToEdgeEnforced(ApplicationInfo info, boolean local,
+            TypedArray windowStyle) {
+        return !windowStyle.getBoolean(R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false)
+                && (info.targetSdkVersion >= ENFORCE_EDGE_TO_EDGE_SDK_VERSION
+                        || (Flags.enforceEdgeToEdge() && (local
+                                // Calling this doesn't require a permission.
+                                ? CompatChanges.isChangeEnabled(ENFORCE_EDGE_TO_EDGE)
+                                // Calling this requires permissions.
+                                : info.isChangeEnabled(ENFORCE_EDGE_TO_EDGE))));
     }
 
     @Override
@@ -2470,6 +2466,13 @@
             System.out.println(s);
         }
 
+        mEdgeToEdgeEnforced = isEdgeToEdgeEnforced(
+                getContext().getApplicationInfo(), true /* local */, a);
+        if (mEdgeToEdgeEnforced) {
+            getAttributes().privateFlags |= PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
+            mDecorFitsSystemWindows = false;
+        }
+
         mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
         int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                 & (~getForcedWindowFlags());
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index a8d0d37..889434f 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -168,12 +168,12 @@
     }
 
     public ConversationLayout(@NonNull Context context, @Nullable AttributeSet attrs,
-                              @AttrRes int defStyleAttr) {
+            @AttrRes int defStyleAttr) {
         super(context, attrs, defStyleAttr);
     }
 
     public ConversationLayout(@NonNull Context context, @Nullable AttributeSet attrs,
-                              @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
     }
 
@@ -432,8 +432,14 @@
         final List<MessagingMessage> newHistoricMessagingMessages =
                 createMessages(newHistoricMessages, /* isHistoric= */true, usePrecomputedText);
 
+        // Add our new MessagingMessages to groups
+        List<List<MessagingMessage>> groups = new ArrayList<>();
+        List<Person> senders = new ArrayList<>();
+        // Lets first find the groups (populate `groups` and `senders`)
+        findGroups(newHistoricMessagingMessages, newMessagingMessages, user, groups, senders);
+
         return new MessagingData(user, showSpinner, unreadCount,
-                newHistoricMessagingMessages, newMessagingMessages);
+                newHistoricMessagingMessages, newMessagingMessages, groups, senders);
     }
 
     /**
@@ -509,21 +515,13 @@
         setUser(messagingData.getUser());
         setUnreadCount(messagingData.getUnreadCount());
 
-        List<MessagingMessage> messages = messagingData.getNewMessagingMessages();
-        List<MessagingMessage> historicMessages = messagingData.getHistoricMessagingMessages();
         // Copy our groups, before they get clobbered
         ArrayList<MessagingGroup> oldGroups = new ArrayList<>(mGroups);
 
-        // Add our new MessagingMessages to groups
-        List<List<MessagingMessage>> groups = new ArrayList<>();
-        List<Person> senders = new ArrayList<>();
-
-        // Lets first find the groups (populate `groups` and `senders`)
-        findGroups(historicMessages, messages, groups, senders);
-
         // Let's now create the views and reorder them accordingly
         //   side-effect: updates mGroups, mAddedGroups
-        createGroupViews(groups, senders, messagingData.getShowSpinner());
+        createGroupViews(messagingData.getGroups(), messagingData.getSenders(),
+                messagingData.getShowSpinner());
 
         // Let's first check which groups were removed altogether and remove them in one animation
         removeGroups(oldGroups);
@@ -536,8 +534,8 @@
             historicMessage.removeMessage(mToRecycle);
         }
 
-        mMessages = messages;
-        mHistoricMessages = historicMessages;
+        mMessages = messagingData.getNewMessagingMessages();
+        mHistoricMessages = messagingData.getHistoricMessagingMessages();
 
         updateHistoricMessageVisibility();
         updateTitleAndNamesDisplay();
@@ -935,7 +933,7 @@
     }
 
     private void createGroupViews(List<List<MessagingMessage>> groups,
-                                  List<Person> senders, boolean showSpinner) {
+            List<Person> senders, boolean showSpinner) {
         mGroups.clear();
         for (int groupIndex = 0; groupIndex < groups.size(); groupIndex++) {
             List<MessagingMessage> group = groups.get(groupIndex);
@@ -983,9 +981,12 @@
         }
     }
 
+    /**
+     * Finds groups and senders from the given messaging messages and fills outGroups and outSenders
+     */
     private void findGroups(List<MessagingMessage> historicMessages,
-                            List<MessagingMessage> messages, List<List<MessagingMessage>> groups,
-                            List<Person> senders) {
+            List<MessagingMessage> messages, Person user, List<List<MessagingMessage>> outGroups,
+            List<Person> outSenders) {
         CharSequence currentSenderKey = null;
         List<MessagingMessage> currentGroup = null;
         int histSize = historicMessages.size();
@@ -1003,14 +1004,14 @@
             isNewGroup |= !TextUtils.equals(key, currentSenderKey);
             if (isNewGroup) {
                 currentGroup = new ArrayList<>();
-                groups.add(currentGroup);
+                outGroups.add(currentGroup);
                 if (sender == null) {
-                    sender = mUser;
+                    sender = user;
                 } else {
                     // Remove all formatting from the sender name
                     sender = sender.toBuilder().setName(Objects.toString(sender.getName())).build();
                 }
-                senders.add(sender);
+                outSenders.add(sender);
                 currentSenderKey = key;
             }
             currentGroup.add(message);
diff --git a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
index 5cda3f2..3e065bf 100644
--- a/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
+++ b/core/java/com/android/internal/widget/EmphasizedNotificationButton.java
@@ -16,8 +16,8 @@
 
 package com.android.internal.widget;
 
+import static android.app.Flags.evenlyDividedCallStyleActionLayout;
 import static android.app.Notification.CallStyle.DEBUG_NEW_ACTION_LAYOUT;
-import static android.app.Notification.CallStyle.USE_NEW_ACTION_LAYOUT;
 import static android.text.style.DynamicDrawableSpan.ALIGN_CENTER;
 
 import android.annotation.NonNull;
@@ -166,7 +166,7 @@
     }
 
     private void setIconToGlue(@Nullable Drawable icon) {
-        if (!USE_NEW_ACTION_LAYOUT) {
+        if (!evenlyDividedCallStyleActionLayout()) {
             Log.e(TAG, "glueIcon: new action layout disabled; doing nothing");
             return;
         }
@@ -207,7 +207,7 @@
     }
 
     private void setLabelToGlue(@Nullable CharSequence label) {
-        if (!USE_NEW_ACTION_LAYOUT) {
+        if (!evenlyDividedCallStyleActionLayout()) {
             Log.e(TAG, "glueLabel: new action layout disabled; doing nothing");
             return;
         }
@@ -255,7 +255,7 @@
             return;
         }
 
-        if (!USE_NEW_ACTION_LAYOUT) {
+        if (!evenlyDividedCallStyleActionLayout()) {
             Log.e(TAG, "glueIconAndLabelIfNeeded: new action layout disabled; doing nothing");
             return;
         }
diff --git a/core/java/com/android/internal/widget/MessagingData.java b/core/java/com/android/internal/widget/MessagingData.java
index 85b0201..42de60e 100644
--- a/core/java/com/android/internal/widget/MessagingData.java
+++ b/core/java/com/android/internal/widget/MessagingData.java
@@ -28,24 +28,33 @@
     private final boolean mShowSpinner;
     private final List<MessagingMessage> mHistoricMessagingMessages;
     private final List<MessagingMessage> mNewMessagingMessages;
+    private final List<List<MessagingMessage>> mGroups;
+    private final List<Person> mSenders;
     private final int mUnreadCount;
 
     MessagingData(Person user, boolean showSpinner,
             List<MessagingMessage> historicMessagingMessages,
-            List<MessagingMessage> newMessagingMessages) {
+            List<MessagingMessage> newMessagingMessages, List<List<MessagingMessage>> groups,
+            List<Person> senders) {
         this(user, showSpinner, /* unreadCount= */0,
-                historicMessagingMessages, newMessagingMessages);
+                historicMessagingMessages, newMessagingMessages,
+                groups,
+                senders);
     }
 
     MessagingData(Person user, boolean showSpinner,
             int unreadCount,
             List<MessagingMessage> historicMessagingMessages,
-            List<MessagingMessage> newMessagingMessages) {
+            List<MessagingMessage> newMessagingMessages,
+            List<List<MessagingMessage>> groups,
+            List<Person> senders) {
         mUser = user;
         mShowSpinner = showSpinner;
         mUnreadCount = unreadCount;
         mHistoricMessagingMessages = historicMessagingMessages;
         mNewMessagingMessages = newMessagingMessages;
+        mGroups = groups;
+        mSenders = senders;
     }
 
     public Person getUser() {
@@ -67,4 +76,12 @@
     public int getUnreadCount() {
         return mUnreadCount;
     }
+
+    public List<Person> getSenders() {
+        return mSenders;
+    }
+
+    public List<List<MessagingMessage>> getGroups() {
+        return mGroups;
+    }
 }
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index b6d7503..d000596 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -189,9 +189,15 @@
                 /* isHistoric= */true, usePrecomputedText);
         final List<MessagingMessage> newMessagingMessages =
                 createMessages(newMessages, /* isHistoric */false, usePrecomputedText);
+        // Let's first find our groups!
+        List<List<MessagingMessage>> groups = new ArrayList<>();
+        List<Person> senders = new ArrayList<>();
+
+        // Lets first find the groups
+        findGroups(historicMessagingMessages, newMessagingMessages, groups, senders);
 
         return new MessagingData(user, showSpinner,
-                historicMessagingMessages, newMessagingMessages);
+                historicMessagingMessages, newMessagingMessages, groups, senders);
     }
 
     /**
@@ -256,10 +262,10 @@
     private void bind(MessagingData messagingData) {
         setUser(messagingData.getUser());
 
-        List<MessagingMessage> historicMessages = messagingData.getHistoricMessagingMessages();
-        List<MessagingMessage> messages = messagingData.getNewMessagingMessages();
+        // Let's now create the views and reorder them accordingly
         ArrayList<MessagingGroup> oldGroups = new ArrayList<>(mGroups);
-        addMessagesToGroups(historicMessages, messages, messagingData.getShowSpinner());
+        createGroupViews(messagingData.getGroups(), messagingData.getSenders(),
+                messagingData.getShowSpinner());
 
         // Let's first check which groups were removed altogether and remove them in one animation
         removeGroups(oldGroups);
@@ -272,8 +278,8 @@
             historicMessage.removeMessage(mToRecycle);
         }
 
-        mMessages = messages;
-        mHistoricMessages = historicMessages;
+        mMessages = messagingData.getNewMessagingMessages();
+        mHistoricMessages = messagingData.getHistoricMessagingMessages();
 
         updateHistoricMessageVisibility();
         updateTitleAndNamesDisplay();
@@ -451,19 +457,6 @@
         }
     }
 
-    private void addMessagesToGroups(List<MessagingMessage> historicMessages,
-            List<MessagingMessage> messages, boolean showSpinner) {
-        // Let's first find our groups!
-        List<List<MessagingMessage>> groups = new ArrayList<>();
-        List<Person> senders = new ArrayList<>();
-
-        // Lets first find the groups
-        findGroups(historicMessages, messages, groups, senders);
-
-        // Let's now create the views and reorder them accordingly
-        createGroupViews(groups, senders, showSpinner);
-    }
-
     private void createGroupViews(List<List<MessagingMessage>> groups,
             List<Person> senders, boolean showSpinner) {
         mGroups.clear();
diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java
index 69d2544..301dc39 100644
--- a/core/java/com/android/internal/widget/NotificationActionListLayout.java
+++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java
@@ -17,7 +17,7 @@
 package com.android.internal.widget;
 
 import static android.app.Notification.CallStyle.DEBUG_NEW_ACTION_LAYOUT;
-import static android.app.Notification.CallStyle.USE_NEW_ACTION_LAYOUT;
+import static android.app.Flags.evenlyDividedCallStyleActionLayout;
 
 import android.annotation.DimenRes;
 import android.app.Notification;
@@ -410,7 +410,7 @@
      */
     @RemotableViewMethod
     public void setEvenlyDividedMode(boolean evenlyDividedMode) {
-        if (evenlyDividedMode && !USE_NEW_ACTION_LAYOUT) {
+        if (evenlyDividedMode && !evenlyDividedCallStyleActionLayout()) {
             Log.e(TAG, "setEvenlyDividedMode(true) called with new action layout disabled; "
                     + "leaving evenly divided mode disabled");
             return;
diff --git a/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java
index adb0c69..096f246 100644
--- a/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -273,6 +273,13 @@
     String getRestrictedAccountType();
 
     /**
+     * @see R.styleable#AndroidManifestApplication_emergencyInstaller
+     * @hide
+     */
+    @Nullable
+    String getEmergencyInstaller();
+
+    /**
      * @see ApplicationInfo#roundIconRes
      * @see R.styleable#AndroidManifestApplication_roundIcon
      */
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 7e325a5..58166bf 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -667,8 +667,9 @@
   }
 }
 
-static void DropCapabilitiesBoundingSet(fail_fn_t fail_fn) {
+static void DropCapabilitiesBoundingSet(fail_fn_t fail_fn, jlong bounding_capabilities) {
   for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {;
+    if ((1LL << i) & bounding_capabilities) continue;
     if (prctl(PR_CAPBSET_DROP, i, 0, 0, 0) == -1) {
       if (errno == EINVAL) {
         ALOGE("prctl(PR_CAPBSET_DROP) failed with EINVAL. Please verify "
@@ -680,6 +681,27 @@
   }
 }
 
+static bool MatchGid(JNIEnv* env, jintArray gids, jint gid, jint gid_to_find) {
+  if (gid == gid_to_find) return true;
+
+  if (gids == nullptr) return false;
+
+  jsize gids_num = env->GetArrayLength(gids);
+  ScopedIntArrayRO native_gid_proxy(env, gids);
+
+  if (native_gid_proxy.get() == nullptr) {
+    RuntimeAbort(env, __LINE__, "Bad gids array");
+  }
+
+  for (int gids_index = 0; gids_index < gids_num; ++gids_index) {
+    if (native_gid_proxy[gids_index] == gid_to_find) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 static void SetInheritable(uint64_t inheritable, fail_fn_t fail_fn) {
   __user_cap_header_struct capheader;
   memset(&capheader, 0, sizeof(capheader));
@@ -1875,9 +1897,9 @@
 // Utility routine to specialize a zygote child process.
 static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, jint runtime_flags,
                              jobjectArray rlimits, jlong permitted_capabilities,
-                             jlong effective_capabilities, jint mount_external,
-                             jstring managed_se_info, jstring managed_nice_name,
-                             bool is_system_server, bool is_child_zygote,
+                             jlong effective_capabilities, jlong bounding_capabilities,
+                             jint mount_external, jstring managed_se_info,
+                             jstring managed_nice_name, bool is_system_server, bool is_child_zygote,
                              jstring managed_instruction_set, jstring managed_app_data_dir,
                              bool is_top_app, jobjectArray pkg_data_info_list,
                              jobjectArray allowlisted_data_info_list, bool mount_data_dirs,
@@ -1891,6 +1913,9 @@
     auto instruction_set = extract_fn(managed_instruction_set);
     auto app_data_dir = extract_fn(managed_app_data_dir);
 
+    // Permit bounding capabilities
+    permitted_capabilities |= bounding_capabilities;
+
     // Keep capabilities across UID change, unless we're staying root.
     if (uid != 0) {
         EnableKeepCapabilities(fail_fn);
@@ -1898,7 +1923,7 @@
 
     SetInheritable(permitted_capabilities, fail_fn);
 
-    DropCapabilitiesBoundingSet(fail_fn);
+    DropCapabilitiesBoundingSet(fail_fn, bounding_capabilities);
 
     bool need_pre_initialize_native_bridge = !is_system_server && instruction_set.has_value() &&
             android::NativeBridgeAvailable() &&
@@ -2165,6 +2190,23 @@
     return capdata[0].effective | (static_cast<uint64_t>(capdata[1].effective) << 32);
 }
 
+static jlong CalculateBoundingCapabilities(JNIEnv* env, jint uid, jint gid, jintArray gids) {
+    jlong capabilities = 0;
+
+    /*
+     * Grant CAP_SYS_NICE to CapInh/CapPrm/CapBnd for processes that can spawn
+     * VMs.  This enables processes to execve on binaries with elevated
+     * capabilities if its file capability bits are set. This does not grant
+     * capability to the parent process(that spawns the VM) as the effective
+     * bits are not set.
+     */
+    if (MatchGid(env, gids, gid, AID_VIRTUALMACHINE)) {
+        capabilities |= (1LL << CAP_SYS_NICE);
+    }
+
+    return capabilities;
+}
+
 static jlong CalculateCapabilities(JNIEnv* env, jint uid, jint gid, jintArray gids,
                                    bool is_child_zygote) {
   jlong capabilities = 0;
@@ -2198,26 +2240,7 @@
    * Grant CAP_BLOCK_SUSPEND to processes that belong to GID "wakelock"
    */
 
-  bool gid_wakelock_found = false;
-  if (gid == AID_WAKELOCK) {
-    gid_wakelock_found = true;
-  } else if (gids != nullptr) {
-    jsize gids_num = env->GetArrayLength(gids);
-    ScopedIntArrayRO native_gid_proxy(env, gids);
-
-    if (native_gid_proxy.get() == nullptr) {
-      RuntimeAbort(env, __LINE__, "Bad gids array");
-    }
-
-    for (int gids_index = 0; gids_index < gids_num; ++gids_index) {
-      if (native_gid_proxy[gids_index] == AID_WAKELOCK) {
-        gid_wakelock_found = true;
-        break;
-      }
-    }
-  }
-
-  if (gid_wakelock_found) {
+  if (MatchGid(env, gids, gid, AID_WAKELOCK)) {
     capabilities |= (1LL << CAP_BLOCK_SUSPEND);
   }
 
@@ -2494,6 +2517,7 @@
         jobjectArray pkg_data_info_list, jobjectArray allowlisted_data_info_list,
         jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides) {
     jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
+    jlong bounding_capabilities = CalculateBoundingCapabilities(env, uid, gid, gids);
 
     if (UNLIKELY(managed_fds_to_close == nullptr)) {
       zygote::ZygoteFailure(env, "zygote", nice_name,
@@ -2532,10 +2556,11 @@
 
     if (pid == 0) {
         SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities,
-                         mount_external, se_info, nice_name, false, is_child_zygote == JNI_TRUE,
-                         instruction_set, app_data_dir, is_top_app == JNI_TRUE, pkg_data_info_list,
-                         allowlisted_data_info_list, mount_data_dirs == JNI_TRUE,
-                         mount_storage_dirs == JNI_TRUE, mount_sysprop_overrides == JNI_TRUE);
+                         bounding_capabilities, mount_external, se_info, nice_name, false,
+                         is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
+                         is_top_app == JNI_TRUE, pkg_data_info_list, allowlisted_data_info_list,
+                         mount_data_dirs == JNI_TRUE, mount_storage_dirs == JNI_TRUE,
+                         mount_sysprop_overrides == JNI_TRUE);
     }
     return pid;
 }
@@ -2568,7 +2593,7 @@
       // System server prcoess does not need data isolation so no need to
       // know pkg_data_info_list.
       SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities,
-                       effective_capabilities, MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
+                       effective_capabilities, 0, MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
                        false, nullptr, nullptr, /* is_top_app= */ false,
                        /* pkg_data_info_list */ nullptr,
                        /* allowlisted_data_info_list */ nullptr, false, false, false);
@@ -2725,12 +2750,14 @@
         jobjectArray allowlisted_data_info_list, jboolean mount_data_dirs,
         jboolean mount_storage_dirs, jboolean mount_sysprop_overrides) {
     jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
+    jlong bounding_capabilities = CalculateBoundingCapabilities(env, uid, gid, gids);
 
     SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities,
-                     mount_external, se_info, nice_name, false, is_child_zygote == JNI_TRUE,
-                     instruction_set, app_data_dir, is_top_app == JNI_TRUE, pkg_data_info_list,
-                     allowlisted_data_info_list, mount_data_dirs == JNI_TRUE,
-                     mount_storage_dirs == JNI_TRUE, mount_sysprop_overrides == JNI_TRUE);
+                     bounding_capabilities, mount_external, se_info, nice_name, false,
+                     is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
+                     is_top_app == JNI_TRUE, pkg_data_info_list, allowlisted_data_info_list,
+                     mount_data_dirs == JNI_TRUE, mount_storage_dirs == JNI_TRUE,
+                     mount_sysprop_overrides == JNI_TRUE);
 }
 
 /**
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index 381580b7..c65794e 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -19,6 +19,7 @@
 per-file android/hardware/sensorprivacy.proto = [email protected],[email protected]
 per-file background_install_control.proto = [email protected],[email protected],[email protected]
 per-file android/content/intent.proto = file:/PACKAGE_MANAGER_OWNERS
+per-file android/hardware/location/context_hub_info.proto = file:/services/core/java/com/android/server/location/contexthub/OWNERS
 
 # Biometrics
 [email protected]
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 104c023..10f75d0 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -612,6 +612,7 @@
     }
     optional Sounds sounds = 72;
 
+    optional SettingProto stylus_pointer_icon_enabled = 99 [ (android.privacy).dest = DEST_AUTOMATIC ];
     optional SettingProto swipe_bottom_to_notification_enabled = 82 [ (android.privacy).dest = DEST_AUTOMATIC ];
     // Defines whether managed profile ringtones should be synced from its
     // parent profile.
@@ -720,5 +721,5 @@
 
     // Please insert fields in alphabetical order and group them into messages
     // if possible (to avoid reaching the method limit).
-    // Next tag = 99;
+    // Next tag = 100;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c71a842..5c764e2 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3216,6 +3216,13 @@
         android:description="@string/permdesc_accessHiddenProfile"
         android:protectionLevel="normal" />
 
+    <!-- @SystemApi @hide Allows privileged applications to get details about hidden profile
+        users.
+        @FlaggedApi("android.multiuser.flags.enable_permission_to_access_hidden_profiles") -->
+    <permission
+        android:name="android.permission.ACCESS_HIDDEN_PROFILES_FULL"
+        android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi @hide Allows starting activities across profiles in the same profile group. -->
     <permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES"
                 android:protectionLevel="signature|role" />
diff --git a/core/res/res/drawable/ic_notification_summary_auto.xml b/core/res/res/drawable/ic_notification_summary_auto.xml
new file mode 100644
index 0000000..908bba8
--- /dev/null
+++ b/core/res/res/drawable/ic_notification_summary_auto.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M260,760Q236,760 218,742Q200,724 200,700L200,140Q200,116 218,98Q236,80 260,80L820,80Q844,80 862,98Q880,116 880,140L880,700Q880,724 862,742Q844,760 820,760L260,760ZM260,700L820,700Q820,700 820,700Q820,700 820,700L820,140Q820,140 820,140Q820,140 820,140L260,140Q260,140 260,140Q260,140 260,140L260,700Q260,700 260,700Q260,700 260,700ZM140,880Q116,880 98,862Q80,844 80,820L80,200L140,200L140,820Q140,820 140,820Q140,820 140,820L760,820L760,880L140,880ZM260,140L260,140Q260,140 260,140Q260,140 260,140L260,700Q260,700 260,700Q260,700 260,700L260,700Q260,700 260,700Q260,700 260,700L260,140Q260,140 260,140Q260,140 260,140Z"/>
+</vector>
diff --git a/core/res/res/layout/autofill_save.xml b/core/res/res/layout/autofill_save.xml
index ddedca2..50ff7c9 100644
--- a/core/res/res/layout/autofill_save.xml
+++ b/core/res/res/layout/autofill_save.xml
@@ -79,16 +79,16 @@
             android:layout_height="1dp"
             style="@style/AutofillHalfSheetDivider" />
 
-        <com.android.internal.widget.ButtonBarLayout
+      <com.android.server.autofill.ui.BottomSheetButtonBarLayout
+          android:id="@+id/autofill_save_button_bar"
             android:layout_width="match_parent"
-            android:layout_height="48dp"
+            android:layout_height="wrap_content"
             android:layout_gravity="end"
             android:clipToPadding="false"
             android:layout_marginTop="8dp"
             android:layout_marginBottom="8dp"
             android:theme="@style/Theme.DeviceDefault.AutofillHalfScreenDialogButton"
             android:orientation="horizontal"
-            android:gravity="center_vertical"
             android:layout_marginStart="@dimen/autofill_save_outer_margin"
             android:layout_marginEnd="@dimen/autofill_save_outer_margin"
             >
@@ -100,12 +100,15 @@
                 android:paddingEnd="12dp"
                 android:paddingTop="0dp"
                 android:paddingBottom="0dp"
+                android:layout_marginTop="4dp"
+                android:layout_marginBottom="4dp"
                 android:minWidth="0dp"
                 style="?android:attr/borderlessButtonStyle"
                 android:text="@string/autofill_save_no">
             </Button>
 
             <Space
+                android:id="@+id/autofill_button_bar_spacer"
                 android:layout_width="0dp"
                 android:layout_height="0dp"
                 android:layout_weight="1"
@@ -116,12 +119,14 @@
                 android:id="@+id/autofill_save_yes"
                 android:layout_width="wrap_content"
                 android:layout_height="40dp"
+                android:layout_marginTop="4dp"
+                android:layout_marginBottom="4dp"
                 android:minWidth="0dp"
                 style="@style/AutofillHalfSheetTonalButton"
                 android:text="@string/autofill_save_yes">
             </Button>
 
-        </com.android.internal.widget.ButtonBarLayout>
+        </com.android.server.autofill.ui.BottomSheetButtonBarLayout>
 
     </com.android.server.autofill.ui.BottomSheetLayout>
 </LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index e7b1d09..908eeeb 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2292,7 +2292,17 @@
              {@link android.R.attr#windowDrawsSystemBarBackgrounds} and the status bar must not
              have been requested to be translucent with
              {@link android.R.attr#windowTranslucentStatus}.
-             Corresponds to {@link android.view.Window#setStatusBarColor(int)}. -->
+             Corresponds to {@link android.view.Window#setStatusBarColor(int)}.
+             <p>If the color is transparent and the window enforces the status bar contrast, the
+             system will determine whether a scrim is necessary and draw one on behalf of the app to
+             ensure that the status bar has enough contrast with the contents of this app, and set
+             an appropriate effective bar background accordingly.
+             See: {@link android.R.attr#enforceStatusBarContrast}
+             <p>If the app targets
+             {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
+             this attribute is ignored.
+             @deprecated Draw proper background behind
+                         {@link android.view.WindowInsets.Type#statusBars()}} instead. -->
         <attr name="statusBarColor" format="color" />
 
         <!-- The color for the navigation bar. If the color is not opaque, consider setting
@@ -2302,7 +2312,18 @@
              {@link android.R.attr#windowDrawsSystemBarBackgrounds} and the navigation bar must not
              have been requested to be translucent with
              {@link android.R.attr#windowTranslucentNavigation}.
-             Corresponds to {@link android.view.Window#setNavigationBarColor(int)}. -->
+             Corresponds to {@link android.view.Window#setNavigationBarColor(int)}.
+             <p>If the color is transparent and the window enforces the navigation bar contrast, the
+             system will determine whether a scrim is necessary and draw one on behalf of the app to
+             ensure that the navigation bar has enough contrast with the contents of this app, and
+             set an appropriate effective bar background accordingly.
+             See: {@link android.R.attr#enforceNavigationBarContrast}
+             <p>If the app targets
+             {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
+             this attribute is ignored.
+             @deprecated Draw proper background behind
+                         {@link android.view.WindowInsets.Type#navigationBars()} or
+                         {@link android.view.WindowInsets.Type#tappableElement()} instead. -->
         <attr name="navigationBarColor" format="color" />
 
         <!-- Shows a thin line of the specified color between the navigation bar and the app
@@ -2311,7 +2332,13 @@
              {@link android.R.attr#windowDrawsSystemBarBackgrounds} and the navigation bar must not
              have been requested to be translucent with
              {@link android.R.attr#windowTranslucentNavigation}.
-             Corresponds to {@link android.view.Window#setNavigationBarDividerColor(int)}. -->
+             Corresponds to {@link android.view.Window#setNavigationBarDividerColor(int)}.
+             <p>If the app targets
+             {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
+             this attribute is ignored.
+             @deprecated Draw proper background behind
+                         {@link android.view.WindowInsets.Type#navigationBars()} or
+                         {@link android.view.WindowInsets.Type#tappableElement()} instead. -->
         <attr name="navigationBarDividerColor" format="color" />
 
         <!-- Sets whether the system should ensure that the status bar has enough
@@ -2327,7 +2354,9 @@
              <p>If the app does not target at least {@link android.os.Build.VERSION_CODES#Q Q},
              this attribute is ignored.
 
-             @see android.view.Window#setStatusBarContrastEnforced -->
+             @see android.view.Window#setStatusBarContrastEnforced
+             @deprecated Draw proper background behind
+                         {@link android.view.WindowInsets.Type#statusBars()}} instead. -->
         <attr name="enforceStatusBarContrast" format="boolean" />
 
         <!-- Sets whether the system should ensure that the navigation bar has enough
@@ -2483,6 +2512,31 @@
             <!-- The icon is shown unless the launching app specified SPLASH_SCREEN_STYLE_EMPTY -->
             <enum name="icon_preferred" value="1" />
         </attr>
+
+        <!-- Flag indicating whether this window would opt-out the edge-to-edge enforcement.
+
+             <p>If this is false, the edge-to-edge enforcement will be applied to the window if its
+             app targets
+             {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above.
+             The affected behaviors are:
+             <ul>
+                 <li>The framework will not fit the content view to the insets and will just pass
+                 through the {@link android.view.WindowInsets} to the content view, as if calling
+                 {@link android.view.Window#setDecorFitsSystemWindows(boolean)} with false.
+                 <li>{@link android.view.WindowManager.LayoutParams#layoutInDisplayCutoutMode} of
+                 the non-floating windows will be set to {@link
+                 android.view.WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS}.
+                 Changing it to other values will cause {@link lang.IllegalArgumentException}.
+                 <li>The framework will set {@link android.R.attr#statusBarColor},
+                 {@link android.R.attr#navigationBarColor}, and
+                 {@link android.R.attr#navigationBarDividerColor} to transparent.
+             </ul>
+
+             <p>If this is true, the edge-to-edge enforcement won't be applied. However, this
+             attribute will be deprecated and disabled in a future SDK level.
+
+             <p>This is false by default. -->
+        <attr name="windowOptOutEdgeToEdgeEnforcement" format="boolean"/>
     </declare-styleable>
 
     <!-- The set of attributes that describe a AlertDialog's theme. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 65c4d9f..d910940 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1608,6 +1608,10 @@
          This is a private attribute, used without android: namespace. -->
     <attr name="updatableSystem" format="boolean" />
 
+    <!-- Allows each installer in the system image to designate another app in the system image to
+        update the installer. -->
+    <attr name="emergencyInstaller" format="string" />
+
     <!-- Specify the type of foreground service. Multiple types can be specified by ORing the flags
          together. -->
     <attr name="foregroundServiceType">
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 951625e..d4e727e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5385,19 +5385,20 @@
      and a second time clipped to the fill level to indicate charge -->
     <bool name="config_batterymeterDualTone">false</bool>
 
-    <!-- The default refresh rate for a given device. This value is used to set the
-         global refresh rate vote, and when set to zero it has no effect on the vote.
-         If this value is non-zero but the hardware composer on the device supports
-         display modes with higher refresh rates, the framework may use those higher
-         refresh rate modes if an app chooses one by setting preferredDisplayModeId
-         or calling setFrameRate().-->
-    <integer name="config_defaultRefreshRate">0</integer>
+    <!-- The default refresh rate for a given device. Change this value to set a higher default
+         refresh rate. If the hardware composer on the device supports display modes with a higher
+         refresh rate than the default value specified here, the framework may use those higher
+         refresh rate modes if an app chooses one by setting preferredDisplayModeId or calling
+         setFrameRate().
+         If a non-zero value is set for config_defaultPeakRefreshRate, then
+         config_defaultRefreshRate may be set to 0, in which case the value set for
+         config_defaultPeakRefreshRate will act as the default frame rate. -->
+    <integer name="config_defaultRefreshRate">60</integer>
 
-    <!-- The default peak refresh rate for a given device. This value is used to set the
-         global peak refresh rate vote, and when set to zero it has no effect on the vote.
-         Change this value to non-zero if you want to prevent the framework from using higher
-         refresh rates, even if display modes with higher refresh rates are available from
-         hardware composer. -->
+    <!-- The default peak refresh rate for a given device. Change this value if you want to prevent
+         the framework from using higher refresh rates, even if display modes with higher refresh
+         rates are available from hardware composer. Only has an effect if the value is
+         non-zero. -->
     <integer name="config_defaultPeakRefreshRate">0</integer>
 
     <!-- External display peak refresh rate for the given device. Change this value if you want to
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 96c4bf4..0acccee 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -887,6 +887,8 @@
     <dimen name="autofill_save_scroll_view_top_margin">16dp</dimen>
     <dimen name="autofill_save_button_bar_padding">16dp</dimen>
     <dimen name="autofill_dialog_corner_radius">24dp</dimen>
+    <dimen name="autofill_button_bar_spacer_width">12dp</dimen>
+    <dimen name="autofill_button_bar_spacer_height">4dp</dimen>
 
     <!-- How much extra space should be left around the autofill dialog -->
     <dimen name="autofill_dialog_offset">72dp</dimen>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 81a8908..4799c37 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -147,6 +147,8 @@
     <public name="useBoundsForWidth"/>
     <!-- @FlaggedApi("android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP") -->
     <public name="autoTransact"/>
+    <!-- @FlaggedApi("com.android.window.flags.enforce_edge_to_edge") -->
+    <public name="windowOptOutEdgeToEdgeEnforcement"/>
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b8a399b..58427b1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3146,6 +3146,7 @@
   <java-symbol type="drawable" name="ic_collapse_notification" />
   <java-symbol type="drawable" name="ic_expand_bundle" />
   <java-symbol type="drawable" name="ic_collapse_bundle" />
+  <java-symbol type="drawable" name="ic_notification_summary_auto" />
   <java-symbol type="dimen" name="notification_header_shrink_min_width" />
   <java-symbol type="dimen" name="notification_header_shrink_hide_width" />
   <java-symbol type="dimen" name="notification_content_margin_start" />
@@ -3727,6 +3728,7 @@
   <java-symbol type="id" name="autofill_save_no" />
   <java-symbol type="id" name="autofill_save_title" />
   <java-symbol type="id" name="autofill_save_yes" />
+  <java-symbol type="id" name="autofill_button_bar_spacer" />
   <java-symbol type="id" name="autofill_service_icon" />
   <java-symbol type="id" name="autofill_dialog_picker"/>
   <java-symbol type="id" name="autofill_dialog_header"/>
@@ -3773,6 +3775,8 @@
   <java-symbol type="dimen" name="autofill_dialog_max_width" />
   <java-symbol type="dimen" name="autofill_dialog_offset"/>
   <java-symbol type="dimen" name="autofill_save_outer_margin"/>
+  <java-symbol type="dimen" name="autofill_button_bar_spacer_width"/>
+  <java-symbol type="dimen" name="autofill_button_bar_spacer_height"/>
 
   <java-symbol type="bool" name="autofill_dialog_horizontal_space_included"/>
 
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 9bb2499..61e6a36 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -127,7 +127,7 @@
     <!-- France: 5 digits, free: 3xxxx, premium [4-8]xxxx, plus EU:
          http://clients.txtnation.com/entries/161972-france-premium-sms-short-code-requirements,
          visual voicemail code for Orange: 21101 -->
-    <shortcode country="fr" premium="[4-8]\\d{4}" free="3\\d{4}|116\\d{3}|21101|20366|555|2051" />
+    <shortcode country="fr" premium="[4-8]\\d{4}" free="3\\d{4}|116\\d{3}|21101|20366|555|2051|33033" />
 
     <!-- United Kingdom (Great Britain): 4-6 digits, common codes [5-8]xxxx, plus EU:
          http://www.short-codes.com/media/Co-regulatoryCodeofPracticeforcommonshortcodes170206.pdf,
@@ -150,6 +150,9 @@
          http://clients.txtnation.com/entries/209633-hungary-premium-sms-short-code-regulations -->
     <shortcode country="hu" pattern="[01](?:\\d{3}|\\d{9})" premium="0691227910|1784" free="116\\d{3}" />
 
+    <!-- Honduras -->
+    <shortcode country="hn" pattern="\\d{4,6}" free="466453" />
+
     <!-- India: 1-5 digits (standard system default, not country specific) -->
     <shortcode country="in" pattern="\\d{1,5}" free="59336|53969" />
 
@@ -171,7 +174,7 @@
     <shortcode country="jp" pattern="\\d{1,5}" free="8083" />
 
     <!-- Kenya: 5 digits, known premium codes listed -->
-    <shortcode country="ke" pattern="\\d{5}" free="21725|21562|40520|23342|40023" />
+    <shortcode country="ke" pattern="\\d{5}" free="21725|21562|40520|23342|40023|24088|23054" />
 
     <!-- Kyrgyzstan: 4 digits, known premium codes listed -->
     <shortcode country="kg" pattern="\\d{4}" premium="415[2367]|444[69]" />
@@ -183,7 +186,7 @@
     <shortcode country="kz" pattern="\\d{4}" premium="335[02]|4161|444[469]|77[2359]0|8444|919[3-5]|968[2-5]" />
 
     <!-- Kuwait: 1-5 digits (standard system default, not country specific) -->
-    <shortcode country="kw" pattern="\\d{1,5}" free="1378|50420|94006|55991" />
+    <shortcode country="kw" pattern="\\d{1,5}" free="1378|50420|94006|55991|50976" />
 
     <!-- Lithuania: 3-5 digits, known premium codes listed, plus EU -->
     <shortcode country="lt" pattern="\\d{3,5}" premium="13[89]1|1394|16[34]5" free="116\\d{3}|1399|1324" />
@@ -195,9 +198,18 @@
     <!-- Latvia: 4 digits, known premium codes listed, plus EU -->
     <shortcode country="lv" pattern="\\d{4}" premium="18(?:19|63|7[1-4])" free="116\\d{3}|1399" />
 
+    <!-- Morocco: 1-5 digits (standard system default, not country specific) -->
+    <shortcode country="ma" pattern="\\d{1,5}" free="53819" />
+
     <!-- Macedonia: 1-6 digits (not confirmed), known premium codes listed -->
     <shortcode country="mk" pattern="\\d{1,6}" free="129005|122" />
 
+    <!-- Malawi: 1-5 digits (standard system default, not country specific) -->
+    <shortcode country="mw" pattern="\\d{1,5}" free="4276" />
+
+    <!-- Mozambique: 1-5 digits (standard system default, not country specific) -->
+    <shortcode country="mz" pattern="\\d{1,5}" free="1714" />
+
     <!-- Mexico: 4-5 digits (not confirmed), known premium codes listed -->
     <shortcode country="mx" pattern="\\d{4,6}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453|550346" />
 
@@ -207,6 +219,9 @@
     <!-- Namibia: 1-5 digits (standard system default, not country specific) -->
     <shortcode country="na" pattern="\\d{1,5}" free="40005" />
 
+    <!-- Nicaragua -->
+    <shortcode country="ni" pattern="\\d{4,6}" free="466453" />
+
     <!-- The Netherlands, 4 digits, known premium codes listed, plus EU -->
     <shortcode country="nl" pattern="\\d{4}" premium="4466|5040" free="116\\d{3}|2223|6225|2223|1662" />
 
@@ -219,8 +234,8 @@
     <!-- New Zealand: 3-4 digits, known premium codes listed -->
     <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|176|2141|3067|3068|3110|3876|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" />
 
-    <!-- Peru: 4-5 digits (not confirmed), known premium codes listed -->
-    <shortcode country="pe" pattern="\\d{4,5}" free="9963|40778" />
+    <!-- Peru: 4-6 digits (not confirmed), known premium codes listed -->
+    <shortcode country="pe" pattern="\\d{4,6}" free="9963|40778|301303" />
 
     <!-- Philippines -->
     <shortcode country="ph" pattern="\\d{1,5}" free="2147|5495|5496" />
@@ -269,6 +284,12 @@
     <!-- Slovakia: 4 digits (premium), plus EU: http://www.cmtelecom.com/premium-sms/slovakia -->
     <shortcode country="sk" premium="\\d{4}" free="116\\d{3}|8000" />
 
+    <!-- Senegal(SN): 1-5 digits (standard system default, not country specific) -->
+    <shortcode country="sn" pattern="\\d{1,5}" free="21215" />
+
+    <!-- El Salvador(SV): 1-5 digits (standard system default, not country specific) -->
+    <shortcode country="sv" pattern="\\d{4,6}" free="466453" />
+
     <!-- Taiwan -->
     <shortcode country="tw" pattern="\\d{4}" free="1922" />
 
@@ -278,15 +299,21 @@
     <!-- Tajikistan: 4 digits, known premium codes listed -->
     <shortcode country="tj" pattern="\\d{4}" premium="11[3-7]1|4161|4333|444[689]" />
 
+    <!-- Tanzania: 1-5 digits (standard system default, not country specific) -->
+    <shortcode country="tz" pattern="\\d{1,5}" free="15046|15234" />
+
     <!-- Turkey -->
     <shortcode country="tr" pattern="\\d{1,5}" free="7529|5528|6493|3193" />
 
     <!-- Ukraine: 4 digits, known premium codes listed -->
     <shortcode country="ua" pattern="\\d{4}" premium="444[3-9]|70[579]4|7540" />
 
+    <!-- Uganda(UG): 4 digits (standard system default, not country specific) -->
+    <shortcode country="ug" pattern="\\d{4}" free="8000" />
+
     <!-- USA: 5-6 digits (premium codes from https://www.premiumsmsrefunds.com/ShortCodes.htm),
          visual voicemail code for T-Mobile: 122 -->
-    <shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:578|711|811)|45814|46(?:157|173|327)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" standard="44567|244444" free="122|87902|21696|24614|28003|30356|33669|40196|41064|41270|43753|44034|46645|52413|56139|57969|61785|66975|75136|76227|81398|83952|85140|86566|86799|95737|96684|99245|611611" />
+    <shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:578|711|811)|45814|46(?:157|173|327)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" standard="44567|244444" free="122|87902|21696|24614|28003|30356|33669|40196|41064|41270|43753|44034|46645|52413|56139|57969|61785|66975|75136|76227|81398|83952|85140|86566|86799|95737|96684|99245|611611|96831" />
 
     <!-- Vietnam: 1-5 digits (standard system default, not country specific) -->
     <shortcode country="vn" pattern="\\d{1,5}" free="5001|9055" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 1b25d7f..ee1a4ac 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -66,6 +66,7 @@
         "android.view.accessibility.flags-aconfig-java",
         "androidx.core_core",
         "androidx.core_core-ktx",
+        "androidx.test.core",
         "androidx.test.espresso.core",
         "androidx.test.ext.junit",
         "androidx.test.runner",
@@ -90,6 +91,7 @@
         "flickerlib-parsers",
         "flickerlib-trace_processor_shell",
         "mockito-target-extended-minus-junit4",
+        "TestParameterInjector",
     ],
 
     libs: [
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java b/core/tests/coretests/res/drawable/adaptiveicon_drawable.xml
similarity index 64%
rename from packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java
rename to core/tests/coretests/res/drawable/adaptiveicon_drawable.xml
index 48fd4fe..dcffe75 100644
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java
+++ b/core/tests/coretests/res/drawable/adaptiveicon_drawable.xml
@@ -1,4 +1,5 @@
-/*
+<?xml version="1.0" encoding="utf-8"?>
+<!--
  * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,17 +13,10 @@
  * WITHOUT WARRANTIES 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.soundpicker;
-
-import android.app.Application;
-
-import dagger.hilt.android.HiltAndroidApp;
-
-/**
- * The main application class for the project.
- */
-@HiltAndroidApp(Application.class)
-public class RingtonePickerApplication extends Hilt_RingtonePickerApplication {
-}
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@android:color/white"/>
+    <foreground android:drawable="@android:color/black"/>
+    <monochrome android:drawable="@android:color/system_accent2_800"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/app/QueuedWorkTest.java b/core/tests/coretests/src/android/app/QueuedWorkTest.java
new file mode 100644
index 0000000..7021187
--- /dev/null
+++ b/core/tests/coretests/src/android/app/QueuedWorkTest.java
@@ -0,0 +1,180 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.Semaphore;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+@SmallTest
+public class QueuedWorkTest {
+
+    private QueuedWork mQueuedWork;
+    private AtomicInteger mCounter;
+
+    private class AddToCounter implements Runnable {
+        private final int mDelta;
+
+        public AddToCounter(int delta) {
+            mDelta = delta;
+        }
+
+        @Override
+        public void run() {
+            mCounter.addAndGet(mDelta);
+        }
+    }
+
+    private class IncrementCounter extends AddToCounter {
+        public IncrementCounter() {
+            super(1);
+        }
+    }
+
+    @Before
+    public void setup() {
+        mQueuedWork = new QueuedWork();
+        mCounter = new AtomicInteger(0);
+    }
+
+    @After
+    public void teardown() {
+        mQueuedWork.waitToFinish();
+        QueuedWork.resetHandler();
+    }
+
+    @Test
+    public void testQueueThenWait() {
+        mQueuedWork.queue(new IncrementCounter(), false);
+        mQueuedWork.waitToFinish();
+        assertThat(mCounter.get()).isEqualTo(1);
+        assertThat(mQueuedWork.hasPendingWork()).isFalse();
+    }
+
+    @Test
+    public void testQueueWithDelayThenWait() {
+        mQueuedWork.queue(new IncrementCounter(), true);
+        mQueuedWork.waitToFinish();
+        assertThat(mCounter.get()).isEqualTo(1);
+        assertThat(mQueuedWork.hasPendingWork()).isFalse();
+    }
+
+    @Test
+    public void testWorkHappensNotOnCallerThread() {
+        AtomicBoolean childThreadStarted = new AtomicBoolean(false);
+        InheritableThreadLocal<Boolean> setTrueInChild =
+                new InheritableThreadLocal<Boolean>() {
+                    @Override
+                    protected Boolean initialValue() {
+                        return false;
+                    }
+
+                    @Override
+                    protected Boolean childValue(Boolean parentValue) {
+                        childThreadStarted.set(true);
+                        return true;
+                    }
+                };
+
+        // Enqueue work to force a worker thread to be created
+        setTrueInChild.get();
+        assertThat(childThreadStarted.get()).isFalse();
+        mQueuedWork.queue(() -> setTrueInChild.get(), false);
+        mQueuedWork.waitToFinish();
+        assertThat(childThreadStarted.get()).isTrue();
+    }
+
+    @Test
+    public void testWaitToFinishDoesNotCreateThread() {
+        InheritableThreadLocal<Boolean> throwInChild =
+                new InheritableThreadLocal<Boolean>() {
+                    @Override
+                    protected Boolean initialValue() {
+                        return false;
+                    }
+
+                    @Override
+                    protected Boolean childValue(Boolean parentValue) {
+                        throw new RuntimeException("New thread should not be started!");
+                    }
+                };
+
+        try {
+            throwInChild.get();
+            // Intentionally don't enqueue work.
+            mQueuedWork.waitToFinish();
+            throwInChild.get();
+            // If a worker thread was unnecessarily started, we will have crashed.
+        } finally {
+            throwInChild.remove();
+        }
+    }
+
+    @Test
+    public void testFinisher() {
+        mQueuedWork.addFinisher(new AddToCounter(3));
+        mQueuedWork.addFinisher(new AddToCounter(7));
+        mQueuedWork.queue(new IncrementCounter(), false);
+        mQueuedWork.waitToFinish();
+        // The queued task and the two finishers all ran
+        assertThat(mCounter.get()).isEqualTo(1 + 3 + 7);
+    }
+
+    @Test
+    public void testRemoveFinisher() {
+        Runnable addThree = new AddToCounter(3);
+        Runnable addSeven = new AddToCounter(7);
+        mQueuedWork.addFinisher(addThree);
+        mQueuedWork.addFinisher(addSeven);
+        mQueuedWork.removeFinisher(addThree);
+        mQueuedWork.queue(new IncrementCounter(), false);
+        mQueuedWork.waitToFinish();
+        // The queued task and the two finishers all ran
+        assertThat(mCounter.get()).isEqualTo(1 + 7);
+    }
+
+    @Test
+    public void testHasPendingWork() {
+        Semaphore releaser = new Semaphore(0);
+        mQueuedWork.queue(
+                () -> {
+                    try {
+                        releaser.acquire();
+                    } catch (InterruptedException e) {
+                        throw new RuntimeException(e);
+                    }
+                }, false);
+        assertThat(mQueuedWork.hasPendingWork()).isTrue();
+        releaser.release();
+        mQueuedWork.waitToFinish();
+        assertThat(mQueuedWork.hasPendingWork()).isFalse();
+    }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
index 6e1c580..0aefef2 100644
--- a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
@@ -26,10 +26,14 @@
 import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.backup.Flags;
+
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -43,7 +47,9 @@
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public class BackupRestoreEventLoggerTest {
-    private static final int DATA_TYPES_ALLOWED = 15;
+    private static final int DATA_TYPES_ALLOWED_AFTER_FLAG = 150;
+
+    private static final int DATA_TYPES_ALLOWED_BEFORE_FLAG = 15;
 
     private static final String DATA_TYPE_1 = "data_type_1";
     private static final String DATA_TYPE_2 = "data_type_2";
@@ -55,6 +61,9 @@
     private BackupRestoreEventLogger mLogger;
     private MessageDigest mHashDigest;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Before
     public void setUp() throws Exception {
         mHashDigest = MessageDigest.getInstance("SHA-256");
@@ -83,10 +92,11 @@
     }
 
     @Test
-    public void testBackupLogger_onlyAcceptsAllowedNumberOfDataTypes() {
+    public void testBackupLogger_datatypeLimitFlagOff_onlyAcceptsAllowedNumberOfDataTypes() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_INCREASE_DATATYPES_FOR_AGENT_LOGGING);
         mLogger = new BackupRestoreEventLogger(BACKUP);
 
-        for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
+        for (int i = 0; i < DATA_TYPES_ALLOWED_BEFORE_FLAG; i++) {
             String dataType = DATA_TYPE_1 + i;
             mLogger.logItemsBackedUp(dataType, /* count */ 5);
             mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null);
@@ -103,10 +113,53 @@
     }
 
     @Test
-    public void testRestoreLogger_onlyAcceptsAllowedNumberOfDataTypes() {
+    public void testRestoreLogger_datatypeLimitFlagOff_onlyAcceptsAllowedNumberOfDataTypes() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_INCREASE_DATATYPES_FOR_AGENT_LOGGING);
         mLogger = new BackupRestoreEventLogger(RESTORE);
 
-        for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
+        for (int i = 0; i < DATA_TYPES_ALLOWED_BEFORE_FLAG; i++) {
+            String dataType = DATA_TYPE_1 + i;
+            mLogger.logItemsRestored(dataType, /* count */ 5);
+            mLogger.logItemsRestoreFailed(dataType, /* count */ 5, /* error */ null);
+            mLogger.logRestoreMetadata(dataType, METADATA_1);
+
+            assertThat(getResultForDataTypeIfPresent(mLogger, dataType)).isNotEqualTo(
+                    Optional.empty());
+        }
+
+        mLogger.logItemsRestored(DATA_TYPE_2, /* count */ 5);
+        mLogger.logItemsRestoreFailed(DATA_TYPE_2, /* count */ 5, /* error */ null);
+        mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1);
+        assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
+    }
+
+    @Test
+    public void testBackupLogger_datatypeLimitFlagOn_onlyAcceptsAllowedNumberOfDataTypes() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_INCREASE_DATATYPES_FOR_AGENT_LOGGING);
+        mLogger = new BackupRestoreEventLogger(BACKUP);
+
+        for (int i = 0; i < DATA_TYPES_ALLOWED_AFTER_FLAG; i++) {
+            String dataType = DATA_TYPE_1 + i;
+            mLogger.logItemsBackedUp(dataType, /* count */ 5);
+            mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null);
+            mLogger.logBackupMetadata(dataType, METADATA_1);
+
+            assertThat(getResultForDataTypeIfPresent(mLogger, dataType)).isNotEqualTo(
+                    Optional.empty());
+        }
+
+        mLogger.logItemsBackedUp(DATA_TYPE_2, /* count */ 5);
+        mLogger.logItemsBackupFailed(DATA_TYPE_2, /* count */ 5, /* error */ null);
+        mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1);
+        assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
+    }
+
+    @Test
+    public void testRestoreLogger_datatypeLimitFlagOn_onlyAcceptsAllowedNumberOfDataTypes() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_INCREASE_DATATYPES_FOR_AGENT_LOGGING);
+        mLogger = new BackupRestoreEventLogger(RESTORE);
+
+        for (int i = 0; i < DATA_TYPES_ALLOWED_AFTER_FLAG; i++) {
             String dataType = DATA_TYPE_1 + i;
             mLogger.logItemsRestored(dataType, /* count */ 5);
             mLogger.logItemsRestoreFailed(dataType, /* count */ 5, /* error */ null);
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
index 36bb8e5..548b8ec 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
@@ -104,7 +104,9 @@
     private void createComplexDatabase() {
         mDatabase.beginTransaction();
         try {
-            mDatabase.execSQL("CREATE TABLE t1 (i int, d double, t text);");
+            // Column "l" is used to test the long variants.  The underlying sqlite type is int,
+            // which is the same as a java long.
+            mDatabase.execSQL("CREATE TABLE t1 (i int, d double, t text, l int);");
             mDatabase.setTransactionSuccessful();
         } finally {
             mDatabase.endTransaction();
@@ -115,7 +117,7 @@
      * A three-value insert for the complex database.
      */
     private String createComplexInsert() {
-        return "INSERT INTO t1 (i, d, t) VALUES (?1, ?2, ?3)";
+        return "INSERT INTO t1 (i, d, t, l) VALUES (?1, ?2, ?3, ?4)";
     }
 
     /**
@@ -209,22 +211,25 @@
         mDatabase.beginTransaction();
         try {
             try (SQLiteRawStatement s = mDatabase.createRawStatement(createComplexInsert())) {
-                for (int i = 0; i < 9; i++) {
-                    int vi = i * 3;
-                    double vd = i * 2.5;
-                    String vt = String.format("text%02dvalue", i);
+                for (int row = 0; row < 9; row++) {
+                    int vi = row * 3;
+                    double vd = row * 2.5;
+                    String vt = String.format("text%02dvalue", row);
+                    long vl = Long.MAX_VALUE - row;
                     s.bindInt(1, vi);
                     s.bindDouble(2, vd);
                     s.bindText(3, vt);
+                    s.bindLong(4, vl);
                     boolean r = s.step();
                     // No row is returned by this query.
                     assertFalse(r);
                     s.reset();
                 }
-                // The last row has a null double and a null text.
+                // The last row has a null double, null text, and null long.
                 s.bindInt(1, 20);
                 s.bindNull(2);
                 s.bindNull(3);
+                s.bindNull(4);
                 assertFalse(s.step());
                 s.reset();
             }
@@ -248,19 +253,31 @@
             mDatabase.endTransaction();
         }
 
-        // Verify that the element created with i == 3 is correct.
+        // Verify that the element created with row == 3 is correct.
         mDatabase.beginTransactionReadOnly();
         try {
-            final String query = "SELECT i, d, t FROM t1 WHERE t = 'text03value'";
+            final String query = "SELECT i, d, t, l FROM t1 WHERE t = 'text03value'";
             try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) {
                 assertTrue(s.step());
-                assertEquals(3, s.getResultColumnCount());
+                assertEquals(4, s.getResultColumnCount());
                 int vi = s.getColumnInt(0);
                 double vd = s.getColumnDouble(1);
                 String vt = s.getColumnText(2);
-                assertEquals(3 * 3, vi);
-                assertEquals(2.5 * 3, vd, 0.1);
+                long vl = s.getColumnLong(3);
+                // The query extracted the third generated row.
+                final int row = 3;
+                assertEquals(3 * row, vi);
+                assertEquals(2.5 * row, vd, 0.1);
                 assertEquals("text03value", vt);
+                assertEquals(Long.MAX_VALUE - row, vl);
+
+                // Verify the column types.  Remember that sqlite integers are the same as Java
+                // long, so the integer and long columns have type INTEGER.
+                assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_INTEGER, s.getColumnType(0));
+                assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_FLOAT, s.getColumnType(1));
+                assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_TEXT, s.getColumnType(2));
+                assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_INTEGER, s.getColumnType(3));
+
                 // No more rows.
                 assertFalse(s.step());
             }
@@ -268,15 +285,24 @@
             mDatabase.endTransaction();
         }
 
+        // Verify that null columns are returned properly.
         mDatabase.beginTransactionReadOnly();
         try {
-            final String query = "SELECT i, d, t FROM t1 WHERE i == 20";
+            final String query = "SELECT i, d, t, l FROM t1 WHERE i == 20";
             try (SQLiteRawStatement s = mDatabase.createRawStatement(query)) {
                 assertTrue(s.step());
-                assertEquals(3, s.getResultColumnCount());
+                assertEquals(4, s.getResultColumnCount());
                 assertEquals(20, s.getColumnInt(0));
                 assertEquals(0.0, s.getColumnDouble(1), 0.01);
                 assertEquals(null, s.getColumnText(2));
+                assertEquals(0, s.getColumnLong(3));
+
+                // Verify the column types.
+                assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_INTEGER, s.getColumnType(0));
+                assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_NULL, s.getColumnType(1));
+                assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_NULL, s.getColumnType(2));
+                assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_NULL, s.getColumnType(3));
+
                 // No more rows.
                 assertFalse(s.step());
             }
@@ -495,6 +521,8 @@
                 // Fetch the entire reference array.
                 s.bindInt(1, 1);
                 assertTrue(s.step());
+                assertEquals(SQLiteRawStatement.SQLITE_DATA_TYPE_BLOB, s.getColumnType(0));
+
                 byte[] a = s.getColumnBlob(0);
                 assertTrue(Arrays.equals(src, a));
                 s.reset();
diff --git a/core/tests/coretests/src/android/graphics/drawable/IconTest.java b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
index 49ed3a8..950925f 100644
--- a/core/tests/coretests/src/android/graphics/drawable/IconTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
@@ -18,8 +18,13 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import android.app.IUriGrantsManager;
 import android.content.ContentProvider;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Bitmap;
@@ -32,13 +37,15 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.RemoteException;
-import android.test.AndroidTestCase;
 import android.util.Log;
 
-import androidx.test.filters.SmallTest;
+import androidx.test.core.app.ApplicationProvider;
 
 import com.android.frameworks.coretests.R;
 
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -46,13 +53,29 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 
-public class IconTest extends AndroidTestCase {
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(TestParameterInjector.class)
+public class IconTest {
     public static final String TAG = IconTest.class.getSimpleName();
+    private Context mContext;
+
     public static void L(String s, Object... parts) {
         Log.d(TAG, (parts.length == 0) ? s : String.format(s, parts));
     }
 
-    @SmallTest
+    private Context getContext() {
+        return mContext;
+    }
+
+    @Before
+    public void setup() {
+        mContext = ApplicationProvider.getApplicationContext();
+    }
+
+    @Test
     public void testWithBitmap() throws Exception {
         final Bitmap bm1 = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
         final Bitmap bm2 = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
@@ -119,7 +142,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testScaleDownIfNecessary() throws Exception {
         final Bitmap bm = Bitmap.createBitmap(4321, 78, Bitmap.Config.ARGB_8888);
         final Icon ic = Icon.createWithBitmap(bm);
@@ -132,7 +155,7 @@
         assertThat(ic.getBitmap().getHeight()).isLessThan(21);
     }
 
-    @SmallTest
+    @Test
     public void testWithAdaptiveBitmap() throws Exception {
         final Bitmap bm1 = Bitmap.createBitmap(150, 150, Bitmap.Config.ARGB_8888);
 
@@ -166,7 +189,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testWithBitmapResource() throws Exception {
         final Bitmap res1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
                 .getBitmap();
@@ -193,7 +216,7 @@
      * Icon resource test that ensures we can load and draw non-bitmaps. (In this case,
      * stat_sys_adb is assumed, and asserted, to be a vector drawable.)
      */
-    @SmallTest
+    @Test
     public void testWithStatSysAdbResource() throws Exception {
         // establish reference bitmap
         final float dp = getContext().getResources().getDisplayMetrics().density;
@@ -244,7 +267,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testWithFile() throws Exception {
         final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
                 .getBitmap();
@@ -268,7 +291,55 @@
         }
     }
 
-    @SmallTest
+    @Test
+    public void testWithAdaptiveIconResource_useMonochrome() throws Exception {
+        final int colorMono = ((ColorDrawable) getContext().getDrawable(
+                android.R.color.system_accent2_800)).getColor();
+        final Icon im1 = Icon.createWithResourceAdaptiveDrawable(getContext().getPackageName(),
+                R.drawable.adaptiveicon_drawable, true, 0.0f);
+        final Drawable draw1 = im1.loadDrawable(mContext);
+        assertThat(draw1 instanceof InsetDrawable).isTrue();
+        ColorDrawable colorDrawable = (ColorDrawable) ((DrawableWrapper) draw1).getDrawable();
+        assertThat(colorDrawable.getColor()).isEqualTo(colorMono);
+    }
+
+    @Test
+    public void testWithAdaptiveIconResource_dontUseMonochrome() throws Exception {
+        final int colorMono = ((ColorDrawable) getContext().getDrawable(
+                android.R.color.system_accent2_800)).getColor();
+        final int colorFg = ((ColorDrawable) getContext().getDrawable(
+                android.R.color.black)).getColor();
+        final int colorBg = ((ColorDrawable) getContext().getDrawable(
+                android.R.color.white)).getColor();
+
+        final Icon im1 = Icon.createWithResourceAdaptiveDrawable(getContext().getPackageName(),
+                R.drawable.adaptiveicon_drawable, false , 0.0f);
+        final Drawable draw1 = im1.loadDrawable(mContext);
+        assertThat(draw1 instanceof AdaptiveIconDrawable).isTrue();
+        ColorDrawable colorDrawableMono = (ColorDrawable) ((AdaptiveIconDrawable) draw1)
+                .getMonochrome();
+        assertThat(colorDrawableMono.getColor()).isEqualTo(colorMono);
+        ColorDrawable colorDrawableFg = (ColorDrawable) ((AdaptiveIconDrawable) draw1)
+                .getForeground();
+        assertThat(colorDrawableFg.getColor()).isEqualTo(colorFg);
+        ColorDrawable colorDrawableBg = (ColorDrawable) ((AdaptiveIconDrawable) draw1)
+                .getBackground();
+        assertThat(colorDrawableBg.getColor()).isEqualTo(colorBg);
+    }
+
+    @Test
+    public void testAdaptiveIconResource_sameAs(@TestParameter boolean useMonochrome)
+            throws Exception {
+        final Icon im1 = Icon.createWithResourceAdaptiveDrawable(getContext().getPackageName(),
+                R.drawable.adaptiveicon_drawable, useMonochrome, 1.0f);
+        final Parcel parcel = Parcel.obtain();
+        im1.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        final Icon im2 = Icon.CREATOR.createFromParcel(parcel);
+        assertThat(im1.sameAs(im2)).isTrue();
+    }
+
+    @Test
     public void testAsync() throws Exception {
         final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
                 .getBitmap();
@@ -311,7 +382,7 @@
         L(TAG, "asyncTest: done");
     }
 
-    @SmallTest
+    @Test
     public void testParcel() throws Exception {
         final Bitmap originalbits = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
                 .getBitmap();
@@ -391,7 +462,7 @@
         return (int) Math.sqrt(maxNumPixels / aspRatio);
     }
 
-    @SmallTest
+    @Test
     public void testScaleDownMaxSizeWithBitmap() throws Exception {
         final int bmpWidth = 13_000;
         final int bmpHeight = 10_000;
@@ -408,7 +479,7 @@
         assertThat(drawable.getIntrinsicHeight()).isEqualTo(maxHeight);
     }
 
-    @SmallTest
+    @Test
     public void testScaleDownMaxSizeWithAdaptiveBitmap() throws Exception {
         final int bmpWidth = 20_000;
         final int bmpHeight = 10_000;
@@ -427,7 +498,7 @@
         assertThat(drawable.getIntrinsicHeight()).isEqualTo(maxHeight);
     }
 
-    @SmallTest
+    @Test
     public void testScaleDownMaxSizeWithResource() throws Exception {
         final Icon ic = Icon.createWithResource(getContext(), R.drawable.test_too_big);
         final BitmapDrawable drawable = (BitmapDrawable) ic.loadDrawable(mContext);
@@ -435,7 +506,7 @@
         assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
     }
 
-    @SmallTest
+    @Test
     public void testScaleDownMaxSizeWithFile() throws Exception {
         final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.test_too_big))
                 .getBitmap();
@@ -450,7 +521,7 @@
         assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
     }
 
-    @SmallTest
+    @Test
     public void testScaleDownMaxSizeWithData() throws Exception {
         final int bmpBpp = 4;
         final Bitmap originalBits = ((BitmapDrawable) getContext().getDrawable(
@@ -465,7 +536,7 @@
         assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
     }
 
-    @SmallTest
+    @Test
     public void testLoadSafeDrawable_loadSuccessful() throws FileNotFoundException {
         int uid = 12345;
         String packageName = "test_pkg";
@@ -509,7 +580,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testLoadSafeDrawable_grantRejected_nullDrawable() throws FileNotFoundException {
         int uid = 12345;
         String packageName = "test_pkg";
diff --git a/core/tests/coretests/src/android/text/TextLineJustificationTest.kt b/core/tests/coretests/src/android/text/TextLineJustificationTest.kt
index a525615..c18f35f 100644
--- a/core/tests/coretests/src/android/text/TextLineJustificationTest.kt
+++ b/core/tests/coretests/src/android/text/TextLineJustificationTest.kt
@@ -126,4 +126,4 @@
 
         TextLine.recycle(tl)
     }
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
index 27869bb..71980c1 100644
--- a/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
+++ b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
@@ -21,7 +21,7 @@
 import android.platform.test.flag.junit.DeviceFlagsValueProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION
+import com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
@@ -40,7 +40,7 @@
     @JvmField
     val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
 
-    @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @RequiresFlagsEnabled(FLAG_LETTER_SPACING_JUSTIFICATION)
     @Test
     fun calculateRunFlagTest() {
         // Only one Bidi run
@@ -84,7 +84,7 @@
                 .isEqualTo(LEFT_EDGE)
     }
 
-    @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @RequiresFlagsEnabled(FLAG_LETTER_SPACING_JUSTIFICATION)
     @Test
     fun resolveRunFlagForSubSequenceTest() {
         val runStart = 5
@@ -221,4 +221,4 @@
                 MIDDLE_OF_LINE, true, runStart, runEnd, runStart + 1, runEnd - 1))
                 .isEqualTo(MIDDLE_OF_LINE)
     }
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
index de55b07..3df3b9d2 100644
--- a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
+++ b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
@@ -20,6 +20,7 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
 
 import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertThat;
@@ -63,7 +64,8 @@
         createPhoneWindowWithTheme(R.style.LayoutInDisplayCutoutModeUnset);
         installDecor();
 
-        if (mPhoneWindow.mEdgeToEdgeEnforced && !mPhoneWindow.isFloating()) {
+        if ((mPhoneWindow.getAttributes().privateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0
+                && !mPhoneWindow.isFloating()) {
             assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode,
                     is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS));
         } else {
diff --git a/data/etc/enhanced-confirmation.xml b/data/etc/enhanced-confirmation.xml
index 4a9dd2f..3b1867c 100644
--- a/data/etc/enhanced-confirmation.xml
+++ b/data/etc/enhanced-confirmation.xml
@@ -21,12 +21,36 @@
 
 Example usage:
 
-    <enhanced-confirmation-trusted-installer
+    <enhanced-confirmation-trusted-package
          package="com.example.app"
-         signature="E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0"/>
+         sha256-cert-digest="E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0"/>
 
-This indicates that "com.example.app" should be exempt from ECM, and that, if "com.example.app" is
-an installer, all packages installed via "com.example.app" will also be exempt from ECM.
+    ...
+
+    <enhanced-confirmation-trusted-installer
+         package="com.example.installer"
+         sha256-cert-digest="E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0"/>
+
+    ...
+
+The "enhanced-confirmation-trusted-package" entry shown above indicates that "com.example.app"
+should be considered a "trusted package". A "trusted package" will be exempt from ECM restrictions.
+
+The "enhanced-confirmation-trusted-installer" entry shown above indicates that
+"com.example.installer" should be considered a "trusted installer". A "trusted installer", and all
+packages that it installs, will be exempt from ECM restrictions. (There are some exceptions to this.
+For example, a trusted installer, at the time of installing an app, can opt the app back in to ECM
+restrictions by setting the app's package source to PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
+or PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE.)
+
+In either case:
+
+- The "package" XML attribute refers to the app's package name.
+- The "sha256-cert-digest" XML attribute refers to the SHA-256 hash of an app signing certificate.
+
+For any entry to successfully apply to a package, both XML attributes must be present, and must
+match the package. That is, the package name must match the "package" attribute, and the app must be
+signed by the signing certificate identified by the "sha256-cert-digest" attribute..
 -->
 
 <config></config>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 91e620c..0baaff0 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -185,6 +185,7 @@
         <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
         <permission name="android.permission.UWB_PRIVILEGED"/>
         <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
+        <permission name="android.permission.UPDATE_CONFIG"/>
         <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
     </privapp-permissions>
 
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index ae61a2d..df95a91 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -17,7 +17,7 @@
 package android.graphics;
 
 import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
-import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
+import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION;
 
 import android.annotation.ColorInt;
 import android.annotation.ColorLong;
@@ -293,7 +293,7 @@
      * {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both
      * {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified.
      */
-    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
     public static final int TEXT_RUN_FLAG_LEFT_EDGE = 0x2000;
 
 
@@ -324,7 +324,7 @@
      * {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both
      * {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified.
      */
-    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
     public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 0x4000;
 
     // These flags are always set on a new/reset paint, even if flags 0 is passed.
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index 45e29a8..f359025 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -154,6 +154,12 @@
     // TYPE_DATA: data offset
     private int             mInt2;
 
+    // TYPE_RESOURCE: use the monochrome drawable from an AdaptiveIconDrawable
+    private boolean mUseMonochrome = false;
+
+    // TYPE_RESOURCE: wrap the monochrome drawable in an InsetDrawable with the specified inset
+    private float mInsetScale = 0.0f;
+
     /**
      * Gets the type of the icon provided.
      * <p>
@@ -368,10 +374,34 @@
             result.setTintList(mTintList);
             result.setTintBlendMode(mBlendMode);
         }
+
+        if (mUseMonochrome) {
+            return crateMonochromeDrawable(result, mInsetScale);
+        }
+
         return result;
     }
 
     /**
+     * Gets the monochrome drawable from an {@link AdaptiveIconDrawable}.
+     *
+     * @param drawable An {@link AdaptiveIconDrawable}
+     * @return Adjusted (wrapped in {@link InsetDrawable}) monochrome drawable
+     *  from an {@link AdaptiveIconDrawable}.
+     * Or the original drawable if no monochrome layer exists.
+     */
+    private static Drawable crateMonochromeDrawable(Drawable drawable, float inset) {
+        if (drawable instanceof AdaptiveIconDrawable) {
+            Drawable monochromeDrawable = ((AdaptiveIconDrawable) drawable).getMonochrome();
+            // wrap with negative inset => scale icon (inspired from BaseIconFactory)
+            if (monochromeDrawable != null) {
+                return new InsetDrawable(monochromeDrawable, inset);
+            }
+        }
+        return drawable;
+    }
+
+    /**
      * Resizes image if size too large for Canvas to draw
      * @param bitmap Bitmap to be resized if size > {@link RecordingCanvas.MAX_BITMAP_SIZE}
      * @return resized bitmap
@@ -693,7 +723,9 @@
                         && Arrays.equals(getDataBytes(), otherIcon.getDataBytes());
             case TYPE_RESOURCE:
                 return getResId() == otherIcon.getResId()
-                        && Objects.equals(getResPackage(), otherIcon.getResPackage());
+                        && Objects.equals(getResPackage(), otherIcon.getResPackage())
+                        && mUseMonochrome == otherIcon.mUseMonochrome
+                        && mInsetScale == otherIcon.mInsetScale;
             case TYPE_URI:
             case TYPE_URI_ADAPTIVE_BITMAP:
                 return Objects.equals(getUriString(), otherIcon.getUriString());
@@ -748,6 +780,26 @@
     }
 
     /**
+     * Create an Icon pointing to a drawable resource.
+     * @param resPackage Name of the package containing the resource in question
+     * @param resId ID of the drawable resource
+     * @param useMonochrome if this icon should use the monochrome res from the adaptive drawable
+     * @hide
+     */
+    public static @NonNull Icon createWithResourceAdaptiveDrawable(@NonNull String resPackage,
+            @DrawableRes int resId, boolean useMonochrome, float inset) {
+        if (resPackage == null) {
+            throw new IllegalArgumentException("Resource package name must not be null.");
+        }
+        final Icon rep = new Icon(TYPE_RESOURCE);
+        rep.mInt1 = resId;
+        rep.mUseMonochrome = useMonochrome;
+        rep.mInsetScale = inset;
+        rep.mString1 = resPackage;
+        return rep;
+    }
+
+    /**
      * Create an Icon pointing to a bitmap in memory.
      * @param bits A valid {@link android.graphics.Bitmap} object
      */
@@ -986,6 +1038,8 @@
                 final int resId = in.readInt();
                 mString1 = pkg;
                 mInt1 = resId;
+                mUseMonochrome = in.readBoolean();
+                mInsetScale = in.readFloat();
                 break;
             case TYPE_DATA:
                 final int len = in.readInt();
@@ -1027,6 +1081,8 @@
             case TYPE_RESOURCE:
                 dest.writeString(getResPackage());
                 dest.writeInt(getResId());
+                dest.writeBoolean(mUseMonochrome);
+                dest.writeFloat(mInsetScale);
                 break;
             case TYPE_DATA:
                 dest.writeInt(getDataLength());
diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java
index 9707126..0e9f29d 100644
--- a/graphics/java/android/graphics/text/LineBreaker.java
+++ b/graphics/java/android/graphics/text/LineBreaker.java
@@ -17,7 +17,7 @@
 package android.graphics.text;
 
 import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
-import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
+import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION;
 
 import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
@@ -183,7 +183,7 @@
     /**
      * Value for justification mode indicating the text is justified by stretching letter spacing.
      */
-    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
     public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2;
 
     /**
diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
index 2beb434..2430e8d 100644
--- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java
+++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
 import android.os.StrictMode;
@@ -218,4 +219,28 @@
             return SYSTEM_ERROR;
         }
     }
+
+    /**
+     * Returns the list of Application UIDs that have auth-bound keys that are bound to
+     * the given SID. This enables warning the user when they are about to invalidate
+     * a SID (for example, removing the LSKF).
+     *
+     * @param userId - The ID of the user the SID is associated with.
+     * @param userSecureId - The SID in question.
+     *
+     * @return A list of app UIDs.
+     */
+    public static long[] getAllAppUidsAffectedBySid(int userId, long userSecureId)
+            throws KeyStoreException {
+        StrictMode.noteDiskWrite();
+        try {
+            return getService().getAppUidsAffectedBySid(userId, userSecureId);
+        } catch (RemoteException | NullPointerException e) {
+            throw new KeyStoreException(SYSTEM_ERROR,
+                    "Failure to connect to Keystore while trying to get apps affected by SID.");
+        } catch (ServiceSpecificException e) {
+            throw new KeyStoreException(e.errorCode,
+                    "Keystore error while trying to get apps affected by SID.");
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 9854e58..a80afe2 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -45,9 +45,6 @@
     <!-- Allow PIP to resize to a slightly bigger state upon touch/showing the menu -->
     <bool name="config_pipEnableResizeForMenu">true</bool>
 
-    <!-- Allow PIP to resize via dragging the corner of PiP. -->
-    <bool name="config_pipEnableDragCornerResize">false</bool>
-
     <!-- PiP minimum size, which is a % based off the shorter side of display width and height -->
     <fraction name="config_pipShortestEdgePercent">40%</fraction>
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index a2a2914..7a4ad0a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -912,9 +912,8 @@
         if (uid != -1) {
             intent.putExtra(Settings.EXTRA_APP_UID, uid);
         }
-        intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
         return intent;
     }
 
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 621c453..0aa8959 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
@@ -919,6 +919,9 @@
         if (mStackView != null) {
             mStackView.setVisibility(INVISIBLE);
             removeFromWindowManagerMaybe();
+        } else if (mLayerView != null) {
+            mLayerView.setVisibility(INVISIBLE);
+            removeFromWindowManagerMaybe();
         }
     }
 
@@ -1403,6 +1406,13 @@
         removeFromWindowManagerMaybe();
         mLayerView = null;
         mStackView = null;
+
+        if (!mBubbleData.hasBubbles()) {
+            // if there are no bubbles, don't create the stack or layer views. they will be created
+            // later when the first bubble is added.
+            return;
+        }
+
         ensureBubbleViewsAndWindowCreated();
 
         // inflate bubble views
@@ -1732,6 +1742,10 @@
         public void removeBubble(Bubble removedBubble) {
             if (mLayerView != null) {
                 mLayerView.removeBubble(removedBubble);
+                if (!mBubbleData.hasBubbles() && !isStackExpanded()) {
+                    mLayerView.setVisibility(INVISIBLE);
+                    removeFromWindowManagerMaybe();
+                }
             }
         }
 
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 b95d258..62f2726 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
@@ -273,6 +273,9 @@
             if (endAction != null) {
                 endAction.run();
             }
+            if (mBubbleData.getBubbles().isEmpty()) {
+                mBubbleController.onAllBubblesAnimatedOut();
+            }
         };
         if (mDragController != null && mDragController.isStuckToDismiss()) {
             mAnimationHelper.animateDismiss(runnable);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
index 8b6c7b6..68d26da 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
@@ -140,7 +140,7 @@
         // spec takes the aspect ratio of the bounds into account, so both width and height
         // scale by the same factor.
         addPipExclusionBoundsChangeCallback((bounds) -> {
-            mBoundsScale = Math.min((float) bounds.width() / mMaxSize.x, 1.0f);
+            updateBoundsScale();
         });
     }
 
@@ -152,6 +152,11 @@
         mSizeSpecSource.onConfigurationChanged();
     }
 
+    /** Update the bounds scale percentage value. */
+    public void updateBoundsScale() {
+        mBoundsScale = Math.min((float) mBounds.width() / mMaxSize.x, 1.0f);
+    }
+
     private void reloadResources() {
         mStashOffset = mContext.getResources().getDimensionPixelSize(R.dimen.pip_stash_offset);
     }
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 52a06e0..9f73f1b 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
@@ -1891,6 +1891,9 @@
     }
 
     private void removeContentOverlay(SurfaceControl surface, Runnable callback) {
+        ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "removeContentOverlay: %s, state=%s, surface=%s",
+                mTaskInfo, mPipTransitionState, surface);
         if (mPipOverlay != null) {
             if (mPipOverlay != surface) {
                 ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
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 896ca96..e018ecc 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
@@ -286,12 +286,6 @@
         // For transition that we don't animate, but contains the PIP leash, we need to update the
         // PIP surface, otherwise it will be reset after the transition.
         if (currentPipTaskChange != null) {
-            // Set the "end" bounds of pip. The default setup uses the start bounds. Since this is
-            // changing the *finish*Transaction, we need to use the end bounds. This will also
-            // make sure that the fade-in animation (below) uses the end bounds as well.
-            if (!currentPipTaskChange.getEndAbsBounds().isEmpty()) {
-                mPipBoundsState.setBounds(currentPipTaskChange.getEndAbsBounds());
-            }
             updatePipForUnhandledTransition(currentPipTaskChange, startTransaction,
                     finishTransaction);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index c5a0102..05d4f53 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -244,6 +244,9 @@
             // The same rotation may have been set by auto PiP-able or fixed rotation. So notify
             // the change with fromRotation=false to apply the rotated destination bounds from
             // PipTaskOrganizer#onMovementBoundsChanged.
+            // We need to update the bounds scale in case this was from fixed rotation, as the
+            // current proportion was computed using the previous orientation max size and is wrong.
+            mPipBoundsState.updateBoundsScale();
             updateMovementBounds(null, false /* fromRotation */,
                     false /* fromImeAdjustment */, false /* fromShelfAdjustment */, t);
             return;
@@ -797,21 +800,15 @@
                     mPipBoundsAlgorithm.getMovementBounds(postChangeBounds),
                     mPipBoundsState.getStashedState());
 
-            // Scale PiP on density dpi change, so it appears to be the same size physically.
-            final boolean densityDpiChanged =
-                    mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0
-                    && (mPipDisplayLayoutState.getDisplayLayout().densityDpi()
-                            != layout.densityDpi());
-            if (densityDpiChanged) {
-                final float scale = (float) layout.densityDpi()
-                        / mPipDisplayLayoutState.getDisplayLayout().densityDpi();
-                postChangeBounds.set(0, 0,
-                        (int) (postChangeBounds.width() * scale),
-                        (int) (postChangeBounds.height() * scale));
-            }
-
             updateDisplayLayout.run();
 
+            // Resize the PiP bounds to be at the same scale relative to the new size spec. For
+            // example, if PiP was resized to 90% of the maximum size on the previous layout,
+            // make sure it is 90% of the new maximum size spec.
+            postChangeBounds.set(0, 0,
+                    (int) (mPipBoundsState.getMaxSize().x * mPipBoundsState.getBoundsScale()),
+                    (int) (mPipBoundsState.getMaxSize().y * mPipBoundsState.getBoundsScale()));
+
             // Calculate the PiP bounds in the new orientation based on same fraction along the
             // rotated movement bounds.
             final Rect postChangeMovementBounds = mPipBoundsAlgorithm.getMovementBounds(
@@ -827,6 +824,10 @@
             mPipBoundsState.setHasUserResizedPip(true);
             mTouchHandler.setUserResizeBounds(postChangeBounds);
 
+            final boolean densityDpiChanged =
+                    mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0
+                            && (mPipDisplayLayoutState.getDisplayLayout().densityDpi()
+                            != layout.densityDpi());
             if (densityDpiChanged) {
                 // Using PipMotionHelper#movePip directly here may cause race condition since
                 // the app content in PiP mode may or may not be updated for the new density dpi.
@@ -1146,6 +1147,11 @@
 
         // Update the display layout
         mPipDisplayLayoutState.rotateTo(toRotation);
+        mTouchHandler.updateMinMaxSize(mPipBoundsState.getAspectRatio());
+
+        postChangeStackBounds.set(0, 0,
+                (int) (mPipBoundsState.getMaxSize().x * mPipBoundsState.getBoundsScale()),
+                (int) (mPipBoundsState.getMaxSize().y * mPipBoundsState.getBoundsScale()));
 
         // Calculate the stack bounds in the new orientation based on same fraction along the
         // rotated movement bounds.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index f175775..5f9195a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -15,19 +15,13 @@
  */
 package com.android.wm.shell.pip.phone;
 
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT;
 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
-import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
-import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.graphics.Region;
 import android.hardware.input.InputManager;
 import android.os.Looper;
 import android.view.BatchedInputEventReceiver;
@@ -41,7 +35,6 @@
 
 import androidx.annotation.VisibleForTesting;
 
-import com.android.internal.policy.TaskResizingAlgorithm;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -53,7 +46,6 @@
 
 import java.io.PrintWriter;
 import java.util.function.Consumer;
-import java.util.function.Function;
 
 /**
  * Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to
@@ -77,7 +69,6 @@
     private final PipPinchResizingAlgorithm mPinchResizingAlgorithm;
     private final int mDisplayId;
     private final ShellExecutor mMainExecutor;
-    private final Region mTmpRegion = new Region();
 
     private final PointF mDownPoint = new PointF();
     private final PointF mDownSecondPoint = new PointF();
@@ -88,24 +79,15 @@
     private final Rect mLastResizeBounds = new Rect();
     private final Rect mUserResizeBounds = new Rect();
     private final Rect mDownBounds = new Rect();
-    private final Rect mDragCornerSize = new Rect();
-    private final Rect mTmpTopLeftCorner = new Rect();
-    private final Rect mTmpTopRightCorner = new Rect();
-    private final Rect mTmpBottomLeftCorner = new Rect();
-    private final Rect mTmpBottomRightCorner = new Rect();
-    private final Rect mDisplayBounds = new Rect();
-    private final Function<Rect, Rect> mMovementBoundsSupplier;
     private final Runnable mUpdateMovementBoundsRunnable;
     private final Consumer<Rect> mUpdateResizeBoundsCallback;
 
-    private int mDelta;
     private float mTouchSlop;
 
     private boolean mAllowGesture;
     private boolean mIsAttached;
     private boolean mIsEnabled;
     private boolean mEnablePinchResize;
-    private boolean mEnableDragCornerResize;
     private boolean mIsSysUiStateValid;
     private boolean mThresholdCrossed;
     private boolean mOngoingPinchToResize = false;
@@ -123,7 +105,7 @@
             PipBoundsState pipBoundsState, PipMotionHelper motionHelper,
             PipTouchState pipTouchState, PipTaskOrganizer pipTaskOrganizer,
             PipDismissTargetHandler pipDismissTargetHandler,
-            Function<Rect, Rect> movementBoundsSupplier, Runnable updateMovementBoundsRunnable,
+            Runnable updateMovementBoundsRunnable,
             PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController,
             ShellExecutor mainExecutor) {
         mContext = context;
@@ -135,7 +117,6 @@
         mPipTouchState = pipTouchState;
         mPipTaskOrganizer = pipTaskOrganizer;
         mPipDismissTargetHandler = pipDismissTargetHandler;
-        mMovementBoundsSupplier = movementBoundsSupplier;
         mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
         mPhonePipMenuController = menuActivityController;
         mPipUiEventLogger = pipUiEventLogger;
@@ -171,20 +152,9 @@
     }
 
     private void reloadResources() {
-        final Resources res = mContext.getResources();
-        mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size);
-        mEnableDragCornerResize = res.getBoolean(R.bool.config_pipEnableDragCornerResize);
         mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
     }
 
-    private void resetDragCorners() {
-        mDragCornerSize.set(0, 0, mDelta, mDelta);
-        mTmpTopLeftCorner.set(mDragCornerSize);
-        mTmpTopRightCorner.set(mDragCornerSize);
-        mTmpBottomLeftCorner.set(mDragCornerSize);
-        mTmpBottomRightCorner.set(mDragCornerSize);
-    }
-
     private void disposeInputChannel() {
         if (mInputEventReceiver != null) {
             mInputEventReceiver.dispose();
@@ -232,7 +202,7 @@
 
     @VisibleForTesting
     void onInputEvent(InputEvent ev) {
-        if (!mEnableDragCornerResize && !mEnablePinchResize) {
+        if (!mEnablePinchResize) {
             // No need to handle anything if neither form of resizing is enabled.
             return;
         }
@@ -260,8 +230,6 @@
 
             if (mEnablePinchResize && mOngoingPinchToResize) {
                 onPinchResize(mv);
-            } else if (mEnableDragCornerResize) {
-                onDragCornerResize(mv);
             }
         }
     }
@@ -273,48 +241,6 @@
         return mCtrlType != CTRL_NONE || mOngoingPinchToResize;
     }
 
-    /**
-     * Check whether the current x,y coordinate is within the region in which drag-resize should
-     * start.
-     * This consists of 4 small squares on the 4 corners of the PIP window, a quarter of which
-     * overlaps with the PIP window while the rest goes outside of the PIP window.
-     *  _ _           _ _
-     * |_|_|_________|_|_|
-     * |_|_|         |_|_|
-     *   |     PIP     |
-     *   |   WINDOW    |
-     *  _|_           _|_
-     * |_|_|_________|_|_|
-     * |_|_|         |_|_|
-     */
-    public boolean isWithinDragResizeRegion(int x, int y) {
-        if (!mEnableDragCornerResize) {
-            return false;
-        }
-
-        final Rect currentPipBounds = mPipBoundsState.getBounds();
-        if (currentPipBounds == null) {
-            return false;
-        }
-        resetDragCorners();
-        mTmpTopLeftCorner.offset(currentPipBounds.left - mDelta / 2,
-                currentPipBounds.top - mDelta /  2);
-        mTmpTopRightCorner.offset(currentPipBounds.right - mDelta / 2,
-                currentPipBounds.top - mDelta /  2);
-        mTmpBottomLeftCorner.offset(currentPipBounds.left - mDelta / 2,
-                currentPipBounds.bottom - mDelta /  2);
-        mTmpBottomRightCorner.offset(currentPipBounds.right - mDelta / 2,
-                currentPipBounds.bottom - mDelta /  2);
-
-        mTmpRegion.setEmpty();
-        mTmpRegion.op(mTmpTopLeftCorner, Region.Op.UNION);
-        mTmpRegion.op(mTmpTopRightCorner, Region.Op.UNION);
-        mTmpRegion.op(mTmpBottomLeftCorner, Region.Op.UNION);
-        mTmpRegion.op(mTmpBottomRightCorner, Region.Op.UNION);
-
-        return mTmpRegion.contains(x, y);
-    }
-
     public boolean isUsingPinchToZoom() {
         return mEnablePinchResize;
     }
@@ -325,62 +251,17 @@
 
     public boolean willStartResizeGesture(MotionEvent ev) {
         if (isInValidSysUiState()) {
-            switch (ev.getActionMasked()) {
-                case MotionEvent.ACTION_DOWN:
-                    if (isWithinDragResizeRegion((int) ev.getRawX(), (int) ev.getRawY())) {
-                        return true;
-                    }
-                    break;
-
-                case MotionEvent.ACTION_POINTER_DOWN:
-                    if (mEnablePinchResize && ev.getPointerCount() == 2) {
-                        onPinchResize(ev);
-                        mOngoingPinchToResize = mAllowGesture;
-                        return mAllowGesture;
-                    }
-                    break;
-
-                default:
-                    break;
+            if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
+                if (mEnablePinchResize && ev.getPointerCount() == 2) {
+                    onPinchResize(ev);
+                    mOngoingPinchToResize = mAllowGesture;
+                    return mAllowGesture;
+                }
             }
         }
         return false;
     }
 
-    private void setCtrlType(int x, int y) {
-        final Rect currentPipBounds = mPipBoundsState.getBounds();
-
-        Rect movementBounds = mMovementBoundsSupplier.apply(currentPipBounds);
-
-        mDisplayBounds.set(movementBounds.left,
-                movementBounds.top,
-                movementBounds.right + currentPipBounds.width(),
-                movementBounds.bottom + currentPipBounds.height());
-
-        if (mTmpTopLeftCorner.contains(x, y) && currentPipBounds.top != mDisplayBounds.top
-                && currentPipBounds.left != mDisplayBounds.left) {
-            mCtrlType |= CTRL_LEFT;
-            mCtrlType |= CTRL_TOP;
-        }
-        if (mTmpTopRightCorner.contains(x, y) && currentPipBounds.top != mDisplayBounds.top
-                && currentPipBounds.right != mDisplayBounds.right) {
-            mCtrlType |= CTRL_RIGHT;
-            mCtrlType |= CTRL_TOP;
-        }
-        if (mTmpBottomRightCorner.contains(x, y)
-                && currentPipBounds.bottom != mDisplayBounds.bottom
-                && currentPipBounds.right != mDisplayBounds.right) {
-            mCtrlType |= CTRL_RIGHT;
-            mCtrlType |= CTRL_BOTTOM;
-        }
-        if (mTmpBottomLeftCorner.contains(x, y)
-                && currentPipBounds.bottom != mDisplayBounds.bottom
-                && currentPipBounds.left != mDisplayBounds.left) {
-            mCtrlType |= CTRL_LEFT;
-            mCtrlType |= CTRL_BOTTOM;
-        }
-    }
-
     private boolean isInValidSysUiState() {
         return mIsSysUiStateValid;
     }
@@ -457,59 +338,6 @@
         }
     }
 
-    private void onDragCornerResize(MotionEvent ev) {
-        int action = ev.getActionMasked();
-        float x = ev.getX();
-        float y = ev.getY() - mOhmOffset;
-        if (action == MotionEvent.ACTION_DOWN) {
-            mLastResizeBounds.setEmpty();
-            mAllowGesture = isInValidSysUiState() && isWithinDragResizeRegion((int) x, (int) y);
-            if (mAllowGesture) {
-                setCtrlType((int) x, (int) y);
-                mDownPoint.set(x, y);
-                mDownBounds.set(mPipBoundsState.getBounds());
-            }
-        } else if (mAllowGesture) {
-            switch (action) {
-                case MotionEvent.ACTION_POINTER_DOWN:
-                    // We do not support multi touch for resizing via drag
-                    mAllowGesture = false;
-                    break;
-                case MotionEvent.ACTION_MOVE:
-                    // Capture inputs
-                    if (!mThresholdCrossed
-                            && Math.hypot(x - mDownPoint.x, y - mDownPoint.y) > mTouchSlop) {
-                        mThresholdCrossed = true;
-                        // Reset the down to begin resizing from this point
-                        mDownPoint.set(x, y);
-                        mInputMonitor.pilferPointers();
-                    }
-                    if (mThresholdCrossed) {
-                        if (mPhonePipMenuController.isMenuVisible()) {
-                            mPhonePipMenuController.hideMenu(ANIM_TYPE_NONE,
-                                    false /* resize */);
-                        }
-                        final Rect currentPipBounds = mPipBoundsState.getBounds();
-                        mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(x, y,
-                                mDownPoint.x, mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x,
-                                mMinSize.y, mMaxSize, true,
-                                mDownBounds.width() > mDownBounds.height()));
-                        mPipBoundsAlgorithm.transformBoundsToAspectRatio(mLastResizeBounds,
-                                mPipBoundsState.getAspectRatio(), false /* useCurrentMinEdgeSize */,
-                                true /* useCurrentSize */);
-                        mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds,
-                                null);
-                        mPipBoundsState.setHasUserResizedPip(true);
-                    }
-                    break;
-                case MotionEvent.ACTION_UP:
-                case MotionEvent.ACTION_CANCEL:
-                    finishResize();
-                    break;
-            }
-        }
-    }
-
     private void snapToMovementBoundsEdge(Rect bounds, Rect movementBounds) {
         final int leftEdge = bounds.left;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 81705e2..11c356d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -212,7 +212,7 @@
         mPipResizeGestureHandler =
                 new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
                         mMotionHelper, mTouchState, pipTaskOrganizer, mPipDismissTargetHandler,
-                        this::getMovementBounds, this::updateMovementBounds, pipUiEventLogger,
+                        this::updateMovementBounds, pipUiEventLogger,
                         menuController, mainExecutor);
         mConnection = new PipAccessibilityInteractionConnection(mContext, pipBoundsState,
                 mMotionHelper, pipTaskOrganizer, mPipBoundsAlgorithm.getSnapAlgorithm(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index a666e20..bfb60c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -253,7 +253,7 @@
                 : taskInfo.topActivityInfo;
         params.layoutInDisplayCutoutMode = a.getInt(
                 R.styleable.Window_windowLayoutInDisplayCutoutMode,
-                PhoneWindow.isEdgeToEdgeEnforced(activityInfo.applicationInfo, false /* local */)
+                PhoneWindow.isEdgeToEdgeEnforced(activityInfo.applicationInfo, false /* local */, a)
                         ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
                         : params.layoutInDisplayCutoutMode);
         params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/OWNERS
new file mode 100644
index 0000000..2ff4d90
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/OWNERS
@@ -0,0 +1,4 @@
+# WM shell transition tracing owners
+# Bug component: 1157642
[email protected]
[email protected]
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
index b3e8bd9..fa331af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
@@ -196,14 +196,18 @@
         mDataSource.trace(ctx -> {
             final ProtoOutputStream os = ctx.newTracePacket();
 
+            final long mappingsToken = os.start(PerfettoTrace.TracePacket.SHELL_HANDLER_MAPPINGS);
             for (Map.Entry<String, Integer> entry : mHandlerMapping.entrySet()) {
                 final String handler = entry.getKey();
                 final int handlerId = entry.getValue();
-                final long token = os.start(PerfettoTrace.TracePacket.SHELL_HANDLER_MAPPINGS);
+
+                final long mappingEntryToken = os.start(PerfettoTrace.ShellHandlerMappings.MAPPING);
                 os.write(PerfettoTrace.ShellHandlerMapping.ID, handlerId);
                 os.write(PerfettoTrace.ShellHandlerMapping.NAME, handler);
-                os.end(token);
+                os.end(mappingEntryToken);
+
             }
+            os.end(mappingsToken);
 
             ctx.flush();
         });
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index 8207b85..07cd682 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -86,6 +86,8 @@
                 .withNavOrTaskBarVisible()
                 .withStatusBarVisible()
                 .waitForAndVerify()
+
+            pipApp.tapPipToShowMenu(wmHelper)
         }
     }
 
@@ -194,6 +196,16 @@
         }
     }
 
+    @Postsubmit
+    @Test
+    fun menuOverlayMatchesTaskSurface() {
+        flicker.assertLayersEnd {
+            val pipAppRegion = visibleRegion(pipApp)
+            val pipMenuRegion = visibleRegion(ComponentNameMatcher.PIP_MENU_OVERLAY)
+            pipAppRegion.coversExactly(pipMenuRegion.region)
+        }
+    }
+
     /** {@inheritDoc} */
     @FlakyTest(bugId = 267424412)
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 9719ba8..cc726cb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -121,7 +121,7 @@
         mPipResizeGestureHandler = new PipResizeGestureHandler(mContext, pipBoundsAlgorithm,
                 mPipBoundsState, motionHelper, mPipTouchState, mPipTaskOrganizer,
                 mPipDismissTargetHandler,
-                (Rect bounds) -> new Rect(), () -> {}, mPipUiEventLogger, mPhonePipMenuController,
+                () -> {}, mPipUiEventLogger, mPhonePipMenuController,
                 mMainExecutor) {
             @Override
             public void pilferPointers() {
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index 6ebfc63..ac75c07 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -41,9 +41,9 @@
 #endif  // __ANDROID__
 }
 
-inline bool inter_character_justification() {
+inline bool letter_spacing_justification() {
 #ifdef __ANDROID__
-    return com_android_text_flags_inter_character_justification();
+    return com_android_text_flags_letter_spacing_justification();
 #else
     return true;
 #endif  // __ANDROID__
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index 5613369..d66d7f8 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -72,7 +72,7 @@
     const minikin::Range contextRange(contextStart, contextStart + contextCount);
     const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
     const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
-    const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification()
+    const minikin::RunFlag minikinRunFlag = text_feature::letter_spacing_justification()
                                                     ? paint->getRunFlag()
                                                     : minikin::RunFlag::NONE;
 
@@ -105,7 +105,7 @@
     const minikin::Range range(start, start + count);
     const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
     const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
-    const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification()
+    const minikin::RunFlag minikinRunFlag = text_feature::letter_spacing_justification()
                                                     ? paint->getRunFlag()
                                                     : minikin::RunFlag::NONE;
 
diff --git a/location/java/android/location/GnssStatus.java b/location/java/android/location/GnssStatus.java
index 09f40e8..970bfda 100644
--- a/location/java/android/location/GnssStatus.java
+++ b/location/java/android/location/GnssStatus.java
@@ -195,7 +195,7 @@
      * <li>SBAS: 120-151, 183-192</li>
      * <li>GLONASS: One of: OSN, or FCN+100
      * <ul>
-     * <li>1-24 as the orbital slot number (OSN) (preferred, if known)</li>
+     * <li>1-25 as the orbital slot number (OSN) (preferred, if known)</li>
      * <li>93-106 as the frequency channel number (FCN) (-7 to +6) plus 100.
      * i.e. encode FCN of -7 as 93, 0 as 100, and +6 as 106</li>
      * </ul></li>
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index 4fbe9ee..6ac9695 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -40,17 +40,6 @@
     },
     {
       "file_patterns": [
-        "[^/]*(Ringtone)[^/]*\\.java"
-      ],
-      "name": "MediaRingtoneTests",
-      "options": [
-        {"exclude-annotation": "androidx.test.filters.LargeTest"},
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-        {"exclude-annotation": "org.junit.Ignore"}
-      ]
-    },
-    {
-      "file_patterns": [
         "[^/]*(LoudnessCodec)[^/]*\\.java"
       ],
       "name": "LoudnessCodecApiTest",
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 69708ec..e9ba779 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -5166,13 +5166,12 @@
      *     dispatch was successfully sent, or {@link #AUDIOFOCUS_REQUEST_DELAYED} if
      *     the request was successful but the dispatch of focus change was delayed due to a fade
      *     operation.
-     * @throws NullPointerException if the {@link AudioFocusInfo} or {@link AudioPolicy} or list of
-     *     other active {@link AudioFocusInfo} are {@code null}.
      * @hide
      */
     @FlaggedApi(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
     @SystemApi
     @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+    @FocusRequestResult
     public int dispatchAudioFocusChangeWithFade(@NonNull AudioFocusInfo afi, int focusChange,
             @NonNull AudioPolicy ap, @NonNull List<AudioFocusInfo> otherActiveAfis,
             @Nullable FadeManagerConfiguration transientFadeMgrConfig) {
diff --git a/media/java/android/media/IRingtonePlayer.aidl b/media/java/android/media/IRingtonePlayer.aidl
index 1e57be2..c96a400 100644
--- a/media/java/android/media/IRingtonePlayer.aidl
+++ b/media/java/android/media/IRingtonePlayer.aidl
@@ -21,7 +21,6 @@
 import android.net.Uri;
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
-import android.os.VibrationEffect;
 
 /**
  * @hide
@@ -30,23 +29,12 @@
     /** Used for Ringtone.java playback */
     @UnsupportedAppUsage
     oneway void play(IBinder token, in Uri uri, in AudioAttributes aa, float volume, boolean looping);
+    oneway void playWithVolumeShaping(IBinder token, in Uri uri, in AudioAttributes aa,
+        float volume, boolean looping, in @nullable VolumeShaper.Configuration volumeShaperConfig);
     oneway void stop(IBinder token);
     boolean isPlaying(IBinder token);
-
-    // RingtoneV1
-    oneway void playWithVolumeShaping(IBinder token, in Uri uri, in AudioAttributes aa,
-            float volume, boolean looping, in @nullable VolumeShaper.Configuration volumeShaperConfig);
     oneway void setPlaybackProperties(IBinder token, float volume, boolean looping,
-            boolean hapticGeneratorEnabled);
-
-    // RingtoneV2
-    oneway void playRemoteRingtone(IBinder token, in Uri uri, in AudioAttributes aa,
-        boolean useExactAudioAttributes, int enabledMedia, in @nullable VibrationEffect ve,
-        float volume, boolean looping, boolean hapticGeneratorEnabled,
-        in @nullable VolumeShaper.Configuration volumeShaperConfig);
-    oneway void setLooping(IBinder token, boolean looping);
-    oneway void setVolume(IBinder token, float volume);
-    oneway void setHapticGeneratorEnabled(IBinder token, boolean hapticGeneratorEnabled);
+        boolean hapticGeneratorEnabled);
 
     /** Used for Notification sound playback. */
     oneway void playAsync(in Uri uri, in UserHandle user, boolean looping, in AudioAttributes aa, float volume);
diff --git a/media/java/android/media/LocalRingtonePlayer.java b/media/java/android/media/LocalRingtonePlayer.java
deleted file mode 100644
index fe7cc3e..0000000
--- a/media/java/android/media/LocalRingtonePlayer.java
+++ /dev/null
@@ -1,408 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.media.audiofx.HapticGenerator;
-import android.net.Uri;
-import android.os.Trace;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.util.Log;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Objects;
-
-/**
- * Plays a ringtone on the local process.
- * @hide
- */
-public class LocalRingtonePlayer
-        implements RingtoneV2.RingtonePlayer, MediaPlayer.OnCompletionListener {
-    private static final String TAG = "LocalRingtonePlayer";
-
-    // keep references on active Ringtones until stopped or completion listener called.
-    private static final ArrayList<LocalRingtonePlayer> sActiveMediaPlayers = new ArrayList<>();
-
-    private final MediaPlayer mMediaPlayer;
-    private final AudioAttributes mAudioAttributes;
-    private final RingtoneV2.RingtonePlayer mVibrationPlayer;
-    private final Ringtone.Injectables mInjectables;
-    private final AudioManager mAudioManager;
-    private final VolumeShaper mVolumeShaper;
-    private HapticGenerator mHapticGenerator;
-
-    private LocalRingtonePlayer(@NonNull MediaPlayer mediaPlayer,
-            @NonNull AudioAttributes audioAttributes, @NonNull Ringtone.Injectables injectables,
-            @NonNull AudioManager audioManager, @Nullable HapticGenerator hapticGenerator,
-            @Nullable VolumeShaper volumeShaper,
-            @Nullable RingtoneV2.RingtonePlayer vibrationPlayer) {
-        Objects.requireNonNull(mediaPlayer);
-        Objects.requireNonNull(audioAttributes);
-        Objects.requireNonNull(injectables);
-        Objects.requireNonNull(audioManager);
-        mMediaPlayer = mediaPlayer;
-        mAudioAttributes = audioAttributes;
-        mInjectables = injectables;
-        mAudioManager = audioManager;
-        mVolumeShaper = volumeShaper;
-        mVibrationPlayer = vibrationPlayer;
-        mHapticGenerator = hapticGenerator;
-    }
-
-    /**
-     * Creates a {@link LocalRingtonePlayer} for a Uri, returning null if the Uri can't be
-     * loaded in the local player.
-     */
-    @Nullable
-    static RingtoneV2.RingtonePlayer create(@NonNull Context context,
-            @NonNull AudioManager audioManager, @NonNull Vibrator vibrator,
-            @NonNull Uri soundUri,
-            @NonNull AudioAttributes audioAttributes,
-            boolean isVibrationOnly,
-            @Nullable VibrationEffect vibrationEffect,
-            @NonNull Ringtone.Injectables injectables,
-            @Nullable VolumeShaper.Configuration volumeShaperConfig,
-            @Nullable AudioDeviceInfo preferredDevice, boolean initialHapticGeneratorEnabled,
-            boolean initialLooping, float initialVolume) {
-        Objects.requireNonNull(context);
-        Objects.requireNonNull(soundUri);
-        Objects.requireNonNull(audioAttributes);
-        Trace.beginSection("createLocalMediaPlayer");
-        MediaPlayer mediaPlayer = injectables.newMediaPlayer();
-        HapticGenerator hapticGenerator = null;
-        try {
-            mediaPlayer.setDataSource(context, soundUri);
-            mediaPlayer.setAudioAttributes(audioAttributes);
-            mediaPlayer.setPreferredDevice(preferredDevice);
-            mediaPlayer.setLooping(initialLooping);
-            mediaPlayer.setVolume(isVibrationOnly ? 0 : initialVolume);
-            if (initialHapticGeneratorEnabled) {
-                hapticGenerator = injectables.createHapticGenerator(mediaPlayer);
-                if (hapticGenerator != null) {
-                    // In practise, this should always be non-null because the initial value is
-                    // not true unless it's available.
-                    hapticGenerator.setEnabled(true);
-                    vibrationEffect = null;  // Don't play the VibrationEffect.
-                }
-            }
-            VolumeShaper volumeShaper = null;
-            if (volumeShaperConfig != null) {
-                volumeShaper = mediaPlayer.createVolumeShaper(volumeShaperConfig);
-            }
-            mediaPlayer.prepare();
-            if (vibrationEffect != null && !audioAttributes.areHapticChannelsMuted()) {
-                if (injectables.hasHapticChannels(mediaPlayer)) {
-                    // Don't play the Vibration effect if the URI has haptic channels.
-                    vibrationEffect = null;
-                }
-            }
-            VibrationEffectPlayer vibrationEffectPlayer = (vibrationEffect == null) ? null :
-                    new VibrationEffectPlayer(
-                            vibrationEffect, audioAttributes, vibrator, initialLooping);
-            if (isVibrationOnly && vibrationEffectPlayer != null) {
-                // Abandon the media player now that it's confirmed to not have haptic channels.
-                mediaPlayer.release();
-                return vibrationEffectPlayer;
-            }
-            return new LocalRingtonePlayer(mediaPlayer, audioAttributes, injectables, audioManager,
-                    hapticGenerator, volumeShaper, vibrationEffectPlayer);
-        } catch (SecurityException | IOException e) {
-            if (hapticGenerator != null) {
-                hapticGenerator.release();
-            }
-            // volume shaper closes with media player
-            mediaPlayer.release();
-            return null;
-        } finally {
-            Trace.endSection();
-        }
-    }
-
-    /**
-     * Creates a {@link LocalRingtonePlayer} for an externally referenced file descriptor. This is
-     * intended for loading a fallback from an internal resource, rather than via a Uri.
-     */
-    @Nullable
-    static LocalRingtonePlayer createForFallback(
-            @NonNull AudioManager audioManager, @NonNull Vibrator vibrator,
-            @NonNull AssetFileDescriptor afd,
-            @NonNull AudioAttributes audioAttributes,
-            @Nullable VibrationEffect vibrationEffect,
-            @NonNull Ringtone.Injectables injectables,
-            @Nullable VolumeShaper.Configuration volumeShaperConfig,
-            @Nullable AudioDeviceInfo preferredDevice,
-            boolean initialLooping, float initialVolume) {
-        // Haptic generator not supported for fallback.
-        Objects.requireNonNull(audioManager);
-        Objects.requireNonNull(afd);
-        Objects.requireNonNull(audioAttributes);
-        Trace.beginSection("createFallbackLocalMediaPlayer");
-
-        MediaPlayer mediaPlayer = injectables.newMediaPlayer();
-        try {
-            if (afd.getDeclaredLength() < 0) {
-                mediaPlayer.setDataSource(afd.getFileDescriptor());
-            } else {
-                mediaPlayer.setDataSource(afd.getFileDescriptor(),
-                        afd.getStartOffset(),
-                        afd.getDeclaredLength());
-            }
-            mediaPlayer.setAudioAttributes(audioAttributes);
-            mediaPlayer.setPreferredDevice(preferredDevice);
-            mediaPlayer.setLooping(initialLooping);
-            mediaPlayer.setVolume(initialVolume);
-            VolumeShaper volumeShaper = null;
-            if (volumeShaperConfig != null) {
-                volumeShaper = mediaPlayer.createVolumeShaper(volumeShaperConfig);
-            }
-            mediaPlayer.prepare();
-            if (vibrationEffect != null && !audioAttributes.areHapticChannelsMuted()) {
-                if (injectables.hasHapticChannels(mediaPlayer)) {
-                    // Don't play the Vibration effect if the URI has haptic channels.
-                    vibrationEffect = null;
-                }
-            }
-            VibrationEffectPlayer vibrationEffectPlayer = (vibrationEffect == null) ? null :
-                    new VibrationEffectPlayer(
-                            vibrationEffect, audioAttributes, vibrator, initialLooping);
-            return new LocalRingtonePlayer(mediaPlayer, audioAttributes,  injectables, audioManager,
-                    /* hapticGenerator= */ null, volumeShaper, vibrationEffectPlayer);
-        } catch (SecurityException | IOException e) {
-            Log.e(TAG, "Failed to open fallback ringtone");
-            // TODO: vibration-effect-only / no-sound LocalRingtonePlayer.
-            mediaPlayer.release();
-            return null;
-        } finally {
-            Trace.endSection();
-        }
-    }
-
-    @Override
-    public boolean play() {
-        // Play ringtones if stream volume is over 0 or if it is a haptic-only ringtone
-        // (typically because ringer mode is vibrate).
-        if (mAudioManager.getStreamVolume(AudioAttributes.toLegacyStreamType(mAudioAttributes))
-                == 0 && (mAudioAttributes.areHapticChannelsMuted() || !hasHapticChannels())) {
-            maybeStartVibration();
-            return true;  // Successfully played while muted.
-        }
-        synchronized (sActiveMediaPlayers) {
-            // We keep-alive when a mediaplayer is active, since its finalizer would stop the
-            // ringtone. This isn't necessary for vibrations in the vibrator service
-            // (i.e. maybeStartVibration in the muted case, above).
-            sActiveMediaPlayers.add(this);
-        }
-
-        mMediaPlayer.setOnCompletionListener(this);
-        mMediaPlayer.start();
-        if (mVolumeShaper != null) {
-            mVolumeShaper.apply(VolumeShaper.Operation.PLAY);
-        }
-        maybeStartVibration();
-        return true;
-    }
-
-    private void maybeStartVibration() {
-        if (mVibrationPlayer != null) {
-            mVibrationPlayer.play();
-        }
-    }
-
-    @Override
-    public boolean isPlaying() {
-        return mMediaPlayer.isPlaying();
-    }
-
-    @Override
-    public void stopAndRelease() {
-        synchronized (sActiveMediaPlayers) {
-            sActiveMediaPlayers.remove(this);
-        }
-        try {
-            mMediaPlayer.stop();
-        } finally {
-            if (mVibrationPlayer != null) {
-                try {
-                    mVibrationPlayer.stopAndRelease();
-                } catch (Exception e) {
-                    Log.e(TAG, "Exception stopping ringtone vibration", e);
-                }
-            }
-            if (mHapticGenerator != null) {
-                mHapticGenerator.release();
-            }
-            mMediaPlayer.setOnCompletionListener(null);
-            mMediaPlayer.reset();
-            mMediaPlayer.release();
-        }
-    }
-
-    @Override
-    public void setPreferredDevice(@Nullable AudioDeviceInfo audioDeviceInfo) {
-        mMediaPlayer.setPreferredDevice(audioDeviceInfo);
-    }
-
-    @Override
-    public void setLooping(boolean looping) {
-        boolean wasLooping = mMediaPlayer.isLooping();
-        if (wasLooping == looping) {
-            return;
-        }
-        mMediaPlayer.setLooping(looping);
-        if (mVibrationPlayer != null) {
-            mVibrationPlayer.setLooping(looping);
-        }
-    }
-
-    @Override
-    public void setHapticGeneratorEnabled(boolean enabled) {
-        if (mVibrationPlayer != null) {
-            // Ignore haptic generator changes if a vibration player is present. The decision to
-            // use one or the other happens before this object is constructed.
-            return;
-        }
-        if (enabled && mHapticGenerator == null && !hasHapticChannels()) {
-            mHapticGenerator = mInjectables.createHapticGenerator(mMediaPlayer);
-        }
-        if (mHapticGenerator != null) {
-            mHapticGenerator.setEnabled(enabled);
-        }
-    }
-
-    @Override
-    public void setVolume(float volume) {
-        mMediaPlayer.setVolume(volume);
-        // no effect on vibration player
-    }
-
-    @Override
-    public boolean hasHapticChannels() {
-        return mInjectables.hasHapticChannels(mMediaPlayer);
-    }
-
-    @Override
-    public void onCompletion(MediaPlayer mp) {
-        synchronized (sActiveMediaPlayers) {
-            sActiveMediaPlayers.remove(this);
-        }
-        mp.setOnCompletionListener(null); // Help the Java GC: break the refcount cycle.
-        // No effect on vibration: either it's looping and this callback only happens when stopped,
-        // or it's not looping, in which case the vibration should play to its own completion.
-    }
-
-    /** A RingtonePlayer that only plays a VibrationEffect. */
-    static class VibrationEffectPlayer implements RingtoneV2.RingtonePlayer {
-        private static final int VIBRATION_LOOP_DELAY_MS = 200;
-        private final VibrationEffect mVibrationEffect;
-        private final VibrationAttributes mVibrationAttributes;
-        private final Vibrator mVibrator;
-        private boolean mIsLooping;
-        private boolean mStartedVibration;
-
-        VibrationEffectPlayer(@NonNull VibrationEffect vibrationEffect,
-                @NonNull AudioAttributes audioAttributes,
-                @NonNull Vibrator vibrator, boolean initialLooping) {
-            mVibrationEffect = vibrationEffect;
-            mVibrationAttributes = new VibrationAttributes.Builder(audioAttributes).build();
-            mVibrator = vibrator;
-            mIsLooping = initialLooping;
-        }
-
-        @Override
-        public boolean play() {
-            if (!mStartedVibration) {
-                try {
-                    // Adjust the vibration effect to loop.
-                    VibrationEffect loopAdjustedEffect =
-                            mVibrationEffect.applyRepeatingIndefinitely(
-                                mIsLooping, VIBRATION_LOOP_DELAY_MS);
-                    mVibrator.vibrate(loopAdjustedEffect, mVibrationAttributes);
-                    mStartedVibration = true;
-                } catch (Exception e) {
-                    // Catch exceptions widely, because we don't want to "leak" looping sounds or
-                    // vibrations if something goes wrong.
-                    Log.e(TAG, "Problem starting " + (mIsLooping ? "looping " : "") + "vibration "
-                            + "for ringtone: " + mVibrationEffect, e);
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        @Override
-        public boolean isPlaying() {
-            return mStartedVibration;
-        }
-
-        @Override
-        public void stopAndRelease() {
-            if (mStartedVibration) {
-                try {
-                    mVibrator.cancel(mVibrationAttributes.getUsage());
-                    mStartedVibration = false;
-                } catch (Exception e) {
-                    // Catch exceptions widely, because we don't want to "leak" looping sounds or
-                    // vibrations if something goes wrong.
-                    Log.e(TAG, "Problem stopping vibration for ringtone", e);
-                }
-            }
-        }
-
-        @Override
-        public void setPreferredDevice(AudioDeviceInfo audioDeviceInfo) {
-            // no-op
-        }
-
-        @Override
-        public void setLooping(boolean looping) {
-            if (looping == mIsLooping) {
-                return;
-            }
-            mIsLooping = looping;
-            if (mStartedVibration) {
-                if (!mIsLooping) {
-                    // Was looping, stop looping
-                    stopAndRelease();
-                }
-                // Else was not looping, but can't interfere with a running vibration without
-                // restarting it, and don't know if it was finished. So do nothing: apps shouldn't
-                // toggle looping after calling play anyway.
-            }
-        }
-
-        @Override
-        public void setHapticGeneratorEnabled(boolean enabled) {
-            // n/a
-        }
-
-        @Override
-        public void setVolume(float volume) {
-            // n/a
-        }
-
-        @Override
-        public boolean hasHapticChannels() {
-            return false;
-        }
-    }
-}
diff --git a/media/java/android/media/OWNERS b/media/java/android/media/OWNERS
index 058c5be..49890c1 100644
--- a/media/java/android/media/OWNERS
+++ b/media/java/android/media/OWNERS
@@ -11,6 +11,3 @@
 
 per-file ExifInterface.java,ExifInterfaceUtils.java,IMediaHTTPConnection.aidl,IMediaHTTPService.aidl,JetPlayer.java,MediaDataSource.java,MediaExtractor.java,MediaHTTPConnection.java,MediaHTTPService.java,MediaPlayer.java=set noparent
 per-file ExifInterface.java,ExifInterfaceUtils.java,IMediaHTTPConnection.aidl,IMediaHTTPService.aidl,JetPlayer.java,MediaDataSource.java,MediaExtractor.java,MediaHTTPConnection.java,MediaHTTPService.java,MediaPlayer.java=file:platform/frameworks/av:/media/janitors/media_solutions_OWNERS
-
-# Haptics team also works on Ringtone
-per-file *Ringtone* = file:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index 8800dc8..e78dc31 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -16,31 +16,27 @@
 
 package android.media;
 
-import android.Manifest;
-import android.annotation.IntDef;
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources.NotFoundException;
 import android.database.Cursor;
 import android.media.audiofx.HapticGenerator;
 import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
 import android.os.RemoteException;
 import android.os.Trace;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
 import android.provider.MediaStore;
 import android.provider.MediaStore.MediaColumns;
 import android.provider.Settings;
 import android.util.Log;
-
 import com.android.internal.annotations.VisibleForTesting;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import java.io.IOException;
+import java.util.ArrayList;
 
 /**
  * Ringtone provides a quick method for playing a ringtone, notification, or
@@ -53,39 +49,7 @@
  */
 public class Ringtone {
     private static final String TAG = "Ringtone";
-
-    /**
-     * The ringtone should only play sound. Any vibration is managed externally.
-     * @hide
-     */
-    public static final int MEDIA_SOUND = 1;
-    /**
-     * The ringtone should only play vibration. Any sound is managed externally.
-     * Requires the {@link android.Manifest.permission#VIBRATE} permission.
-     * @hide
-     */
-    public static final int MEDIA_VIBRATION = 1 << 1;
-    /**
-     * The ringtone should play sound and vibration.
-     * @hide
-     */
-    public static final int MEDIA_SOUND_AND_VIBRATION = MEDIA_SOUND | MEDIA_VIBRATION;
-
-    // This is not a public value, because apps shouldn't enable "all" media - that wouldn't be
-    // safe if new media types were added.
-    static final int MEDIA_ALL = MEDIA_SOUND | MEDIA_VIBRATION;
-
-    /**
-     * Declares the types of media that this Ringtone is allowed to play.
-     * @hide
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = "MEDIA_", value = {
-            MEDIA_SOUND,
-            MEDIA_VIBRATION,
-            MEDIA_SOUND_AND_VIBRATION,
-    })
-    public @interface RingtoneMedia {}
+    private static final boolean LOGD = true;
 
     private static final String[] MEDIA_COLUMNS = new String[] {
         MediaStore.Audio.Media._ID,
@@ -95,70 +59,51 @@
     private static final String MEDIA_SELECTION = MediaColumns.MIME_TYPE + " LIKE 'audio/%' OR "
             + MediaColumns.MIME_TYPE + " IN ('application/ogg', 'application/x-flac')";
 
-    // Flag-selected ringtone implementation to use.
-    private final ApiInterface mApiImpl;
+    // keep references on active Ringtones until stopped or completion listener called.
+    private static final ArrayList<Ringtone> sActiveRingtones = new ArrayList<Ringtone>();
+
+    private final Context mContext;
+    private final AudioManager mAudioManager;
+    private VolumeShaper.Configuration mVolumeShaperConfig;
+    private VolumeShaper mVolumeShaper;
+
+    /**
+     * Flag indicating if we're allowed to fall back to remote playback using
+     * {@link #mRemotePlayer}. Typically this is false when we're the remote
+     * player and there is nobody else to delegate to.
+     */
+    private final boolean mAllowRemote;
+    private final IRingtonePlayer mRemotePlayer;
+    private final Binder mRemoteToken;
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private MediaPlayer mLocalPlayer;
+    private final MyOnCompletionListener mCompletionListener = new MyOnCompletionListener();
+    private HapticGenerator mHapticGenerator;
+
+    @UnsupportedAppUsage
+    private Uri mUri;
+    private String mTitle;
+
+    private AudioAttributes mAudioAttributes = new AudioAttributes.Builder()
+            .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+            .build();
+    private boolean mPreferBuiltinDevice;
+    // playback properties, use synchronized with mPlaybackSettingsLock
+    private boolean mIsLooping = false;
+    private float mVolume = 1.0f;
+    private boolean mHapticGeneratorEnabled = false;
+    private final Object mPlaybackSettingsLock = new Object();
 
     /** {@hide} */
     @UnsupportedAppUsage
     public Ringtone(Context context, boolean allowRemote) {
-        mApiImpl = new RingtoneV1(context, allowRemote);
-    }
-
-    /**
-     * Constructor for legacy V1 initialization paths using non-public APIs on RingtoneV1.
-     */
-    private Ringtone(RingtoneV1 ringtoneV1) {
-        mApiImpl = ringtoneV1;
-    }
-
-    private Ringtone(Builder builder, @Ringtone.RingtoneMedia int effectiveEnabledMedia,
-            @NonNull AudioAttributes effectiveAudioAttributes,
-            @Nullable VibrationEffect effectiveVibrationEffect,
-            boolean effectiveHapticGeneratorEnabled) {
-        mApiImpl = new RingtoneV2(builder.mContext, builder.mInjectables, builder.mAllowRemote,
-                effectiveEnabledMedia, builder.mUri, effectiveAudioAttributes,
-                builder.mUseExactAudioAttributes, builder.mVolumeShaperConfig,
-                builder.mPreferBuiltinDevice, builder.mInitialSoundVolume, builder.mLooping,
-                effectiveHapticGeneratorEnabled, effectiveVibrationEffect);
-    }
-
-    /**
-     * Temporary V1 constructor for legacy V1 paths with audio attributes.
-     * @hide
-     */
-    public static Ringtone createV1WithCustomAudioAttributes(
-            Context context, AudioAttributes audioAttributes, Uri uri,
-            VolumeShaper.Configuration volumeShaperConfig, boolean allowRemote) {
-        RingtoneV1 ringtoneV1 = new RingtoneV1(context, allowRemote);
-        ringtoneV1.setAudioAttributesField(audioAttributes);
-        ringtoneV1.setUri(uri, volumeShaperConfig);
-        ringtoneV1.reinitializeActivePlayer();
-        return new Ringtone(ringtoneV1);
-    }
-
-    /**
-     * Temporary V1 constructor for legacy V1 paths with stream type.
-     * @hide
-     */
-    public static Ringtone createV1WithCustomStreamType(
-            Context context, int streamType, Uri uri,
-            VolumeShaper.Configuration volumeShaperConfig) {
-        RingtoneV1 ringtoneV1 = new RingtoneV1(context, /* allowRemote= */ true);
-        if (streamType >= 0) {
-            ringtoneV1.setStreamType(streamType);
-        }
-        ringtoneV1.setUri(uri, volumeShaperConfig);
-        if (!ringtoneV1.reinitializeActivePlayer()) {
-            Log.e(TAG, "Failed to open ringtone " + uri);
-            return null;
-        }
-        return new Ringtone(ringtoneV1);
-    }
-
-    /** @hide */
-    @RingtoneMedia
-    public int getEnabledMedia() {
-        return mApiImpl.getEnabledMedia();
+        mContext = context;
+        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        mAllowRemote = allowRemote;
+        mRemotePlayer = allowRemote ? mAudioManager.getRingtonePlayer() : null;
+        mRemoteToken = allowRemote ? new Binder() : null;
     }
 
     /**
@@ -169,7 +114,10 @@
      */
     @Deprecated
     public void setStreamType(int streamType) {
-        mApiImpl.setStreamType(streamType);
+        PlayerBase.deprecateStreamTypeForPlayback(streamType, "Ringtone", "setStreamType()");
+        setAudioAttributes(new AudioAttributes.Builder()
+                .setInternalLegacyStreamType(streamType)
+                .build());
     }
 
     /**
@@ -181,7 +129,7 @@
      */
     @Deprecated
     public int getStreamType() {
-        return mApiImpl.getStreamType();
+        return AudioAttributes.toLegacyStreamType(mAudioAttributes);
     }
 
     /**
@@ -190,45 +138,54 @@
      */
     public void setAudioAttributes(AudioAttributes attributes)
             throws IllegalArgumentException {
-        mApiImpl.setAudioAttributes(attributes);
+        setAudioAttributesField(attributes);
+        // The audio attributes have to be set before the media player is prepared.
+        // Re-initialize it.
+        setUri(mUri, mVolumeShaperConfig);
+        createLocalMediaPlayer();
     }
 
     /**
-     * Returns the vibration effect that this ringtone was created with, if vibration is enabled.
-     * Otherwise, returns null.
+     * Same as {@link #setAudioAttributes(AudioAttributes)} except this one does not create
+     * the media player.
      * @hide
      */
-    @Nullable
-    public VibrationEffect getVibrationEffect() {
-        return mApiImpl.getVibrationEffect();
-    }
-
-    /** @hide */
-    @VisibleForTesting
-    public boolean getPreferBuiltinDevice() {
-        return mApiImpl.getPreferBuiltinDevice();
-    }
-
-    /** @hide */
-    @VisibleForTesting
-    public VolumeShaper.Configuration getVolumeShaperConfig() {
-        return mApiImpl.getVolumeShaperConfig();
+    public void setAudioAttributesField(@Nullable AudioAttributes attributes) {
+        if (attributes == null) {
+            throw new IllegalArgumentException("Invalid null AudioAttributes for Ringtone");
+        }
+        mAudioAttributes = attributes;
     }
 
     /**
-     * Returns whether this player is local only, or can defer to the remote player. The
-     * result may differ from the builder if there is no remote player available at all.
-     * @hide
+     * Finds the output device of type {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER}. This device is
+     * the one on which outgoing audio for SIM calls is played.
+     *
+     * @param audioManager the audio manage.
+     * @return the {@link AudioDeviceInfo} corresponding to the builtin device, or {@code null} if
+     *     none can be found.
      */
-    @VisibleForTesting
-    public boolean isLocalOnly() {
-        return mApiImpl.isLocalOnly();
+    private AudioDeviceInfo getBuiltinDevice(AudioManager audioManager) {
+        AudioDeviceInfo[] deviceList = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        for (AudioDeviceInfo device : deviceList) {
+            if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+                return device;
+            }
+        }
+        return null;
     }
 
-    /** @hide */
-    @VisibleForTesting
-    public boolean isUsingRemotePlayer() {
-        return mApiImpl.isUsingRemotePlayer();
+    /**
+     * Sets the preferred device of the ringtong playback to the built-in device.
+     *
+     * @hide
+     */
+    public boolean preferBuiltinDevice(boolean enable) {
+        mPreferBuiltinDevice = enable;
+        if (mLocalPlayer == null) {
+            return true;
+        }
+        return mLocalPlayer.setPreferredDevice(getBuiltinDevice(mAudioManager));
     }
 
     /**
@@ -237,16 +194,76 @@
      * false if it did not succeed and can't be tried remotely.
      * @hide
      */
-    public boolean reinitializeActivePlayer() {
-        return mApiImpl.reinitializeActivePlayer();
+    public boolean createLocalMediaPlayer() {
+        Trace.beginSection("createLocalMediaPlayer");
+        if (mUri == null) {
+            Log.e(TAG, "Could not create media player as no URI was provided.");
+            return mAllowRemote && mRemotePlayer != null;
+        }
+        destroyLocalPlayer();
+        // try opening uri locally before delegating to remote player
+        mLocalPlayer = new MediaPlayer();
+        try {
+            mLocalPlayer.setDataSource(mContext, mUri);
+            mLocalPlayer.setAudioAttributes(mAudioAttributes);
+            mLocalPlayer.setPreferredDevice(
+                    mPreferBuiltinDevice ? getBuiltinDevice(mAudioManager) : null);
+            synchronized (mPlaybackSettingsLock) {
+                applyPlaybackProperties_sync();
+            }
+            if (mVolumeShaperConfig != null) {
+                mVolumeShaper = mLocalPlayer.createVolumeShaper(mVolumeShaperConfig);
+            }
+            mLocalPlayer.prepare();
+
+        } catch (SecurityException | IOException e) {
+            destroyLocalPlayer();
+            if (!mAllowRemote) {
+                Log.w(TAG, "Remote playback not allowed: " + e);
+            }
+        }
+
+        if (LOGD) {
+            if (mLocalPlayer != null) {
+                Log.d(TAG, "Successfully created local player");
+            } else {
+                Log.d(TAG, "Problem opening; delegating to remote player");
+            }
+        }
+        Trace.endSection();
+        return mLocalPlayer != null || (mAllowRemote && mRemotePlayer != null);
     }
 
     /**
      * Same as AudioManager.hasHapticChannels except it assumes an already created ringtone.
+     * If the ringtone has not been created, it will load based on URI provided at {@link #setUri}
+     * and if not URI has been set, it will assume no haptic channels are present.
      * @hide
      */
     public boolean hasHapticChannels() {
-        return mApiImpl.hasHapticChannels();
+        // FIXME: support remote player, or internalize haptic channels support and remove entirely.
+        try {
+            android.os.Trace.beginSection("Ringtone.hasHapticChannels");
+            if (mLocalPlayer != null) {
+                for(MediaPlayer.TrackInfo trackInfo : mLocalPlayer.getTrackInfo()) {
+                    if (trackInfo.hasHapticChannels()) {
+                        return true;
+                    }
+                }
+            }
+        } finally {
+            android.os.Trace.endSection();
+        }
+        return false;
+    }
+
+    /**
+     * Returns whether a local player has been created for this ringtone.
+     * @hide
+     */
+    @VisibleForTesting
+    public boolean hasLocalPlayer() {
+        return mLocalPlayer != null;
     }
 
     /**
@@ -255,7 +272,7 @@
      *     {@link #setAudioAttributes(AudioAttributes)} or the default attributes if none were set.
      */
     public AudioAttributes getAudioAttributes() {
-        return mApiImpl.getAudioAttributes();
+        return mAudioAttributes;
     }
 
     /**
@@ -263,7 +280,10 @@
      * @param looping whether to loop or not.
      */
     public void setLooping(boolean looping) {
-        mApiImpl.setLooping(looping);
+        synchronized (mPlaybackSettingsLock) {
+            mIsLooping = looping;
+            applyPlaybackProperties_sync();
+        }
     }
 
     /**
@@ -271,7 +291,9 @@
      * @return true if this player loops when playing.
      */
     public boolean isLooping() {
-        return mApiImpl.isLooping();
+        synchronized (mPlaybackSettingsLock) {
+            return mIsLooping;
+        }
     }
 
     /**
@@ -280,7 +302,12 @@
      *   corresponds to no attenuation being applied.
      */
     public void setVolume(float volume) {
-        mApiImpl.setVolume(volume);
+        synchronized (mPlaybackSettingsLock) {
+            if (volume < 0.0f) { volume = 0.0f; }
+            if (volume > 1.0f) { volume = 1.0f; }
+            mVolume = volume;
+            applyPlaybackProperties_sync();
+        }
     }
 
     /**
@@ -288,7 +315,9 @@
      * @return a value between 0.0f and 1.0f.
      */
     public float getVolume() {
-        return mApiImpl.getVolume();
+        synchronized (mPlaybackSettingsLock) {
+            return mVolume;
+        }
     }
 
     /**
@@ -299,7 +328,14 @@
      * @see android.media.audiofx.HapticGenerator#isAvailable()
      */
     public boolean setHapticGeneratorEnabled(boolean enabled) {
-        return mApiImpl.setHapticGeneratorEnabled(enabled);
+        if (!HapticGenerator.isAvailable()) {
+            return false;
+        }
+        synchronized (mPlaybackSettingsLock) {
+            mHapticGeneratorEnabled = enabled;
+            applyPlaybackProperties_sync();
+        }
+        return true;
     }
 
     /**
@@ -307,7 +343,35 @@
      * @return true if the HapticGenerator is enabled.
      */
     public boolean isHapticGeneratorEnabled() {
-        return mApiImpl.isHapticGeneratorEnabled();
+        synchronized (mPlaybackSettingsLock) {
+            return mHapticGeneratorEnabled;
+        }
+    }
+
+    /**
+     * Must be called synchronized on mPlaybackSettingsLock
+     */
+    private void applyPlaybackProperties_sync() {
+        if (mLocalPlayer != null) {
+            mLocalPlayer.setVolume(mVolume);
+            mLocalPlayer.setLooping(mIsLooping);
+            if (mHapticGenerator == null && mHapticGeneratorEnabled) {
+                mHapticGenerator = HapticGenerator.create(mLocalPlayer.getAudioSessionId());
+            }
+            if (mHapticGenerator != null) {
+                mHapticGenerator.setEnabled(mHapticGeneratorEnabled);
+            }
+        } else if (mAllowRemote && (mRemotePlayer != null)) {
+            try {
+                mRemotePlayer.setPlaybackProperties(
+                        mRemoteToken, mVolume, mIsLooping, mHapticGeneratorEnabled);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Problem setting playback properties: ", e);
+            }
+        } else {
+            Log.w(TAG,
+                    "Neither local nor remote player available when applying playback properties");
+        }
     }
 
     /**
@@ -317,7 +381,8 @@
      * @param context A context used for querying.
      */
     public String getTitle(Context context) {
-        return mApiImpl.getTitle(context);
+        if (mTitle != null) return mTitle;
+        return mTitle = getTitle(context, mUri, true /*followSettingsUri*/, mAllowRemote);
     }
 
     /**
@@ -391,24 +456,126 @@
         return title;
     }
 
+    /**
+     * Set {@link Uri} to be used for ringtone playback.
+     * {@link IRingtonePlayer}.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void setUri(Uri uri) {
+        setUri(uri, null);
+    }
+
+    /**
+     * @hide
+     */
+    public void setVolumeShaperConfig(@Nullable VolumeShaper.Configuration volumeShaperConfig) {
+        mVolumeShaperConfig = volumeShaperConfig;
+    }
+
+    /**
+     * Set {@link Uri} to be used for ringtone playback. Attempts to open
+     * locally, otherwise will delegate playback to remote
+     * {@link IRingtonePlayer}. Add {@link VolumeShaper} if required.
+     *
+     * @hide
+     */
+    public void setUri(Uri uri, @Nullable VolumeShaper.Configuration volumeShaperConfig) {
+        mVolumeShaperConfig = volumeShaperConfig;
+        mUri = uri;
+        if (mUri == null) {
+            destroyLocalPlayer();
+        }
+    }
+
     /** {@hide} */
     @UnsupportedAppUsage
     public Uri getUri() {
-        return mApiImpl.getUri();
+        return mUri;
     }
 
     /**
      * Plays the ringtone.
      */
     public void play() {
-        mApiImpl.play();
+        if (mLocalPlayer != null) {
+            // Play ringtones if stream volume is over 0 or if it is a haptic-only ringtone
+            // (typically because ringer mode is vibrate).
+            if (mAudioManager.getStreamVolume(AudioAttributes.toLegacyStreamType(mAudioAttributes))
+                    != 0) {
+                startLocalPlayer();
+            } else if (!mAudioAttributes.areHapticChannelsMuted() && hasHapticChannels()) {
+                // is haptic only ringtone
+                startLocalPlayer();
+            }
+        } else if (mAllowRemote && (mRemotePlayer != null) && (mUri != null)) {
+            final Uri canonicalUri = mUri.getCanonicalUri();
+            final boolean looping;
+            final float volume;
+            synchronized (mPlaybackSettingsLock) {
+                looping = mIsLooping;
+                volume = mVolume;
+            }
+            try {
+                mRemotePlayer.playWithVolumeShaping(mRemoteToken, canonicalUri, mAudioAttributes,
+                        volume, looping, mVolumeShaperConfig);
+            } catch (RemoteException e) {
+                if (!playFallbackRingtone()) {
+                    Log.w(TAG, "Problem playing ringtone: " + e);
+                }
+            }
+        } else {
+            if (!playFallbackRingtone()) {
+                Log.w(TAG, "Neither local nor remote playback available");
+            }
+        }
     }
 
     /**
      * Stops a playing ringtone.
      */
     public void stop() {
-        mApiImpl.stop();
+        if (mLocalPlayer != null) {
+            destroyLocalPlayer();
+        } else if (mAllowRemote && (mRemotePlayer != null)) {
+            try {
+                mRemotePlayer.stop(mRemoteToken);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Problem stopping ringtone: " + e);
+            }
+        }
+    }
+
+    private void destroyLocalPlayer() {
+        if (mLocalPlayer != null) {
+            if (mHapticGenerator != null) {
+                mHapticGenerator.release();
+                mHapticGenerator = null;
+            }
+            mLocalPlayer.setOnCompletionListener(null);
+            mLocalPlayer.reset();
+            mLocalPlayer.release();
+            mLocalPlayer = null;
+            mVolumeShaper = null;
+            synchronized (sActiveRingtones) {
+                sActiveRingtones.remove(this);
+            }
+        }
+    }
+
+    private void startLocalPlayer() {
+        if (mLocalPlayer == null) {
+            return;
+        }
+        synchronized (sActiveRingtones) {
+            sActiveRingtones.add(this);
+        }
+        mLocalPlayer.setOnCompletionListener(mCompletionListener);
+        mLocalPlayer.start();
+        if (mVolumeShaper != null) {
+            mVolumeShaper.apply(VolumeShaper.Operation.PLAY);
+        }
     }
 
     /**
@@ -417,353 +584,87 @@
      * @return True if playing, false otherwise.
      */
     public boolean isPlaying() {
-        return mApiImpl.isPlaying();
-    }
-
-    /**
-     * Build a {@link Ringtone} to easily play sounds for ringtones, alarms and notifications.
-     *
-     * TODO: when un-hide, deprecate Ringtone: setAudioAttributes, setLooping,
-     *       setHapticGeneratorEnabled (no-effect if MEDIA_VIBRATION),
-     *       static RingtoneManager.getRingtone.
-     * @hide
-     */
-    public static final class Builder {
-        private final Context mContext;
-        private final int mEnabledMedia;
-        private Uri mUri;
-        private final AudioAttributes mAudioAttributes;
-        private boolean mUseExactAudioAttributes = false;
-        // Not a static default since it doesn't really need to be in memory forever.
-        private Injectables mInjectables = new Injectables();
-        private VolumeShaper.Configuration mVolumeShaperConfig;
-        private boolean mPreferBuiltinDevice = false;
-        private boolean mAllowRemote = true;
-        private boolean mHapticGeneratorEnabled = false;
-        private float mInitialSoundVolume = 1.0f;
-        private boolean mLooping = false;
-        private VibrationEffect mVibrationEffect;
-
-        /**
-         * Constructs a builder to play the given media types from the mediaUri. If the mediaUri
-         * is null (for example, an unset-setting), then fallback logic will dictate what plays.
-         *
-         * <p>When built, if the ringtone is already known to be a no-op, such as explicitly
-         * silent, then the {@link #build} may return null.
-         *
-         * @param context The context for playing the ringtone.
-         * @param enabledMedia Which media to play. Media not included is implicitly muted. Device
-         *                     settings such as volume and vibrate-only may also affect which
-         *                     media is played.
-         * @param audioAttributes The attributes to use for playback, which affects the volumes and
-         *                        settings that are applied.
-         */
-        public Builder(@NonNull Context context, @RingtoneMedia int enabledMedia,
-                @NonNull AudioAttributes audioAttributes) {
-            mContext = context;
-            mEnabledMedia = enabledMedia;
-            mAudioAttributes = audioAttributes;
-        }
-
-        /**
-         * Inject test intercepters for static methods.
-         * @hide
-         */
-        @NonNull
-        public Builder setInjectables(Injectables injectables) {
-            mInjectables = injectables;
-            return this;
-        }
-
-        /**
-         * The uri for the ringtone media to play. This is typically a user's preference for the
-         * sound. If null, then it is treated as though the user's preference is unset and
-         * fallback behavior, such as using the default ringtone setting, are used instead.
-         *
-         * When sound media is enabled, this is assumed to be a sound URI.
-         */
-        @NonNull
-        public Builder setUri(@Nullable Uri uri) {
-            mUri = uri;
-            return this;
-        }
-
-        /**
-         * Sets the VibrationEffect to use if vibration is enabled on this ringtone. The caller
-         * should use {@link android.os.Vibrator#areVibrationFeaturesSupported} to ensure
-         * that the effect is usable on this device, otherwise system defaults will be used.
-         *
-         * <p>Vibration will only happen if the Builder was created with media type
-         * {@link Ringtone#MEDIA_VIBRATION} or {@link Ringtone#MEDIA_SOUND_AND_VIBRATION}, and
-         * the application has the {@link android.Manifest.permission#VIBRATE} permission.
-         *
-         * <p>If the Ringtone is looping when it is played, then the VibrationEffect will be
-         * modified to loop. Similarly, if the ringtone is not looping, a repeating
-         * VibrationEffect will be modified to be non-repeating when the ringtone is played. Calls
-         * to {@link Ringtone#setLooping} after the ringtone has started playing will stop a looping
-         * vibration, but has no effect otherwise: specifically it will not restart vibration.
-         */
-        @NonNull
-        public Builder setVibrationEffect(@NonNull VibrationEffect effect) {
-            mVibrationEffect = effect;
-            return this;
-        }
-
-        /**
-         * Sets whether the resulting ringtone should loop until {@link Ringtone#stop()} is called,
-         * or just play once.
-         */
-        @NonNull
-        public Builder setLooping(boolean looping) {
-            mLooping = looping;
-            return this;
-        }
-
-        /**
-         * Sets the VolumeShaper.Configuration to apply to the ringtone.
-         * @hide
-         */
-        @NonNull
-        public Builder setVolumeShaperConfig(
-                @Nullable VolumeShaper.Configuration volumeShaperConfig) {
-            mVolumeShaperConfig = volumeShaperConfig;
-            return this;
-        }
-
-        /**
-         * Whether to enable or disable the haptic generator.
-         * @hide
-         */
-        @NonNull
-        public Builder setEnableHapticGenerator(boolean enabled) {
-            // Note that this property is mutable (but deprecated) on the Ringtone class itself.
-            mHapticGeneratorEnabled = enabled;
-            return this;
-        }
-
-        /**
-         * Sets the initial sound volume for the ringtone.
-         */
-        @NonNull
-        public Builder setInitialSoundVolume(float initialSoundVolume) {
-            mInitialSoundVolume = initialSoundVolume;
-            return this;
-        }
-
-        /**
-         * Sets the preferred device of the ringtone playback to the built-in device. This is
-         * only for use by the system server with known-good Uris.
-         * @hide
-         */
-        @NonNull
-        public Builder setPreferBuiltinDevice() {
-            mPreferBuiltinDevice = true;
-            mAllowRemote = false;  // Already in system.
-            return this;
-        }
-
-        /**
-         * Indicates that {@link AudioAttributes#areHapticChannelsMuted()} on the builder's
-         * AudioAttributes should not be overridden. This is used to enable legacy behavior of
-         * calling {@link Ringtone#setAudioAttributes} on an already-created ringtone, and can in
-         * turn cause vibration during a "sound-only" session or can suppress audio-coupled
-         * haptics that would usually take priority (therefore potentially falling back to
-         * the VibrationEffect or system defaults).
-         *
-         * <p>Without this setting, the haptic channels will be automatically muted or not by the
-         * Ringtone according to whether vibration is enabled or not.
-         *
-         * <p>This is for internal-use only. New applications should configure the vibration
-         * behavior explicitly with the (TODO: future RingtoneSetting.setVibrationSource).
-         * Handling haptic channels outside Ringtone leads to extra loads of the sound uri.
-         * @hide
-         */
-        @NonNull
-        public Builder setUseExactAudioAttributes(boolean useExactAttrs) {
-            mUseExactAudioAttributes = useExactAttrs;
-            return this;
-        }
-
-        /**
-         * Prevent fallback to the remote service. This is primarily intended for use within the
-         * remote IRingtonePlayer service itself, to avoid loops.
-         * @hide
-         */
-        @NonNull
-        public Builder setLocalOnly() {
-            mAllowRemote = false;
-            return this;
-        }
-
-        private boolean isVibrationEnabledAndAvailable() {
-            if ((mEnabledMedia & MEDIA_VIBRATION) == 0) {
-                return false;
-            }
-            Vibrator vibrator = mContext.getSystemService(Vibrator.class);
-            if (!vibrator.hasVibrator()) {
-                return false;
-            }
-            if (mContext.checkSelfPermission(Manifest.permission.VIBRATE)
-                    != PackageManager.PERMISSION_GRANTED) {
-                Log.w(TAG, "Ringtone requests vibration enabled, but no VIBRATE permission");
-                return false;
-            }
-            return true;
-        }
-
-        /**
-         * Returns the built Ringtone, or null if there was a problem loading the Uri and there
-         * are no fallback options available.
-         */
-        @Nullable
-        public Ringtone build() {
-            @Ringtone.RingtoneMedia int effectiveEnabledMedia = mEnabledMedia;
-            VibrationEffect effectiveVibrationEffect = mVibrationEffect;
-
-            // Normalize media to that supported on this SDK level.
-            if (effectiveEnabledMedia != (effectiveEnabledMedia & MEDIA_ALL)) {
-                Log.e(TAG, "Unsupported media type: " + effectiveEnabledMedia);
-                effectiveEnabledMedia = effectiveEnabledMedia & MEDIA_ALL;
-            }
-            final boolean effectiveHapticGenerator;
-            final boolean hapticChannelsSupported;
-            AudioAttributes effectiveAudioAttributes = mAudioAttributes;
-            final boolean hapticChannelsMuted = mAudioAttributes.areHapticChannelsMuted();
-            if (!isVibrationEnabledAndAvailable()) {
-                // Vibration isn't active: turn off everything that might cause extra work.
-                effectiveEnabledMedia &= ~MEDIA_VIBRATION;
-                effectiveHapticGenerator = false;
-                effectiveVibrationEffect = null;
-                if (!mUseExactAudioAttributes && !hapticChannelsMuted) {
-                    effectiveAudioAttributes = new AudioAttributes.Builder(effectiveAudioAttributes)
-                            .setHapticChannelsMuted(true)
-                            .build();
-                }
-            } else {
-                // Vibration is active.
-                effectiveHapticGenerator =
-                        mHapticGeneratorEnabled && mInjectables.isHapticGeneratorAvailable();
-                hapticChannelsSupported = mInjectables.isHapticPlaybackSupported();
-                // Haptic channels are preferred if they are available, and not explicitly muted.
-                // We won't know if haptic channels are available until loading the media player,
-                // and since the media player needs to be reset to change audio attributes, then
-                // we proactively enable the channels - it won't matter if they aren't present.
-                if (!mUseExactAudioAttributes) {
-                    boolean shouldBeMuted = effectiveHapticGenerator || !hapticChannelsSupported;
-                    if (shouldBeMuted != hapticChannelsMuted) {
-                        effectiveAudioAttributes =
-                                new AudioAttributes.Builder(effectiveAudioAttributes)
-                                .setHapticChannelsMuted(shouldBeMuted)
-                                .build();
-                    }
-                }
-                // If no contextual vibration, then try loading the default one for the URI.
-                if (mVibrationEffect == null && mUri != null) {
-                    effectiveVibrationEffect = VibrationEffect.get(mUri, mContext);
-                }
-            }
+        if (mLocalPlayer != null) {
+            return mLocalPlayer.isPlaying();
+        } else if (mAllowRemote && (mRemotePlayer != null)) {
             try {
-                Ringtone ringtone = new Ringtone(this, effectiveEnabledMedia,
-                        effectiveAudioAttributes, effectiveVibrationEffect,
-                        effectiveHapticGenerator);
-                if (ringtone.reinitializeActivePlayer()) {
-                    return ringtone;
-                } else {
-                    Log.e(TAG, "Failed to open ringtone " + mUri);
-                    return null;
-                }
-            } catch (Exception ex) {
-                // Catching Exception isn't great, but was done in the old
-                // RingtoneManager.getRingtone and hides errors like DocumentsProvider throwing
-                // IllegalArgumentException instead of FileNotFoundException, and also robolectric
-                // failures when ShadowMediaPlayer wasn't pre-informed of the ringtone.
-                Log.e(TAG, "Failed while opening ringtone " + mUri, ex);
-                return null;
+                return mRemotePlayer.isPlaying(mRemoteToken);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Problem checking ringtone: " + e);
+                return false;
             }
-        }
-    }
-
-    /**
-     * Interface for intercepting static methods and constructors, for unit testing only.
-     * @hide
-     */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public static class Injectables {
-        /** Intercept {@code new MediaPlayer()}. */
-        @NonNull
-        public MediaPlayer newMediaPlayer() {
-            return new MediaPlayer();
-        }
-
-        /** Intercept {@link HapticGenerator#isAvailable}. */
-        public boolean isHapticGeneratorAvailable() {
-            return HapticGenerator.isAvailable();
-        }
-
-        /**
-         * Intercept {@link HapticGenerator#create} using
-         * {@link MediaPlayer#getAudioSessionId()} from the given media player.
-         */
-        @Nullable
-        public HapticGenerator createHapticGenerator(@NonNull MediaPlayer mediaPlayer) {
-            return HapticGenerator.create(mediaPlayer.getAudioSessionId());
-        }
-
-        /** Returns the result of {@link AudioManager#isHapticPlaybackSupported()}. */
-        public boolean isHapticPlaybackSupported() {
-            return AudioManager.isHapticPlaybackSupported();
-        }
-
-        /**
-         * Returns whether the MediaPlayer tracks have haptic channels. This is the same as
-         * AudioManager.hasHapticChannels, except it uses an already prepared MediaPlayer to avoid
-         * loading the metadata a second time.
-         */
-        public boolean hasHapticChannels(MediaPlayer mp) {
-            try {
-                Trace.beginSection("Ringtone.hasHapticChannels");
-                for (MediaPlayer.TrackInfo trackInfo : mp.getTrackInfo()) {
-                    if (trackInfo.hasHapticChannels()) {
-                        return true;
-                    }
-                }
-            } finally {
-                Trace.endSection();
-            }
+        } else {
+            Log.w(TAG, "Neither local nor remote playback available");
             return false;
         }
-
     }
 
-    /**
-     * Interface for alternative Ringtone implementations. See the public Ringtone methods that
-     * delegate to these for documentation.
-     * @hide
-     */
-    interface ApiInterface {
-        void setStreamType(int streamType);
-        int getStreamType();
-        void setAudioAttributes(AudioAttributes attributes);
-        boolean getPreferBuiltinDevice();
-        VolumeShaper.Configuration getVolumeShaperConfig();
-        boolean isLocalOnly();
-        boolean isUsingRemotePlayer();
-        boolean reinitializeActivePlayer();
-        boolean hasHapticChannels();
-        AudioAttributes getAudioAttributes();
-        void setLooping(boolean looping);
-        boolean isLooping();
-        void setVolume(float volume);
-        float getVolume();
-        boolean setHapticGeneratorEnabled(boolean enabled);
-        boolean isHapticGeneratorEnabled();
-        String getTitle(Context context);
-        Uri getUri();
-        void play();
-        void stop();
-        boolean isPlaying();
-        // V2 future-public methods
-        @RingtoneMedia int getEnabledMedia();
-        VibrationEffect getVibrationEffect();
+    private boolean playFallbackRingtone() {
+        int streamType = AudioAttributes.toLegacyStreamType(mAudioAttributes);
+        if (mAudioManager.getStreamVolume(streamType) == 0) {
+            return false;
+        }
+        int ringtoneType = RingtoneManager.getDefaultType(mUri);
+        if (ringtoneType != -1 &&
+                RingtoneManager.getActualDefaultRingtoneUri(mContext, ringtoneType) == null) {
+            Log.w(TAG, "not playing fallback for " + mUri);
+            return false;
+        }
+        // Default ringtone, try fallback ringtone.
+        try {
+            AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(
+                    com.android.internal.R.raw.fallbackring);
+            if (afd == null) {
+                Log.e(TAG, "Could not load fallback ringtone");
+                return false;
+            }
+            mLocalPlayer = new MediaPlayer();
+            if (afd.getDeclaredLength() < 0) {
+                mLocalPlayer.setDataSource(afd.getFileDescriptor());
+            } else {
+                mLocalPlayer.setDataSource(afd.getFileDescriptor(),
+                        afd.getStartOffset(),
+                        afd.getDeclaredLength());
+            }
+            mLocalPlayer.setAudioAttributes(mAudioAttributes);
+            synchronized (mPlaybackSettingsLock) {
+                applyPlaybackProperties_sync();
+            }
+            if (mVolumeShaperConfig != null) {
+                mVolumeShaper = mLocalPlayer.createVolumeShaper(mVolumeShaperConfig);
+            }
+            mLocalPlayer.prepare();
+            startLocalPlayer();
+            afd.close();
+        } catch (IOException ioe) {
+            destroyLocalPlayer();
+            Log.e(TAG, "Failed to open fallback ringtone");
+            return false;
+        } catch (NotFoundException nfe) {
+            Log.e(TAG, "Fallback ringtone does not exist");
+            return false;
+        }
+        return true;
+    }
+
+    void setTitle(String title) {
+        mTitle = title;
+    }
+
+    @Override
+    protected void finalize() {
+        if (mLocalPlayer != null) {
+            mLocalPlayer.release();
+        }
+    }
+
+    class MyOnCompletionListener implements MediaPlayer.OnCompletionListener {
+        @Override
+        public void onCompletion(MediaPlayer mp) {
+            synchronized (sActiveRingtones) {
+                sActiveRingtones.remove(Ringtone.this);
+            }
+            mp.setOnCompletionListener(null); // Help the Java GC: break the refcount cycle.
+        }
     }
 }
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index b5a9ae2..3432b3f 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -16,7 +16,7 @@
 
 package android.media;
 
-import android.annotation.IntDef;
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -30,27 +30,24 @@
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
 import android.content.res.AssetFileDescriptor;
 import android.database.Cursor;
 import android.database.StaleDataException;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.os.vibrator.Flags;
-import android.os.vibrator.persistence.VibrationXmlParser;
 import android.provider.BaseColumns;
 import android.provider.MediaStore;
 import android.provider.MediaStore.Audio.AudioColumns;
 import android.provider.MediaStore.MediaColumns;
 import android.provider.Settings;
 import android.provider.Settings.System;
-import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.database.SortCursor;
@@ -61,8 +58,6 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -122,53 +117,6 @@
     public static final String ACTION_RINGTONE_PICKER = "android.intent.action.RINGTONE_PICKER";
 
     /**
-     * Given to the ringtone picker as a string that represents the category of ringtone picker that
-     * should be used. This value should also be returned once a ringtone is selected.
-     * <p>
-     * The categories are:
-     * <li>{@link #CATEGORY_RINGTONE_PICKER_SOUND}
-     * <li>{@link #CATEGORY_RINGTONE_PICKER_VIBRATION}
-     * <li>{@link #CATEGORY_RINGTONE_PICKER_RINGTONE}
-     * <li>{@link Intent#CATEGORY_DEFAULT}
-     *
-     * <p> If the category is {@link Intent#CATEGORY_DEFAULT} or absent, then the picker will
-     * default to a sound-only ringtone picker.
-     *
-     * <p> If the selected category was not supported, then the returned category will be null.
-     *
-     * @hide
-     */
-    public static final String EXTRA_RINGTONE_PICKER_CATEGORY =
-            "android.intent.extra.ringtone.RINGTONE_PICKER_CATEGORY";
-
-    /**
-     * A sound-only ringtone picker.
-     *
-     * @hide
-     * @see #EXTRA_RINGTONE_PICKER_CATEGORY
-     */
-    public static final String CATEGORY_RINGTONE_PICKER_SOUND =
-            "android.net.category.RINGTONE_PICKER_SOUND";
-
-    /**
-     * A vibration-only ringtone picker.
-     *
-     * @hide
-     * @see #EXTRA_RINGTONE_PICKER_CATEGORY
-     */
-    public static final String CATEGORY_RINGTONE_PICKER_VIBRATION =
-            "android.net.category.RINGTONE_PICKER_VIBRATION";
-
-    /**
-     * A combined sound and vibration ringtone picker.
-     *
-     * @hide
-     * @see #EXTRA_RINGTONE_PICKER_CATEGORY
-     */
-    public static final String CATEGORY_RINGTONE_PICKER_RINGTONE =
-            "android.net.category.RINGTONE_PICKER_RINGTONE";
-
-    /**
      * Given to the ringtone picker as a boolean. Whether to show an item for
      * "Default".
      * 
@@ -209,18 +157,6 @@
      */
     public static final String EXTRA_RINGTONE_EXISTING_URI =
             "android.intent.extra.ringtone.EXISTING_URI";
-
-    /**
-     * Similar to #EXTRA_RINGTONE_EXISTING_URI but the {@link Uri} can include both sound and
-     * vibration.
-     * <p>This can include silent sound/vibration explicitly by setting that part of the URI to
-     * null.
-     *
-     * @hide
-     * @see #ACTION_RINGTONE_PICKER
-     */
-    public static final String EXTRA_RINGTONE_EXISTING_RINGTONE_URI =
-            "android.intent.extra.ringtone.RINGTONE_EXISTING_RINGTONE_URI";
     
     /**
      * Given to the ringtone picker as a {@link Uri}. The {@link Uri} of the
@@ -273,30 +209,21 @@
      */
     public static final String EXTRA_RINGTONE_PICKED_URI =
             "android.intent.extra.ringtone.PICKED_URI";
-
-    /**
-     * Declares the allowed types of media for this RingtoneManager.
-     * @hide
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = "MEDIA_", value = {
-            Ringtone.MEDIA_SOUND,
-            Ringtone.MEDIA_VIBRATION,
-    })
-    public @interface MediaType {}
-
+    
     // Make sure the column ordering and then ..._COLUMN_INDEX are in sync
     
-    private static final String[] MEDIA_AUDIO_COLUMNS = new String[] {
+    private static final String[] INTERNAL_COLUMNS = new String[] {
         MediaStore.Audio.Media._ID,
         MediaStore.Audio.Media.TITLE,
         MediaStore.Audio.Media.TITLE,
         MediaStore.Audio.Media.TITLE_KEY,
     };
 
-    private static final String[] MEDIA_VIBRATION_COLUMNS = new String[]{
-            MediaStore.Files.FileColumns._ID,
-            MediaStore.Files.FileColumns.TITLE,
+    private static final String[] MEDIA_COLUMNS = new String[] {
+        MediaStore.Audio.Media._ID,
+        MediaStore.Audio.Media.TITLE,
+        MediaStore.Audio.Media.TITLE,
+        MediaStore.Audio.Media.TITLE_KEY,
     };
 
     /**
@@ -324,9 +251,7 @@
     private Cursor mCursor;
 
     private int mType = TYPE_RINGTONE;
-    @MediaType
-    private int mMediaType = Ringtone.MEDIA_SOUND;
-
+    
     /**
      * If a column (item from this list) exists in the Cursor, its value must
      * be true (value of 1) for the row to be returned.
@@ -393,41 +318,6 @@
     }
 
     /**
-     * Sets the media type that will be listed by the RingtoneManager.
-     *
-     * <p>This method should be called before calling {@link RingtoneManager#getCursor()}.
-     *
-     * @hide
-     */
-    public void setMediaType(@MediaType int mediaType) {
-        if (mCursor != null) {
-            throw new IllegalStateException(
-                    "Setting media should be done before calling getCursor().");
-        }
-
-        switch (mediaType) {
-            case Ringtone.MEDIA_SOUND:
-            case Ringtone.MEDIA_VIBRATION:
-                mMediaType = mediaType;
-                break;
-            default:
-                throw new IllegalArgumentException("Unsupported media type " + mediaType);
-        }
-    }
-
-    /**
-     * Returns the RingtoneManagers media type.
-     *
-     * @return the media type.
-     * @see #setMediaType
-     * @hide
-     */
-    @MediaType
-    public int getMediaType() {
-        return mMediaType;
-    }
-
-    /**
      * Sets which type(s) of ringtones will be listed by this.
      * 
      * @param type The type(s), one or more of {@link #TYPE_RINGTONE},
@@ -465,25 +355,6 @@
         }
     }
 
-    /** @hide */
-    @NonNull
-    public static AudioAttributes getDefaultAudioAttributes(int ringtoneType) {
-        AudioAttributes.Builder builder = new AudioAttributes.Builder();
-        switch (ringtoneType) {
-            case TYPE_ALARM:
-                builder.setUsage(AudioAttributes.USAGE_ALARM);
-                break;
-            case TYPE_NOTIFICATION:
-                builder.setUsage(AudioAttributes.USAGE_NOTIFICATION);
-                break;
-            default:  // ringtone or all
-                builder.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE);
-                break;
-        }
-        builder.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION);
-        return builder.build();
-    }
-
     /**
      * Whether retrieving another {@link Ringtone} will stop playing the
      * previously retrieved {@link Ringtone}.
@@ -564,19 +435,19 @@
             return mCursor;
         }
 
-        ArrayList<Cursor> cursors = new ArrayList<>();
-
-        cursors.add(queryMediaStore(/* internal= */ true));
-        cursors.add(queryMediaStore(/* internal= */ false));
+        ArrayList<Cursor> ringtoneCursors = new ArrayList<Cursor>();
+        ringtoneCursors.add(getInternalRingtones());
+        ringtoneCursors.add(getMediaRingtones());
 
         if (mIncludeParentRingtones) {
             Cursor parentRingtonesCursor = getParentProfileRingtones();
             if (parentRingtonesCursor != null) {
-                cursors.add(parentRingtonesCursor);
+                ringtoneCursors.add(parentRingtonesCursor);
             }
         }
-        return mCursor = new SortCursor(cursors.toArray(new Cursor[cursors.size()]),
-                getSortOrderForMedia(mMediaType));
+
+        return mCursor = new SortCursor(ringtoneCursors.toArray(new Cursor[ringtoneCursors.size()]),
+                MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
     }
 
     private Cursor getParentProfileRingtones() {
@@ -588,7 +459,9 @@
                 // We don't need to re-add the internal ringtones for the work profile since
                 // they are the same as the personal profile. We just need the external
                 // ringtones.
-                return queryMediaStore(parentContext, /* internal= */ false);
+                final Cursor res = getMediaRingtones(parentContext);
+                return new ExternalRingtonesCursorWrapper(res, ContentProvider.maybeAddUserId(
+                        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, parentInfo.id));
             }
         }
         return null;
@@ -606,32 +479,11 @@
             mPreviousRingtone.stop();
         }
 
-        Ringtone ringtone;
-        Uri positionUri = getRingtoneUri(position);
-        if (Flags.hapticsCustomizationRingtoneV2Enabled()) {
-            mPreviousRingtone = new Ringtone.Builder(
-                    mContext, mMediaType, getDefaultAudioAttributes(mType))
-                    .setUri(positionUri)
-                    .build();
-        } else {
-            mPreviousRingtone = createRingtoneV1WithStreamType(mContext, positionUri,
-                    inferStreamType(), /* volumeShaperConfig= */ null);
-        }
+        mPreviousRingtone =
+                getRingtone(mContext, getRingtoneUri(position), inferStreamType(), true);
         return mPreviousRingtone;
     }
 
-    private static Ringtone createRingtoneV1WithStreamType(
-            final Context context, Uri ringtoneUri, int streamType,
-            @Nullable VolumeShaper.Configuration volumeShaperConfig) {
-        try {
-            return Ringtone.createV1WithCustomStreamType(context, streamType, ringtoneUri,
-                    volumeShaperConfig);
-        } catch (Exception ex) {
-            Log.e(TAG, "Failed to open ringtone " + ringtoneUri + ": " + ex);
-        }
-        return null;
-    }
-
     /**
      * Gets a {@link Uri} for the ringtone at the given position in the {@link Cursor}.
      * 
@@ -783,13 +635,11 @@
      */
     public static Uri getValidRingtoneUri(Context context) {
         final RingtoneManager rm = new RingtoneManager(context);
-
-        Uri uri = getValidRingtoneUriFromCursorAndClose(context,
-                rm.queryMediaStore(/* internal= */ true));
+        
+        Uri uri = getValidRingtoneUriFromCursorAndClose(context, rm.getInternalRingtones());
 
         if (uri == null) {
-            uri = getValidRingtoneUriFromCursorAndClose(context,
-                    rm.queryMediaStore(/* internal= */ false));
+            uri = getValidRingtoneUriFromCursorAndClose(context, rm.getMediaRingtones());
         }
         
         return uri;
@@ -810,26 +660,28 @@
         }
     }
 
-    private Cursor queryMediaStore(boolean internal) {
-        return queryMediaStore(mContext, internal);
+    @UnsupportedAppUsage
+    private Cursor getInternalRingtones() {
+        final Cursor res = query(
+                MediaStore.Audio.Media.INTERNAL_CONTENT_URI, INTERNAL_COLUMNS,
+                constructBooleanTrueWhereClause(mFilterColumns),
+                null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
+        return new ExternalRingtonesCursorWrapper(res, MediaStore.Audio.Media.INTERNAL_CONTENT_URI);
     }
 
-    private Cursor queryMediaStore(Context context, boolean internal) {
-        Uri contentUri = getContentUriForMedia(mMediaType, internal);
-        String[] columns =
-                mMediaType == Ringtone.MEDIA_VIBRATION ? MEDIA_VIBRATION_COLUMNS
-                        : MEDIA_AUDIO_COLUMNS;
-        String whereClause = getWhereClauseForMedia(mMediaType, mFilterColumns);
-        String sortOrder = getSortOrderForMedia(mMediaType);
+    private Cursor getMediaRingtones() {
+        final Cursor res = getMediaRingtones(mContext);
+        return new ExternalRingtonesCursorWrapper(res, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI);
+    }
 
-        Cursor cursor = query(contentUri, columns, whereClause, /* selectionArgs= */ null,
-                sortOrder, context);
-
-        if (context.getUserId() != mContext.getUserId()) {
-            contentUri = ContentProvider.maybeAddUserId(contentUri, context.getUserId());
-        }
-
-        return new ExternalRingtonesCursorWrapper(cursor, contentUri);
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private Cursor getMediaRingtones(Context context) {
+        // MediaStore now returns ringtones on other storage devices, even when
+        // we don't have storage or audio permissions
+        return query(
+                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MEDIA_COLUMNS,
+                constructBooleanTrueWhereClause(mFilterColumns), null,
+                MediaStore.Audio.Media.DEFAULT_SORT_ORDER, context);
     }
 
     private void setFilterColumnsList(int type) {
@@ -848,56 +700,6 @@
             columns.add(MediaStore.Audio.AudioColumns.IS_ALARM);
         }
     }
-
-    /**
-     * Returns the sort order for the specified media.
-     *
-     * @param media The RingtoneManager media type.
-     * @return The sort order column.
-     */
-    private static String getSortOrderForMedia(@MediaType int media) {
-        return media == Ringtone.MEDIA_VIBRATION ? MediaStore.Files.FileColumns.TITLE
-                : MediaStore.Audio.Media.DEFAULT_SORT_ORDER;
-    }
-
-    /**
-     * Returns the content URI based on the specified media and whether it's internal or external
-     * storage.
-     *
-     * @param media    The RingtoneManager media type.
-     * @param internal Whether it's for internal or external storage.
-     * @return The media content URI.
-     */
-    private static Uri getContentUriForMedia(@MediaType int media, boolean internal) {
-        switch (media) {
-            case Ringtone.MEDIA_VIBRATION:
-                return MediaStore.Files.getContentUri(
-                        internal ? MediaStore.VOLUME_INTERNAL : MediaStore.VOLUME_EXTERNAL);
-            case Ringtone.MEDIA_SOUND:
-                return internal ? MediaStore.Audio.Media.INTERNAL_CONTENT_URI
-                        : MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
-            default:
-                throw new IllegalArgumentException("Unsupported media type " + media);
-        }
-    }
-
-    /**
-     * Constructs a where clause based on the media type. This will be used to find all matching
-     * sound or vibration files.
-     *
-     * @param media   The RingtoneManager media type.
-     * @param columns The columns that must be true, when media type is {@link Ringtone#MEDIA_SOUND}
-     * @return The where clause.
-     */
-    private static String getWhereClauseForMedia(@MediaType int media, List<String> columns) {
-        // TODO(b/296213309): Filtering by ringtone-type isn't supported yet for vibrations.
-        if (media == Ringtone.MEDIA_VIBRATION) {
-            return TextUtils.formatSimple("(%s='%s')", MediaStore.Files.FileColumns.MIME_TYPE,
-                    VibrationXmlParser.APPLICATION_VIBRATION_XML_MIME_TYPE);
-        }
-
-        return constructBooleanTrueWhereClause(columns);
-    }
     
     /**
      * Constructs a where clause that consists of at least one column being 1
@@ -927,6 +729,14 @@
 
         return sb.toString();
     }
+    
+    private Cursor query(Uri uri,
+            String[] projection,
+            String selection,
+            String[] selectionArgs,
+            String sortOrder) {
+        return query(uri, projection, selection, selectionArgs, sortOrder, mContext);
+    }
 
     private Cursor query(Uri uri,
             String[] projection,
@@ -954,14 +764,40 @@
      * @return A {@link Ringtone} for the given URI, or null.
      */
     public static Ringtone getRingtone(final Context context, Uri ringtoneUri) {
-        if (Flags.hapticsCustomizationRingtoneV2Enabled()) {
-            return new Ringtone.Builder(
-                    context, Ringtone.MEDIA_SOUND, getDefaultAudioAttributes(-1))
-                    .setUri(ringtoneUri)
-                    .build();
-        } else {
-            return createRingtoneV1WithStreamType(context, ringtoneUri, -1, null);
-        }
+        // Don't set the stream type
+        return getRingtone(context, ringtoneUri, -1, true);
+    }
+
+    /**
+     * Returns a {@link Ringtone} with {@link VolumeShaper} if required for a given sound URI.
+     * <p>
+     * If the given URI cannot be opened for any reason, this method will
+     * attempt to fallback on another sound. If it cannot find any, it will
+     * return null.
+     *
+     * @param context A context used to query.
+     * @param ringtoneUri The {@link Uri} of a sound or ringtone.
+     * @param volumeShaperConfig config for volume shaper of the ringtone if applied.
+     * @return A {@link Ringtone} for the given URI, or null.
+     *
+     * @hide
+     */
+    public static Ringtone getRingtone(
+            final Context context, Uri ringtoneUri,
+            @Nullable VolumeShaper.Configuration volumeShaperConfig) {
+        // Don't set the stream type
+        return getRingtone(context, ringtoneUri, -1 /* streamType */, volumeShaperConfig, true);
+    }
+
+    /**
+     * @hide
+     */
+    public static Ringtone getRingtone(final Context context, Uri ringtoneUri,
+            @Nullable VolumeShaper.Configuration volumeShaperConfig,
+            boolean createLocalMediaPlayer) {
+        // Don't set the stream type
+        return getRingtone(context, ringtoneUri, -1 /* streamType */, volumeShaperConfig,
+                createLocalMediaPlayer);
     }
 
     /**
@@ -970,23 +806,64 @@
     public static Ringtone getRingtone(final Context context, Uri ringtoneUri,
             @Nullable VolumeShaper.Configuration volumeShaperConfig,
             AudioAttributes audioAttributes) {
-        // TODO: move caller(s) away from this method: inline the builder call.
-        if (Flags.hapticsCustomizationRingtoneV2Enabled()) {
-            return new Ringtone.Builder(context, Ringtone.MEDIA_SOUND, audioAttributes)
-                    .setUri(ringtoneUri)
-                    .setVolumeShaperConfig(volumeShaperConfig)
-                    .setUseExactAudioAttributes(true)  // May be using audio-coupled via attrs
-                    .build();
-        } else {
-            try {
-                return Ringtone.createV1WithCustomAudioAttributes(context, audioAttributes,
-                        ringtoneUri, volumeShaperConfig, /* allowRemote= */ true);
-            } catch (Exception ex) {
-                // Match broad catching of createRingtoneV1.
-                Log.e(TAG, "Failed to open ringtone " + ringtoneUri + ": " + ex);
+        // Don't set the stream type
+        Ringtone ringtone = getRingtone(context, ringtoneUri, -1 /* streamType */,
+                volumeShaperConfig, false);
+        if (ringtone != null) {
+            ringtone.setAudioAttributesField(audioAttributes);
+            if (!ringtone.createLocalMediaPlayer()) {
+                Log.e(TAG, "Failed to open ringtone " + ringtoneUri);
                 return null;
             }
         }
+        return ringtone;
+    }
+
+    //FIXME bypass the notion of stream types within the class
+    /**
+     * Returns a {@link Ringtone} for a given sound URI on the given stream
+     * type. Normally, if you change the stream type on the returned
+     * {@link Ringtone}, it will re-create the {@link MediaPlayer}. This is just
+     * an optimized route to avoid that.
+     *
+     * @param streamType The stream type for the ringtone, or -1 if it should
+     *            not be set (and the default used instead).
+     * @param createLocalMediaPlayer when true, the ringtone returned will be fully
+     *      created otherwise, it will require the caller to create the media player manually
+     *      {@link Ringtone#createLocalMediaPlayer()} in order to play the Ringtone.
+     * @see #getRingtone(Context, Uri)
+     */
+    @UnsupportedAppUsage
+    private static Ringtone getRingtone(final Context context, Uri ringtoneUri, int streamType,
+            boolean createLocalMediaPlayer) {
+        return getRingtone(context, ringtoneUri, streamType, null /* volumeShaperConfig */,
+                createLocalMediaPlayer);
+    }
+
+    private static Ringtone getRingtone(final Context context, Uri ringtoneUri, int streamType,
+            @Nullable VolumeShaper.Configuration volumeShaperConfig,
+            boolean createLocalMediaPlayer) {
+        try {
+            final Ringtone r = new Ringtone(context, true);
+            if (streamType >= 0) {
+                //FIXME deprecated call
+                r.setStreamType(streamType);
+            }
+
+            r.setVolumeShaperConfig(volumeShaperConfig);
+            r.setUri(ringtoneUri, volumeShaperConfig);
+            if (createLocalMediaPlayer) {
+                if (!r.createLocalMediaPlayer()) {
+                    Log.e(TAG, "Failed to open ringtone " + ringtoneUri);
+                    return null;
+                }
+            }
+            return r;
+        } catch (Exception ex) {
+            Log.e(TAG, "Failed to open ringtone " + ringtoneUri + ": " + ex);
+        }
+
+        return null;
     }
 
     /**
diff --git a/media/java/android/media/RingtoneV1.java b/media/java/android/media/RingtoneV1.java
deleted file mode 100644
index 3c54d4a..0000000
--- a/media/java/android/media/RingtoneV1.java
+++ /dev/null
@@ -1,614 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-import android.annotation.Nullable;
-import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.Resources.NotFoundException;
-import android.media.audiofx.HapticGenerator;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Build;
-import android.os.RemoteException;
-import android.os.Trace;
-import android.os.VibrationEffect;
-import android.provider.MediaStore;
-import android.provider.MediaStore.MediaColumns;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.IOException;
-import java.util.ArrayList;
-
-/**
- * Hosts original Ringtone implementation, retained for flagging large builder+vibration features
- * in RingtoneV2.java. This does not support new features in the V2 builder.
- *
- * Only modified methods are moved here.
- *
- * @hide
- */
-class RingtoneV1 implements Ringtone.ApiInterface {
-    private static final String TAG = "RingtoneV1";
-    private static final boolean LOGD = true;
-
-    private static final String[] MEDIA_COLUMNS = new String[] {
-            MediaStore.Audio.Media._ID,
-            MediaStore.Audio.Media.TITLE
-    };
-    /** Selection that limits query results to just audio files */
-    private static final String MEDIA_SELECTION = MediaColumns.MIME_TYPE + " LIKE 'audio/%' OR "
-            + MediaColumns.MIME_TYPE + " IN ('application/ogg', 'application/x-flac')";
-
-    // keep references on active Ringtones until stopped or completion listener called.
-    private static final ArrayList<RingtoneV1> sActiveRingtones = new ArrayList<>();
-
-    private final Context mContext;
-    private final AudioManager mAudioManager;
-    private VolumeShaper.Configuration mVolumeShaperConfig;
-    private VolumeShaper mVolumeShaper;
-
-    /**
-     * Flag indicating if we're allowed to fall back to remote playback using
-     * {@link #mRemotePlayer}. Typically this is false when we're the remote
-     * player and there is nobody else to delegate to.
-     */
-    private final boolean mAllowRemote;
-    private final IRingtonePlayer mRemotePlayer;
-    private final Binder mRemoteToken;
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private MediaPlayer mLocalPlayer;
-    private final MyOnCompletionListener mCompletionListener = new MyOnCompletionListener();
-    private HapticGenerator mHapticGenerator;
-
-    @UnsupportedAppUsage
-    private Uri mUri;
-    private String mTitle;
-
-    private AudioAttributes mAudioAttributes = new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
-            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-            .build();
-    private boolean mPreferBuiltinDevice;
-    // playback properties, use synchronized with mPlaybackSettingsLock
-    private boolean mIsLooping = false;
-    private float mVolume = 1.0f;
-    private boolean mHapticGeneratorEnabled = false;
-    private final Object mPlaybackSettingsLock = new Object();
-
-    /** {@hide} */
-    @UnsupportedAppUsage
-    public RingtoneV1(Context context, boolean allowRemote) {
-        mContext = context;
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        mAllowRemote = allowRemote;
-        mRemotePlayer = allowRemote ? mAudioManager.getRingtonePlayer() : null;
-        mRemoteToken = allowRemote ? new Binder() : null;
-    }
-
-    /**
-     * Sets the stream type where this ringtone will be played.
-     *
-     * @param streamType The stream, see {@link AudioManager}.
-     * @deprecated use {@link #setAudioAttributes(AudioAttributes)}
-     */
-    @Deprecated
-    public void setStreamType(int streamType) {
-        PlayerBase.deprecateStreamTypeForPlayback(streamType, "Ringtone", "setStreamType()");
-        setAudioAttributes(new AudioAttributes.Builder()
-                .setInternalLegacyStreamType(streamType)
-                .build());
-    }
-
-    /**
-     * Gets the stream type where this ringtone will be played.
-     *
-     * @return The stream type, see {@link AudioManager}.
-     * @deprecated use of stream types is deprecated, see
-     *     {@link #setAudioAttributes(AudioAttributes)}
-     */
-    @Deprecated
-    public int getStreamType() {
-        return AudioAttributes.toLegacyStreamType(mAudioAttributes);
-    }
-
-    /**
-     * Sets the {@link AudioAttributes} for this ringtone.
-     * @param attributes the non-null attributes characterizing this ringtone.
-     */
-    public void setAudioAttributes(AudioAttributes attributes)
-            throws IllegalArgumentException {
-        setAudioAttributesField(attributes);
-        // The audio attributes have to be set before the media player is prepared.
-        // Re-initialize it.
-        setUri(mUri, mVolumeShaperConfig);
-        reinitializeActivePlayer();
-    }
-
-    /**
-     * Same as {@link #setAudioAttributes(AudioAttributes)} except this one does not create
-     * the media player.
-     * @hide
-     */
-    public void setAudioAttributesField(@Nullable AudioAttributes attributes) {
-        if (attributes == null) {
-            throw new IllegalArgumentException("Invalid null AudioAttributes for Ringtone");
-        }
-        mAudioAttributes = attributes;
-    }
-
-    /**
-     * Finds the output device of type {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER}. This device is
-     * the one on which outgoing audio for SIM calls is played.
-     *
-     * @param audioManager the audio manage.
-     * @return the {@link AudioDeviceInfo} corresponding to the builtin device, or {@code null} if
-     *     none can be found.
-     */
-    private AudioDeviceInfo getBuiltinDevice(AudioManager audioManager) {
-        AudioDeviceInfo[] deviceList = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
-        for (AudioDeviceInfo device : deviceList) {
-            if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
-                return device;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Sets the preferred device of the ringtong playback to the built-in device.
-     *
-     * @hide
-     */
-    public boolean preferBuiltinDevice(boolean enable) {
-        mPreferBuiltinDevice = enable;
-        if (mLocalPlayer == null) {
-            return true;
-        }
-        return mLocalPlayer.setPreferredDevice(getBuiltinDevice(mAudioManager));
-    }
-
-    /**
-     * Creates a local media player for the ringtone using currently set attributes.
-     * @return true if media player creation succeeded or is deferred,
-     * false if it did not succeed and can't be tried remotely.
-     * @hide
-     */
-    public boolean reinitializeActivePlayer() {
-        Trace.beginSection("reinitializeActivePlayer");
-        if (mUri == null) {
-            Log.e(TAG, "Could not create media player as no URI was provided.");
-            return mAllowRemote && mRemotePlayer != null;
-        }
-        destroyLocalPlayer();
-        // try opening uri locally before delegating to remote player
-        mLocalPlayer = new MediaPlayer();
-        try {
-            mLocalPlayer.setDataSource(mContext, mUri);
-            mLocalPlayer.setAudioAttributes(mAudioAttributes);
-            mLocalPlayer.setPreferredDevice(
-                    mPreferBuiltinDevice ? getBuiltinDevice(mAudioManager) : null);
-            synchronized (mPlaybackSettingsLock) {
-                applyPlaybackProperties_sync();
-            }
-            if (mVolumeShaperConfig != null) {
-                mVolumeShaper = mLocalPlayer.createVolumeShaper(mVolumeShaperConfig);
-            }
-            mLocalPlayer.prepare();
-
-        } catch (SecurityException | IOException e) {
-            destroyLocalPlayer();
-            if (!mAllowRemote) {
-                Log.w(TAG, "Remote playback not allowed: " + e);
-            }
-        }
-
-        if (LOGD) {
-            if (mLocalPlayer != null) {
-                Log.d(TAG, "Successfully created local player");
-            } else {
-                Log.d(TAG, "Problem opening; delegating to remote player");
-            }
-        }
-        Trace.endSection();
-        return mLocalPlayer != null || (mAllowRemote && mRemotePlayer != null);
-    }
-
-    /**
-     * Same as AudioManager.hasHapticChannels except it assumes an already created ringtone.
-     * If the ringtone has not been created, it will load based on URI provided at {@link #setUri}
-     * and if not URI has been set, it will assume no haptic channels are present.
-     * @hide
-     */
-    public boolean hasHapticChannels() {
-        // FIXME: support remote player, or internalize haptic channels support and remove entirely.
-        try {
-            android.os.Trace.beginSection("Ringtone.hasHapticChannels");
-            if (mLocalPlayer != null) {
-                for(MediaPlayer.TrackInfo trackInfo : mLocalPlayer.getTrackInfo()) {
-                    if (trackInfo.hasHapticChannels()) {
-                        return true;
-                    }
-                }
-            }
-        } finally {
-            android.os.Trace.endSection();
-        }
-        return false;
-    }
-
-    /**
-     * Returns whether a local player has been created for this ringtone.
-     * @hide
-     */
-    @VisibleForTesting
-    public boolean hasLocalPlayer() {
-        return mLocalPlayer != null;
-    }
-
-    public @Ringtone.RingtoneMedia int getEnabledMedia() {
-        return Ringtone.MEDIA_SOUND;  // RingtoneV2 only
-    }
-
-    public VibrationEffect getVibrationEffect() {
-        return null;  // RingtoneV2 only
-    }
-
-    /**
-     * Returns the {@link AudioAttributes} used by this object.
-     * @return the {@link AudioAttributes} that were set with
-     *     {@link #setAudioAttributes(AudioAttributes)} or the default attributes if none were set.
-     */
-    public AudioAttributes getAudioAttributes() {
-        return mAudioAttributes;
-    }
-
-    /**
-     * Sets the player to be looping or non-looping.
-     * @param looping whether to loop or not.
-     */
-    public void setLooping(boolean looping) {
-        synchronized (mPlaybackSettingsLock) {
-            mIsLooping = looping;
-            applyPlaybackProperties_sync();
-        }
-    }
-
-    /**
-     * Returns whether the looping mode was enabled on this player.
-     * @return true if this player loops when playing.
-     */
-    public boolean isLooping() {
-        synchronized (mPlaybackSettingsLock) {
-            return mIsLooping;
-        }
-    }
-
-    /**
-     * Sets the volume on this player.
-     * @param volume a raw scalar in range 0.0 to 1.0, where 0.0 mutes this player, and 1.0
-     *   corresponds to no attenuation being applied.
-     */
-    public void setVolume(float volume) {
-        synchronized (mPlaybackSettingsLock) {
-            if (volume < 0.0f) { volume = 0.0f; }
-            if (volume > 1.0f) { volume = 1.0f; }
-            mVolume = volume;
-            applyPlaybackProperties_sync();
-        }
-    }
-
-    /**
-     * Returns the volume scalar set on this player.
-     * @return a value between 0.0f and 1.0f.
-     */
-    public float getVolume() {
-        synchronized (mPlaybackSettingsLock) {
-            return mVolume;
-        }
-    }
-
-    /**
-     * Enable or disable the {@link android.media.audiofx.HapticGenerator} effect. The effect can
-     * only be enabled on devices that support the effect.
-     *
-     * @return true if the HapticGenerator effect is successfully enabled. Otherwise, return false.
-     * @see android.media.audiofx.HapticGenerator#isAvailable()
-     */
-    public boolean setHapticGeneratorEnabled(boolean enabled) {
-        if (!HapticGenerator.isAvailable()) {
-            return false;
-        }
-        synchronized (mPlaybackSettingsLock) {
-            mHapticGeneratorEnabled = enabled;
-            applyPlaybackProperties_sync();
-        }
-        return true;
-    }
-
-    /**
-     * Return whether the {@link android.media.audiofx.HapticGenerator} effect is enabled or not.
-     * @return true if the HapticGenerator is enabled.
-     */
-    public boolean isHapticGeneratorEnabled() {
-        synchronized (mPlaybackSettingsLock) {
-            return mHapticGeneratorEnabled;
-        }
-    }
-
-    /**
-     * Must be called synchronized on mPlaybackSettingsLock
-     */
-    private void applyPlaybackProperties_sync() {
-        if (mLocalPlayer != null) {
-            mLocalPlayer.setVolume(mVolume);
-            mLocalPlayer.setLooping(mIsLooping);
-            if (mHapticGenerator == null && mHapticGeneratorEnabled) {
-                mHapticGenerator = HapticGenerator.create(mLocalPlayer.getAudioSessionId());
-            }
-            if (mHapticGenerator != null) {
-                mHapticGenerator.setEnabled(mHapticGeneratorEnabled);
-            }
-        } else if (mAllowRemote && (mRemotePlayer != null)) {
-            try {
-                mRemotePlayer.setPlaybackProperties(
-                        mRemoteToken, mVolume, mIsLooping, mHapticGeneratorEnabled);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Problem setting playback properties: ", e);
-            }
-        } else {
-            Log.w(TAG,
-                    "Neither local nor remote player available when applying playback properties");
-        }
-    }
-
-    /**
-     * Returns a human-presentable title for ringtone. Looks in media
-     * content provider. If not in either, uses the filename
-     *
-     * @param context A context used for querying.
-     */
-    public String getTitle(Context context) {
-        if (mTitle != null) return mTitle;
-        return mTitle = Ringtone.getTitle(context, mUri, true /*followSettingsUri*/, mAllowRemote);
-    }
-
-    /**
-     * Set {@link Uri} to be used for ringtone playback.
-     * {@link IRingtonePlayer}.
-     *
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public void setUri(Uri uri) {
-        setUri(uri, null);
-    }
-
-    /**
-     * @hide
-     */
-    public void setVolumeShaperConfig(@Nullable VolumeShaper.Configuration volumeShaperConfig) {
-        mVolumeShaperConfig = volumeShaperConfig;
-    }
-
-    /**
-     * Set {@link Uri} to be used for ringtone playback. Attempts to open
-     * locally, otherwise will delegate playback to remote
-     * {@link IRingtonePlayer}. Add {@link VolumeShaper} if required.
-     *
-     * @hide
-     */
-    public void setUri(Uri uri, @Nullable VolumeShaper.Configuration volumeShaperConfig) {
-        mVolumeShaperConfig = volumeShaperConfig;
-        mUri = uri;
-        if (mUri == null) {
-            destroyLocalPlayer();
-        }
-    }
-
-    /** {@hide} */
-    @UnsupportedAppUsage
-    public Uri getUri() {
-        return mUri;
-    }
-
-    /**
-     * Plays the ringtone.
-     */
-    public void play() {
-        if (mLocalPlayer != null) {
-            // Play ringtones if stream volume is over 0 or if it is a haptic-only ringtone
-            // (typically because ringer mode is vibrate).
-            if (mAudioManager.getStreamVolume(AudioAttributes.toLegacyStreamType(mAudioAttributes))
-                    != 0) {
-                startLocalPlayer();
-            } else if (!mAudioAttributes.areHapticChannelsMuted() && hasHapticChannels()) {
-                // is haptic only ringtone
-                startLocalPlayer();
-            }
-        } else if (mAllowRemote && (mRemotePlayer != null) && (mUri != null)) {
-            final Uri canonicalUri = mUri.getCanonicalUri();
-            final boolean looping;
-            final float volume;
-            synchronized (mPlaybackSettingsLock) {
-                looping = mIsLooping;
-                volume = mVolume;
-            }
-            try {
-                mRemotePlayer.playWithVolumeShaping(mRemoteToken, canonicalUri, mAudioAttributes,
-                        volume, looping, mVolumeShaperConfig);
-            } catch (RemoteException e) {
-                if (!playFallbackRingtone()) {
-                    Log.w(TAG, "Problem playing ringtone: " + e);
-                }
-            }
-        } else {
-            if (!playFallbackRingtone()) {
-                Log.w(TAG, "Neither local nor remote playback available");
-            }
-        }
-    }
-
-    /**
-     * Stops a playing ringtone.
-     */
-    public void stop() {
-        if (mLocalPlayer != null) {
-            destroyLocalPlayer();
-        } else if (mAllowRemote && (mRemotePlayer != null)) {
-            try {
-                mRemotePlayer.stop(mRemoteToken);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Problem stopping ringtone: " + e);
-            }
-        }
-    }
-
-    private void destroyLocalPlayer() {
-        if (mLocalPlayer != null) {
-            if (mHapticGenerator != null) {
-                mHapticGenerator.release();
-                mHapticGenerator = null;
-            }
-            mLocalPlayer.setOnCompletionListener(null);
-            mLocalPlayer.reset();
-            mLocalPlayer.release();
-            mLocalPlayer = null;
-            mVolumeShaper = null;
-            synchronized (sActiveRingtones) {
-                sActiveRingtones.remove(this);
-            }
-        }
-    }
-
-    private void startLocalPlayer() {
-        if (mLocalPlayer == null) {
-            return;
-        }
-        synchronized (sActiveRingtones) {
-            sActiveRingtones.add(this);
-        }
-        if (LOGD) {
-            Log.d(TAG, "Starting ringtone playback");
-        }
-        mLocalPlayer.setOnCompletionListener(mCompletionListener);
-        mLocalPlayer.start();
-        if (mVolumeShaper != null) {
-            mVolumeShaper.apply(VolumeShaper.Operation.PLAY);
-        }
-    }
-
-    /**
-     * Whether this ringtone is currently playing.
-     *
-     * @return True if playing, false otherwise.
-     */
-    public boolean isPlaying() {
-        if (mLocalPlayer != null) {
-            return mLocalPlayer.isPlaying();
-        } else if (mAllowRemote && (mRemotePlayer != null)) {
-            try {
-                return mRemotePlayer.isPlaying(mRemoteToken);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Problem checking ringtone: " + e);
-                return false;
-            }
-        } else {
-            Log.w(TAG, "Neither local nor remote playback available");
-            return false;
-        }
-    }
-
-    private boolean playFallbackRingtone() {
-        int streamType = AudioAttributes.toLegacyStreamType(mAudioAttributes);
-        if (mAudioManager.getStreamVolume(streamType) == 0) {
-            return false;
-        }
-        int ringtoneType = RingtoneManager.getDefaultType(mUri);
-        if (ringtoneType != -1 &&
-                RingtoneManager.getActualDefaultRingtoneUri(mContext, ringtoneType) == null) {
-            Log.w(TAG, "not playing fallback for " + mUri);
-            return false;
-        }
-        // Default ringtone, try fallback ringtone.
-        try {
-            AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(
-                    com.android.internal.R.raw.fallbackring);
-            if (afd == null) {
-                Log.e(TAG, "Could not load fallback ringtone");
-                return false;
-            }
-            mLocalPlayer = new MediaPlayer();
-            if (afd.getDeclaredLength() < 0) {
-                mLocalPlayer.setDataSource(afd.getFileDescriptor());
-            } else {
-                mLocalPlayer.setDataSource(afd.getFileDescriptor(),
-                        afd.getStartOffset(),
-                        afd.getDeclaredLength());
-            }
-            mLocalPlayer.setAudioAttributes(mAudioAttributes);
-            synchronized (mPlaybackSettingsLock) {
-                applyPlaybackProperties_sync();
-            }
-            if (mVolumeShaperConfig != null) {
-                mVolumeShaper = mLocalPlayer.createVolumeShaper(mVolumeShaperConfig);
-            }
-            mLocalPlayer.prepare();
-            startLocalPlayer();
-            afd.close();
-        } catch (IOException ioe) {
-            destroyLocalPlayer();
-            Log.e(TAG, "Failed to open fallback ringtone");
-            return false;
-        } catch (NotFoundException nfe) {
-            Log.e(TAG, "Fallback ringtone does not exist");
-            return false;
-        }
-        return true;
-    }
-
-    public boolean getPreferBuiltinDevice() {
-        return mPreferBuiltinDevice;
-    }
-
-    public VolumeShaper.Configuration getVolumeShaperConfig() {
-        return mVolumeShaperConfig;
-    }
-
-    public boolean isLocalOnly() {
-        return mAllowRemote;
-    }
-
-    public boolean isUsingRemotePlayer() {
-        // V2 testing api, but this is the v1 approximation.
-        return (mLocalPlayer == null) && mAllowRemote && (mRemotePlayer != null);
-    }
-
-    class MyOnCompletionListener implements MediaPlayer.OnCompletionListener {
-        @Override
-        public void onCompletion(MediaPlayer mp) {
-            synchronized (sActiveRingtones) {
-                sActiveRingtones.remove(RingtoneV1.this);
-            }
-            mp.setOnCompletionListener(null); // Help the Java GC: break the refcount cycle.
-        }
-    }
-}
diff --git a/media/java/android/media/RingtoneV2.java b/media/java/android/media/RingtoneV2.java
deleted file mode 100644
index f1a8155..0000000
--- a/media/java/android/media/RingtoneV2.java
+++ /dev/null
@@ -1,690 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.Resources.NotFoundException;
-import android.media.Ringtone.Injectables;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.Trace;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.provider.MediaStore;
-import android.provider.MediaStore.MediaColumns;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * New Ringtone implementation, supporting vibration as well as sound, and configuration via a
- * builder. During flagged transition, the original implementation is in RingtoneV1.java.
- *
- * Only modified methods are moved here.
- *
- * @hide
- */
-class RingtoneV2 implements Ringtone.ApiInterface {
-    private static final String TAG = "RingtoneV2";
-
-    /**
-     * The ringtone should only play sound. Any vibration is managed externally.
-     * @hide
-     */
-    public static final int MEDIA_SOUND = 1;
-    /**
-     * The ringtone should only play vibration. Any sound is managed externally.
-     * Requires the {@link android.Manifest.permission#VIBRATE} permission.
-     * @hide
-     */
-    public static final int MEDIA_VIBRATION = 1 << 1;
-    /**
-     * The ringtone should play sound and vibration.
-     * @hide
-     */
-    public static final int MEDIA_SOUND_AND_VIBRATION = MEDIA_SOUND | MEDIA_VIBRATION;
-
-    // This is not a public value, because apps shouldn't enable "all" media - that wouldn't be
-    // safe if new media types were added.
-    static final int MEDIA_ALL = MEDIA_SOUND | MEDIA_VIBRATION;
-
-    /**
-     * Declares the types of media that this Ringtone is allowed to play.
-     * @hide
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = "MEDIA_", value = {
-            MEDIA_SOUND,
-            MEDIA_VIBRATION,
-            MEDIA_SOUND_AND_VIBRATION,
-    })
-    public @interface RingtoneMedia {}
-
-    private static final String[] MEDIA_COLUMNS = new String[] {
-        MediaStore.Audio.Media._ID,
-        MediaStore.Audio.Media.TITLE
-    };
-    /** Selection that limits query results to just audio files */
-    private static final String MEDIA_SELECTION = MediaColumns.MIME_TYPE + " LIKE 'audio/%' OR "
-            + MediaColumns.MIME_TYPE + " IN ('application/ogg', 'application/x-flac')";
-
-    private final Context mContext;
-    private final Vibrator mVibrator;
-    private final AudioManager mAudioManager;
-    private VolumeShaper.Configuration mVolumeShaperConfig;
-
-    /**
-     * Flag indicating if we're allowed to fall back to remote playback using
-     * {@link #mRemoteRingtoneService}. Typically this is false when we're the remote
-     * player and there is nobody else to delegate to.
-     */
-    private final boolean mAllowRemote;
-    private final IRingtonePlayer mRemoteRingtoneService;
-    private final Injectables mInjectables;
-
-    private final int mEnabledMedia;
-
-    private final Uri mUri;
-    private String mTitle;
-
-    private AudioAttributes mAudioAttributes;
-    private boolean mUseExactAudioAttributes;
-    private boolean mPreferBuiltinDevice;
-    private RingtonePlayer mActivePlayer;
-    // playback properties, use synchronized with mPlaybackSettingsLock
-    private boolean mIsLooping;
-    private float mVolume;
-    private boolean mHapticGeneratorEnabled;
-    private final Object mPlaybackSettingsLock = new Object();
-    private final VibrationEffect mVibrationEffect;
-
-    /** Only for use by Ringtone constructor */
-    RingtoneV2(@NonNull Context context, @NonNull Injectables injectables,
-                       boolean allowRemote, @Ringtone.RingtoneMedia int enabledMedia,
-                       @Nullable Uri uri, @NonNull AudioAttributes audioAttributes,
-                       boolean useExactAudioAttributes,
-                       @Nullable VolumeShaper.Configuration volumeShaperConfig,
-                       boolean preferBuiltinDevice, float soundVolume, boolean looping,
-                       boolean hapticGeneratorEnabled, @Nullable VibrationEffect vibrationEffect) {
-        // Context
-        mContext = context;
-        mInjectables = injectables;
-        mVibrator = mContext.getSystemService(Vibrator.class);
-        mAudioManager = mContext.getSystemService(AudioManager.class);
-        mRemoteRingtoneService = allowRemote ? mAudioManager.getRingtonePlayer() : null;
-        mAllowRemote = (mRemoteRingtoneService != null);  // Only set if allowed, and present.
-
-        // Properties potentially propagated to remote player.
-        mEnabledMedia = enabledMedia;
-        mUri = uri;
-        mAudioAttributes = audioAttributes;
-        mUseExactAudioAttributes = useExactAudioAttributes;
-        mVolumeShaperConfig = volumeShaperConfig;
-        mPreferBuiltinDevice = preferBuiltinDevice;  // system-only, not supported for remote play.
-        mVolume = soundVolume;
-        mIsLooping = looping;
-        mHapticGeneratorEnabled = hapticGeneratorEnabled;
-        mVibrationEffect = vibrationEffect;
-    }
-
-    /** @hide */
-    @RingtoneMedia
-    public int getEnabledMedia() {
-        return mEnabledMedia;
-    }
-
-    /**
-     * Sets the stream type where this ringtone will be played.
-     *
-     * @param streamType The stream, see {@link AudioManager}.
-     * @deprecated use {@link #setAudioAttributes(AudioAttributes)}
-     */
-    @Deprecated
-    public void setStreamType(int streamType) {
-        setAudioAttributes(
-                getAudioAttributesForLegacyStreamType(streamType, "setStreamType()"));
-    }
-
-    private AudioAttributes getAudioAttributesForLegacyStreamType(int streamType, String originOp) {
-        PlayerBase.deprecateStreamTypeForPlayback(streamType, "Ringtone", originOp);
-        return new AudioAttributes.Builder()
-                .setInternalLegacyStreamType(streamType)
-                .build();
-    }
-
-    /**
-     * Gets the stream type where this ringtone will be played.
-     *
-     * @return The stream type, see {@link AudioManager}.
-     * @deprecated use of stream types is deprecated, see
-     *     {@link #setAudioAttributes(AudioAttributes)}
-     */
-    @Deprecated
-    public int getStreamType() {
-        return AudioAttributes.toLegacyStreamType(mAudioAttributes);
-    }
-
-    /**
-     * Sets the {@link AudioAttributes} for this ringtone.
-     * @param attributes the non-null attributes characterizing this ringtone.
-     */
-    public void setAudioAttributes(AudioAttributes attributes)
-            throws IllegalArgumentException {
-        // TODO: deprecate this method - it will be done with a builder.
-        if (attributes == null) {
-            throw new IllegalArgumentException("Invalid null AudioAttributes for Ringtone");
-        }
-        mAudioAttributes = attributes;
-        // Setting the audio attributes requires re-initializing the player.
-        if (mActivePlayer != null) {
-            // The audio attributes have to be set before the media player is prepared.
-            // Re-initialize it.
-            reinitializeActivePlayer();
-        }
-    }
-
-    /**
-     * Returns the vibration effect that this ringtone was created with, if vibration is enabled.
-     * Otherwise, returns null.
-     * @hide
-     */
-    @Nullable
-    public VibrationEffect getVibrationEffect() {
-        return mVibrationEffect;
-    }
-
-    /** @hide */
-    @VisibleForTesting
-    public boolean getPreferBuiltinDevice() {
-        return mPreferBuiltinDevice;
-    }
-
-    /** @hide */
-    @VisibleForTesting
-    public VolumeShaper.Configuration getVolumeShaperConfig() {
-        return mVolumeShaperConfig;
-    }
-
-    /**
-     * Returns whether this player is local only, or can defer to the remote player. The
-     * result may differ from the builder if there is no remote player available at all.
-     * @hide
-     */
-    @VisibleForTesting
-    public boolean isLocalOnly() {
-        return !mAllowRemote;
-    }
-
-    /** @hide */
-    @VisibleForTesting
-    public boolean isUsingRemotePlayer() {
-        return mActivePlayer instanceof RemoteRingtonePlayer;
-    }
-
-    /**
-     * Finds the output device of type {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER}. This device is
-     * the one on which outgoing audio for SIM calls is played.
-     *
-     * @param audioManager the audio manage.
-     * @return the {@link AudioDeviceInfo} corresponding to the builtin device, or {@code null} if
-     *     none can be found.
-     */
-    private AudioDeviceInfo getBuiltinDevice(AudioManager audioManager) {
-        AudioDeviceInfo[] deviceList = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
-        for (AudioDeviceInfo device : deviceList) {
-            if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
-                return device;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Creates a local media player for the ringtone using currently set attributes.
-     * @return true if media player creation succeeded or is deferred,
-     * false if it did not succeed and can't be tried remotely.
-     * @hide
-     */
-    public boolean reinitializeActivePlayer() {
-        // Try creating a local media player, or fallback to creating a remote one.
-        Trace.beginSection("reinitializeActivePlayer");
-        try {
-            if (mActivePlayer != null) {
-                // This would only happen if calling the deprecated setAudioAttributes after
-                // building the Ringtone.
-                stopAndReleaseActivePlayer();
-            }
-
-            boolean vibrationOnly = (mEnabledMedia & MEDIA_ALL) == MEDIA_VIBRATION;
-            // Vibration can come from the audio file if using haptic generator or if haptic
-            // channels are a possibility.
-            boolean maybeAudioVibration = mUri != null && mInjectables.isHapticPlaybackSupported()
-                    && (mHapticGeneratorEnabled || !mAudioAttributes.areHapticChannelsMuted());
-
-            // VibrationEffect only, use the simplified player without checking for haptic channels.
-            if (vibrationOnly && !maybeAudioVibration && mVibrationEffect != null) {
-                mActivePlayer = new LocalRingtonePlayer.VibrationEffectPlayer(
-                        mVibrationEffect, mAudioAttributes, mVibrator, mIsLooping);
-                return true;
-            }
-
-            AudioDeviceInfo preferredDevice =
-                    mPreferBuiltinDevice ? getBuiltinDevice(mAudioManager) : null;
-            if (mUri != null) {
-                mActivePlayer = LocalRingtonePlayer.create(mContext, mAudioManager, mVibrator, mUri,
-                        mAudioAttributes, vibrationOnly, mVibrationEffect, mInjectables,
-                        mVolumeShaperConfig, preferredDevice, mHapticGeneratorEnabled, mIsLooping,
-                        mVolume);
-            } else {
-                // Using the remote player won't help play a null Uri. Revert straight to fallback.
-                // The vibration-only case was already covered above.
-                mActivePlayer = createFallbackRingtonePlayer();
-                // Fall through to attempting remote fallback play if null.
-            }
-
-            if (mActivePlayer == null && mAllowRemote) {
-                mActivePlayer = new RemoteRingtonePlayer(mRemoteRingtoneService, mUri,
-                        mAudioAttributes, mUseExactAudioAttributes, mEnabledMedia, mVibrationEffect,
-                        mVolumeShaperConfig, mHapticGeneratorEnabled, mIsLooping, mVolume);
-            }
-
-            return mActivePlayer != null;
-        } finally {
-            if (mActivePlayer != null) {
-                Log.d(TAG, "Initialized ringtone player with " + mActivePlayer.getClass());
-            } else {
-                Log.d(TAG, "Failed to initialize ringtone player");
-            }
-            Trace.endSection();
-        }
-    }
-
-    @Nullable
-    private LocalRingtonePlayer createFallbackRingtonePlayer() {
-        int ringtoneType = RingtoneManager.getDefaultType(mUri);
-        if (ringtoneType != -1
-                && RingtoneManager.getActualDefaultRingtoneUri(mContext, ringtoneType) == null) {
-            Log.w(TAG, "not playing fallback for " + mUri);
-            return null;
-        }
-        // Default ringtone, try fallback ringtone.
-        try (AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(
-                    com.android.internal.R.raw.fallbackring)) {
-            if (afd == null) {
-                Log.e(TAG, "Could not load fallback ringtone");
-                return null;
-            }
-
-            AudioDeviceInfo preferredDevice =
-                    mPreferBuiltinDevice ? getBuiltinDevice(mAudioManager) : null;
-            return LocalRingtonePlayer.createForFallback(mAudioManager, mVibrator, afd,
-                    mAudioAttributes, mVibrationEffect, mInjectables, mVolumeShaperConfig,
-                    preferredDevice, mIsLooping, mVolume);
-        } catch (NotFoundException nfe) {
-            Log.e(TAG, "Fallback ringtone does not exist");
-            return null;
-        } catch (IOException e) {
-            // As with the above messages, not including much information about the
-            // failure so as not to expose details of the fallback ringtone resource.
-            Log.e(TAG, "Exception reading fallback ringtone");
-            return null;
-        }
-    }
-
-    /**
-     * Same as AudioManager.hasHapticChannels except it assumes an already created ringtone.
-     * @hide
-     */
-    public boolean hasHapticChannels() {
-        return (mActivePlayer == null) ? false : mActivePlayer.hasHapticChannels();
-    }
-
-    /**
-     * Returns the {@link AudioAttributes} used by this object.
-     * @return the {@link AudioAttributes} that were set with
-     *     {@link #setAudioAttributes(AudioAttributes)} or the default attributes if none were set.
-     */
-    public AudioAttributes getAudioAttributes() {
-        return mAudioAttributes;
-    }
-
-    /**
-     * Sets the player to be looping or non-looping.
-     * @param looping whether to loop or not.
-     */
-    public void setLooping(boolean looping) {
-        synchronized (mPlaybackSettingsLock) {
-            mIsLooping = looping;
-            if (mActivePlayer != null) {
-                mActivePlayer.setLooping(looping);
-            }
-        }
-    }
-
-    /**
-     * Returns whether the looping mode was enabled on this player.
-     * @return true if this player loops when playing.
-     */
-    public boolean isLooping() {
-        synchronized (mPlaybackSettingsLock) {
-            return mIsLooping;
-        }
-    }
-
-    /**
-     * Sets the volume on this player.
-     * @param volume a raw scalar in range 0.0 to 1.0, where 0.0 mutes this player, and 1.0
-     *   corresponds to no attenuation being applied.
-     */
-    public void setVolume(float volume) {
-        // Ignore if sound not enabled.
-        if ((mEnabledMedia & MEDIA_SOUND) == 0) {
-            return;
-        }
-        if (volume < 0.0f) {
-            volume = 0.0f;
-        } else if (volume > 1.0f) {
-            volume = 1.0f;
-        }
-
-        synchronized (mPlaybackSettingsLock) {
-            mVolume = volume;
-            if (mActivePlayer != null) {
-                mActivePlayer.setVolume(volume);
-            }
-        }
-    }
-
-    /**
-     * Returns the volume scalar set on this player.
-     * @return a value between 0.0f and 1.0f.
-     */
-    public float getVolume() {
-        synchronized (mPlaybackSettingsLock) {
-            return mVolume;
-        }
-    }
-
-    /**
-     * Enable or disable the {@link android.media.audiofx.HapticGenerator} effect. The effect can
-     * only be enabled on devices that support the effect.
-     *
-     * @return true if the HapticGenerator effect is successfully enabled. Otherwise, return false.
-     * @see android.media.audiofx.HapticGenerator#isAvailable()
-     */
-    public boolean setHapticGeneratorEnabled(boolean enabled) {
-        if (!mInjectables.isHapticGeneratorAvailable()) {
-            return false;
-        }
-        synchronized (mPlaybackSettingsLock) {
-            mHapticGeneratorEnabled = enabled;
-            if (mActivePlayer != null) {
-                mActivePlayer.setHapticGeneratorEnabled(enabled);
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Return whether the {@link android.media.audiofx.HapticGenerator} effect is enabled or not.
-     * @return true if the HapticGenerator is enabled.
-     */
-    public boolean isHapticGeneratorEnabled() {
-        synchronized (mPlaybackSettingsLock) {
-            return mHapticGeneratorEnabled;
-        }
-    }
-
-    /**
-     * Returns a human-presentable title for ringtone. Looks in media
-     * content provider. If not in either, uses the filename
-     *
-     * @param context A context used for querying.
-     */
-    public String getTitle(Context context) {
-        if (mTitle != null) return mTitle;
-        return mTitle = Ringtone.getTitle(context, mUri, true /*followSettingsUri*/, mAllowRemote);
-    }
-
-
-    /** {@hide} */
-    @UnsupportedAppUsage
-    public Uri getUri() {
-        return mUri;
-    }
-
-    /**
-     * Plays the ringtone.
-     */
-    public void play() {
-        if (mActivePlayer != null) {
-            Log.d(TAG, "Starting ringtone playback");
-            if (mActivePlayer.play()) {
-                return;
-            } else {
-                // Discard active player: play() is only meant to be called once.
-                stopAndReleaseActivePlayer();
-            }
-        }
-        if (!playFallbackRingtone()) {
-            Log.w(TAG, "Neither local nor remote playback available");
-        }
-    }
-
-    /**
-     * Stops a playing ringtone.
-     */
-    public void stop() {
-        stopAndReleaseActivePlayer();
-    }
-
-    private void stopAndReleaseActivePlayer() {
-        if (mActivePlayer != null) {
-            mActivePlayer.stopAndRelease();
-            mActivePlayer = null;
-        }
-    }
-
-    /**
-     * Whether this ringtone is currently playing.
-     *
-     * @return True if playing, false otherwise.
-     */
-    public boolean isPlaying() {
-        if (mActivePlayer != null) {
-            return mActivePlayer.isPlaying();
-        } else {
-            Log.w(TAG, "No active ringtone player");
-            return false;
-        }
-    }
-
-    /**
-     * Fallback during the play stage rather than initialization, typically due to an issue
-     * communicating with the remote player.
-     */
-    private boolean playFallbackRingtone() {
-        if (mActivePlayer != null) {
-            Log.wtf(TAG, "Playing fallback ringtone with another active player");
-            stopAndReleaseActivePlayer();
-        }
-        int streamType = AudioAttributes.toLegacyStreamType(mAudioAttributes);
-        if (mAudioManager.getStreamVolume(streamType) == 0) {
-            // TODO: Return true? If volume is off, this is a successful play.
-            return false;
-        }
-        mActivePlayer = createFallbackRingtonePlayer();
-        if (mActivePlayer == null) {
-            return false;  // the create method logs if it returns null.
-        } else if (mActivePlayer.play()) {
-            return true;
-        } else {
-            stopAndReleaseActivePlayer();
-            return false;
-        }
-    }
-
-    void setTitle(String title) {
-        mTitle = title;
-    }
-
-    /**
-     * Play a specific ringtone. This interface is implemented by either local (this process) or
-     * proxied-remote playback via AudioManager.getRingtonePlayer, so that the caller
-     * (Ringtone class) can just use a single player after the initial creation.
-     * @hide
-     */
-    interface RingtonePlayer {
-        /**
-         * Start playing the ringtone, returning false if there was a problem that
-         * requires falling back to the fallback ringtone resource.
-         */
-        boolean play();
-        boolean isPlaying();
-        void stopAndRelease();
-
-        // Mutating playback methods.
-        void setPreferredDevice(@Nullable AudioDeviceInfo audioDeviceInfo);
-        void setLooping(boolean looping);
-        void setHapticGeneratorEnabled(boolean enabled);
-        void setVolume(float volume);
-
-        boolean hasHapticChannels();
-    }
-
-    /**
-     * Remote RingtonePlayer. All operations are delegated via the IRingtonePlayer interface, which
-     * should ultimately be backed by a RingtoneLocalPlayer within the system services.
-     */
-    static class RemoteRingtonePlayer implements RingtonePlayer {
-        private final IBinder mRemoteToken = new Binder();
-        private final IRingtonePlayer mRemoteRingtoneService;
-        private final Uri mCanonicalUri;
-        private final int mEnabledMedia;
-        private final VibrationEffect mVibrationEffect;
-        private final VolumeShaper.Configuration mVolumeShaperConfig;
-        private final AudioAttributes mAudioAttributes;
-        private final boolean mUseExactAudioAttributes;
-        private boolean mIsLooping;
-        private float mVolume;
-        private boolean mHapticGeneratorEnabled;
-
-        RemoteRingtonePlayer(@NonNull IRingtonePlayer remoteRingtoneService,
-                @NonNull Uri uri, @NonNull AudioAttributes audioAttributes,
-                boolean useExactAudioAttributes,
-                @RingtoneMedia int enabledMedia, @Nullable VibrationEffect vibrationEffect,
-                @Nullable VolumeShaper.Configuration volumeShaperConfig,
-                boolean hapticGeneratorEnabled, boolean initialIsLooping, float initialVolume) {
-            mRemoteRingtoneService = remoteRingtoneService;
-            mCanonicalUri = (uri == null) ? null : uri.getCanonicalUri();
-            mAudioAttributes = audioAttributes;
-            mUseExactAudioAttributes = useExactAudioAttributes;
-            mEnabledMedia = enabledMedia;
-            mVibrationEffect = vibrationEffect;
-            mVolumeShaperConfig = volumeShaperConfig;
-            mHapticGeneratorEnabled = hapticGeneratorEnabled;
-            mIsLooping = initialIsLooping;
-            mVolume = initialVolume;
-        }
-
-        @Override
-        public boolean play() {
-            try {
-                mRemoteRingtoneService.playRemoteRingtone(mRemoteToken, mCanonicalUri,
-                        mAudioAttributes, mUseExactAudioAttributes, mEnabledMedia, mVibrationEffect,
-                        mVolume, mIsLooping, mHapticGeneratorEnabled, mVolumeShaperConfig);
-                return true;
-            } catch (RemoteException e) {
-                Log.w(TAG, "Problem playing ringtone: " + e);
-                return false;
-            }
-        }
-
-        @Override
-        public boolean isPlaying() {
-            try {
-                return mRemoteRingtoneService.isPlaying(mRemoteToken);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Problem checking ringtone isPlaying: " + e);
-                return false;
-            }
-        }
-
-        @Override
-        public void stopAndRelease() {
-            try {
-                mRemoteRingtoneService.stop(mRemoteToken);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Problem stopping ringtone: " + e);
-            }
-        }
-
-        @Override
-        public void setPreferredDevice(@Nullable AudioDeviceInfo audioDeviceInfo) {
-            // un-implemented for remote (but not used outside system).
-        }
-
-        @Override
-        public void setLooping(boolean looping) {
-            mIsLooping = looping;
-            try {
-                mRemoteRingtoneService.setLooping(mRemoteToken, looping);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Problem setting looping: " + e);
-            }
-        }
-
-        @Override
-        public void setHapticGeneratorEnabled(boolean enabled) {
-            mHapticGeneratorEnabled = enabled;
-            try {
-                mRemoteRingtoneService.setHapticGeneratorEnabled(mRemoteToken, enabled);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Problem setting hapticGeneratorEnabled: " + e);
-            }
-        }
-
-        @Override
-        public void setVolume(float volume) {
-            mVolume = volume;
-            try {
-                mRemoteRingtoneService.setVolume(mRemoteToken, volume);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Problem setting volume: " + e);
-            }
-        }
-
-        @Override
-        public boolean hasHapticChannels() {
-            // FIXME: support remote player, or internalize haptic channels support and remove
-            // entirely.
-            return false;
-        }
-    }
-
-}
diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java
index e8adfaf..48ca4bf 100644
--- a/media/java/android/media/audiopolicy/AudioProductStrategy.java
+++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java
@@ -50,6 +50,14 @@
 
     private static final String TAG = "AudioProductStrategy";
 
+    /**
+     * The audio flags that will affect product strategy selection.
+     */
+    private static final int AUDIO_FLAGS_AFFECT_STRATEGY_SELECTION =
+            AudioAttributes.FLAG_AUDIBILITY_ENFORCED
+                    | AudioAttributes.FLAG_SCO
+                    | AudioAttributes.FLAG_BEACON;
+
     private final AudioAttributesGroup[] mAudioAttributesGroups;
     private final String mName;
     /**
@@ -438,8 +446,8 @@
                 || (attr.getSystemUsage() == refAttr.getSystemUsage()))
             && ((refAttr.getContentType() == AudioAttributes.CONTENT_TYPE_UNKNOWN)
                 || (attr.getContentType() == refAttr.getContentType()))
-            && ((refAttr.getAllFlags() == 0)
-                || (attr.getAllFlags() != 0
+            && (((refAttr.getAllFlags() & AUDIO_FLAGS_AFFECT_STRATEGY_SELECTION) == 0)
+                || ((attr.getAllFlags() & AUDIO_FLAGS_AFFECT_STRATEGY_SELECTION) != 0
                 && (attr.getAllFlags() & refAttr.getAllFlags()) == refAttr.getAllFlags()))
             && ((refFormattedTags.length() == 0) || refFormattedTags.equals(cliFormattedTags));
     }
diff --git a/media/java/android/media/browse/MediaBrowserUtils.java b/media/java/android/media/browse/MediaBrowserUtils.java
index 19d9f00..8c008bc 100644
--- a/media/java/android/media/browse/MediaBrowserUtils.java
+++ b/media/java/android/media/browse/MediaBrowserUtils.java
@@ -18,6 +18,9 @@
 
 import android.os.Bundle;
 
+import java.util.Collections;
+import java.util.List;
+
 /**
  * @hide
  */
@@ -75,4 +78,29 @@
         }
         return false;
     }
+
+    /**
+     * Returns a paged version of the given {@code list}, using the paging parameters in {@code
+     * options}.
+     */
+    public static List<MediaBrowser.MediaItem> applyPagingOptions(
+            List<MediaBrowser.MediaItem> list, final Bundle options) {
+        if (list == null) {
+            return null;
+        }
+        int page = options.getInt(MediaBrowser.EXTRA_PAGE, -1);
+        int pageSize = options.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1);
+        if (page == -1 && pageSize == -1) {
+            return list;
+        }
+        int fromIndex = pageSize * page;
+        int toIndex = fromIndex + pageSize;
+        if (page < 0 || pageSize < 1 || fromIndex >= list.size()) {
+            return Collections.EMPTY_LIST;
+        }
+        if (toIndex > list.size()) {
+            toIndex = list.size();
+        }
+        return list.subList(fromIndex, toIndex);
+    }
 }
diff --git a/media/java/android/media/tv/BroadcastInfoRequest.java b/media/java/android/media/tv/BroadcastInfoRequest.java
index 694756c..eb980a3 100644
--- a/media/java/android/media/tv/BroadcastInfoRequest.java
+++ b/media/java/android/media/tv/BroadcastInfoRequest.java
@@ -16,9 +16,11 @@
 
 package android.media.tv;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
+import android.media.tv.flags.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -51,14 +53,14 @@
     /**
      * Request option: one-way
      * <p> With this option, no response is expected after sending the request.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
     public static final int REQUEST_OPTION_ONEWAY = 2;
     /**
      * Request option: one-shot
      * <p> With this option, only one response will be given per request.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
     public static final int REQUEST_OPTION_ONESHOT = 3;
 
     public static final @NonNull Parcelable.Creator<BroadcastInfoRequest> CREATOR =
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 672f58b..aed3e60 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -556,85 +556,85 @@
     /**
      * Informs the application that the session has been tuned to the given channel.
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      * @see SESSION_DATA_KEY_CHANNEL_URI
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_TYPE_TUNED = "tuned";
 
     /**
      * Sends the type and ID of a selected track. This is used to inform the application that a
      * specific track is selected.
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      * @see SESSION_DATA_KEY_TRACK_TYPE
      * @see SESSION_DATA_KEY_TRACK_ID
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_TYPE_TRACK_SELECTED = "track_selected";
 
     /**
      * Sends the list of all audio/video/subtitle tracks.
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      * @see SESSION_DATA_KEY_TRACKS
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_TYPE_TRACKS_CHANGED = "tracks_changed";
 
     /**
      * Informs the application that the video is now available for watching.
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
-     * @hide
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_TYPE_VIDEO_AVAILABLE = "video_available";
 
     /**
      * Informs the application that the video became unavailable for some reason.
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      * @see SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_TYPE_VIDEO_UNAVAILABLE = "video_unavailable";
 
     /**
      * Notifies response for broadcast info.
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      * @see SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_TYPE_BROADCAST_INFO_RESPONSE =
             "broadcast_info_response";
 
     /**
      * Notifies response for advertisement.
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      * @see SESSION_DATA_KEY_AD_RESPONSE
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_TYPE_AD_RESPONSE = "ad_response";
 
     /**
      * Notifies the advertisement buffer is consumed.
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      * @see SESSION_DATA_KEY_AD_BUFFER
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_TYPE_AD_BUFFER_CONSUMED = "ad_buffer_consumed";
 
     /**
      * Sends the TV message.
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      * @see TvInputService.Session#notifyTvMessage(int, Bundle)
      * @see SESSION_DATA_KEY_TV_MESSAGE_TYPE
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_TYPE_TV_MESSAGE = "tv_message";
 
 
@@ -657,9 +657,9 @@
      *
      * <p> Type: android.net.Uri
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
-     * @hide
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_KEY_CHANNEL_URI = "channel_uri";
 
     /**
@@ -671,9 +671,9 @@
      * <p> Type: Integer
      *
      * @see TvTrackInfo#getType()
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
-     * @hide
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_KEY_TRACK_TYPE = "track_type";
 
     /**
@@ -682,9 +682,9 @@
      * <p> Type: String
      *
      * @see TvTrackInfo#getId()
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
-     * @hide
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_KEY_TRACK_ID = "track_id";
 
     /**
@@ -692,9 +692,9 @@
      *
      * <p> Type: {@code java.util.List<android.media.tv.TvTrackInfo> }
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
-     * @hide
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_KEY_TRACKS = "tracks";
 
     /**
@@ -704,9 +704,9 @@
      *
      * <p> Type: Integer
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
-     * @hide
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_KEY_VIDEO_UNAVAILABLE_REASON =
             "video_unavailable_reason";
 
@@ -715,9 +715,9 @@
      *
      * <p> Type: android.media.tv.BroadcastInfoResponse
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
-     * @hide
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_KEY_BROADCAST_INFO_RESPONSE = "broadcast_info_response";
 
     /**
@@ -725,9 +725,9 @@
      *
      * <p> Type: android.media.tv.AdResponse
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
-     * @hide
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_KEY_AD_RESPONSE = "ad_response";
 
     /**
@@ -735,9 +735,9 @@
      *
      * <p> Type: android.media.tv.AdBuffer
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
-     * @hide
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer";
 
     /**
@@ -747,9 +747,9 @@
      *
      * <p> Type: Integer
      *
-     * @see TvInputService.Session#notifyTvInputSessionData(String, Bundle)
-     * @hide
+     * @see TvInputService.Session#sendTvInputSessionData(String, Bundle)
      */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
     public static final String SESSION_DATA_KEY_TV_MESSAGE_TYPE = "tv_message_type";
 
 
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 6b03041..6658918 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -768,12 +768,12 @@
         /**
          * Informs the application that the video freeze state has been updated.
          *
-         * When {@code true}, the video is frozen on the last frame but audio playback remains
+         * <p>When {@code true}, the video is frozen on the last frame but audio playback remains
          * active.
          *
          * @param isFrozen Whether or not the video is frozen
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
         public void notifyVideoFreezeUpdated(boolean isFrozen) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
@@ -1262,7 +1262,7 @@
         }
 
         /**
-         * Notifies data related to this session to corresponding linked
+         * Sends data related to this session to corresponding linked
          * {@link android.media.tv.ad.TvAdService} object via TvAdView.
          *
          * <p>Methods like {@link #notifyBroadcastInfoResponse(BroadcastInfoResponse)} sends the
@@ -1272,21 +1272,21 @@
          *
          * @param type data type
          * @param data the related data values
-         * @hide
          */
-        public void notifyTvInputSessionData(
+        @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
+        public void sendTvInputSessionData(
                 @NonNull @TvInputManager.SessionDataType String type, @NonNull Bundle data) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
                 @Override
                 public void run() {
                     try {
-                        if (DEBUG) Log.d(TAG, "notifyTvInputSessionData");
+                        if (DEBUG) Log.d(TAG, "sendTvInputSessionData");
                         if (mSessionCallback != null) {
                             mSessionCallback.onTvInputSessionData(type, data);
                         }
                     } catch (RemoteException e) {
-                        Log.w(TAG, "error in notifyTvInputSessionData", e);
+                        Log.w(TAG, "error in sendTvInputSessionData", e);
                     }
                 }
             });
@@ -1441,10 +1441,10 @@
          *
          * @param type the type of the data
          * @param data a bundle contains the data received
-         * @see android.media.tv.ad.TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+         * @see android.media.tv.ad.TvAdService.Session#sendTvAdSessionData(String, Bundle)
          * @see android.media.tv.ad.TvAdView#setTvView(TvView)
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
         public void onTvAdSessionData(
                 @NonNull @TvAdManager.SessionDataType String type, @NonNull Bundle data) {
         }
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index ffc121e..e604cb7 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -683,13 +683,15 @@
      * Sets whether or not the video is frozen. While the video is frozen, audio playback will
      * continue.
      *
-     * <p> This should be invoked after a {@link TvInteractiveAppService.Session#requestCommand} is
+     * <p>This should be invoked after a {@link TvInteractiveAppService.Session#requestCommand} is
      * received with the command to freeze the video.
      *
-     * <p> This will freeze the video to the last frame when the state is set to {@code true}.
+     * <p>This will freeze the video to the last frame when the state is set to {@code true}.
+     *
+     * @see TvView.TvInputCallback#setVideoFrozen(boolean)
      * @param isFrozen whether or not the video is frozen.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
     public void setVideoFrozen(boolean isFrozen) {
         if (mSession != null) {
             mSession.setVideoFrozen(isFrozen);
@@ -1325,6 +1327,16 @@
         public void onTvMessage(@NonNull String inputId,
                 @TvInputManager.TvMessageType int type, @NonNull Bundle data) {
         }
+
+        /**
+         * This is called when the video freeze status is updated.
+         *
+         * @see #setVideoFrozen(boolean)
+         * @param inputId The ID of the TV input bound to this view.
+         * @param isFrozen Whether or not the video is currently frozen on the las
+         */
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+        public void onVideoFreezeUpdated(@NonNull String inputId, boolean isFrozen) {}
     }
 
     /**
@@ -1753,5 +1765,19 @@
                 mCallback.onTvMessage(mInputId, type, data);
             }
         }
+
+        @Override
+        public void onVideoFreezeUpdated(Session session, boolean isFrozen) {
+            if (DEBUG) {
+                Log.d(TAG, "onVideoFreezeUpdated(isFrozen=" + isFrozen + ")");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onVideoFreezeUpdated - session not created");
+                return;
+            }
+            if (mCallback != null) {
+                mCallback.onVideoFreezeUpdated(mInputId, isFrozen);
+            }
+        }
     }
 }
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index f373bed..59b10c6 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -68,7 +68,6 @@
      * <p>Type: String
      *
      * @see #sendAppLinkCommand(String, Bundle)
-     * @hide
      */
     public static final String APP_LINK_KEY_PACKAGE_NAME = "package_name";
 
@@ -77,7 +76,6 @@
      * <p>Type: String
      *
      * @see #sendAppLinkCommand(String, Bundle)
-     * @hide
      */
     public static final String APP_LINK_KEY_CLASS_NAME = "class_name";
 
@@ -86,7 +84,6 @@
      * <p>Type: String
      *
      * @see #sendAppLinkCommand(String, Bundle)
-     * @hide
      */
     public static final String APP_LINK_KEY_COMMAND_TYPE = "command_type";
 
@@ -95,7 +92,6 @@
      * <p>Type: String
      *
      * @see #sendAppLinkCommand(String, Bundle)
-     * @hide
      */
     public static final String APP_LINK_KEY_SERVICE_ID = "service_id";
 
@@ -104,7 +100,6 @@
      * <p>Type: String
      *
      * @see #sendAppLinkCommand(String, Bundle)
-     * @hide
      */
     public static final String APP_LINK_KEY_BACK_URI = "back_uri";
 
@@ -112,7 +107,6 @@
      * Broadcast intent action to send app command to TV app.
      *
      * @see #sendAppLinkCommand(String, Bundle)
-     * @hide
      */
     public static final String ACTION_APP_LINK_COMMAND =
             "android.media.tv.ad.action.APP_LINK_COMMAND";
@@ -123,7 +117,6 @@
      *
      * @see #sendAppLinkCommand(String, Bundle)
      * @see #ACTION_APP_LINK_COMMAND
-     * @hide
      */
     public static final String INTENT_KEY_TV_INPUT_ID = "tv_input_id";
 
@@ -134,7 +127,6 @@
      * @see #sendAppLinkCommand(String, Bundle)
      * @see #ACTION_APP_LINK_COMMAND
      * @see TvAdServiceInfo#getId()
-     * @hide
      */
     public static final String INTENT_KEY_AD_SERVICE_ID = "ad_service_id";
 
@@ -144,7 +136,6 @@
      *
      * @see #sendAppLinkCommand(String, Bundle)
      * @see #ACTION_APP_LINK_COMMAND
-     * @hide
      */
     public static final String INTENT_KEY_CHANNEL_URI = "channel_uri";
 
@@ -155,7 +146,6 @@
      *
      * @see #sendAppLinkCommand(String, Bundle)
      * @see #ACTION_APP_LINK_COMMAND
-     * @hide
      */
     public static final String INTENT_KEY_COMMAND_TYPE = "command_type";
 
@@ -171,36 +161,32 @@
     /**
      * Sends an advertisement request to be processed by the related TV input.
      *
-     * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+     * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
      * @see SESSION_DATA_KEY_AD_REQUEST
-     * @hide
      */
     public static final String SESSION_DATA_TYPE_AD_REQUEST = "ad_request";
 
     /**
      * Notifies the advertisement buffer is ready.
      *
-     * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+     * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
      * @see SESSION_DATA_KEY_AD_BUFFER
-     * @hide
      */
     public static final String SESSION_DATA_TYPE_AD_BUFFER_READY = "ad_buffer_ready";
 
     /**
      * Sends request for broadcast info.
      *
-     * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+     * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
      * @see SESSION_DATA_KEY_BROADCAST_INFO_RESQUEST
-     * @hide
      */
     public static final String SESSION_DATA_TYPE_BROADCAST_INFO_REQUEST = "broadcast_info_request";
 
     /**
      * Removes request for broadcast info.
      *
-     * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
+     * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
      * @see SESSION_DATA_KEY_BROADCAST_INFO_REQUEST_ID
-     * @hide
      */
     public static final String SESSION_DATA_TYPE_REMOVE_BROADCAST_INFO_REQUEST =
             "remove_broadcast_info_request";
@@ -220,8 +206,7 @@
      *
      * <p> Type: android.media.tv.AdRequest
      *
-     * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
-     * @hide
+     * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
      */
     public static final String SESSION_DATA_KEY_AD_REQUEST = "ad_request";
 
@@ -230,8 +215,7 @@
      *
      * <p> Type: android.media.tv.AdBuffer
      *
-     * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
-     * @hide
+     * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
      */
     public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer";
 
@@ -240,8 +224,7 @@
      *
      * <p> Type: android.media.tv.BroadcastInfoRequest
      *
-     * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
-     * @hide
+     * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
      */
     public static final String SESSION_DATA_KEY_BROADCAST_INFO_REQUEST = "broadcast_info_request";
 
@@ -250,8 +233,7 @@
      *
      * <p> Type: Integer
      *
-     * @see TvAdService.Session#notifyTvAdSessionData(String, Bundle)
-     * @hide
+     * @see TvAdService.Session#sendTvAdSessionData(String, Bundle)
      */
     public static final String SESSION_DATA_KEY_REQUEST_ID = "request_id";
 
@@ -494,8 +476,17 @@
      *
      * @param serviceId The ID of TV AD service which the command to be sent to. The ID can be found
      *                  in {@link TvAdServiceInfo#getId()}.
-     * @param command The command to be sent.
-     * @hide
+     * @param command The command to be sent. The command is a bundle with the following keys:
+     *                <ul>
+     *                <li>{@link #APP_LINK_KEY_PACKAGE_NAME}: The package name of the app to be
+     *                launched.
+     *                <li>{@link #APP_LINK_KEY_CLASS_NAME}: The class name of the app to be
+     *                launched.
+     *                <li>{@link #APP_LINK_KEY_COMMAND_TYPE}: The command type.
+     *                <li>{@link #APP_LINK_KEY_SERVICE_ID}: The ID of the TV AD service.
+     *                <li>{@link #APP_LINK_KEY_BACK_URI}: The URI to be used to return to the
+     *                previous app.
+     *                </ul>
      */
     public void sendAppLinkCommand(@NonNull String serviceId, @NonNull Bundle command) {
         try {
@@ -510,7 +501,6 @@
      *
      * @param callback A callback used to monitor status of the TV AD services.
      * @param executor A {@link Executor} that the status change will be delivered to.
-     * @hide
      */
     public void registerCallback(
             @CallbackExecutor @NonNull Executor executor,
@@ -526,7 +516,6 @@
      * Unregisters the existing {@link TvAdServiceCallback}.
      *
      * @param callback The existing callback to remove.
-     * @hide
      */
     public void unregisterCallback(@NonNull final TvAdServiceCallback callback) {
         Preconditions.checkNotNull(callback);
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
index 953b5cf..6c8a8fd 100644
--- a/media/java/android/media/tv/ad/TvAdService.java
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -136,7 +136,6 @@
      * Called when app link command is received.
      *
      * @see TvAdManager#sendAppLinkCommand(String, Bundle)
-     * @hide
      */
     public void onAppLinkCommand(@NonNull Bundle command) {
     }
@@ -199,7 +198,6 @@
          *
          * @param enable {@code true} if you want to enable the media view. {@code false}
          *            otherwise.
-         * @hide
          */
         @CallSuper
         public void setMediaViewEnabled(final boolean enable) {
@@ -225,7 +223,6 @@
          * Returns {@code true} if media view is enabled, {@code false} otherwise.
          *
          * @see #setMediaViewEnabled(boolean)
-         * @hide
          */
         public boolean isMediaViewEnabled() {
             return mMediaViewEnabled;
@@ -253,21 +250,18 @@
 
         /**
          * Starts TvAdService session.
-         * @hide
          */
         public void onStartAdService() {
         }
 
         /**
          * Stops TvAdService session.
-         * @hide
          */
         public void onStopAdService() {
         }
 
         /**
          * Resets TvAdService session.
-         * @hide
          */
         public void onResetAdService() {
         }
@@ -618,9 +612,8 @@
          *
          * @param type the type of the data
          * @param data a bundle contains the data received
-         * @see android.media.tv.TvInputService.Session#notifyTvAdSessionData(String, Bundle)
+         * @see android.media.tv.TvInputService.Session#sendTvInputSessionData(String, Bundle)
          * @see android.media.tv.ad.TvAdView#setTvView(TvView)
-         * @hide
          */
         public void onTvInputSessionData(
                 @NonNull @TvInputManager.SessionDataType String type, @NonNull Bundle data) {
@@ -636,7 +629,6 @@
          *
          * @param width The width of the media view, in pixels.
          * @param height The height of the media view, in pixels.
-         * @hide
          */
         public void onMediaViewSizeChanged(@Px int width, @Px int height) {
         }
@@ -646,7 +638,6 @@
          * implementation can override this method and return its own view.
          *
          * @return a view attached to the media window. {@code null} if no media view is created.
-         * @hide
          */
         @Nullable
         public View onCreateMediaView() {
@@ -654,27 +645,26 @@
         }
 
         /**
-         * Notifies data related to this session to corresponding linked
+         * Sends data related to this session to corresponding linked
          * {@link android.media.tv.TvInputService} object via TvView.
          *
          * @param type data type
          * @param data the related data values
          * @see TvAdView#setTvView(TvView)
-         * @hide
          */
-        public void notifyTvAdSessionData(
+        public void sendTvAdSessionData(
                 @NonNull @TvAdManager.SessionDataType String type, @NonNull Bundle data) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
                 @Override
                 public void run() {
                     try {
-                        if (DEBUG) Log.d(TAG, "notifyTvAdSessionData");
+                        if (DEBUG) Log.d(TAG, "sendTvAdSessionData");
                         if (mSessionCallback != null) {
                             mSessionCallback.onTvAdSessionData(type, data);
                         }
                     } catch (RemoteException e) {
-                        Log.w(TAG, "error in notifyTvAdSessionData", e);
+                        Log.w(TAG, "error in sendTvAdSessionData", e);
                     }
                 }
             });
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index be88506..ee01468 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -160,7 +160,6 @@
      * @param tvView the TvView to be linked to this TvAdView via linking of Sessions. {@code null}
      *               to unlink the TvView.
      * @return {@code true} if it's linked successfully; {@code false} otherwise.
-     * @hide
      */
     public boolean setTvView(@Nullable TvView tvView) {
         if (tvView == null) {
@@ -259,7 +258,6 @@
      * Resets this TvAdView to release its resources.
      *
      * <p>It can be reused by call {@link #prepareAdService(String, String)}.
-     * @hide
      */
     public void reset() {
         if (DEBUG) Log.d(TAG, "reset()");
@@ -362,7 +360,6 @@
      *
      * @param event The input event.
      * @return {@code true} if the event was handled by the view, {@code false} otherwise.
-     * @hide
      */
     public boolean dispatchUnhandledInputEvent(@NonNull InputEvent event) {
         if (mOnUnhandledInputEventListener != null) {
@@ -381,7 +378,6 @@
      * @param event The input event.
      * @return If you handled the event, return {@code true}. If you want to allow the event to be
      *         handled by the next receiver, return {@code false}.
-     * @hide
      */
     public boolean onUnhandledInputEvent(@NonNull InputEvent event) {
         return false;
@@ -392,7 +388,6 @@
      * by the TV AD service.
      *
      * @param listener The callback to be invoked when the unhandled input event is received.
-     * @hide
      */
     public void setOnUnhandledInputEventListener(
             @NonNull @CallbackExecutor Executor executor,
@@ -407,7 +402,6 @@
      *
      * @see #setOnUnhandledInputEventListener(Executor, OnUnhandledInputEventListener)
      * @see #clearOnUnhandledInputEventListener()
-     * @hide
      */
     @Nullable
     public OnUnhandledInputEventListener getOnUnhandledInputEventListener() {
@@ -416,7 +410,6 @@
 
     /**
      * Clears the {@link OnUnhandledInputEventListener}.
-     * @hide
      */
     public void clearOnUnhandledInputEventListener() {
         mOnUnhandledInputEventListener = null;
@@ -453,7 +446,6 @@
 
     /**
      * Starts the AD service.
-     * @hide
      */
     public void startAdService() {
         if (DEBUG) {
@@ -466,7 +458,6 @@
 
     /**
      * Stops the AD service.
-     * @hide
      */
     public void stopAdService() {
         if (DEBUG) {
@@ -481,7 +472,6 @@
      * Resets the AD service.
      *
      * <p>This releases the resources of the corresponding {@link TvAdService.Session}.
-     * @hide
      */
     public void resetAdService() {
         if (DEBUG) {
@@ -622,7 +612,6 @@
 
     /**
      * Interface definition for a callback to be invoked when the unhandled input event is received.
-     * @hide
      */
     public interface OnUnhandledInputEventListener {
         /**
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 498eec6..7cf32ec 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -2634,8 +2634,8 @@
 
         /**
          * This is called when
-         * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, int, byte[])} is
-         * called.
+         * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, int, byte[])}
+         * is called.
          *
          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
          * @param signingId the ID to identify the request.
@@ -2644,7 +2644,6 @@
          * @param host The host of the SSL CLient Authentication Server
          * @param port The port of the SSL Client Authentication Server
          * @param data the original bytes to be signed.
-         * @hide
          */
         public void onRequestSigning(
                 Session session, String signingId, String algorithm, String host,
@@ -2657,7 +2656,6 @@
          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
          * @param host the host name of the SSL authentication server.
          * @param port the port of the SSL authentication server. E.g., 443
-         * @hide
          */
         public void onRequestCertificate(Session session, String host, int port) {
         }
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 6b0620c..eba26d4 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -742,8 +742,8 @@
          * @param host the host name of the SSL authentication server.
          * @param port the port of the SSL authentication server. E.g., 443
          * @param cert the SSL certificate received.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
         public void onCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) {
         }
 
@@ -896,13 +896,13 @@
         }
 
         /**
-         * Called when video becomes frozen or unfrozen. Audio playback will continue while
-         * video will be frozen to the last frame if {@code true}.
+         * Called when video becomes frozen or unfrozen. Audio playback will continue while video
+         * will be frozen to the last frame if {@code true}.
+         *
          * @param isFrozen Whether or not the video is frozen.
-         * @hide
          */
-        public void onVideoFreezeUpdated(boolean isFrozen) {
-        }
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+        public void onVideoFreezeUpdated(boolean isFrozen) {}
 
         /**
          * Called when content is allowed.
@@ -1666,9 +1666,9 @@
          * @see #onSigningResult(String, byte[])
          * @see TvInteractiveAppView#createBiInteractiveApp(Uri, Bundle)
          * @see TvInteractiveAppView#BI_INTERACTIVE_APP_KEY_ALIAS
-         * @hide
          */
         @CallSuper
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
         public void requestSigning(@NonNull String signingId, @NonNull String algorithm,
                 @NonNull String host, int port, @NonNull byte[] data) {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -1695,8 +1695,9 @@
          *
          * @param host the host name of the SSL authentication server.
          * @param port the port of the SSL authentication server. E.g., 443
-         * @hide
          */
+        @CallSuper
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
         public void requestCertificate(@NonNull String host, int port) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 584ea84..29a3b98 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -723,12 +723,12 @@
     }
 
     /**
-     * Alerts the TV Interactive app that the video freeze state has been updated.
-     * If {@code true}, the video is frozen on the last frame while audio playback continues.
+     * Alerts the TV Interactive app that the video freeze state has been updated. If {@code true},
+     * the video is frozen on the last frame while audio playback continues.
      *
      * @param isFrozen Whether the video is frozen.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
     public void notifyVideoFreezeUpdated(boolean isFrozen) {
         if (DEBUG) {
             Log.d(TAG, "notifyVideoFreezeUpdated");
@@ -760,12 +760,12 @@
     }
 
     /**
-     * Send the requested SSL certificate to the TV Interactive App
+     * Sends the requested SSL certificate to the TV Interactive App
      * @param host the host name of the SSL authentication server.
      * @param port the port of the SSL authentication server. E.g., 443
      * @param cert the SSL certificate requested
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
     public void sendCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) {
         if (DEBUG) {
             Log.d(TAG, "sendCertificate");
@@ -1390,6 +1390,37 @@
         }
 
         /**
+         * This is called when
+         * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, int, byte[])}
+         * is called.
+         *
+         * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @param signingId the ID to identify the request.
+         * @param algorithm the standard name of the signature algorithm requested, such as
+         *                  MD5withRSA, SHA256withDSA, etc.
+         * @param host The hostname of the SSL authentication server.
+         * @param port The port of the SSL authentication server.
+         * @param data the original bytes to be signed.
+         */
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+        public void onRequestSigning(@NonNull String iAppServiceId, @NonNull String signingId,
+                @NonNull String algorithm, @NonNull String host, int port, @NonNull byte[] data) {
+        }
+
+        /**
+         * This is called when
+         * {@link TvInteractiveAppService.Session#requestCertificate(String, int)} is called.
+         *
+         * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @param host The hostname of the SSL authentication server.
+         * @param port The port of the SSL authentication server.
+         */
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+        public void onRequestCertificate(@NonNull String iAppServiceId, @NonNull String host,
+                int port) {
+        }
+
+        /**
          * This is called when {@link TvInteractiveAppService.Session#setTvRecordingInfo(String,
          * TvRecordingInfo)} is called.
          *
@@ -1957,5 +1988,34 @@
                 mCallback.onRequestSigning(mIAppServiceId, id, algorithm, alias, data);
             }
         }
+
+        @Override
+        public void onRequestSigning(
+                Session session, String id, String algorithm, String host, int port, byte[] data) {
+            if (DEBUG) {
+                Log.d(TAG, "onRequestSigning");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onRequestSigning - session not created");
+                return;
+            }
+            if (mCallback != null && Flags.tiafVApis()) {
+                mCallback.onRequestSigning(mIAppServiceId, id, algorithm, host, port, data);
+            }
+        }
+
+        @Override
+        public void onRequestCertificate(Session session, String host, int port) {
+            if (DEBUG) {
+                Log.d(TAG, "onRequestCertificate");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onRequestCertificate - session not created");
+                return;
+            }
+            if (mCallback != null && Flags.tiafVApis()) {
+                mCallback.onRequestCertificate(mIAppServiceId, host, port);
+            }
+        }
     }
 }
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index e8ef464..ba7ab9a 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -48,7 +48,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -728,7 +727,7 @@
 
                 List<MediaBrowser.MediaItem> filteredList =
                         (flag & RESULT_FLAG_OPTION_NOT_HANDLED) != 0
-                                ? applyOptions(list, options) : list;
+                                ? MediaBrowserUtils.applyPagingOptions(list, options) : list;
                 final ParceledListSlice<MediaBrowser.MediaItem> pls;
                 if (filteredList == null) {
                     pls = null;
@@ -762,27 +761,6 @@
         }
     }
 
-    private List<MediaBrowser.MediaItem> applyOptions(List<MediaBrowser.MediaItem> list,
-            final Bundle options) {
-        if (list == null) {
-            return null;
-        }
-        int page = options.getInt(MediaBrowser.EXTRA_PAGE, -1);
-        int pageSize = options.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1);
-        if (page == -1 && pageSize == -1) {
-            return list;
-        }
-        int fromIndex = pageSize * page;
-        int toIndex = fromIndex + pageSize;
-        if (page < 0 || pageSize < 1 || fromIndex >= list.size()) {
-            return Collections.EMPTY_LIST;
-        }
-        if (toIndex > list.size()) {
-            toIndex = list.size();
-        }
-        return list.subList(fromIndex, toIndex);
-    }
-
     private void performLoadItem(String itemId, final ConnectionRecord connection,
             final ResultReceiver receiver) {
         final Result<MediaBrowser.MediaItem> result =
diff --git a/media/tests/MediaFrameworkTest/Android.bp b/media/tests/MediaFrameworkTest/Android.bp
index 7a329bc..1325fc1 100644
--- a/media/tests/MediaFrameworkTest/Android.bp
+++ b/media/tests/MediaFrameworkTest/Android.bp
@@ -22,7 +22,6 @@
         "android-ex-camera2",
         "android.media.playback.flags-aconfig-java",
         "flag-junit",
-        "testables",
         "testng",
         "truth",
     ],
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/OWNERS b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/OWNERS
deleted file mode 100644
index 6d5f82c..0000000
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Haptics team also works on Ringtone
-per-file *Ringtone* = file:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java
deleted file mode 100644
index 3c0c684..0000000
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java
+++ /dev/null
@@ -1,840 +0,0 @@
-/*
- * 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.mediaframeworktest.unit;
-
-import static android.media.Ringtone.MEDIA_SOUND;
-import static android.media.Ringtone.MEDIA_SOUND_AND_VIBRATION;
-import static android.media.Ringtone.MEDIA_VIBRATION;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.doCallRealMethod;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.AssetFileDescriptor;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.IRingtonePlayer;
-import android.media.MediaPlayer;
-import android.media.Ringtone;
-import android.media.audiofx.HapticGenerator;
-import android.net.Uri;
-import android.os.IBinder;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.testing.TestableContext;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.mediaframeworktest.R;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runner.RunWith;
-import org.junit.runners.model.Statement;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.io.FileNotFoundException;
-import java.util.ArrayDeque;
-import java.util.Map;
-import java.util.Queue;
-
-@RunWith(AndroidJUnit4.class)
-public class RingtoneTest {
-
-    private static final Uri SOUND_URI = Uri.parse("content://fake-sound-uri");
-
-    private static final AudioAttributes RINGTONE_ATTRIBUTES =
-            audioAttributes(AudioAttributes.USAGE_NOTIFICATION_RINGTONE);
-    private static final AudioAttributes RINGTONE_ATTRIBUTES_WITH_HC =
-            new AudioAttributes.Builder(RINGTONE_ATTRIBUTES).setHapticChannelsMuted(false).build();
-    private static final VibrationAttributes RINGTONE_VIB_ATTRIBUTES =
-            new VibrationAttributes.Builder(RINGTONE_ATTRIBUTES).build();
-
-    private static final VibrationEffect VIBRATION_EFFECT =
-            VibrationEffect.createWaveform(new long[] { 0, 100, 50, 100}, -1);
-    private static final VibrationEffect VIBRATION_EFFECT_REPEATING =
-            VibrationEffect.createWaveform(new long[] { 0, 100, 50, 100, 50}, 1);
-
-    @Rule
-    public final RingtoneInjectablesTrackingTestRule
-            mMediaPlayerRule = new RingtoneInjectablesTrackingTestRule();
-
-    @Captor private ArgumentCaptor<IBinder> mIBinderCaptor;
-    @Mock private IRingtonePlayer mMockRemotePlayer;
-    @Mock private Vibrator mMockVibrator;
-    private AudioManager mSpyAudioManager;
-    private TestableContext mContext;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        TestableContext testContext =
-                new TestableContext(InstrumentationRegistry.getTargetContext(), null);
-        testContext.getTestablePermissions().setPermission(Manifest.permission.VIBRATE,
-                PackageManager.PERMISSION_GRANTED);
-        AudioManager realAudioManager = testContext.getSystemService(AudioManager.class);
-        mSpyAudioManager = spy(realAudioManager);
-        when(mSpyAudioManager.getRingtonePlayer()).thenReturn(mMockRemotePlayer);
-        testContext.addMockSystemService(AudioManager.class, mSpyAudioManager);
-        testContext.addMockSystemService(Vibrator.class, mMockVibrator);
-
-        mContext = spy(testContext);
-    }
-
-    @Test
-    public void testRingtone_fullLifecycleUsingLocalMediaPlayer() throws Exception {
-        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
-        Ringtone ringtone =
-                newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES).setUri(SOUND_URI).build();
-        assertThat(ringtone).isNotNull();
-        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
-
-        // Verify all the properties.
-        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND);
-        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
-        assertThat(ringtone.getAudioAttributes()).isEqualTo(RINGTONE_ATTRIBUTES);
-        assertThat(ringtone.getVolume()).isEqualTo(1.0f);
-        assertThat(ringtone.isLooping()).isEqualTo(false);
-        assertThat(ringtone.isHapticGeneratorEnabled()).isEqualTo(false);
-        assertThat(ringtone.getPreferBuiltinDevice()).isFalse();
-        assertThat(ringtone.getVolumeShaperConfig()).isNull();
-        assertThat(ringtone.isLocalOnly()).isFalse();
-
-        // Prepare
-        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES);
-        verify(mockMediaPlayer).setVolume(1.0f);
-        verify(mockMediaPlayer).setLooping(false);
-        verify(mockMediaPlayer).prepare();
-
-        // Play
-        ringtone.play();
-        verifyLocalPlay(mockMediaPlayer);
-
-        // Verify dynamic controls.
-        ringtone.setVolume(0.8f);
-        verify(mockMediaPlayer).setVolume(0.8f);
-        when(mockMediaPlayer.isLooping()).thenReturn(false);
-        ringtone.setLooping(true);
-        verify(mockMediaPlayer).isLooping();
-        verify(mockMediaPlayer).setLooping(true);
-        HapticGenerator mockHapticGenerator =
-                mMediaPlayerRule.expectHapticGenerator(mockMediaPlayer);
-        ringtone.setHapticGeneratorEnabled(true);
-        verify(mockHapticGenerator).setEnabled(true);
-
-        // Release
-        ringtone.stop();
-        verifyLocalStop(mockMediaPlayer);
-
-        // This test is intended to strictly verify all interactions with MediaPlayer in a local
-        // playback case. This shouldn't be necessary in other tests that have the same basic
-        // setup.
-        verifyNoMoreInteractions(mockMediaPlayer);
-        verify(mockHapticGenerator).release();
-        verifyNoMoreInteractions(mockHapticGenerator);
-        verifyZeroInteractions(mMockRemotePlayer);
-        verifyZeroInteractions(mMockVibrator);
-    }
-
-    @Test
-    public void testRingtone_localMediaPlayerWithAudioCoupledOverride() throws Exception {
-        // Audio coupled playback is enabled in the incoming attributes, plus an instruction
-        // to leave the attributes alone. This test verifies that the attributes reach the
-        // media player without changing.
-        final AudioAttributes audioAttributes = RINGTONE_ATTRIBUTES_WITH_HC;
-        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
-        mMediaPlayerRule.setHasHapticChannels(mockMediaPlayer, true);
-        Ringtone ringtone =
-                newBuilder(MEDIA_SOUND, audioAttributes)
-                        .setUri(SOUND_URI)
-                        .setUseExactAudioAttributes(true)
-                        .build();
-        assertThat(ringtone).isNotNull();
-        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
-
-        // Verify all the properties.
-        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND);
-        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
-        assertThat(ringtone.getAudioAttributes()).isEqualTo(audioAttributes);
-
-        // Prepare
-        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, audioAttributes);
-        verify(mockMediaPlayer).prepare();
-
-        // Play
-        ringtone.play();
-        verifyLocalPlay(mockMediaPlayer);
-
-        // Release
-        ringtone.stop();
-        verifyLocalStop(mockMediaPlayer);
-
-        verifyZeroInteractions(mMockRemotePlayer);
-        verifyZeroInteractions(mMockVibrator);
-    }
-
-    @Test
-    public void testRingtone_fullLifecycleUsingRemoteMediaPlayer() throws Exception {
-        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
-        setupFileNotFound(mockMediaPlayer, SOUND_URI);
-        Ringtone ringtone =
-                newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
-                .setUri(SOUND_URI)
-                .build();
-        assertThat(ringtone).isNotNull();
-        assertThat(ringtone.isUsingRemotePlayer()).isTrue();
-
-        // Verify all the properties.
-        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND);
-        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
-        assertThat(ringtone.getAudioAttributes()).isEqualTo(RINGTONE_ATTRIBUTES);
-        assertThat(ringtone.getVolume()).isEqualTo(1.0f);
-        assertThat(ringtone.isLooping()).isEqualTo(false);
-        assertThat(ringtone.isHapticGeneratorEnabled()).isEqualTo(false);
-        assertThat(ringtone.getPreferBuiltinDevice()).isFalse();
-        assertThat(ringtone.getVolumeShaperConfig()).isNull();
-        assertThat(ringtone.isLocalOnly()).isFalse();
-
-        // Initialization did try to create a local media player.
-        verify(mockMediaPlayer).setDataSource(mContext, SOUND_URI);
-        // setDataSource throws file not found, so nothing else will happen on the local player.
-        verify(mockMediaPlayer).release();
-
-        // Delegates to remote media player.
-        ringtone.play();
-        verify(mMockRemotePlayer).playRemoteRingtone(mIBinderCaptor.capture(), eq(SOUND_URI),
-                eq(RINGTONE_ATTRIBUTES), eq(false), eq(MEDIA_SOUND), isNull(),
-                eq(1.0f), eq(false), eq(false), isNull());
-        IBinder remoteToken = mIBinderCaptor.getValue();
-
-        // Verify dynamic controls.
-        ringtone.setVolume(0.8f);
-        verify(mMockRemotePlayer).setVolume(remoteToken, 0.8f);
-        ringtone.setLooping(true);
-        verify(mMockRemotePlayer).setLooping(remoteToken, true);
-        ringtone.setHapticGeneratorEnabled(true);
-        verify(mMockRemotePlayer).setHapticGeneratorEnabled(remoteToken, true);
-
-        ringtone.stop();
-        verify(mMockRemotePlayer).stop(remoteToken);
-        verifyNoMoreInteractions(mMockRemotePlayer);
-        verifyNoMoreInteractions(mockMediaPlayer);
-        verifyZeroInteractions(mMockVibrator);
-    }
-
-    @Test
-    public void testRingtone_localMediaWithVibration() throws Exception {
-        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
-        when(mMockVibrator.hasVibrator()).thenReturn(true);
-        Ringtone ringtone =
-                newBuilder(MEDIA_SOUND_AND_VIBRATION, RINGTONE_ATTRIBUTES)
-                        .setUri(SOUND_URI)
-                        .setVibrationEffect(VIBRATION_EFFECT)
-                        .build();
-        assertThat(ringtone).isNotNull();
-        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
-        verify(mMockVibrator).hasVibrator();
-
-        // Verify all the properties.
-        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND_AND_VIBRATION);
-        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
-        assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
-
-        // Prepare
-        // Uses attributes with haptic channels enabled, but will use the effect when there aren't
-        // any present.
-        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
-        verify(mockMediaPlayer).setVolume(1.0f);
-        verify(mockMediaPlayer).setLooping(false);
-        verify(mockMediaPlayer).prepare();
-
-        // Play
-        ringtone.play();
-
-        verifyLocalPlay(mockMediaPlayer);
-        verify(mMockVibrator).vibrate(VIBRATION_EFFECT, RINGTONE_VIB_ATTRIBUTES);
-
-        // Verify dynamic controls.
-        ringtone.setVolume(0.8f);
-        verify(mockMediaPlayer).setVolume(0.8f);
-
-        // Set looping doesn't affect an already-started vibration.
-        when(mockMediaPlayer.isLooping()).thenReturn(false);  // Checks original
-        ringtone.setLooping(true);
-        verify(mockMediaPlayer).isLooping();
-        verify(mockMediaPlayer).setLooping(true);
-
-        // This is ignored because there's a vibration effect being used.
-        ringtone.setHapticGeneratorEnabled(true);
-
-        // Release
-        ringtone.stop();
-        verifyLocalStop(mockMediaPlayer);
-        verify(mMockVibrator).cancel(VibrationAttributes.USAGE_RINGTONE);
-
-        // This test is intended to strictly verify all interactions with MediaPlayer in a local
-        // playback case. This shouldn't be necessary in other tests that have the same basic
-        // setup.
-        verifyNoMoreInteractions(mockMediaPlayer);
-        verifyZeroInteractions(mMockRemotePlayer);
-        verifyNoMoreInteractions(mMockVibrator);
-    }
-
-    @Test
-    public void testRingtone_localMediaWithVibrationOnly() throws Exception {
-        when(mMockVibrator.hasVibrator()).thenReturn(true);
-        Ringtone ringtone =
-                newBuilder(MEDIA_VIBRATION, RINGTONE_ATTRIBUTES)
-                        // TODO: set sound uri too in diff test
-                        .setVibrationEffect(VIBRATION_EFFECT)
-                        .build();
-        assertThat(ringtone).isNotNull();
-        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
-        verify(mMockVibrator).hasVibrator();
-
-        // Verify all the properties.
-        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_VIBRATION);
-        assertThat(ringtone.getUri()).isNull();
-        assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
-
-        // Play
-        ringtone.play();
-
-        verify(mMockVibrator).vibrate(VIBRATION_EFFECT, RINGTONE_VIB_ATTRIBUTES);
-
-        // Verify dynamic controls (no-op without sound)
-        ringtone.setVolume(0.8f);
-
-        // Set looping doesn't affect an already-started vibration.
-        ringtone.setLooping(true);
-
-        // This is ignored because there's a vibration effect being used and no sound.
-        ringtone.setHapticGeneratorEnabled(true);
-
-        // Release
-        ringtone.stop();
-        verify(mMockVibrator).cancel(VibrationAttributes.USAGE_RINGTONE);
-
-        // This test is intended to strictly verify all interactions with MediaPlayer in a local
-        // playback case. This shouldn't be necessary in other tests that have the same basic
-        // setup.
-        verifyZeroInteractions(mMockRemotePlayer);
-        verifyNoMoreInteractions(mMockVibrator);
-    }
-
-    @Test
-    public void testRingtone_localMediaWithVibrationOnlyAndSoundUriNoHapticChannels()
-            throws Exception {
-        // A media player will still be created for vibration-only because the vibration can come
-        // from haptic channels on the sound file (although in this case it doesn't).
-        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
-        mMediaPlayerRule.setHasHapticChannels(mockMediaPlayer, false);
-        when(mMockVibrator.hasVibrator()).thenReturn(true);
-        Ringtone ringtone =
-                newBuilder(MEDIA_VIBRATION, RINGTONE_ATTRIBUTES)
-                        .setUri(SOUND_URI)
-                        .setVibrationEffect(VIBRATION_EFFECT)
-                        .build();
-        assertThat(ringtone).isNotNull();
-        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
-        verify(mMockVibrator).hasVibrator();
-
-        // Verify all the properties.
-        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_VIBRATION);
-        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
-        assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
-
-        // Prepare
-        // Uses attributes with haptic channels enabled, but will abandon the MediaPlayer when it
-        // knows there aren't any.
-        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
-        verify(mockMediaPlayer).setVolume(0.0f);  // Vibration-only: sound muted.
-        verify(mockMediaPlayer).setLooping(false);
-        verify(mockMediaPlayer).prepare();
-        verify(mockMediaPlayer).release();  // abandoned: no haptic channels.
-
-        // Play
-        ringtone.play();
-
-        verify(mMockVibrator).vibrate(VIBRATION_EFFECT, RINGTONE_VIB_ATTRIBUTES);
-
-        // Verify dynamic controls (no-op without sound)
-        ringtone.setVolume(0.8f);
-
-        // Set looping doesn't affect an already-started vibration.
-        ringtone.setLooping(true);
-
-        // This is ignored because there's a vibration effect being used and no sound.
-        ringtone.setHapticGeneratorEnabled(true);
-
-        // Release
-        ringtone.stop();
-        verify(mMockVibrator).cancel(VibrationAttributes.USAGE_RINGTONE);
-
-        // This test is intended to strictly verify all interactions with MediaPlayer in a local
-        // playback case. This shouldn't be necessary in other tests that have the same basic
-        // setup.
-        verifyZeroInteractions(mMockRemotePlayer);
-        verifyNoMoreInteractions(mMockVibrator);
-        verifyNoMoreInteractions(mockMediaPlayer);
-    }
-
-    @Test
-    public void testRingtone_localMediaWithVibrationOnlyAndSoundUriWithHapticChannels()
-            throws Exception {
-        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
-        when(mMockVibrator.hasVibrator()).thenReturn(true);
-        mMediaPlayerRule.setHasHapticChannels(mockMediaPlayer, true);
-        Ringtone ringtone =
-                newBuilder(MEDIA_VIBRATION, RINGTONE_ATTRIBUTES)
-                        .setUri(SOUND_URI)
-                        .setVibrationEffect(VIBRATION_EFFECT)
-                        .build();
-        assertThat(ringtone).isNotNull();
-        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
-        verify(mMockVibrator).hasVibrator();
-
-        // Verify all the properties.
-        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_VIBRATION);
-        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
-        assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
-
-        // Prepare
-        // Uses attributes with haptic channels enabled, but will use the effect when there aren't
-        // any present.
-        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
-        verify(mockMediaPlayer).setVolume(0.0f);  // Vibration-only: sound muted.
-        verify(mockMediaPlayer).setLooping(false);
-        verify(mockMediaPlayer).prepare();
-
-        // Play
-        ringtone.play();
-        // Vibrator.vibrate isn't called because the vibration comes from the sound.
-        verifyLocalPlay(mockMediaPlayer);
-
-        // Verify dynamic controls (no-op without sound)
-        ringtone.setVolume(0.8f);
-
-        when(mockMediaPlayer.isLooping()).thenReturn(false);  // Checks original
-        ringtone.setLooping(true);
-        verify(mockMediaPlayer).isLooping();
-        verify(mockMediaPlayer).setLooping(true);
-
-        // This is ignored because it's using haptic channels.
-        ringtone.setHapticGeneratorEnabled(true);
-
-        // Release
-        ringtone.stop();
-        verifyLocalStop(mockMediaPlayer);
-
-        // This test is intended to strictly verify all interactions with MediaPlayer in a local
-        // playback case. This shouldn't be necessary in other tests that have the same basic
-        // setup.
-        verifyZeroInteractions(mMockRemotePlayer);
-        verifyZeroInteractions(mMockVibrator);
-    }
-
-    @Test
-    public void testRingtone_localMediaWithVibrationPrefersHapticChannels() throws Exception {
-        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
-        mMediaPlayerRule.setHasHapticChannels(mockMediaPlayer, true);
-        when(mMockVibrator.hasVibrator()).thenReturn(true);
-        Ringtone ringtone =
-                newBuilder(MEDIA_SOUND_AND_VIBRATION, RINGTONE_ATTRIBUTES)
-                        .setUri(SOUND_URI)
-                        .setVibrationEffect(VIBRATION_EFFECT)
-                        .build();
-        assertThat(ringtone).isNotNull();
-        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
-        verify(mMockVibrator).hasVibrator();
-
-        // Verify all the properties.
-        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND_AND_VIBRATION);
-        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
-        assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
-
-        // Prepare
-        // The attributes here have haptic channels enabled (unlike above)
-        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
-        verify(mockMediaPlayer).prepare();
-
-        // Play
-        ringtone.play();
-        when(mockMediaPlayer.isPlaying()).thenReturn(true);
-        verifyLocalPlay(mockMediaPlayer);
-
-        // Release
-        ringtone.stop();
-        verifyLocalStop(mockMediaPlayer);
-
-        verifyZeroInteractions(mMockRemotePlayer);
-        // Nothing after the initial hasVibrator - it uses audio-coupled.
-        verifyNoMoreInteractions(mMockVibrator);
-    }
-
-    @Test
-    public void testRingtone_localMediaWithVibrationButSoundMuted() throws Exception {
-        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
-        mMediaPlayerRule.setHasHapticChannels(mockMediaPlayer, false);
-        doReturn(0).when(mSpyAudioManager)
-                .getStreamVolume(AudioAttributes.toLegacyStreamType(RINGTONE_ATTRIBUTES));
-        when(mMockVibrator.hasVibrator()).thenReturn(true);
-        Ringtone ringtone =
-                newBuilder(MEDIA_SOUND_AND_VIBRATION, RINGTONE_ATTRIBUTES)
-                        .setUri(SOUND_URI)
-                        .setVibrationEffect(VIBRATION_EFFECT)
-                        .build();
-        assertThat(ringtone).isNotNull();
-        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
-        verify(mMockVibrator).hasVibrator();
-
-        // Verify all the properties.
-        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND_AND_VIBRATION);
-        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
-        assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
-
-        // Prepare
-        // The attributes here have haptic channels enabled (unlike above)
-        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
-        verify(mockMediaPlayer).prepare();
-
-        // Play
-        ringtone.play();
-        // The media player is never played, because sound is muted.
-        verify(mockMediaPlayer, never()).start();
-        when(mockMediaPlayer.isPlaying()).thenReturn(true);
-        verify(mMockVibrator).vibrate(VIBRATION_EFFECT, RINGTONE_VIB_ATTRIBUTES);
-
-        // Release
-        ringtone.stop();
-        verify(mockMediaPlayer).release();
-        verify(mMockVibrator).cancel(VibrationAttributes.USAGE_RINGTONE);
-
-        verifyZeroInteractions(mMockRemotePlayer);
-        // Nothing after the initial hasVibrator - it uses audio-coupled.
-        verifyNoMoreInteractions(mMockVibrator);
-    }
-
-    @Test
-    public void testRingtone_nullMediaOnBuilderUsesFallback() throws Exception {
-        AssetFileDescriptor testResourceFd =
-                mContext.getResources().openRawResourceFd(R.raw.shortmp3);
-        // Ensure it will flow as expected.
-        assertThat(testResourceFd).isNotNull();
-        assertThat(testResourceFd.getDeclaredLength()).isAtLeast(0);
-        mContext.getOrCreateTestableResources()
-                .addOverride(com.android.internal.R.raw.fallbackring, testResourceFd);
-
-        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
-        Ringtone ringtone = newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
-                .setUri(null)
-                .build();
-        assertThat(ringtone).isNotNull();
-        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
-
-        // Delegates straight to fallback in local player.
-        // Prepare
-        verifyLocalPlayerFallbackSetup(mockMediaPlayer, testResourceFd, RINGTONE_ATTRIBUTES);
-        verify(mockMediaPlayer).setVolume(1.0f);
-        verify(mockMediaPlayer).setLooping(false);
-        verify(mockMediaPlayer).prepare();
-
-        // Play
-        ringtone.play();
-        verifyLocalPlay(mockMediaPlayer);
-
-        // Release
-        ringtone.stop();
-        verifyLocalStop(mockMediaPlayer);
-
-        verifyNoMoreInteractions(mockMediaPlayer);
-        verifyNoMoreInteractions(mMockRemotePlayer);
-    }
-
-    @Test
-    public void testRingtone_nullMediaOnBuilderUsesFallbackViaRemote() throws Exception {
-        mContext.getOrCreateTestableResources()
-                .addOverride(com.android.internal.R.raw.fallbackring, null);
-        Ringtone ringtone = newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
-                .setUri(null)
-                .setLooping(true) // distinct from haptic generator, to match plumbing
-                .build();
-        assertThat(ringtone).isNotNull();
-        // Local player fallback fails as the resource isn't found (no media player creation is
-        // attempted), and then goes on to create the remote player.
-        assertThat(ringtone.isUsingRemotePlayer()).isTrue();
-
-        ringtone.play();
-        verify(mMockRemotePlayer).playRemoteRingtone(mIBinderCaptor.capture(), isNull(),
-                eq(RINGTONE_ATTRIBUTES), eq(false),
-                eq(MEDIA_SOUND), isNull(),
-                eq(1.0f), eq(true), eq(false), isNull());
-        ringtone.stop();
-        verify(mMockRemotePlayer).stop(mIBinderCaptor.getValue());
-        verifyNoMoreInteractions(mMockRemotePlayer);
-    }
-
-    @Test
-    public void testRingtone_noMediaSetOnBuilderFallbackFailsAndNoRemote() throws Exception {
-        mContext.getOrCreateTestableResources()
-                .addOverride(com.android.internal.R.raw.fallbackring, null);
-        Ringtone ringtone = newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
-                .setUri(null)
-                .setLocalOnly()
-                .build();
-        // Local player fallback fails as the resource isn't found (no media player creation is
-        // attempted), and since there is no local player, the ringtone ends up having nothing to
-        // do.
-        assertThat(ringtone).isNull();
-    }
-
-    private Ringtone.Builder newBuilder(@Ringtone.RingtoneMedia int ringtoneMedia,
-            AudioAttributes audioAttributes) {
-        return new Ringtone.Builder(mContext, ringtoneMedia, audioAttributes)
-                .setInjectables(mMediaPlayerRule.injectables);
-    }
-
-    private static AudioAttributes audioAttributes(int audioUsage) {
-        return new AudioAttributes.Builder()
-                .setUsage(audioUsage)
-                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-                .build();
-    }
-
-    /** Makes the mock get some sort of file access problem. */
-    private void setupFileNotFound(MediaPlayer mockMediaPlayer, Uri uri) throws Exception {
-        doThrow(new FileNotFoundException("Fake file not found"))
-                .when(mockMediaPlayer).setDataSource(any(Context.class), eq(uri));
-    }
-
-    private void verifyLocalPlayerSetup(MediaPlayer mockPlayer, Uri expectedUri,
-            AudioAttributes expectedAudioAttributes) throws Exception {
-        verify(mockPlayer).setDataSource(mContext, expectedUri);
-        verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
-        verify(mockPlayer).setPreferredDevice(null);
-        verify(mockPlayer).prepare();
-    }
-
-    private void verifyLocalPlayerFallbackSetup(MediaPlayer mockPlayer, AssetFileDescriptor afd,
-            AudioAttributes expectedAudioAttributes) throws Exception {
-        // This is very specific but it's a simple way to test that the test resource matches.
-        if (afd.getDeclaredLength() < 0) {
-            verify(mockPlayer).setDataSource(afd.getFileDescriptor());
-        } else {
-            verify(mockPlayer).setDataSource(afd.getFileDescriptor(),
-                    afd.getStartOffset(),
-                    afd.getDeclaredLength());
-        }
-        verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
-        verify(mockPlayer).setPreferredDevice(null);
-        verify(mockPlayer).prepare();
-    }
-
-    private void verifyLocalPlay(MediaPlayer mockMediaPlayer) {
-        verify(mockMediaPlayer).setOnCompletionListener(any());
-        verify(mockMediaPlayer).start();
-    }
-
-    private void verifyLocalStop(MediaPlayer mockMediaPlayer) {
-        verify(mockMediaPlayer).stop();
-        verify(mockMediaPlayer).setOnCompletionListener(isNull());
-        verify(mockMediaPlayer).reset();
-        verify(mockMediaPlayer).release();
-    }
-
-    /**
-     * This rule ensures that all expected media player creations from the factory do actually
-     * occur. The reason for this level of control is that creating a media player is fairly
-     * expensive and blocking, so we do want unit tests of this class to "declare" interactions
-     * of all created media players.
-     *
-     * This needs to be a TestRule so that the teardown assertions can be skipped if the test has
-     * failed (and media player assertions may just be a distracting side effect). Otherwise, the
-     * teardown failures hide the real test ones.
-     */
-    public static class RingtoneInjectablesTrackingTestRule implements TestRule {
-        public Ringtone.Injectables injectables = new TestInjectables();
-        public boolean hapticGeneratorAvailable = true;
-
-        // Queue of (local) media players, in order of expected creation. Enqueue using
-        // expectNewMediaPlayer(), dequeued by the media player factory passed to Ringtone.
-        // This queue is asserted to be empty at the end of the test.
-        private Queue<MediaPlayer> mMockMediaPlayerQueue = new ArrayDeque<>();
-
-        // Similar to media players, but for haptic generator, which also needs releasing.
-        private Map<MediaPlayer, HapticGenerator> mMockHapticGeneratorMap = new ArrayMap<>();
-
-        // Media players with haptic channels.
-        private ArraySet<MediaPlayer> mHapticChannels = new ArraySet<>();
-
-        @Override
-        public Statement apply(Statement base, Description description) {
-            return new Statement() {
-                @Override
-                public void evaluate() throws Throwable {
-                    base.evaluate();
-                    // Only assert if the test didn't fail (base.evaluate() would throw).
-                    assertWithMessage("Test setup an expectLocalMediaPlayer but it wasn't consumed")
-                            .that(mMockMediaPlayerQueue).isEmpty();
-                    // Only assert if the test didn't fail (base.evaluate() would throw).
-                    assertWithMessage(
-                            "Test setup an expectLocalHapticGenerator but it wasn't consumed")
-                            .that(mMockHapticGeneratorMap).isEmpty();
-                }
-            };
-        }
-
-        private TestMediaPlayer expectLocalMediaPlayer() {
-            TestMediaPlayer mockMediaPlayer = Mockito.mock(TestMediaPlayer.class);
-            // Delegate to simulated methods. This means they can be verified but also reflect
-            // realistic transitions from the TestMediaPlayer.
-            doCallRealMethod().when(mockMediaPlayer).start();
-            doCallRealMethod().when(mockMediaPlayer).stop();
-            doCallRealMethod().when(mockMediaPlayer).setLooping(anyBoolean());
-            when(mockMediaPlayer.isLooping()).thenCallRealMethod();
-            when(mockMediaPlayer.isLooping()).thenCallRealMethod();
-            mMockMediaPlayerQueue.add(mockMediaPlayer);
-            return mockMediaPlayer;
-        }
-
-        private HapticGenerator expectHapticGenerator(MediaPlayer mockMediaPlayer) {
-            HapticGenerator mockHapticGenerator = Mockito.mock(HapticGenerator.class);
-            // A test should never want this.
-            assertWithMessage("Can't expect a second haptic generator created "
-                    + "for one media player")
-                    .that(mMockHapticGeneratorMap.put(mockMediaPlayer, mockHapticGenerator))
-                    .isNull();
-            return mockHapticGenerator;
-        }
-
-        private void setHasHapticChannels(MediaPlayer mp, boolean hasHapticChannels) {
-            if (hasHapticChannels) {
-                mHapticChannels.add(mp);
-            } else {
-                mHapticChannels.remove(mp);
-            }
-        }
-
-        private class TestInjectables extends Ringtone.Injectables {
-            @Override
-            public MediaPlayer newMediaPlayer() {
-                assertWithMessage(
-                        "Unexpected MediaPlayer creation. Bug or need expectNewMediaPlayer")
-                        .that(mMockMediaPlayerQueue)
-                        .isNotEmpty();
-                return mMockMediaPlayerQueue.remove();
-            }
-
-            @Override
-            public boolean isHapticGeneratorAvailable() {
-                return hapticGeneratorAvailable;
-            }
-
-            @Override
-            public HapticGenerator createHapticGenerator(MediaPlayer mediaPlayer) {
-                HapticGenerator mockHapticGenerator = mMockHapticGeneratorMap.remove(mediaPlayer);
-                assertWithMessage("Unexpected HapticGenerator creation. "
-                        + "Bug or need expectHapticGenerator")
-                        .that(mockHapticGenerator)
-                        .isNotNull();
-                return mockHapticGenerator;
-            }
-
-            @Override
-            public boolean isHapticPlaybackSupported() {
-                return true;
-            }
-
-            @Override
-            public boolean hasHapticChannels(MediaPlayer mp) {
-                return mHapticChannels.contains(mp);
-            }
-        }
-    }
-
-    /**
-     * MediaPlayer relies on a native backend and so its necessary to intercept calls from
-     * fake usage hitting them.
-     *
-     * Mocks don't work directly on native calls, but if they're overridden then it does work.
-     * Some basic state faking is also done to make the mocks more realistic.
-     */
-    private static class TestMediaPlayer extends MediaPlayer {
-        private boolean mIsPlaying = false;
-        private boolean mIsLooping = false;
-
-        @Override
-        public void start() {
-            mIsPlaying = true;
-        }
-
-        @Override
-        public void stop() {
-            mIsPlaying = false;
-        }
-
-        @Override
-        public void setLooping(boolean value) {
-            mIsLooping = value;
-        }
-
-        @Override
-        public boolean isLooping() {
-            return mIsLooping;
-        }
-
-        @Override
-        public boolean isPlaying() {
-            return mIsPlaying;
-        }
-
-        void simulatePlayingFinished() {
-            if (!mIsPlaying) {
-                throw new IllegalStateException(
-                        "Attempted to pretend playing finished when not playing");
-            }
-            mIsPlaying = false;
-        }
-    }
-}
diff --git a/media/tests/ringtone/Android.bp b/media/tests/ringtone/Android.bp
deleted file mode 100644
index 55b98c4..0000000
--- a/media/tests/ringtone/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-package {
-    // See: http://go/android-license-faq
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test {
-    name: "MediaRingtoneTests",
-
-    srcs: ["src/**/*.java"],
-
-    libs: [
-        "android.test.runner",
-        "android.test.base",
-    ],
-
-    static_libs: [
-        "androidx.test.rules",
-        "testng",
-        "androidx.test.ext.truth",
-        "frameworks-base-testutils",
-    ],
-
-    test_suites: [
-        "device-tests",
-        "automotive-tests",
-    ],
-
-    platform_apis: true,
-    certificate: "platform",
-}
diff --git a/media/tests/ringtone/AndroidManifest.xml b/media/tests/ringtone/AndroidManifest.xml
deleted file mode 100644
index 27eda07..0000000
--- a/media/tests/ringtone/AndroidManifest.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2023 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.framework.base.media.ringtone.tests">
-
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.MANAGE_USERS" />
-
-    <application android:debuggable="true">
-        <uses-library android:name="android.test.runner" />
-
-        <activity android:name="MediaRingtoneTests"
-                  android:label="Media Ringtone Tests"
-                  android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.framework.base.media.ringtone.tests"
-                     android:label="Media Ringtone Tests"/>
-</manifest>
diff --git a/media/tests/ringtone/TEST_MAPPING b/media/tests/ringtone/TEST_MAPPING
deleted file mode 100644
index 6f25c14..0000000
--- a/media/tests/ringtone/TEST_MAPPING
+++ /dev/null
@@ -1,20 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "MediaRingtoneTests",
-      "options": [
-        {"exclude-annotation": "androidx.test.filters.LargeTest"},
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-        {"exclude-annotation": "org.junit.Ignore"}
-      ]
-    }
-  ],
-  "postsubmit": [
-    {
-      "name": "MediaRingtoneTests",
-      "options": [
-        {"exclude-annotation": "org.junit.Ignore"}
-      ]
-    }
-  ]
-}
\ No newline at end of file
diff --git a/media/tests/ringtone/res/raw/test_haptic_file.ahv b/media/tests/ringtone/res/raw/test_haptic_file.ahv
deleted file mode 100644
index d6eba1a..0000000
--- a/media/tests/ringtone/res/raw/test_haptic_file.ahv
+++ /dev/null
@@ -1,17 +0,0 @@
-<vibration-effect>
-  <waveform-effect>
-    <waveform-entry durationMs="63" amplitude="255"/>
-    <waveform-entry durationMs="63" amplitude="231"/>
-    <waveform-entry durationMs="63" amplitude="208"/>
-    <waveform-entry durationMs="63" amplitude="185"/>
-    <waveform-entry durationMs="63" amplitude="162"/>
-    <waveform-entry durationMs="63" amplitude="139"/>
-    <waveform-entry durationMs="63" amplitude="115"/>
-    <waveform-entry durationMs="63" amplitude="92"/>
-    <waveform-entry durationMs="63" amplitude="69"/>
-    <waveform-entry durationMs="63" amplitude="46"/>
-    <waveform-entry durationMs="63" amplitude="23"/>
-    <waveform-entry durationMs="63" amplitude="0"/>
-    <waveform-entry durationMs="1250" amplitude="0"/>
-  </waveform-effect>
-</vibration-effect>
diff --git a/media/tests/ringtone/res/raw/test_sound_file.mp3 b/media/tests/ringtone/res/raw/test_sound_file.mp3
deleted file mode 100644
index c1b2fdf..0000000
--- a/media/tests/ringtone/res/raw/test_sound_file.mp3
+++ /dev/null
Binary files differ
diff --git a/media/tests/ringtone/src/com/android/media/RingtoneManagerTest.java b/media/tests/ringtone/src/com/android/media/RingtoneManagerTest.java
deleted file mode 100644
index a92b298..0000000
--- a/media/tests/ringtone/src/com/android/media/RingtoneManagerTest.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.media;
-
-import static com.google.android.mms.ContentType.AUDIO_MP3;
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assume.assumeTrue;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Environment;
-import android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
-import android.os.vibrator.persistence.VibrationXmlParser;
-import android.provider.MediaStore;
-import android.text.TextUtils;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.framework.base.media.ringtone.tests.R;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-@RunWith(Parameterized.class)
-public class RingtoneManagerTest {
-    @RingtoneManager.MediaType
-    private final int mMediaType;
-    private final List<Uri> mAddedFilesUri;
-    private Context mContext;
-    private RingtoneManager mRingtoneManager;
-    private long mTimestamp;
-
-    @Parameterized.Parameters(name = "media = {0}")
-    public static Iterable<?> data() {
-        return Arrays.asList(Ringtone.MEDIA_SOUND, Ringtone.MEDIA_VIBRATION);
-    }
-
-    public RingtoneManagerTest(@RingtoneManager.MediaType int mediaType) {
-        mMediaType = mediaType;
-        mAddedFilesUri = new ArrayList<>();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
-        mTimestamp = SystemClock.uptimeMillis();
-        mRingtoneManager = new RingtoneManager(mContext);
-        mRingtoneManager.setMediaType(mMediaType);
-    }
-
-    @After
-    public void tearDown() {
-        // Clean up media store
-        for (Uri fileUri : mAddedFilesUri) {
-            mContext.getContentResolver().delete(fileUri, null);
-        }
-    }
-
-    @Test
-    public void testSetMediaType_withValidValue_setsMediaCorrectly() {
-        mRingtoneManager.setMediaType(mMediaType);
-        assertThat(mRingtoneManager.getMediaType()).isEqualTo(mMediaType);
-    }
-
-    @Test
-    public void testSetMediaType_withInvalidValue_throwsException() {
-        assertThrows(IllegalArgumentException.class, () -> mRingtoneManager.setMediaType(999));
-    }
-
-    @Test
-    public void testSetMediaType_afterCallingGetCursor_throwsException() {
-        mRingtoneManager.getCursor();
-        assertThrows(IllegalStateException.class, () -> mRingtoneManager.setMediaType(mMediaType));
-    }
-
-    @Test
-    public void testGetRingtone_ringtoneHasCorrectTitle() throws Exception {
-        String fileName = generateUniqueFileName("new_file");
-        Ringtone ringtone = addNewRingtoneToMediaStore(mRingtoneManager, fileName);
-
-        assertThat(ringtone.getTitle(mContext)).isEqualTo(fileName);
-    }
-
-    @Test
-    public void testGetRingtone_ringtoneCanBePlayedAndStopped() throws Exception {
-        //TODO(b/261571543) Remove this assumption once we support playing vibrations.
-        assumeTrue(mMediaType == Ringtone.MEDIA_SOUND);
-        String fileName = generateUniqueFileName("new_file");
-        Ringtone ringtone = addNewRingtoneToMediaStore(mRingtoneManager, fileName);
-
-        ringtone.play();
-        assertThat(ringtone.isPlaying()).isTrue();
-
-        ringtone.stop();
-        assertThat(ringtone.isPlaying()).isFalse();
-    }
-
-    @Test
-    public void testGetCursor_withDifferentMedia_returnsCorrectCursor() throws Exception {
-        RingtoneManager audioRingtoneManager = new RingtoneManager(mContext);
-        String audioFileName = generateUniqueFileName("ringtone");
-        addNewRingtoneToMediaStore(audioRingtoneManager, audioFileName);
-
-        RingtoneManager vibrationRingtoneManager = new RingtoneManager(mContext);
-        vibrationRingtoneManager.setMediaType(Ringtone.MEDIA_VIBRATION);
-        String vibrationFileName = generateUniqueFileName("vibration");
-        addNewRingtoneToMediaStore(vibrationRingtoneManager, vibrationFileName);
-
-        Cursor audioCursor = audioRingtoneManager.getCursor();
-        Cursor vibrationCursor = vibrationRingtoneManager.getCursor();
-
-        List<String> audioTitles = extractRecordTitles(audioCursor);
-        List<String> vibrationTitles = extractRecordTitles(vibrationCursor);
-
-        assertThat(audioTitles).contains(audioFileName);
-        assertThat(audioTitles).doesNotContain(vibrationFileName);
-
-        assertThat(vibrationTitles).contains(vibrationFileName);
-        assertThat(vibrationTitles).doesNotContain(audioFileName);
-    }
-
-    private List<String> extractRecordTitles(Cursor cursor) {
-        List<String> titles = new ArrayList<>();
-
-        if (cursor.moveToFirst()) {
-            do {
-                String title = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX);
-                titles.add(title);
-            } while (cursor.moveToNext());
-        }
-
-        return titles;
-    }
-
-    private Ringtone addNewRingtoneToMediaStore(RingtoneManager ringtoneManager, String fileName)
-            throws Exception {
-        Uri fileUri = ringtoneManager.getMediaType() == Ringtone.MEDIA_SOUND ? addAudioFile(
-                fileName) : addVibrationFile(fileName);
-        mAddedFilesUri.add(fileUri);
-
-        int ringtonePosition = ringtoneManager.getRingtonePosition(fileUri);
-        Ringtone ringtone = ringtoneManager.getRingtone(ringtonePosition);
-        // Validate this is the expected ringtone.
-        assertThat(ringtone.getUri()).isEqualTo(fileUri);
-        return ringtone;
-    }
-
-    private Uri addAudioFile(String fileName) throws Exception {
-        ContentResolver resolver = mContext.getContentResolver();
-        ContentValues contentValues = new ContentValues();
-        contentValues.put(MediaStore.Audio.Media.DISPLAY_NAME, fileName + ".mp3");
-        contentValues.put(MediaStore.Audio.Media.RELATIVE_PATH, Environment.DIRECTORY_RINGTONES);
-        contentValues.put(MediaStore.Audio.Media.MIME_TYPE, AUDIO_MP3);
-        contentValues.put(MediaStore.Audio.Media.TITLE, fileName);
-        contentValues.put(MediaStore.Audio.Media.IS_RINGTONE, 1);
-
-        Uri contentUri = resolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
-                contentValues);
-        writeRawDataToFile(resolver, contentUri, R.raw.test_sound_file);
-
-        return resolver.canonicalizeOrElse(contentUri);
-    }
-
-    private Uri addVibrationFile(String fileName) throws Exception {
-        ContentResolver resolver = mContext.getContentResolver();
-        ContentValues contentValues = new ContentValues();
-        contentValues.put(MediaStore.Files.FileColumns.DISPLAY_NAME, fileName + ".ahv");
-        contentValues.put(MediaStore.Files.FileColumns.RELATIVE_PATH,
-                Environment.DIRECTORY_DOWNLOADS);
-        contentValues.put(MediaStore.Files.FileColumns.MIME_TYPE,
-                VibrationXmlParser.APPLICATION_VIBRATION_XML_MIME_TYPE);
-        contentValues.put(MediaStore.Files.FileColumns.TITLE, fileName);
-
-        Uri contentUri = resolver.insert(MediaStore.Files.getContentUri(MediaStore
-                .VOLUME_EXTERNAL), contentValues);
-        writeRawDataToFile(resolver, contentUri, R.raw.test_haptic_file);
-
-        return resolver.canonicalizeOrElse(contentUri);
-    }
-
-    private void writeRawDataToFile(ContentResolver resolver, Uri contentUri, int rawResource)
-            throws Exception {
-        try (ParcelFileDescriptor pfd =
-                     resolver.openFileDescriptor(contentUri, "w", null)) {
-            InputStream inputStream = mContext.getResources().openRawResource(rawResource);
-            FileOutputStream outputStream = new FileOutputStream(pfd.getFileDescriptor());
-            outputStream.write(inputStream.readAllBytes());
-
-            inputStream.close();
-            outputStream.flush();
-            outputStream.close();
-
-        } catch (Exception e) {
-            throw new Exception("Failed to write data to file", e);
-        }
-    }
-
-    private String generateUniqueFileName(String prefix) {
-        return TextUtils.formatSimple("%s_%d", prefix, mTimestamp);
-    }
-
-}
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 28cf250..845a8f9 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -232,13 +232,13 @@
     method public final void sendResponseApdu(byte[]);
     field public static final int DEACTIVATION_DESELECTED = 1; // 0x1
     field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA";
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN";
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP";
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA";
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN";
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
+    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE";
     field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_A = 65; // 0x0041 'A'
     field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_B = 66; // 0x0042 'B'
     field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_F = 70; // 0x0046 'F'
-    field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE";
     field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_OFF = 88; // 0x0058 'X'
     field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_ON = 79; // 0x004f 'O'
     field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_UNKNOWN = 85; // 0x0055 'U'
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 7680136..dd2e174 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -23,7 +23,7 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
     method @FlaggedApi("android.nfc.enable_nfc_charging") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setWlcEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
-    method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void unregisterNfcVendorNciCallback(@NonNull android.nfc.NfcAdapter.NfcVendorNciCallback);
+    method @FlaggedApi("android.nfc.nfc_vendor_cmd") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void unregisterNfcVendorNciCallback(@NonNull android.nfc.NfcAdapter.NfcVendorNciCallback);
     method @FlaggedApi("android.nfc.enable_nfc_charging") public void unregisterWlcStateListener(@NonNull android.nfc.NfcAdapter.WlcStateListener);
     field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC = "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
     field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int MESSAGE_TYPE_COMMAND = 1; // 0x1
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 782af5f..252f46f 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -3103,7 +3103,7 @@
      * @hide
      */
     @SystemApi
-    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @FlaggedApi(Flags.FLAG_NFC_VENDOR_CMD)
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     public void unregisterNfcVendorNciCallback(@NonNull NfcVendorNciCallback callback) {
         mNfcVendorNciCallbackListener.unregister(callback);
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
index 7cd2533..89b0322 100644
--- a/nfc/java/android/nfc/cardemulation/HostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -244,11 +244,11 @@
     public static final String KEY_DATA = "data";
 
     /**
-     * POLLING_LOOP_TYPE_KEY is the Bundle key for the type of
+     * KEY_POLLING_LOOP_TYPE is the Bundle key for the type of
      * polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
      */
     @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
-    public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE";
+    public static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE";
 
     /**
      * POLLING_LOOP_TYPE_A is the value associated with the key
@@ -299,33 +299,33 @@
     public static final char POLLING_LOOP_TYPE_UNKNOWN = 'U';
 
     /**
-     * POLLING_LOOP_DATA is the Bundle key for the raw data of captured from
+     * KEY_POLLING_LOOP_DATA is the Bundle key for the raw data of captured from
      * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
      * when the frame type isn't recognized.
      */
     @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
-    public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA";
+    public static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA";
 
     /**
-     * POLLING_LOOP_GAIN_KEY is the Bundle key for the field strength of
+     * KEY_POLLING_LOOP_GAIN is the Bundle key for the field strength of
      * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
      * when the frame type isn't recognized.
      */
     @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
-    public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN";
+    public static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN";
 
     /**
-     * POLLING_LOOP_TIMESTAMP_KEY is the Bundle key for the timestamp of
+     * KEY_POLLING_LOOP_TIMESTAMP is the Bundle key for the timestamp of
      * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
      * when the frame type isn't recognized.
      */
     @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
-    public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP";
+    public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
 
     /**
      * @hide
      */
-    public static final String POLLING_LOOP_FRAMES_BUNDLE_KEY =
+    public static final String KEY_POLLING_LOOP_FRAMES_BUNDLE =
             "android.nfc.cardemulation.POLLING_FRAMES";
 
     /**
@@ -405,7 +405,7 @@
                 break;
                 case MSG_POLLING_LOOP:
                     ArrayList<Bundle> frames =
-                            msg.getData().getParcelableArrayList(POLLING_LOOP_FRAMES_BUNDLE_KEY,
+                            msg.getData().getParcelableArrayList(KEY_POLLING_LOOP_FRAMES_BUNDLE,
                             Bundle.class);
                     processPollingFrames(frames);
                     break;
diff --git a/packages/CompanionDeviceManager/Android.bp b/packages/CompanionDeviceManager/Android.bp
index f6458c2..ce32ec4 100644
--- a/packages/CompanionDeviceManager/Android.bp
+++ b/packages/CompanionDeviceManager/Android.bp
@@ -46,4 +46,6 @@
     ],
 
     platform_apis: true,
+
+    generate_product_characteristics_rro: true,
 }
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
index fdda9ea..910ff96 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
@@ -17,7 +17,7 @@
                 android:id="@android:id/content"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:minWidth="@dimen/dropdown_touch_target_min_width"
+                android:minHeight="@dimen/dropdown_touch_target_min_height"
                 android:orientation="horizontal"
                 android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
                 android:elevation="3dp">
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index c7c2fda..4bf0e99 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -17,7 +17,7 @@
                 android:id="@android:id/content"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:minWidth="@dimen/dropdown_touch_target_min_width"
+                android:minHeight="@dimen/dropdown_touch_target_min_height"
                 android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
                 android:elevation="3dp">
 
diff --git a/packages/CredentialManager/res/values/dimens.xml b/packages/CredentialManager/res/values/dimens.xml
index 53852cb..b47a4dc 100644
--- a/packages/CredentialManager/res/values/dimens.xml
+++ b/packages/CredentialManager/res/values/dimens.xml
@@ -26,6 +26,6 @@
     <dimen name="autofill_dropdown_textview_min_width">112dp</dimen>
     <dimen name="autofill_dropdown_textview_max_width">230dp</dimen>
     <dimen name="dropdown_layout_horizontal_margin">24dp</dimen>
-    <integer name="autofill_max_visible_datasets">3</integer>
-    <dimen name="dropdown_touch_target_min_width">48dp</dimen>
+    <integer name="autofill_max_visible_datasets">5</integer>
+    <dimen name="dropdown_touch_target_min_height">48dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 30681f3..b13df61 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -58,6 +58,7 @@
     private val providerEnabledList: List<ProviderData>
     private val providerDisabledList: List<DisabledProviderData>?
     val resultReceiver: ResultReceiver?
+    val finalResponseReceiver: ResultReceiver?
 
     var initialUiState: UiState
 
@@ -105,10 +106,16 @@
             ResultReceiver::class.java
         )
 
+        finalResponseReceiver = intent.getParcelableExtra(
+                Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
+                ResultReceiver::class.java
+        )
+
         isReqForAllOptions = intent.getBooleanExtra(
                 Constants.EXTRA_REQ_FOR_ALL_OPTIONS,
                 /*defaultValue=*/ false
-        )
+        ) || (requestInfo?.isShowAllOptionsRequested ?: false) // TODO(b/323552850) - Remove
+        // usage on Constants.EXTRA_REQ_FOR_ALL_OPTIONS once it is deprecated.
 
         val cancellationRequest = getCancelUiRequest(intent)
         val cancelUiRequestState = cancellationRequest?.let {
@@ -199,7 +206,7 @@
     }
 
     fun onCancel(cancelCode: Int) {
-        sendCancellationCode(cancelCode, requestInfo?.token, resultReceiver)
+        sendCancellationCode(cancelCode, requestInfo?.token, resultReceiver, finalResponseReceiver)
     }
 
     fun onOptionSelected(
@@ -218,6 +225,10 @@
         )
         val resultDataBundle = Bundle()
         UserSelectionDialogResult.addToBundle(userSelectionDialogResult, resultDataBundle)
+
+        resultDataBundle.putParcelable(Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
+                finalResponseReceiver)
+
         resultReceiver?.send(
             BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION,
             resultDataBundle
@@ -285,10 +296,14 @@
         fun sendCancellationCode(
             cancelCode: Int,
             requestToken: IBinder?,
-            resultReceiver: ResultReceiver?
+            resultReceiver: ResultReceiver?,
+            finalResponseReceiver: ResultReceiver?
         ) {
             if (requestToken != null && resultReceiver != null) {
                 val resultData = Bundle()
+                resultData.putParcelable(Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
+                        finalResponseReceiver)
+
                 BaseDialogResult.addToBundle(BaseDialogResult(requestToken), resultData)
                 resultReceiver.send(cancelCode, resultData)
             }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 05aa548..26a97cd 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -216,13 +216,18 @@
             android.credentials.selection.Constants.EXTRA_RESULT_RECEIVER,
             ResultReceiver::class.java
         )
+        val finalResponseResultReceiver = intent.getParcelableExtra(
+                android.credentials.selection.Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
+                ResultReceiver::class.java
+        )
+
         val requestInfo = intent.extras?.getParcelable(
             RequestInfo.EXTRA_REQUEST_INFO,
             RequestInfo::class.java
         )
         CredentialManagerRepo.sendCancellationCode(
             BaseDialogResult.RESULT_CODE_DATA_PARSING_FAILURE,
-            requestInfo?.token, resultReceiver
+            requestInfo?.token, resultReceiver, finalResponseResultReceiver
         )
         this.finish()
     }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 07f1fa3..8fde5d7 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -19,13 +19,12 @@
 import android.app.PendingIntent
 import android.app.assist.AssistStructure
 import android.content.Context
-import android.credentials.Credential
+import android.content.Intent
 import android.credentials.CredentialManager
-import android.credentials.CredentialOption
-import android.credentials.GetCandidateCredentialsException
-import android.credentials.GetCandidateCredentialsResponse
 import android.credentials.GetCredentialRequest
-import android.credentials.GetCredentialResponse
+import android.credentials.GetCandidateCredentialsResponse
+import android.credentials.GetCandidateCredentialsException
+import android.credentials.CredentialOption
 import android.credentials.selection.Entry
 import android.credentials.selection.GetCredentialProviderData
 import android.credentials.selection.ProviderData
@@ -47,7 +46,6 @@
 import android.service.credentials.CredentialProviderService
 import android.util.Log
 import android.view.autofill.AutofillId
-import android.view.autofill.AutofillValue
 import android.view.autofill.IAutoFillManagerClient
 import android.widget.RemoteViews
 import android.widget.inline.InlinePresentationSpec
@@ -116,8 +114,10 @@
             return
         }
 
+        val responseClientState = Bundle()
+        responseClientState.putBoolean(WEBVIEW_REQUESTED_CREDENTIAL_KEY, false)
         val getCredRequest: GetCredentialRequest? = getCredManRequest(structure, sessionId,
-                requestId)
+                requestId, responseClientState)
         if (getCredRequest == null) {
             Log.i(TAG, "No credential manager request found")
             callback.onFailure("No credential manager request found")
@@ -129,31 +129,9 @@
         val outcome = object : OutcomeReceiver<GetCandidateCredentialsResponse,
                 GetCandidateCredentialsException> {
             override fun onResult(result: GetCandidateCredentialsResponse) {
-                Log.i(TAG, "getCandidateCredentials onResponse")
-
-                if (result.getCredentialResponse != null) {
-                    val autofillId: AutofillId? = result.getCredentialResponse
-                            .credential.data.getParcelable(
-                                    CredentialProviderService.EXTRA_AUTOFILL_ID,
-                                    AutofillId::class.java)
-                    Log.i(TAG, "getCandidateCredentials final response, autofillId: " +
-                            autofillId)
-
-                    if (autofillId != null) {
-                        autofillCallback.autofill(
-                                sessionId,
-                                mutableListOf(autofillId),
-                                mutableListOf(
-                                        AutofillValue.forText(
-                                                convertResponseToJson(result.getCredentialResponse)
-                                        )
-                                ),
-                                false)
-                    }
-                    return
-                }
-
-                val fillResponse = convertToFillResponse(result, request)
+                Log.i(TAG, "getCandidateCredentials onResult")
+                val fillResponse = convertToFillResponse(result, request,
+                    responseClientState)
                 if (fillResponse != null) {
                     callback.onSuccess(fillResponse)
                 } else {
@@ -178,57 +156,6 @@
         )
     }
 
-    // TODO(b/318118018): Use from Jetpack
-    private fun convertResponseToJson(response: GetCredentialResponse): String? {
-        try {
-            val jsonObject = JSONObject()
-            jsonObject.put("type", "get")
-            val jsonCred = JSONObject()
-            jsonCred.put("type", response.credential.type)
-            jsonCred.put("data", credentialToJSON(
-                    response.credential))
-            jsonObject.put("credential", jsonCred)
-            return jsonObject.toString()
-        } catch (e: JSONException) {
-            Log.i(
-                    TAG, "Exception while constructing response JSON: " +
-                    e.message
-            )
-        }
-        return null
-    }
-
-    // TODO(b/318118018): Replace with calls to Jetpack
-    private fun credentialToJSON(credential: Credential): JSONObject? {
-        Log.i(TAG, "credentialToJSON")
-        try {
-            if (credential.type == "android.credentials.TYPE_PASSWORD_CREDENTIAL") {
-                Log.i(TAG, "toJSON PasswordCredential")
-
-                val json = JSONObject()
-                val id = credential.data.getString("androidx.credentials.BUNDLE_KEY_ID")
-                val pass = credential.data.getString("androidx.credentials.BUNDLE_KEY_PASSWORD")
-                json.put("androidx.credentials.BUNDLE_KEY_ID", id)
-                json.put("androidx.credentials.BUNDLE_KEY_PASSWORD", pass)
-                return json
-            } else if (credential.type == "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL") {
-                Log.i(TAG, "toJSON PublicKeyCredential")
-
-                val json = JSONObject()
-                val responseJson = credential
-                        .data
-                        .getString("androidx.credentials.BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON")
-                json.put("androidx.credentials.BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON",
-                        responseJson)
-                return json
-            }
-        } catch (e: JSONException) {
-            Log.i(TAG, "issue while converting credential response to JSON")
-        }
-        Log.i(TAG, "Unsupported credential type")
-        return null
-    }
-
     private fun getEntryToIconMap(
             candidateProviderDataList: List<GetCredentialProviderData>
     ): Map<String, Icon> {
@@ -260,7 +187,8 @@
 
     private fun convertToFillResponse(
             getCredResponse: GetCandidateCredentialsResponse,
-            filLRequest: FillRequest
+            filLRequest: FillRequest,
+            responseClientState: Bundle
     ): FillResponse? {
         val candidateProviders = getCredResponse.candidateProviderDataList
         if (candidateProviders.isEmpty()) {
@@ -271,6 +199,7 @@
         val autofillIdToProvidersMap: Map<AutofillId, ArrayList<GetCredentialProviderData>> =
                 mapAutofillIdToProviders(candidateProviders)
         val fillResponseBuilder = FillResponse.Builder()
+        fillResponseBuilder.setFlags(FillResponse.FLAG_CREDENTIAL_MANAGER_RESPONSE)
         var validFillResponse = false
         autofillIdToProvidersMap.forEach { (autofillId, providers) ->
             validFillResponse = processProvidersForAutofillId(
@@ -281,6 +210,7 @@
         if (!validFillResponse) {
             return null
         }
+        fillResponseBuilder.setClientState(responseClientState)
         return fillResponseBuilder.build()
     }
 
@@ -313,7 +243,7 @@
         maxInlineItemCount = maxInlineItemCount.coerceAtMost(inlineMaxSuggestedCount)
         val lastDropdownDatasetIndex = Settings.Global.getInt(this.contentResolver,
                 Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS,
-                (maxDropdownDisplayLimit - 1).coerceAtMost(totalEntryCount - 1))
+                (maxDropdownDisplayLimit - 1)).coerceAtMost(totalEntryCount - 1)
 
         var i = 0
         var datasetAdded = false
@@ -382,7 +312,7 @@
                                             presentationBuilder.build())
                                             .build())
                             .setAuthentication(pendingIntent.intentSender)
-                            .setAuthenticationExtras(fillInIntent.extras)
+                            .setCredentialFillInIntent(fillInIntent)
                             .build())
             datasetAdded = true
             i++
@@ -402,11 +332,11 @@
     }
 
     private fun createInlinePresentation(
-        primaryEntry: CredentialEntryInfo,
-        pendingIntent: PendingIntent,
-        icon: Icon,
-        spec: InlinePresentationSpec,
-        duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>
+            primaryEntry: CredentialEntryInfo,
+            pendingIntent: PendingIntent,
+            icon: Icon,
+            spec: InlinePresentationSpec,
+            duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>
     ): InlinePresentation {
         val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY &&
             primaryEntry.displayName != null) {
@@ -432,7 +362,8 @@
             fillResponseBuilder: FillResponse.Builder
     ) {
         val presentationBuilder = Presentations.Builder()
-                .setMenuPresentation(RemoteViewsFactory.createMoreSignInOptionsPresentation(this))
+                .setMenuPresentation(
+                        RemoteViewsFactory.createMoreSignInOptionsPresentation(this))
 
         fillResponseBuilder.addDataset(
                 Dataset.Builder()
@@ -472,9 +403,8 @@
                 .setInlinePresentation(InlinePresentation(
                         sliceBuilder.build().slice, spec, /* pinned= */ true))
 
-        val extraBundle = Bundle()
-        extraBundle.putParcelableArrayList(
-                        ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, providerDataList)
+        val extrasIntent = Intent()
+        extrasIntent.putExtra(ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, providerDataList)
 
         fillResponseBuilder.addDataset(
                 dataSetBuilder
@@ -484,7 +414,7 @@
                                         presentationBuilder.build())
                                         .build())
                         .setAuthentication(bottomSheetPendingIntent.intentSender)
-                        .setAuthenticationExtras(extraBundle)
+                        .setCredentialFillInIntent(extrasIntent)
                         .build()
         )
     }
@@ -578,10 +508,11 @@
     private fun getCredManRequest(
             structure: AssistStructure,
             sessionId: Int,
-            requestId: Int
+            requestId: Int,
+            responseClientState: Bundle
     ): GetCredentialRequest? {
         val credentialOptions: MutableList<CredentialOption> = mutableListOf()
-        traverseStructure(structure, credentialOptions)
+        traverseStructure(structure, credentialOptions, responseClientState)
 
         if (credentialOptions.isNotEmpty()) {
             val dataBundle = Bundle()
@@ -596,7 +527,8 @@
 
     private fun traverseStructure(
             structure: AssistStructure,
-            cmRequests: MutableList<CredentialOption>
+            cmRequests: MutableList<CredentialOption>,
+            responseClientState: Bundle
     ) {
         val windowNodes: List<AssistStructure.WindowNode> =
                 structure.run {
@@ -604,16 +536,17 @@
                 }
 
         windowNodes.forEach { windowNode: AssistStructure.WindowNode ->
-            traverseNode(windowNode.rootViewNode, cmRequests)
+            traverseNode(windowNode.rootViewNode, cmRequests, responseClientState)
         }
     }
 
     private fun traverseNode(
             viewNode: AssistStructure.ViewNode,
-            cmRequests: MutableList<CredentialOption>
+            cmRequests: MutableList<CredentialOption>,
+            responseClientState: Bundle
     ) {
         viewNode.autofillId?.let {
-            val options = getCredentialOptionsFromViewNode(viewNode, it)
+            val options = getCredentialOptionsFromViewNode(viewNode, it, responseClientState)
             cmRequests.addAll(options)
         }
 
@@ -623,20 +556,23 @@
                 }
 
         children.forEach { childNode: AssistStructure.ViewNode ->
-            traverseNode(childNode, cmRequests)
+            traverseNode(childNode, cmRequests, responseClientState)
         }
     }
 
     private fun getCredentialOptionsFromViewNode(
             viewNode: AssistStructure.ViewNode,
-            autofillId: AutofillId
+            autofillId: AutofillId,
+            responseClientState: Bundle
     ): List<CredentialOption> {
-        // TODO(b/293945193) Replace with isCredential check from viewNode
         val credentialHints: MutableList<String> = mutableListOf()
         if (viewNode.autofillHints != null) {
             for (hint in viewNode.autofillHints!!) {
                 if (hint.startsWith(CRED_HINT_PREFIX)) {
                     credentialHints.add(hint.substringAfter(CRED_HINT_PREFIX))
+                    if (viewNode.webDomain != null) {
+                        responseClientState.putBoolean(WEBVIEW_REQUESTED_CREDENTIAL_KEY, true)
+                    }
                 }
             }
         }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
index 790f5b1..f8e22ee 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 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.
@@ -58,9 +58,9 @@
 
         scrollable(Screen.SinglePasswordScreen.route) {
             SinglePasswordScreen(
-                state = viewModel.uiState.value as SingleEntry,
+                credentialSelectorUiState = viewModel.uiState.value as SingleEntry,
+                screenIcon = null,
                 columnState = it.columnState,
-                onCloseApp = onCloseApp,
             )
         }
     }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt
index 3ed0c9c..b2812d3 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/AccountRow.kt
@@ -31,7 +31,7 @@
 @Composable
 fun AccountRow(
     primaryText: String,
-    secondaryText: String,
+    secondaryText: String? = null,
     modifier: Modifier = Modifier,
 ) {
     Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
@@ -42,14 +42,16 @@
             maxLines = 1,
             style = MaterialTheme.typography.title2
         )
-        Text(
-            text = secondaryText,
-            modifier = Modifier.padding(top = 7.dp),
-            color = Color(0xFFCAC5BC),
-            overflow = TextOverflow.Ellipsis,
-            maxLines = 2,
-            style = MaterialTheme.typography.body1,
-        )
+        if (secondaryText != null) {
+            Text(
+                text = secondaryText,
+                modifier = Modifier.padding(top = 7.dp),
+                color = Color(0xFFCAC5BC),
+                overflow = TextOverflow.Ellipsis,
+                maxLines = 2,
+                style = MaterialTheme.typography.body1,
+            )
+        }
     }
 }
 
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
index 3297991..5590219 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
@@ -76,7 +76,7 @@
     Chip(
         label = labelParam,
         onClick = onClick,
-        modifier = modifier,
+        modifier = modifier.fillMaxWidth(),
         secondaryLabel = secondaryLabelParam,
         icon = iconParam,
         colors = colors,
@@ -104,7 +104,6 @@
         label = stringResource(R.string.dialog_sign_in_options_button),
         onClick = onClick,
         modifier = Modifier
-            .fillMaxWidth()
             .padding(top = TOPPADDING)
     )
 }
@@ -121,7 +120,6 @@
         label = stringResource(R.string.dialog_continue_button),
         onClick = onClick,
         modifier = Modifier
-            .fillMaxWidth()
             .padding(top = TOPPADDING),
         colors = ChipDefaults.primaryChipColors(),
     )
@@ -139,7 +137,6 @@
         label = stringResource(R.string.dialog_dismiss_button),
         onClick = onClick,
         modifier = Modifier
-            .fillMaxWidth()
             .padding(top = TOPPADDING),
     )
 }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
deleted file mode 100644
index a368de2..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasskeyUiModel.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * 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.0N
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.credentialmanager.ui.model
-
-data class PasskeyUiModel(
-    val name: String,
-    val email: String,
-)
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/UiState.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/UiState.kt
new file mode 100644
index 0000000..42a88e2
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/UiState.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.credentialmanager.ui.screens.single
+
+import androidx.activity.result.IntentSenderRequest
+
+sealed class UiState {
+    data object CredentialScreen : UiState()
+
+    data class CredentialSelected(
+        val intentSenderRequest: IntentSenderRequest?
+    ) : UiState()
+
+    data object Cancel : UiState()
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
index 2878b0b..92d8a39 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2023 The Android Open Source Project
+ * 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.
@@ -18,44 +18,119 @@
 
 package com.android.credentialmanager.ui.screens.single.passkey
 
+import android.graphics.drawable.Drawable
+import androidx.compose.foundation.layout.Column
+import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.rememberNavController
+import com.android.credentialmanager.CredentialSelectorUiState
+import com.android.credentialmanager.model.get.CredentialEntryInfo
 import com.android.credentialmanager.R
+import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
 import com.android.credentialmanager.ui.components.AccountRow
+import com.android.credentialmanager.ui.components.ContinueChip
+import com.android.credentialmanager.ui.components.DismissChip
 import com.android.credentialmanager.ui.components.SignInHeader
+import com.android.credentialmanager.ui.components.SignInOptionsChip
 import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
+import com.android.credentialmanager.ui.screens.single.UiState
 import com.google.android.horologist.annotations.ExperimentalHorologistApi
 import com.google.android.horologist.compose.layout.ScalingLazyColumnState
 
 @OptIn(ExperimentalHorologistApi::class)
 @Composable
 fun SinglePasskeyScreen(
-    name: String,
-    email: String,
+    credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry,
+    screenIcon: Drawable?,
     columnState: ScalingLazyColumnState,
     modifier: Modifier = Modifier,
+    viewModel: SinglePasskeyScreenViewModel = hiltViewModel(),
+    navController: NavHostController = rememberNavController(),
+) {
+    viewModel.initialize(credentialSelectorUiState.entry)
+
+    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+
+    when (val state = uiState) {
+        UiState.CredentialScreen -> {
+            SinglePasskeyScreen(
+                credentialSelectorUiState.entry,
+                screenIcon,
+                columnState,
+                modifier,
+                viewModel
+            )
+        }
+
+        is UiState.CredentialSelected -> {
+            val launcher = rememberLauncherForActivityResult(
+                StartBalIntentSenderForResultContract()
+            ) {
+                viewModel.onPasskeyInfoRetrieved(it.resultCode, null)
+            }
+
+            SideEffect {
+                state.intentSenderRequest?.let {
+                    launcher.launch(it)
+                }
+            }
+        }
+
+        UiState.Cancel -> {
+            // TODO(b/322797032) add valid navigation path here for going back
+            navController.popBackStack()
+        }
+    }
+}
+
+@OptIn(ExperimentalHorologistApi::class)
+@Composable
+fun SinglePasskeyScreen(
+    entry: CredentialEntryInfo,
+    screenIcon: Drawable?,
+    columnState: ScalingLazyColumnState,
+    modifier: Modifier = Modifier,
+    viewModel: SinglePasskeyScreenViewModel,
 ) {
     SingleAccountScreen(
         headerContent = {
             SignInHeader(
-                icon = null,
+                icon = screenIcon,
                 title = stringResource(R.string.use_passkey_title),
             )
         },
         accountContent = {
-            AccountRow(
-                primaryText = name,
-                secondaryText = email,
-                modifier = Modifier.padding(top = 10.dp),
-            )
+            if (entry.displayName != null) {
+                AccountRow(
+                    primaryText = checkNotNull(entry.displayName),
+                    secondaryText = entry.userName,
+                    modifier = Modifier.padding(top = 10.dp),
+                )
+            } else {
+                AccountRow(
+                    primaryText = entry.userName,
+                    modifier = Modifier.padding(top = 10.dp),
+                )
+            }
         },
         columnState = columnState,
         modifier = modifier.padding(horizontal = 10.dp)
     ) {
         item {
+            Column {
+                ContinueChip(viewModel::onContinueClick)
+                SignInOptionsChip(viewModel::onSignInOptionsClick)
+                DismissChip(viewModel::onDismissClick)
+            }
         }
     }
 }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt
new file mode 100644
index 0000000..35c39f6
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.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.credentialmanager.ui.screens.single.passkey
+
+import android.content.Intent
+import android.credentials.selection.UserSelectionDialogResult
+import android.credentials.selection.ProviderPendingIntentResponse
+import androidx.annotation.MainThread
+import androidx.lifecycle.ViewModel
+import com.android.credentialmanager.ktx.getIntentSenderRequest
+import com.android.credentialmanager.model.Request
+import com.android.credentialmanager.client.CredentialManagerClient
+import com.android.credentialmanager.model.get.CredentialEntryInfo
+import dagger.hilt.android.lifecycle.HiltViewModel
+import com.android.credentialmanager.ui.screens.single.UiState
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import javax.inject.Inject
+
+@HiltViewModel
+class SinglePasskeyScreenViewModel @Inject constructor(
+    private val credentialManagerClient: CredentialManagerClient,
+) : ViewModel() {
+
+    private val _uiState =
+        MutableStateFlow<UiState>(UiState.CredentialScreen)
+    val uiState: StateFlow<UiState> = _uiState
+
+    private lateinit var requestGet: Request.Get
+    private lateinit var entryInfo: CredentialEntryInfo
+
+
+    @MainThread
+    fun initialize(entry: CredentialEntryInfo) {
+        this.entryInfo = entry
+    }
+
+    fun onDismissClick() {
+        _uiState.value = UiState.Cancel
+    }
+
+    fun onContinueClick() {
+        _uiState.value = UiState.CredentialSelected(
+            intentSenderRequest = entryInfo.getIntentSenderRequest()
+        )
+    }
+
+    fun onSignInOptionsClick() {
+    }
+
+    fun onPasskeyInfoRetrieved(
+        resultCode: Int? = null,
+        resultData: Intent? = null,
+    ) {
+        val userSelectionDialogResult = UserSelectionDialogResult(
+            requestGet.token,
+            entryInfo.providerId,
+            entryInfo.entryKey,
+            entryInfo.entrySubkey,
+            if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
+        )
+        credentialManagerClient.sendResult(userSelectionDialogResult)
+    }
+}
+
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
index 95ef7db..a8be944 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
@@ -18,8 +18,9 @@
 
 package com.android.credentialmanager.ui.screens.single.password
 
-import android.util.Log
+import android.graphics.drawable.Drawable
 import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.SideEffect
@@ -29,47 +30,52 @@
 import androidx.compose.ui.unit.dp
 import androidx.hilt.navigation.compose.hiltViewModel
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntry
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.rememberNavController
+import com.android.credentialmanager.CredentialSelectorUiState
 import com.android.credentialmanager.R
-import com.android.credentialmanager.TAG
 import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
 import com.android.credentialmanager.ui.components.PasswordRow
+import com.android.credentialmanager.ui.components.ContinueChip
+import com.android.credentialmanager.ui.components.DismissChip
 import com.android.credentialmanager.ui.components.SignInHeader
-import com.android.credentialmanager.ui.model.PasswordUiModel
+import com.android.credentialmanager.ui.components.SignInOptionsChip
 import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
+import com.android.credentialmanager.model.get.CredentialEntryInfo
+import com.android.credentialmanager.ui.screens.single.UiState
 import com.google.android.horologist.annotations.ExperimentalHorologistApi
 import com.google.android.horologist.compose.layout.ScalingLazyColumnState
 
+@OptIn(ExperimentalHorologistApi::class)
 @Composable
 fun SinglePasswordScreen(
-    state: SingleEntry,
+    credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry,
+    screenIcon: Drawable?,
     columnState: ScalingLazyColumnState,
-    onCloseApp: () -> Unit,
     modifier: Modifier = Modifier,
     viewModel: SinglePasswordScreenViewModel = hiltViewModel(),
+    navController: NavHostController = rememberNavController(),
 ) {
-    viewModel.initialize(state.entry)
+    viewModel.initialize(credentialSelectorUiState.entry)
 
     val uiState by viewModel.uiState.collectAsStateWithLifecycle()
 
     when (val state = uiState) {
-        SinglePasswordScreenUiState.Idle -> {
-            // TODO: b/301206470 implement latency version of the screen
-        }
-
-        is SinglePasswordScreenUiState.Loaded -> {
+        UiState.CredentialScreen -> {
             SinglePasswordScreen(
-                passwordUiModel = state.passwordUiModel,
-                columnState = columnState,
-                modifier = modifier
+                credentialSelectorUiState.entry,
+                screenIcon,
+                columnState,
+                modifier,
+                viewModel
             )
         }
 
-        is SinglePasswordScreenUiState.PasswordSelected -> {
+        is UiState.CredentialSelected -> {
             val launcher = rememberLauncherForActivityResult(
                 StartBalIntentSenderForResultContract()
             ) {
-                viewModel.onPasswordInfoRetrieved(it.resultCode, it.data)
+                viewModel.onPasswordInfoRetrieved(it.resultCode, null)
             }
 
             SideEffect {
@@ -79,37 +85,32 @@
             }
         }
 
-        SinglePasswordScreenUiState.Cancel -> {
-            // TODO: b/301206470 implement navigation for when user taps cancel
-        }
-
-        SinglePasswordScreenUiState.Error -> {
-            // TODO: b/301206470 implement navigation for when there is an error to load screen
-        }
-
-        SinglePasswordScreenUiState.Completed -> {
-            Log.d(TAG, "Received signal to finish the activity.")
-            onCloseApp()
+        UiState.Cancel -> {
+            // TODO(b/322797032) add valid navigation path here for going back
+            navController.popBackStack()
         }
     }
 }
 
+@OptIn(ExperimentalHorologistApi::class)
 @Composable
-fun SinglePasswordScreen(
-    passwordUiModel: PasswordUiModel,
+private fun SinglePasswordScreen(
+    entry: CredentialEntryInfo,
+    screenIcon: Drawable?,
     columnState: ScalingLazyColumnState,
     modifier: Modifier = Modifier,
+    viewModel: SinglePasswordScreenViewModel,
 ) {
     SingleAccountScreen(
         headerContent = {
             SignInHeader(
-                icon = null,
+                icon = screenIcon,
                 title = stringResource(R.string.use_password_title),
             )
         },
         accountContent = {
             PasswordRow(
-                email = passwordUiModel.email,
+                email = entry.userName,
                 modifier = Modifier.padding(top = 10.dp),
             )
         },
@@ -117,6 +118,13 @@
         modifier = modifier.padding(horizontal = 10.dp)
     ) {
         item {
+            Column {
+                ContinueChip(viewModel::onContinueClick)
+                SignInOptionsChip(viewModel::onSignInOptionsClick)
+                DismissChip(viewModel::onDismissClick)
+            }
         }
     }
 }
+
+
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
index c9c66b4..3f841b8 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
@@ -17,16 +17,15 @@
 package com.android.credentialmanager.ui.screens.single.password
 
 import android.content.Intent
-import android.credentials.selection.ProviderPendingIntentResponse
 import android.credentials.selection.UserSelectionDialogResult
-import androidx.activity.result.IntentSenderRequest
+import android.credentials.selection.ProviderPendingIntentResponse
 import androidx.annotation.MainThread
 import androidx.lifecycle.ViewModel
 import com.android.credentialmanager.ktx.getIntentSenderRequest
 import com.android.credentialmanager.model.Request
 import com.android.credentialmanager.client.CredentialManagerClient
 import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.ui.model.PasswordUiModel
+import com.android.credentialmanager.ui.screens.single.UiState
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -37,36 +36,31 @@
     private val credentialManagerClient: CredentialManagerClient,
 ) : ViewModel() {
 
-    private var initializeCalled = false
-
     private lateinit var requestGet: Request.Get
     private lateinit var entryInfo: CredentialEntryInfo
 
     private val _uiState =
-        MutableStateFlow<SinglePasswordScreenUiState>(SinglePasswordScreenUiState.Idle)
-    val uiState: StateFlow<SinglePasswordScreenUiState> = _uiState
+        MutableStateFlow<UiState>(UiState.CredentialScreen)
+    val uiState: StateFlow<UiState> = _uiState
 
     @MainThread
     fun initialize(entryInfo: CredentialEntryInfo) {
-        if (initializeCalled) return
-        initializeCalled = true
-        _uiState.value = SinglePasswordScreenUiState.Loaded(
-            PasswordUiModel(
-                email = entryInfo.userName,
-            )
-        )
+        this.entryInfo = entryInfo
     }
 
-    fun onCancelClick() {
-        _uiState.value = SinglePasswordScreenUiState.Cancel
+    fun onDismissClick() {
+        _uiState.value = UiState.Cancel
     }
 
-    fun onOKClick() {
-        _uiState.value = SinglePasswordScreenUiState.PasswordSelected(
+    fun onContinueClick() {
+        _uiState.value = UiState.CredentialSelected(
             intentSenderRequest = entryInfo.getIntentSenderRequest()
         )
     }
 
+    fun onSignInOptionsClick() {
+    }
+
     fun onPasswordInfoRetrieved(
         resultCode: Int? = null,
         resultData: Intent? = null,
@@ -79,18 +73,5 @@
             if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
         )
         credentialManagerClient.sendResult(userSelectionDialogResult)
-        _uiState.value = SinglePasswordScreenUiState.Completed
     }
 }
-
-sealed class SinglePasswordScreenUiState {
-    data object Idle : SinglePasswordScreenUiState()
-    data class Loaded(val passwordUiModel: PasswordUiModel) : SinglePasswordScreenUiState()
-    data class PasswordSelected(
-        val intentSenderRequest: IntentSenderRequest?
-    ) : SinglePasswordScreenUiState()
-
-    data object Cancel : SinglePasswordScreenUiState()
-    data object Error : SinglePasswordScreenUiState()
-    data object Completed : SinglePasswordScreenUiState()
-}
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index f425f52..3242935 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -288,7 +288,7 @@
          cannot happen immediately because the device is offline (has no internet connection.
          [CHAR LIMIT=none] -->
     <string name="unarchive_error_offline_body">
-        This app will automatically restore when you\'re connected to the internet
+        To restore this app, check your internet connection and try again
     </string>
 
     <!-- Dialog title shown when the user is trying to restore an app but a generic error happened.
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
index b2b7b61..5f5f1d5 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -6,7 +6,6 @@
 [email protected]
 [email protected]
 [email protected]
[email protected]
 [email protected]
 
 # Exempt resource files (because they are in a flat directory and too hard to manage via OWNERS)
diff --git a/packages/SettingsLib/Spa/spa/res/values/themes.xml b/packages/SettingsLib/Spa/spa/res/values/themes.xml
index 4b5a9bc..b55dd1b 100644
--- a/packages/SettingsLib/Spa/spa/res/values/themes.xml
+++ b/packages/SettingsLib/Spa/spa/res/values/themes.xml
@@ -24,4 +24,8 @@
     </style>
 
     <style name="Theme.SpaLib.Dialog" parent="Theme.Material3.DayNight.Dialog"/>
+    <style name="Theme.SpaLib.BottomSheetDialog" parent="Theme.SpaLib">
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:windowIsTranslucent">true</item>
+    </style>
 </resources>
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
index b34c310c..e704505 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
@@ -18,7 +18,6 @@
 
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.width
@@ -95,56 +94,60 @@
         if (options.isNotEmpty()) {
             ExposedDropdownMenu(
                 expanded = expanded,
-                modifier = Modifier
-                    .fillMaxWidth()
-                    .width(with(LocalDensity.current) { dropDownWidth.toDp() }),
+                modifier = Modifier.width(with(LocalDensity.current) { dropDownWidth.toDp() }),
                 onDismissRequest = { expanded = false },
             ) {
                 options.forEachIndexed { index, option ->
-                    TextButton(
-                        modifier = Modifier
-                            .fillMaxHeight()
-                            .fillMaxWidth(),
-                        onClick = {
-                            if (selectedOptionsState.contains(index)) {
-                                if (index == allIndex)
-                                    selectedOptionsState.clear()
-                                else {
-                                    selectedOptionsState.remove(
-                                        index
-                                    )
-                                    if (selectedOptionsState.contains(allIndex))
-                                        selectedOptionsState.remove(
-                                            allIndex
-                                        )
-                                }
-                            } else {
-                                selectedOptionsState.add(
-                                    index
-                                )
-                            }
-                            onSelectedOptionStateChange()
-                        }) {
-                        Row(
-                            modifier = Modifier
-                                .fillMaxHeight()
-                                .fillMaxWidth(),
-                            horizontalArrangement = Arrangement.Start,
-                            verticalAlignment = Alignment.CenterVertically
-                        ) {
-                            Checkbox(
-                                checked = selectedOptionsState.contains(index),
-                                onCheckedChange = null,
-                            )
-                            Text(text = option)
-                        }
-                    }
+                    CheckboxItem(
+                        selectedOptionsState,
+                        index,
+                        allIndex,
+                        onSelectedOptionStateChange,
+                        option,
+                    )
                 }
             }
         }
     }
 }
 
+@Composable
+private fun CheckboxItem(
+    selectedOptionsState: SnapshotStateList<Int>,
+    index: Int,
+    allIndex: Int,
+    onSelectedOptionStateChange: () -> Unit,
+    option: String
+) {
+    TextButton(
+        modifier = Modifier.fillMaxWidth(),
+        onClick = {
+            if (selectedOptionsState.contains(index)) {
+                if (index == allIndex) {
+                    selectedOptionsState.clear()
+                } else {
+                    selectedOptionsState.remove(index)
+                    selectedOptionsState.remove(allIndex)
+                }
+            } else {
+                selectedOptionsState.add(index)
+            }
+            onSelectedOptionStateChange()
+        }) {
+        Row(
+            modifier = Modifier.fillMaxWidth(),
+            horizontalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround),
+            verticalAlignment = Alignment.CenterVertically
+        ) {
+            Checkbox(
+                checked = selectedOptionsState.contains(index),
+                onCheckedChange = null,
+            )
+            Text(text = option)
+        }
+    }
+}
+
 @Preview
 @Composable
 private fun ActionButtonsPreview() {
@@ -158,4 +161,4 @@
             enabled = true,
             onSelectedOptionStateChange = {})
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index 0a98791..988afd7 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -17,10 +17,11 @@
 package com.android.settingslib.spaprivileged.model.app
 
 import android.content.Context
-import android.content.pm.FeatureFlags
-import android.content.pm.FeatureFlagsImpl
 import android.content.Intent
 import android.content.pm.ApplicationInfo
+import android.content.pm.FeatureFlags
+import android.content.pm.FeatureFlagsImpl
+import android.content.pm.Flags
 import android.content.pm.PackageManager
 import android.content.pm.PackageManager.ApplicationInfoFlags
 import android.content.pm.ResolveInfo
@@ -85,13 +86,7 @@
         loadInstantApps: Boolean,
         matchAnyUserForAdmin: Boolean,
     ): List<ApplicationInfo> = coroutineScope {
-        val hiddenSystemModulesDeferred = async {
-            packageManager.getInstalledModules(0)
-                .filter { it.isHidden }
-                .map { it.packageName }
-                .filterNotNull()
-                .toSet()
-        }
+        val hiddenSystemModulesDeferred = async { packageManager.getHiddenSystemModules() }
         val hideWhenDisabledPackagesDeferred = async {
             context.resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)
         }
@@ -205,6 +200,15 @@
     private fun isSystemApp(app: ApplicationInfo, homeOrLauncherPackages: Set<String>): Boolean =
         app.isSystemApp && !app.isUpdatedSystemApp && app.packageName !in homeOrLauncherPackages
 
+    private fun PackageManager.getHiddenSystemModules(): Set<String> {
+        val moduleInfos = getInstalledModules(0).filter { it.isHidden }
+        val hiddenApps = moduleInfos.mapNotNull { it.packageName }.toMutableSet()
+        if (Flags.provideInfoOfApkInApex()) {
+            hiddenApps += moduleInfos.flatMap { it.apkInApexPackageNames }
+        }
+        return hiddenApps
+    }
+
     companion object {
         private fun ApplicationInfo.isInAppList(
             showInstantApps: Boolean,
diff --git a/packages/SettingsLib/SpaPrivileged/tests/Android.bp b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
index a28ebc6..458fcc9 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
@@ -35,5 +35,6 @@
         "androidx.test.ext.junit",
         "androidx.test.runner",
         "mockito-target-minus-junit4",
+        "flag-junit",
     ],
 }
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index f292231..efd53a4 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -21,6 +21,7 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.FakeFeatureFlagsImpl
 import android.content.pm.Flags
+import android.content.pm.ModuleInfo
 import android.content.pm.PackageManager
 import android.content.pm.PackageManager.ApplicationInfoFlags
 import android.content.pm.PackageManager.ResolveInfoFlags
@@ -28,6 +29,7 @@
 import android.content.pm.UserInfo
 import android.content.res.Resources
 import android.os.UserManager
+import android.platform.test.flag.junit.SetFlagsRule
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.internal.R
@@ -35,6 +37,7 @@
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runTest
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.any
@@ -50,6 +53,9 @@
 
 @RunWith(AndroidJUnit4::class)
 class AppListRepositoryTest {
+    @get:Rule
+    val mSetFlagsRule = SetFlagsRule()
+
     private val resources = mock<Resources> {
         on { getStringArray(R.array.config_hideWhenDisabled_packageNames) } doReturn emptyArray()
     }
@@ -273,6 +279,38 @@
     }
 
     @Test
+    fun loadApps_hasApkInApexInfo_shouldNotIncludeAllHiddenApps() = runTest {
+        mSetFlagsRule.enableFlags(Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX)
+        packageManager.stub {
+            on { getInstalledModules(any()) } doReturn listOf(HIDDEN_MODULE)
+        }
+        mockInstalledApplications(
+            listOf(NORMAL_APP, HIDDEN_APEX_APP, HIDDEN_MODULE_APP),
+            ADMIN_USER_ID
+        )
+
+        val appList = repository.loadApps(userId = ADMIN_USER_ID)
+
+        assertThat(appList).containsExactly(NORMAL_APP)
+    }
+
+    @Test
+    fun loadApps_noApkInApexInfo_shouldNotIncludeHiddenSystemModule() = runTest {
+        mSetFlagsRule.disableFlags(Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX)
+        packageManager.stub {
+            on { getInstalledModules(any()) } doReturn listOf(HIDDEN_MODULE)
+        }
+        mockInstalledApplications(
+            listOf(NORMAL_APP, HIDDEN_APEX_APP, HIDDEN_MODULE_APP),
+            ADMIN_USER_ID
+        )
+
+        val appList = repository.loadApps(userId = ADMIN_USER_ID)
+
+        assertThat(appList).containsExactly(NORMAL_APP, HIDDEN_APEX_APP)
+    }
+
+    @Test
     fun showSystemPredicate_showSystem() = runTest {
         val app = SYSTEM_APP
 
@@ -402,6 +440,20 @@
             isArchived = true
         }
 
+        val HIDDEN_APEX_APP = ApplicationInfo().apply {
+            packageName = "hidden.apex.package"
+        }
+
+        val HIDDEN_MODULE_APP = ApplicationInfo().apply {
+            packageName = "hidden.module.package"
+        }
+
+        val HIDDEN_MODULE = ModuleInfo().apply {
+            packageName = "hidden.module.package"
+            apkInApexPackageNames = listOf("hidden.apex.package")
+            isHidden = true
+        }
+
         fun resolveInfoOf(packageName: String) = ResolveInfo().apply {
             activityInfo = ActivityInfo().apply {
                 this.packageName = packageName
diff --git a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
index 2ded3c6..89d6ac3 100644
--- a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
+++ b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
@@ -38,6 +38,17 @@
             android:src="@drawable/add_a_photo_circled"
             android:layout_gravity="bottom|right"/>
     </FrameLayout>
+    <TextView
+        android:id="@+id/edit_user_info_message"
+        android:gravity="center"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="6dp"
+        android:layout_marginEnd="6dp"
+        android:layout_marginTop="24dp"
+        android:textAppearance="@style/android:TextAppearance.Material.Body1"
+        android:text="@string/edit_user_info_message"
+        />
 
     <EditText
         android:id="@+id/user_name"
@@ -46,6 +57,8 @@
         android:layout_gravity="center"
         android:minWidth="200dp"
         android:layout_marginStart="6dp"
+        android:layout_marginEnd="6dp"
+        android:layout_marginTop="24dp"
         android:minHeight="@dimen/min_tap_target_size"
         android:ellipsize="end"
         android:singleLine="true"
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 2e64212..1092a16 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1484,6 +1484,9 @@
     <!-- Title for the preference to enter the nickname of the user to display in the user switcher [CHAR LIMIT=25]-->
     <string name="user_nickname">Nickname</string>
 
+    <!-- Confirmation message on dialog for editing user name and profile picture. Inform user on who will be able to see the changes [CHAR LIMIT=NONE]-->
+    <string name="edit_user_info_message">Your name and picture will be visible to anyone that uses this device.</string>
+
     <!-- Label for adding a new user in the user switcher [CHAR LIMIT=35] -->
     <string name="user_add_user">Add user</string>
     <!-- Label for adding a new guest in the user switcher [CHAR LIMIT=35] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index fb14a17..58e0a89 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -41,6 +41,7 @@
 import android.os.UserManager;
 import android.print.PrintManager;
 import android.provider.Settings;
+import android.provider.Settings.Secure;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
@@ -66,18 +67,29 @@
 import com.android.settingslib.utils.BuildCompatUtils;
 
 import java.text.NumberFormat;
+import java.time.Duration;
 import java.util.List;
 
 public class Utils {
 
     private static final String TAG = "Utils";
 
-    @VisibleForTesting
-    static final String STORAGE_MANAGER_ENABLED_PROPERTY =
-            "ro.storage_manager.enabled";
-
     public static final String INCOMPATIBLE_CHARGER_WARNING_DISABLED =
             "incompatible_charger_warning_disabled";
+    public static final String WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP =
+            "wireless_charging_notification_timestamp";
+
+    @VisibleForTesting
+    static final String STORAGE_MANAGER_ENABLED_PROPERTY = "ro.storage_manager.enabled";
+
+    @VisibleForTesting static final long WIRELESS_CHARGING_DEFAULT_TIMESTAMP = -1L;
+
+    @VisibleForTesting
+    static final long WIRELESS_CHARGING_NOTIFICATION_THRESHOLD_MILLIS =
+            Duration.ofDays(30).toMillis();
+
+    @VisibleForTesting
+    static final String WIRELESS_CHARGING_WARNING_ENABLED = "wireless_charging_warning_enabled";
 
     private static Signature[] sSystemSignature;
     private static String sPermissionControllerPackageName;
@@ -101,19 +113,19 @@
         R.drawable.ic_show_x_wifi_signal_4
     };
 
-    public static void updateLocationEnabled(Context context, boolean enabled, int userId,
-            int source) {
+    /** Update the location enable state. */
+    public static void updateLocationEnabled(
+            @NonNull Context context, boolean enabled, int userId, int source) {
         Settings.Secure.putIntForUser(
-                context.getContentResolver(), Settings.Secure.LOCATION_CHANGER, source,
-                userId);
+                context.getContentResolver(), Settings.Secure.LOCATION_CHANGER, source, userId);
 
         LocationManager locationManager = context.getSystemService(LocationManager.class);
         locationManager.setLocationEnabledForUser(enabled, UserHandle.of(userId));
     }
 
     /**
-     * Return string resource that best describes combination of tethering
-     * options available on this device.
+     * Return string resource that best describes combination of tethering options available on this
+     * device.
      */
     public static int getTetheringLabel(TetheringManager tm) {
         String[] usbRegexs = tm.getTetherableUsbRegexs();
@@ -141,14 +153,12 @@
         }
     }
 
-    /**
-     * Returns a label for the user, in the form of "User: user name" or "Work profile".
-     */
+    /** Returns a label for the user, in the form of "User: user name" or "Work profile". */
     public static String getUserLabel(Context context, UserInfo info) {
         String name = info != null ? info.name : null;
         if (info.isManagedProfile()) {
             // We use predefined values for managed profiles
-            return  BuildCompatUtils.isAtLeastT()
+            return BuildCompatUtils.isAtLeastT()
                     ? getUpdatableManagedUserTitle(context)
                     : context.getString(R.string.managed_user_title);
         } else if (info.isGuest()) {
@@ -164,14 +174,14 @@
 
     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     private static String getUpdatableManagedUserTitle(Context context) {
-        return context.getSystemService(DevicePolicyManager.class).getResources().getString(
-                WORK_PROFILE_USER_LABEL,
-                () -> context.getString(R.string.managed_user_title));
+        return context.getSystemService(DevicePolicyManager.class)
+                .getResources()
+                .getString(
+                        WORK_PROFILE_USER_LABEL,
+                        () -> context.getString(R.string.managed_user_title));
     }
 
-    /**
-     * Returns a circular icon for a user.
-     */
+    /** Returns a circular icon for a user. */
     public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) {
         final int iconSize = UserIconDrawable.getDefaultSize(context);
         if (user.isManagedProfile()) {
@@ -185,12 +195,14 @@
                 return new UserIconDrawable(iconSize).setIcon(icon).bake();
             }
         }
-        return new UserIconDrawable(iconSize).setIconDrawable(
-                UserIcons.getDefaultUserIcon(context.getResources(), user.id, /* light= */ false))
+        return new UserIconDrawable(iconSize)
+                .setIconDrawable(
+                        UserIcons.getDefaultUserIcon(
+                                context.getResources(), user.id, /* light= */ false))
                 .bake();
     }
 
-    /** Formats a double from 0.0..100.0 with an option to round **/
+    /** Formats a double from 0.0..100.0 with an option to round */
     public static String formatPercentage(double percentage, boolean round) {
         final int localPercentage = round ? Math.round((float) percentage) : (int) percentage;
         return formatPercentage(localPercentage);
@@ -222,23 +234,27 @@
      *
      * @param context the context
      * @param batteryChangedIntent battery broadcast intent received from {@link
-     *                             Intent.ACTION_BATTERY_CHANGED}.
+     *     Intent.ACTION_BATTERY_CHANGED}.
      * @param compactStatus to present compact battery charging string if {@code true}
      * @return battery status string
      */
-    public static String getBatteryStatus(Context context, Intent batteryChangedIntent,
-            boolean compactStatus) {
-        final int status = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_STATUS,
-                BatteryManager.BATTERY_STATUS_UNKNOWN);
+    @NonNull
+    public static String getBatteryStatus(
+            @NonNull Context context, @NonNull Intent batteryChangedIntent, boolean compactStatus) {
+        final int status =
+                batteryChangedIntent.getIntExtra(
+                        BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN);
         final Resources res = context.getResources();
 
         String statusString = res.getString(R.string.battery_info_status_unknown);
         final BatteryStatus batteryStatus = new BatteryStatus(batteryChangedIntent);
 
         if (batteryStatus.isCharged()) {
-            statusString = res.getString(compactStatus
-                    ? R.string.battery_info_status_full_charged
-                    : R.string.battery_info_status_full);
+            statusString =
+                    res.getString(
+                            compactStatus
+                                    ? R.string.battery_info_status_full_charged
+                                    : R.string.battery_info_status_full);
         } else {
             if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
                 if (compactStatus) {
@@ -246,12 +262,12 @@
                 } else if (batteryStatus.isPluggedInWired()) {
                     switch (batteryStatus.getChargingSpeed(context)) {
                         case BatteryStatus.CHARGING_FAST:
-                            statusString = res.getString(
-                                    R.string.battery_info_status_charging_fast);
+                            statusString =
+                                    res.getString(R.string.battery_info_status_charging_fast);
                             break;
                         case BatteryStatus.CHARGING_SLOWLY:
-                            statusString = res.getString(
-                                    R.string.battery_info_status_charging_slow);
+                            statusString =
+                                    res.getString(R.string.battery_info_status_charging_slow);
                             break;
                         default:
                             statusString = res.getString(R.string.battery_info_status_charging);
@@ -311,7 +327,7 @@
 
     @ColorInt
     public static int applyAlphaAttr(Context context, int attr, int inputColor) {
-        TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+        TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
         float alpha = ta.getFloat(0, 0);
         ta.recycle();
         return applyAlpha(alpha, inputColor);
@@ -320,7 +336,10 @@
     @ColorInt
     public static int applyAlpha(float alpha, int inputColor) {
         alpha *= Color.alpha(inputColor);
-        return Color.argb((int) (alpha), Color.red(inputColor), Color.green(inputColor),
+        return Color.argb(
+                (int) (alpha),
+                Color.red(inputColor),
+                Color.green(inputColor),
                 Color.blue(inputColor));
     }
 
@@ -329,19 +348,17 @@
         return getColorAttrDefaultColor(context, attr, 0);
     }
 
-    /**
-     * Get color styled attribute {@code attr}, default to {@code defValue} if not found.
-     */
+    /** Get color styled attribute {@code attr}, default to {@code defValue} if not found. */
     @ColorInt
     public static int getColorAttrDefaultColor(Context context, int attr, @ColorInt int defValue) {
-        TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+        TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
         @ColorInt int colorAccent = ta.getColor(0, defValue);
         ta.recycle();
         return colorAccent;
     }
 
     public static ColorStateList getColorAttr(Context context, int attr) {
-        TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+        TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
         ColorStateList stateList = null;
         try {
             stateList = ta.getColorStateList(0);
@@ -356,35 +373,38 @@
     }
 
     public static int getThemeAttr(Context context, int attr, int defaultValue) {
-        TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+        TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
         int theme = ta.getResourceId(0, defaultValue);
         ta.recycle();
         return theme;
     }
 
     public static Drawable getDrawable(Context context, int attr) {
-        TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
+        TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
         Drawable drawable = ta.getDrawable(0);
         ta.recycle();
         return drawable;
     }
 
     /**
-    * Create a color matrix suitable for a ColorMatrixColorFilter that modifies only the color but
-    * preserves the alpha for a given drawable
-    * @param color
-    * @return a color matrix that uses the source alpha and given color
-    */
+     * Create a color matrix suitable for a ColorMatrixColorFilter that modifies only the color but
+     * preserves the alpha for a given drawable
+     *
+     * @return a color matrix that uses the source alpha and given color
+     */
     public static ColorMatrix getAlphaInvariantColorMatrixForColor(@ColorInt int color) {
         int r = Color.red(color);
         int g = Color.green(color);
         int b = Color.blue(color);
 
-        ColorMatrix cm = new ColorMatrix(new float[] {
-                0, 0, 0, 0, r,
-                0, 0, 0, 0, g,
-                0, 0, 0, 0, b,
-                0, 0, 0, 1, 0 });
+        ColorMatrix cm =
+                new ColorMatrix(
+                        new float[] {
+                            0, 0, 0, 0, r,
+                            0, 0, 0, 0, g,
+                            0, 0, 0, 0, b,
+                            0, 0, 0, 1, 0
+                        });
 
         return cm;
     }
@@ -393,7 +413,7 @@
      * Create a ColorMatrixColorFilter to tint a drawable but retain its alpha characteristics
      *
      * @return a ColorMatrixColorFilter which changes the color of the output but is invariant on
-     * the source alpha
+     *     the source alpha
      */
     public static ColorFilter getAlphaInvariantColorFilterForColor(@ColorInt int color) {
         return new ColorMatrixColorFilter(getAlphaInvariantColorMatrixForColor(color));
@@ -402,16 +422,17 @@
     /**
      * Determine whether a package is a "system package", in which case certain things (like
      * disabling notifications or disabling the package altogether) should be disallowed.
-     * <p>
-     * Note: This function is just for UI treatment, and should not be used for security purposes.
      *
-     * @deprecated Use {@link ApplicationInfo#isSignedWithPlatformKey()} and
-     * {@link #isEssentialPackage} instead.
+     * <p>Note: This function is just for UI treatment, and should not be used for security
+     * purposes.
+     *
+     * @deprecated Use {@link ApplicationInfo#isSignedWithPlatformKey()} and {@link
+     *     #isEssentialPackage} instead.
      */
     @Deprecated
     public static boolean isSystemPackage(Resources resources, PackageManager pm, PackageInfo pkg) {
         if (sSystemSignature == null) {
-            sSystemSignature = new Signature[]{getSystemSignature(pm)};
+            sSystemSignature = new Signature[] {getSystemSignature(pm)};
         }
         return (sSystemSignature[0] != null && sSystemSignature[0].equals(getFirstSignature(pkg)))
                 || isEssentialPackage(resources, pm, pkg.packageName);
@@ -435,8 +456,8 @@
 
     /**
      * Determine whether a package is a "essential package".
-     * <p>
-     * In which case certain things (like disabling the package) should be disallowed.
+     *
+     * <p>In which case certain things (like disabling the package) should be disallowed.
      */
     public static boolean isEssentialPackage(
             Resources resources, PackageManager pm, String packageName) {
@@ -462,14 +483,12 @@
      * returns {@code false}.
      */
     public static boolean isDeviceProvisioningPackage(Resources resources, String packageName) {
-        String deviceProvisioningPackage = resources.getString(
-                com.android.internal.R.string.config_deviceProvisioningPackage);
+        String deviceProvisioningPackage =
+                resources.getString(com.android.internal.R.string.config_deviceProvisioningPackage);
         return deviceProvisioningPackage != null && deviceProvisioningPackage.equals(packageName);
     }
 
-    /**
-     * Fetch the package name of the default WebView provider.
-     */
+    /** Fetch the package name of the default WebView provider. */
     @Nullable
     private static String getDefaultWebViewPackageName() {
         if (sDefaultWebViewPackageName != null) {
@@ -503,8 +522,8 @@
     /**
      * Returns the Wifi icon resource for a given RSSI level.
      *
-     * @param showX True if a connected Wi-Fi network has the problem which should show Pie+x
-     *              signal icon to users.
+     * @param showX True if a connected Wi-Fi network has the problem which should show Pie+x signal
+     *     icon to users.
      * @param level The number of bars to show (0-4)
      * @throws IllegalArgumentException if an invalid RSSI level is given.
      */
@@ -520,10 +539,7 @@
         try {
             defaultDays =
                     resources.getInteger(
-                            com.android
-                                    .internal
-                                    .R
-                                    .integer
+                            com.android.internal.R.integer
                                     .config_storageManagerDaystoRetainDefault);
         } catch (Resources.NotFoundException e) {
             // We are likely in a test environment.
@@ -535,7 +551,7 @@
         return !context.getSystemService(TelephonyManager.class).isDataCapable();
     }
 
-    /** Returns if the automatic storage management feature is turned on or not. **/
+    /** Returns if the automatic storage management feature is turned on or not. */
     public static boolean isStorageManagerEnabled(Context context) {
         boolean isDefaultOn;
         try {
@@ -543,15 +559,14 @@
         } catch (Resources.NotFoundException e) {
             isDefaultOn = false;
         }
-        return Settings.Secure.getInt(context.getContentResolver(),
-                Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED,
-                isDefaultOn ? 1 : 0)
+        return Settings.Secure.getInt(
+                        context.getContentResolver(),
+                        Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED,
+                        isDefaultOn ? 1 : 0)
                 != 0;
     }
 
-    /**
-     * get that {@link AudioManager#getMode()} is in ringing/call/communication(VoIP) status.
-     */
+    /** get that {@link AudioManager#getMode()} is in ringing/call/communication(VoIP) status. */
     public static boolean isAudioModeOngoingCall(Context context) {
         final AudioManager audioManager = context.getSystemService(AudioManager.class);
         final int audioMode = audioManager.getMode();
@@ -561,8 +576,8 @@
     }
 
     /**
-     * Return the service state is in-service or not.
-     * To make behavior consistent with SystemUI and Settings/AboutPhone/SIM status UI
+     * Return the service state is in-service or not. To make behavior consistent with SystemUI and
+     * Settings/AboutPhone/SIM status UI
      *
      * @param serviceState Service state. {@link ServiceState}
      */
@@ -581,13 +596,12 @@
     }
 
     /**
-     * Return the combined service state.
-     * To make behavior consistent with SystemUI and Settings/AboutPhone/SIM status UI.
+     * Return the combined service state. To make behavior consistent with SystemUI and
+     * Settings/AboutPhone/SIM status UI.
      *
-     * This method returns a single service state int if either the voice reg state is
-     * {@link ServiceState#STATE_IN_SERVICE} or if data network is registered via a
-     * WWAN transport type. We consider the combined service state of an IWLAN network
-     * to be OOS.
+     * <p>This method returns a single service state int if either the voice reg state is {@link
+     * ServiceState#STATE_IN_SERVICE} or if data network is registered via a WWAN transport type. We
+     * consider the combined service state of an IWLAN network to be OOS.
      *
      * @param serviceState Service state. {@link ServiceState}
      */
@@ -618,9 +632,10 @@
     // on either a WLAN or WWAN network. Since we want to exclude the WLAN network, we can
     // query the WWAN network directly and check for its registration state
     private static boolean isDataRegInWwanAndInService(ServiceState serviceState) {
-        final NetworkRegistrationInfo networkRegWwan = serviceState.getNetworkRegistrationInfo(
-                NetworkRegistrationInfo.DOMAIN_PS,
-                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        final NetworkRegistrationInfo networkRegWwan =
+                serviceState.getNetworkRegistrationInfo(
+                        NetworkRegistrationInfo.DOMAIN_PS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
 
         if (networkRegWwan == null) {
             return false;
@@ -633,8 +648,8 @@
     public static Drawable getBadgedIcon(Context context, Drawable icon, UserHandle user) {
         int userType = UserIconInfo.TYPE_MAIN;
         try {
-            UserInfo ui = context.getSystemService(UserManager.class).getUserInfo(
-                    user.getIdentifier());
+            UserInfo ui =
+                    context.getSystemService(UserManager.class).getUserInfo(user.getIdentifier());
             if (ui != null) {
                 if (ui.isCloneProfile()) {
                     userType = UserIconInfo.TYPE_CLONED;
@@ -650,15 +665,16 @@
         try (IconFactory iconFactory = IconFactory.obtain(context)) {
             return iconFactory
                     .createBadgedIconBitmap(
-                            icon,
-                            new IconOptions().setUser(new UserIconInfo(user, userType)))
+                            icon, new IconOptions().setUser(new UserIconInfo(user, userType)))
                     .newIcon(context);
         }
     }
 
     /** Get the {@link Drawable} that represents the app icon */
     public static Drawable getBadgedIcon(Context context, ApplicationInfo appInfo) {
-        return getBadgedIcon(context, appInfo.loadUnbadgedIcon(context.getPackageManager()),
+        return getBadgedIcon(
+                context,
+                appInfo.loadUnbadgedIcon(context.getPackageManager()),
                 UserHandle.getUserHandleForUid(appInfo.uid));
     }
 
@@ -669,10 +685,11 @@
      * @param source bitmap to apply round corner.
      * @param cornerRadius corner radius value.
      */
-    public static Bitmap convertCornerRadiusBitmap(@NonNull Context context,
-            @NonNull Bitmap source, @NonNull float cornerRadius) {
-        final Bitmap roundedBitmap = Bitmap.createBitmap(source.getWidth(), source.getHeight(),
-                Bitmap.Config.ARGB_8888);
+    @NonNull
+    public static Bitmap convertCornerRadiusBitmap(
+            @NonNull Context context, @NonNull Bitmap source, @NonNull float cornerRadius) {
+        final Bitmap roundedBitmap =
+                Bitmap.createBitmap(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888);
         final RoundedBitmapDrawable drawable =
                 RoundedBitmapDrawableFactory.create(context.getResources(), source);
         drawable.setAntiAlias(true);
@@ -687,9 +704,6 @@
      * Returns the WifiInfo for the underlying WiFi network of the VCN network, returns null if the
      * input NetworkCapabilities is not for a VCN network with underlying WiFi network.
      *
-     * TODO(b/238425913): Move this method to be inside systemui not settingslib once we've migrated
-     *   off of {@link WifiStatusTracker} and {@link NetworkControllerImpl}.
-     *
      * @param networkCapabilities NetworkCapabilities of the network.
      */
     @Nullable
@@ -708,8 +722,9 @@
         // Avoid the caller doesn't have permission to read the "Settings.Secure" data.
         try {
             // Whether the incompatible charger warning is disabled or not
-            if (Settings.Secure.getInt(context.getContentResolver(),
-                    INCOMPATIBLE_CHARGER_WARNING_DISABLED, 0) == 1) {
+            if (Settings.Secure.getInt(
+                            context.getContentResolver(), INCOMPATIBLE_CHARGER_WARNING_DISABLED, 0)
+                    == 1) {
                 Log.d(tag, "containsIncompatibleChargers: disabled");
                 return false;
             }
@@ -718,8 +733,7 @@
             return false;
         }
 
-        final List<UsbPort> usbPortList =
-                context.getSystemService(UsbManager.class).getPorts();
+        final List<UsbPort> usbPortList = context.getSystemService(UsbManager.class).getPorts();
         if (usbPortList == null || usbPortList.isEmpty()) {
             return false;
         }
@@ -760,4 +774,85 @@
         return false;
     }
 
+    /** Whether to show the wireless charging notification. */
+    public static boolean shouldShowWirelessChargingNotification(
+            @NonNull Context context, @NonNull String tag) {
+        try {
+            return shouldShowWirelessChargingNotificationInternal(context, tag);
+        } catch (Exception e) {
+            Log.e(tag, "shouldShowWirelessChargingNotification()", e);
+            return false;
+        }
+    }
+
+    /** Stores the timestamp of the wireless charging notification. */
+    public static void updateWirelessChargingNotificationTimestamp(
+            @NonNull Context context, long timestamp, @NonNull String tag) {
+        try {
+            Secure.putLong(
+                    context.getContentResolver(),
+                    WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP,
+                    timestamp);
+        } catch (Exception e) {
+            Log.e(tag, "setWirelessChargingNotificationTimestamp()", e);
+        }
+    }
+
+    /** Whether to show the wireless charging warning in Settings. */
+    public static boolean shouldShowWirelessChargingWarningTip(
+            @NonNull Context context, @NonNull String tag) {
+        try {
+            return Secure.getInt(context.getContentResolver(), WIRELESS_CHARGING_WARNING_ENABLED, 0)
+                    == 1;
+        } catch (Exception e) {
+            Log.e(tag, "shouldShowWirelessChargingWarningTip()", e);
+        }
+        return false;
+    }
+
+    /** Stores the state of whether the wireless charging warning in Settings is enabled. */
+    public static void updateWirelessChargingWarningEnabled(
+            @NonNull Context context, boolean enabled, @NonNull String tag) {
+        try {
+            Secure.putInt(
+                    context.getContentResolver(),
+                    WIRELESS_CHARGING_WARNING_ENABLED,
+                    enabled ? 1 : 0);
+        } catch (Exception e) {
+            Log.e(tag, "setWirelessChargingWarningEnabled()", e);
+        }
+    }
+
+    private static boolean shouldShowWirelessChargingNotificationInternal(
+            @NonNull Context context, @NonNull String tag) {
+        final long lastNotificationTimeMillis =
+                Secure.getLong(
+                        context.getContentResolver(),
+                        WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP,
+                        WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
+        if (isWirelessChargingNotificationDisabled(lastNotificationTimeMillis)) {
+            return false;
+        }
+        if (isInitialWirelessChargingNotification(lastNotificationTimeMillis)) {
+            updateWirelessChargingNotificationTimestamp(context, System.currentTimeMillis(), tag);
+            updateWirelessChargingWarningEnabled(context, /* enabled= */ true, tag);
+            return true;
+        }
+        final long durationMillis = System.currentTimeMillis() - lastNotificationTimeMillis;
+        final boolean show = durationMillis > WIRELESS_CHARGING_NOTIFICATION_THRESHOLD_MILLIS;
+        Log.d(tag, "shouldShowWirelessChargingNotification = " + show);
+        if (show) {
+            updateWirelessChargingNotificationTimestamp(context, System.currentTimeMillis(), tag);
+            updateWirelessChargingWarningEnabled(context, /* enabled= */ true, tag);
+        }
+        return show;
+    }
+
+    private static boolean isWirelessChargingNotificationDisabled(long lastNotificationTimeMillis) {
+        return lastNotificationTimeMillis == Long.MIN_VALUE;
+    }
+
+    private static boolean isInitialWirelessChargingNotification(long lastNotificationTimeMillis) {
+        return lastNotificationTimeMillis == WIRELESS_CHARGING_DEFAULT_TIMESTAMP;
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
index 07de7fd..c482995 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
@@ -22,6 +22,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -164,12 +165,16 @@
 
         try {
             final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */);
-            // Check if the package is contained in an APEX. There is no public API to properly
-            // check whether a given APK package comes from an APEX registered as module.
-            // Therefore we conservatively assume that any package scanned from an /apex path is
-            // a system package.
-            return pkg.applicationInfo.sourceDir.startsWith(
-                    Environment.getApexDirectory().getAbsolutePath());
+            if (Flags.provideInfoOfApkInApex()) {
+                return pkg.getApexPackageName() != null;
+            } else {
+                // Check if the package is contained in an APEX. There is no public API to properly
+                // check whether a given APK package comes from an APEX registered as module.
+                // Therefore we conservatively assume that any package scanned from an /apex path is
+                // a system package.
+                return pkg.applicationInfo.sourceDir.startsWith(
+                        Environment.getApexDirectory().getAbsolutePath());
+            }
         } catch (PackageManager.NameNotFoundException e) {
             return false;
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 8e1067f..e3012cd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -27,6 +27,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
 import android.content.pm.IPackageManager;
 import android.content.pm.IPackageStatsObserver;
 import android.content.pm.ModuleInfo;
@@ -226,6 +227,11 @@
         final List<ModuleInfo> moduleInfos = mPm.getInstalledModules(0 /* flags */);
         for (ModuleInfo info : moduleInfos) {
             mSystemModules.put(info.getPackageName(), info.isHidden());
+            if (Flags.provideInfoOfApkInApex()) {
+                for (String apkInApexPackageName : info.getApkInApexPackageNames()) {
+                    mSystemModules.put(apkInApexPackageName, info.isHidden());
+                }
+            }
         }
 
         /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/model/RoutingSession.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/model/RoutingSession.kt
new file mode 100644
index 0000000..a98f3e2
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/model/RoutingSession.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.volume.data.model
+
+import android.media.RoutingSessionInfo
+
+/** Models a routing session which is created when a media route is selected. */
+data class RoutingSession(
+    val routingSessionInfo: RoutingSessionInfo,
+    val isVolumeSeekBarEnabled: Boolean,
+    val isMediaOutputDisabled: Boolean,
+)
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 6761aa7..f729c04 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -16,15 +16,12 @@
 
 package com.android.settingslib.volume.data.repository
 
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
 import android.media.AudioDeviceInfo
 import android.media.AudioManager
 import android.media.AudioManager.OnCommunicationDeviceChangedListener
 import androidx.concurrent.futures.DirectExecutor
 import com.android.internal.util.ConcurrentUtils
+import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
 import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.settingslib.volume.shared.model.AudioStreamModel
 import com.android.settingslib.volume.shared.model.RingerMode
@@ -32,7 +29,6 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.callbackFlow
@@ -40,7 +36,6 @@
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
@@ -77,7 +72,7 @@
 }
 
 class AudioRepositoryImpl(
-    private val context: Context,
+    private val audioManagerIntentsReceiver: AudioManagerIntentsReceiver,
     private val audioManager: AudioManager,
     private val backgroundCoroutineContext: CoroutineContext,
     private val coroutineScope: CoroutineScope,
@@ -93,30 +88,9 @@
             .flowOn(backgroundCoroutineContext)
             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), audioManager.mode)
 
-    private val audioManagerIntents: SharedFlow<String> =
-        callbackFlow {
-                val receiver =
-                    object : BroadcastReceiver() {
-                        override fun onReceive(context: Context?, intent: Intent) {
-                            intent.action?.let { action -> launch { send(action) } }
-                        }
-                    }
-                context.registerReceiver(
-                    receiver,
-                    IntentFilter().apply {
-                        for (action in allActions) {
-                            addAction(action)
-                        }
-                    }
-                )
-
-                awaitClose { context.unregisterReceiver(receiver) }
-            }
-            .shareIn(coroutineScope, SharingStarted.WhileSubscribed())
-
     override val ringerMode: StateFlow<RingerMode> =
-        audioManagerIntents
-            .filter { ringerActions.contains(it) }
+        audioManagerIntentsReceiver.intents
+            .filter { AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION == it.action }
             .map { RingerMode(audioManager.ringerModeInternal) }
             .flowOn(backgroundCoroutineContext)
             .stateIn(
@@ -146,8 +120,7 @@
                 )
 
     override suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> {
-        return audioManagerIntents
-            .filter { modelActions.contains(it) }
+        return audioManagerIntentsReceiver.intents
             .map { getCurrentAudioStream(audioStream) }
             .flowOn(backgroundCoroutineContext)
     }
@@ -189,20 +162,4 @@
             // return STREAM_VOICE_CALL in getAudioStream
             audioManager.getStreamMinVolume(AudioManager.STREAM_VOICE_CALL)
         }
-
-    private companion object {
-        val modelActions =
-            setOf(
-                AudioManager.STREAM_MUTE_CHANGED_ACTION,
-                AudioManager.MASTER_MUTE_CHANGED_ACTION,
-                AudioManager.VOLUME_CHANGED_ACTION,
-                AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
-                AudioManager.STREAM_DEVICES_CHANGED_ACTION,
-            )
-        val ringerActions =
-            setOf(
-                AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
-            )
-        val allActions = ringerActions + modelActions
-    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
index 1597b77..aa9ae76 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
@@ -15,8 +15,13 @@
  */
 package com.android.settingslib.volume.data.repository
 
+import android.media.AudioManager
+import android.media.MediaRouter2Manager
+import android.media.RoutingSessionInfo
 import com.android.settingslib.media.LocalMediaManager
 import com.android.settingslib.media.MediaDevice
+import com.android.settingslib.volume.data.model.RoutingSession
+import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
@@ -24,10 +29,15 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
 
 /** Repository providing data about connected media devices. */
 interface LocalMediaRepository {
@@ -37,43 +47,55 @@
 
     /** Currently connected media device */
     val currentConnectedDevice: StateFlow<MediaDevice?>
+
+    val remoteRoutingSessions: StateFlow<Collection<RoutingSession>>
+
+    suspend fun adjustSessionVolume(sessionId: String?, volume: Int)
 }
 
 class LocalMediaRepositoryImpl(
+    audioManagerIntentsReceiver: AudioManagerIntentsReceiver,
     private val localMediaManager: LocalMediaManager,
+    private val mediaRouter2Manager: MediaRouter2Manager,
     coroutineScope: CoroutineScope,
-    backgroundContext: CoroutineContext,
+    private val backgroundContext: CoroutineContext,
 ) : LocalMediaRepository {
 
-    private val deviceUpdates: Flow<DevicesUpdate> = callbackFlow {
-        val callback =
-            object : LocalMediaManager.DeviceCallback {
-                override fun onDeviceListUpdate(newDevices: List<MediaDevice>?) {
-                    trySend(DevicesUpdate.DeviceListUpdate(newDevices ?: emptyList()))
-                }
+    private val devicesChanges =
+        audioManagerIntentsReceiver.intents.filter {
+            AudioManager.STREAM_DEVICES_CHANGED_ACTION == it.action
+        }
+    private val mediaDevicesUpdates: Flow<DevicesUpdate> =
+        callbackFlow {
+                val callback =
+                    object : LocalMediaManager.DeviceCallback {
+                        override fun onDeviceListUpdate(newDevices: List<MediaDevice>?) {
+                            trySend(DevicesUpdate.DeviceListUpdate(newDevices ?: emptyList()))
+                        }
 
-                override fun onSelectedDeviceStateChanged(
-                    device: MediaDevice?,
-                    state: Int,
-                ) {
-                    trySend(DevicesUpdate.SelectedDeviceStateChanged)
-                }
+                        override fun onSelectedDeviceStateChanged(
+                            device: MediaDevice?,
+                            state: Int,
+                        ) {
+                            trySend(DevicesUpdate.SelectedDeviceStateChanged)
+                        }
 
-                override fun onDeviceAttributesChanged() {
-                    trySend(DevicesUpdate.DeviceAttributesChanged)
+                        override fun onDeviceAttributesChanged() {
+                            trySend(DevicesUpdate.DeviceAttributesChanged)
+                        }
+                    }
+                localMediaManager.registerCallback(callback)
+                localMediaManager.startScan()
+
+                awaitClose {
+                    localMediaManager.stopScan()
+                    localMediaManager.unregisterCallback(callback)
                 }
             }
-        localMediaManager.registerCallback(callback)
-        localMediaManager.startScan()
-
-        awaitClose {
-            localMediaManager.stopScan()
-            localMediaManager.unregisterCallback(callback)
-        }
-    }
+            .shareIn(coroutineScope, SharingStarted.WhileSubscribed(), replay = 0)
 
     override val mediaDevices: StateFlow<Collection<MediaDevice>> =
-        deviceUpdates
+        mediaDevicesUpdates
             .mapNotNull {
                 if (it is DevicesUpdate.DeviceListUpdate) {
                     it.newDevices ?: emptyList()
@@ -85,7 +107,7 @@
             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyList())
 
     override val currentConnectedDevice: StateFlow<MediaDevice?> =
-        deviceUpdates
+        merge(devicesChanges, mediaDevicesUpdates)
             .map { localMediaManager.currentConnectedDevice }
             .stateIn(
                 coroutineScope,
@@ -93,6 +115,30 @@
                 localMediaManager.currentConnectedDevice
             )
 
+    override val remoteRoutingSessions: StateFlow<Collection<RoutingSession>> =
+        merge(devicesChanges, mediaDevicesUpdates)
+            .onStart { emit(Unit) }
+            .map { localMediaManager.remoteRoutingSessions.map(::toRoutingSession) }
+            .flowOn(backgroundContext)
+            .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyList())
+
+    override suspend fun adjustSessionVolume(sessionId: String?, volume: Int) {
+        withContext(backgroundContext) {
+            if (sessionId == null) {
+                localMediaManager.adjustSessionVolume(volume)
+            } else {
+                localMediaManager.adjustSessionVolume(sessionId, volume)
+            }
+        }
+    }
+
+    private fun toRoutingSession(info: RoutingSessionInfo): RoutingSession =
+        RoutingSession(
+            info,
+            isMediaOutputDisabled = mediaRouter2Manager.getTransferableRoutes(info).isEmpty(),
+            isVolumeSeekBarEnabled = localMediaManager.shouldEnableVolumeSeekBar(info)
+        )
+
     private sealed interface DevicesUpdate {
 
         data class DeviceListUpdate(val newDevices: List<MediaDevice>?) : DevicesUpdate
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt
index 93aa90d..ab8c6b8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt
@@ -16,30 +16,23 @@
 
 package com.android.settingslib.volume.data.repository
 
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
 import android.media.AudioManager
 import android.media.session.MediaController
 import android.media.session.MediaSessionManager
 import android.media.session.PlaybackState
 import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.settingslib.bluetooth.headsetAudioModeChanges
+import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
 
 /** Provides controllers for currently active device media sessions. */
 interface MediaControllerRepository {
@@ -49,40 +42,25 @@
 }
 
 class MediaControllerRepositoryImpl(
-    private val context: Context,
+    audioManagerIntentsReceiver: AudioManagerIntentsReceiver,
     private val mediaSessionManager: MediaSessionManager,
     localBluetoothManager: LocalBluetoothManager?,
     coroutineScope: CoroutineScope,
     backgroundContext: CoroutineContext,
 ) : MediaControllerRepository {
 
-    private val devicesChanges: Flow<Unit> =
-        callbackFlow {
-                val receiver =
-                    object : BroadcastReceiver() {
-                        override fun onReceive(context: Context?, intent: Intent?) {
-                            if (AudioManager.STREAM_DEVICES_CHANGED_ACTION == intent?.action) {
-                                launch { send(Unit) }
-                            }
-                        }
-                    }
-                context.registerReceiver(
-                    receiver,
-                    IntentFilter(AudioManager.STREAM_DEVICES_CHANGED_ACTION)
-                )
-
-                awaitClose { context.unregisterReceiver(receiver) }
-            }
-            .shareIn(coroutineScope, SharingStarted.WhileSubscribed(), replay = 0)
-
+    private val devicesChanges =
+        audioManagerIntentsReceiver.intents.filter {
+            AudioManager.STREAM_DEVICES_CHANGED_ACTION == it.action
+        }
     override val activeMediaController: StateFlow<MediaController?> =
-        combine(
-                localBluetoothManager?.headsetAudioModeChanges?.onStart { emit(Unit) }
-                    ?: emptyFlow(),
-                devicesChanges.onStart { emit(Unit) },
-            ) { _, _ ->
-                getActiveLocalMediaController()
+        buildList {
+                localBluetoothManager?.headsetAudioModeChanges?.let { add(it) }
+                add(devicesChanges)
             }
+            .merge()
+            .onStart { emit(Unit) }
+            .map { getActiveLocalMediaController() }
             .flowOn(backgroundContext)
             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/LocalMediaInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/LocalMediaInteractor.kt
new file mode 100644
index 0000000..f621335
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/LocalMediaInteractor.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.volume.domain.interactor
+
+import com.android.settingslib.media.MediaDevice
+import com.android.settingslib.volume.data.repository.LocalMediaRepository
+import com.android.settingslib.volume.domain.model.RoutingSession
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+class LocalMediaInteractor(
+    private val repository: LocalMediaRepository,
+    coroutineScope: CoroutineScope,
+) {
+
+    /** Available devices list */
+    val mediaDevices: StateFlow<Collection<MediaDevice>>
+        get() = repository.mediaDevices
+
+    /** Currently connected media device */
+    val currentConnectedDevice: StateFlow<MediaDevice?>
+        get() = repository.currentConnectedDevice
+
+    val remoteRoutingSessions: StateFlow<List<RoutingSession>> =
+        repository.remoteRoutingSessions
+            .map { sessions ->
+                sessions.map {
+                    RoutingSession(
+                        routingSessionInfo = it.routingSessionInfo,
+                        isMediaOutputDisabled = it.isMediaOutputDisabled,
+                        isVolumeSeekBarEnabled =
+                            it.isVolumeSeekBarEnabled && it.routingSessionInfo.volumeMax > 0
+                    )
+                }
+            }
+            .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyList())
+
+    suspend fun adjustSessionVolume(sessionId: String?, volume: Int) =
+        repository.adjustSessionVolume(sessionId, volume)
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/model/RoutingSession.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/model/RoutingSession.kt
new file mode 100644
index 0000000..dfc4703
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/model/RoutingSession.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.volume.domain.model
+
+import android.media.RoutingSessionInfo
+
+/** Models a routing session which is created when a media route is selected. */
+data class RoutingSession(
+    val routingSessionInfo: RoutingSessionInfo,
+    val isMediaOutputDisabled: Boolean,
+    val isVolumeSeekBarEnabled: Boolean,
+)
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerIntentsReceiver.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerIntentsReceiver.kt
new file mode 100644
index 0000000..9fa4c86
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerIntentsReceiver.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.settingslib.volume.shared
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.media.AudioManager
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.launch
+
+/** Exposes [AudioManager] intents as a observable shared flow. */
+interface AudioManagerIntentsReceiver {
+
+    val intents: SharedFlow<Intent>
+}
+
+class AudioManagerIntentsReceiverImpl(
+    private val context: Context,
+    coroutineScope: CoroutineScope,
+) : AudioManagerIntentsReceiver {
+
+    private val allActions: Collection<String>
+        get() =
+            setOf(
+                AudioManager.STREAM_MUTE_CHANGED_ACTION,
+                AudioManager.MASTER_MUTE_CHANGED_ACTION,
+                AudioManager.VOLUME_CHANGED_ACTION,
+                AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
+                AudioManager.STREAM_DEVICES_CHANGED_ACTION,
+            )
+
+    override val intents: SharedFlow<Intent> =
+        callbackFlow {
+                val receiver =
+                    object : BroadcastReceiver() {
+                        override fun onReceive(context: Context?, intent: Intent?) {
+                            launch { send(intent) }
+                        }
+                    }
+                context.registerReceiver(
+                    receiver,
+                    IntentFilter().apply {
+                        for (action in allActions) {
+                            addAction(action)
+                        }
+                    }
+                )
+
+                awaitClose { context.unregisterReceiver(receiver) }
+            }
+            .filterNotNull()
+            .filter { intent -> allActions.contains(intent.action) }
+            .shareIn(coroutineScope, SharingStarted.WhileSubscribed())
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
index 7b70c64..48b04db 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -16,13 +16,11 @@
 
 package com.android.settingslib.volume.data.repository
 
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
 import android.media.AudioDeviceInfo
 import android.media.AudioManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.settingslib.volume.shared.FakeAudioManagerIntentsReceiver
 import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.settingslib.volume.shared.model.AudioStreamModel
 import com.android.settingslib.volume.shared.model.RingerMode
@@ -30,6 +28,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -51,17 +50,16 @@
 @RunWith(AndroidJUnit4::class)
 class AudioRepositoryTest {
 
-    @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
     @Captor
     private lateinit var modeListenerCaptor: ArgumentCaptor<AudioManager.OnModeChangedListener>
     @Captor
     private lateinit var communicationDeviceListenerCaptor:
         ArgumentCaptor<AudioManager.OnCommunicationDeviceChangedListener>
 
-    @Mock private lateinit var context: Context
     @Mock private lateinit var audioManager: AudioManager
     @Mock private lateinit var communicationDevice: AudioDeviceInfo
 
+    private val intentsReceiver = FakeAudioManagerIntentsReceiver()
     private val volumeByStream: MutableMap<Int, Int> = mutableMapOf()
     private val isAffectedByRingerModeByStream: MutableMap<Int, Boolean> = mutableMapOf()
     private val isMuteByStream: MutableMap<Int, Boolean> = mutableMapOf()
@@ -98,7 +96,7 @@
 
         underTest =
             AudioRepositoryImpl(
-                context,
+                intentsReceiver,
                 audioManager,
                 testScope.testScheduler,
                 testScope.backgroundScope,
@@ -270,8 +268,7 @@
     }
 
     private fun triggerIntent(action: String) {
-        verify(context).registerReceiver(receiverCaptor.capture(), any())
-        receiverCaptor.value.onReceive(context, Intent(action))
+        testScope.launch { intentsReceiver.triggerIntent(action) }
     }
 
     private companion object {
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeLocalMediaRepository.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeLocalMediaRepository.kt
new file mode 100644
index 0000000..642b72c
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeLocalMediaRepository.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.settingslib.volume.data.repository
+
+import com.android.settingslib.media.MediaDevice
+import com.android.settingslib.volume.data.model.RoutingSession
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeLocalMediaRepository : LocalMediaRepository {
+
+    private val volumeBySession: MutableMap<String?, Int> = mutableMapOf()
+
+    private val mutableMediaDevices = MutableStateFlow<Collection<MediaDevice>>(emptyList())
+    override val mediaDevices: StateFlow<Collection<MediaDevice>>
+        get() = mutableMediaDevices.asStateFlow()
+
+    private val mutableCurrentConnectedDevice = MutableStateFlow<MediaDevice?>(null)
+    override val currentConnectedDevice: StateFlow<MediaDevice?>
+        get() = mutableCurrentConnectedDevice.asStateFlow()
+
+    private val mutableRemoteRoutingSessions =
+        MutableStateFlow<Collection<RoutingSession>>(emptyList())
+    override val remoteRoutingSessions: StateFlow<Collection<RoutingSession>>
+        get() = mutableRemoteRoutingSessions.asStateFlow()
+
+    fun updateMediaDevices(devices: Collection<MediaDevice>) {
+        mutableMediaDevices.value = devices
+    }
+
+    fun updateCurrentConnectedDevice(device: MediaDevice?) {
+        mutableCurrentConnectedDevice.value = device
+    }
+
+    fun updateRemoteRoutingSessions(sessions: List<RoutingSession>) {
+        mutableRemoteRoutingSessions.value = sessions
+    }
+
+    fun getSessionVolume(sessionId: String?): Int = volumeBySession.getOrDefault(sessionId, 0)
+
+    override suspend fun adjustSessionVolume(sessionId: String?, volume: Int) {
+        volumeBySession[sessionId] = volume
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt
index d106bce..dc9ea10 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt
@@ -15,10 +15,15 @@
  */
 package com.android.settingslib.volume.data.repository
 
+import android.media.MediaRoute2Info
+import android.media.MediaRouter2Manager
+import android.media.RoutingSessionInfo
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.media.LocalMediaManager
 import com.android.settingslib.media.MediaDevice
+import com.android.settingslib.volume.data.model.RoutingSession
+import com.android.settingslib.volume.shared.FakeAudioManagerIntentsReceiver
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
@@ -32,6 +37,10 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
@@ -44,10 +53,12 @@
     @Mock private lateinit var localMediaManager: LocalMediaManager
     @Mock private lateinit var mediaDevice1: MediaDevice
     @Mock private lateinit var mediaDevice2: MediaDevice
+    @Mock private lateinit var mediaRouter2Manager: MediaRouter2Manager
 
     @Captor
     private lateinit var deviceCallbackCaptor: ArgumentCaptor<LocalMediaManager.DeviceCallback>
 
+    private val intentsReceiver = FakeAudioManagerIntentsReceiver()
     private val testScope = TestScope()
 
     private lateinit var underTest: LocalMediaRepository
@@ -58,7 +69,9 @@
 
         underTest =
             LocalMediaRepositoryImpl(
+                intentsReceiver,
                 localMediaManager,
+                mediaRouter2Manager,
                 testScope.backgroundScope,
                 testScope.testScheduler,
             )
@@ -97,4 +110,78 @@
             assertThat(currentConnectedDevice).isEqualTo(mediaDevice1)
         }
     }
+
+    @Test
+    fun kek() {
+        testScope.runTest {
+            `when`(localMediaManager.remoteRoutingSessions)
+                .thenReturn(
+                    listOf(
+                        testRoutingSessionInfo1,
+                        testRoutingSessionInfo2,
+                        testRoutingSessionInfo3,
+                    )
+                )
+            `when`(localMediaManager.shouldEnableVolumeSeekBar(any())).then {
+                (it.arguments[0] as RoutingSessionInfo) == testRoutingSessionInfo1
+            }
+            `when`(mediaRouter2Manager.getTransferableRoutes(any<RoutingSessionInfo>())).then {
+                if ((it.arguments[0] as RoutingSessionInfo) == testRoutingSessionInfo2) {
+                    return@then listOf(mock(MediaRoute2Info::class.java))
+                }
+                emptyList<MediaRoute2Info>()
+            }
+            var remoteRoutingSessions: Collection<RoutingSession>? = null
+            underTest.remoteRoutingSessions
+                .onEach { remoteRoutingSessions = it }
+                .launchIn(backgroundScope)
+
+            runCurrent()
+
+            assertThat(remoteRoutingSessions)
+                .containsExactlyElementsIn(
+                    listOf(
+                        RoutingSession(
+                            routingSessionInfo = testRoutingSessionInfo1,
+                            isVolumeSeekBarEnabled = true,
+                            isMediaOutputDisabled = true,
+                        ),
+                        RoutingSession(
+                            routingSessionInfo = testRoutingSessionInfo2,
+                            isVolumeSeekBarEnabled = false,
+                            isMediaOutputDisabled = false,
+                        ),
+                        RoutingSession(
+                            routingSessionInfo = testRoutingSessionInfo3,
+                            isVolumeSeekBarEnabled = false,
+                            isMediaOutputDisabled = true,
+                        )
+                    )
+                )
+        }
+    }
+
+    @Test
+    fun adjustSessionVolume_adjusts() {
+        testScope.runTest {
+            var volume = 0
+            `when`(localMediaManager.adjustSessionVolume(anyString(), anyInt())).then {
+                volume = it.arguments[1] as Int
+                Unit
+            }
+
+            underTest.adjustSessionVolume("test_session", 10)
+
+            assertThat(volume).isEqualTo(10)
+        }
+    }
+
+    private companion object {
+        val testRoutingSessionInfo1 =
+            RoutingSessionInfo.Builder("id_1", "test.pkg.1").addSelectedRoute("route_1").build()
+        val testRoutingSessionInfo2 =
+            RoutingSessionInfo.Builder("id_2", "test.pkg.2").addSelectedRoute("route_2").build()
+        val testRoutingSessionInfo3 =
+            RoutingSessionInfo.Builder("id_3", "test.pkg.3").addSelectedRoute("route_3").build()
+    }
 }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt
index f07b1bff..430d733 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt
@@ -16,9 +16,6 @@
 
 package com.android.settingslib.volume.data.repository
 
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
 import android.media.AudioManager
 import android.media.session.MediaController
 import android.media.session.MediaController.PlaybackInfo
@@ -29,6 +26,7 @@
 import com.android.settingslib.bluetooth.BluetoothCallback
 import com.android.settingslib.bluetooth.BluetoothEventManager
 import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.volume.shared.FakeAudioManagerIntentsReceiver
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
@@ -52,10 +50,8 @@
 @SmallTest
 class MediaControllerRepositoryImplTest {
 
-    @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
     @Captor private lateinit var callbackCaptor: ArgumentCaptor<BluetoothCallback>
 
-    @Mock private lateinit var context: Context
     @Mock private lateinit var mediaSessionManager: MediaSessionManager
     @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
     @Mock private lateinit var eventManager: BluetoothEventManager
@@ -70,6 +66,7 @@
     @Mock private lateinit var localPlaybackInfo: PlaybackInfo
 
     private val testScope = TestScope()
+    private val intentsReceiver = FakeAudioManagerIntentsReceiver()
 
     private lateinit var underTest: MediaControllerRepository
 
@@ -97,7 +94,7 @@
 
         underTest =
             MediaControllerRepositoryImpl(
-                context,
+                intentsReceiver,
                 mediaSessionManager,
                 localBluetoothManager,
                 testScope.backgroundScope,
@@ -124,7 +121,7 @@
                 .launchIn(backgroundScope)
             runCurrent()
 
-            triggerDevicesChange()
+            intentsReceiver.triggerIntent(AudioManager.STREAM_DEVICES_CHANGED_ACTION)
             triggerOnAudioModeChanged()
             runCurrent()
 
@@ -149,7 +146,7 @@
                 .launchIn(backgroundScope)
             runCurrent()
 
-            triggerDevicesChange()
+            intentsReceiver.triggerIntent(AudioManager.STREAM_DEVICES_CHANGED_ACTION)
             triggerOnAudioModeChanged()
             runCurrent()
 
@@ -157,22 +154,19 @@
         }
     }
 
-    private fun triggerDevicesChange() {
-        verify(context).registerReceiver(receiverCaptor.capture(), any())
-        receiverCaptor.value.onReceive(context, Intent(AudioManager.STREAM_DEVICES_CHANGED_ACTION))
-    }
-
     private fun triggerOnAudioModeChanged() {
         verify(eventManager).registerCallback(callbackCaptor.capture())
         callbackCaptor.value.onAudioModeChanged()
     }
 
     private companion object {
-        val statePlaying =
+        val statePlaying: PlaybackState =
             PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0, 0f).build()
-        val stateError = PlaybackState.Builder().setState(PlaybackState.STATE_ERROR, 0, 0f).build()
-        val stateStopped =
+        val stateError: PlaybackState =
+            PlaybackState.Builder().setState(PlaybackState.STATE_ERROR, 0, 0f).build()
+        val stateStopped: PlaybackState =
             PlaybackState.Builder().setState(PlaybackState.STATE_STOPPED, 0, 0f).build()
-        val stateNone = PlaybackState.Builder().setState(PlaybackState.STATE_NONE, 0, 0f).build()
+        val stateNone: PlaybackState =
+            PlaybackState.Builder().setState(PlaybackState.STATE_NONE, 0, 0f).build()
     }
 }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/FakeAudioManagerIntentsReceiver.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/FakeAudioManagerIntentsReceiver.kt
new file mode 100644
index 0000000..530690a
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/FakeAudioManagerIntentsReceiver.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.settingslib.volume.shared
+
+import android.content.Intent
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+
+class FakeAudioManagerIntentsReceiver : AudioManagerIntentsReceiver {
+
+    private val mutableIntents = MutableSharedFlow<Intent>()
+    override val intents: SharedFlow<Intent> = mutableIntents.asSharedFlow()
+
+    suspend fun triggerIntent(intent: Intent) {
+        mutableIntents.emit(intent)
+    }
+
+    suspend fun triggerIntent(action: String) {
+        triggerIntent(Intent(action))
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index a88a9c7..2d07e5d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -16,6 +16,9 @@
 package com.android.settingslib;
 
 import static com.android.settingslib.Utils.STORAGE_MANAGER_ENABLED_PROPERTY;
+import static com.android.settingslib.Utils.WIRELESS_CHARGING_DEFAULT_TIMESTAMP;
+import static com.android.settingslib.Utils.shouldShowWirelessChargingWarningTip;
+import static com.android.settingslib.Utils.updateWirelessChargingNotificationTimestamp;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -28,10 +31,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
-import android.hardware.usb.flags.Flags;
 import android.hardware.usb.UsbManager;
 import android.hardware.usb.UsbPort;
 import android.hardware.usb.UsbPortStatus;
+import android.hardware.usb.flags.Flags;
 import android.location.LocationManager;
 import android.media.AudioManager;
 import android.os.BatteryManager;
@@ -59,6 +62,7 @@
 import org.robolectric.annotation.Implements;
 import org.robolectric.shadows.ShadowSettings;
 
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -72,21 +76,16 @@
     private static final String PERCENTAGE_49 = "49%";
     private static final String PERCENTAGE_50 = "50%";
     private static final String PERCENTAGE_100 = "100%";
+    private static final long CURRENT_TIMESTAMP = System.currentTimeMillis();
 
     private AudioManager mAudioManager;
     private Context mContext;
-    @Mock
-    private LocationManager mLocationManager;
-    @Mock
-    private ServiceState mServiceState;
-    @Mock
-    private NetworkRegistrationInfo mNetworkRegistrationInfo;
-    @Mock
-    private UsbPort mUsbPort;
-    @Mock
-    private UsbManager mUsbManager;
-    @Mock
-    private UsbPortStatus mUsbPortStatus;
+    @Mock private LocationManager mLocationManager;
+    @Mock private ServiceState mServiceState;
+    @Mock private NetworkRegistrationInfo mNetworkRegistrationInfo;
+    @Mock private UsbPort mUsbPort;
+    @Mock private UsbManager mUsbManager;
+    @Mock private UsbPortStatus mUsbPortStatus;
 
     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
@@ -102,27 +101,37 @@
 
     @After
     public void reset() {
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Utils.INCOMPATIBLE_CHARGER_WARNING_DISABLED, 0);
+        Settings.Secure.putInt(
+                mContext.getContentResolver(), Utils.INCOMPATIBLE_CHARGER_WARNING_DISABLED, 0);
     }
 
     @Test
     public void testUpdateLocationEnabled() {
         int currentUserId = ActivityManager.getCurrentUser();
-        Utils.updateLocationEnabled(mContext, true, currentUserId,
-                Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
+        Utils.updateLocationEnabled(
+                mContext, true, currentUserId, Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
 
-        assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
-                Settings.Secure.LOCATION_CHANGER,
-                Settings.Secure.LOCATION_CHANGER_UNKNOWN)).isEqualTo(
-                Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
+        assertThat(
+                        Settings.Secure.getInt(
+                                mContext.getContentResolver(),
+                                Settings.Secure.LOCATION_CHANGER,
+                                Settings.Secure.LOCATION_CHANGER_UNKNOWN))
+                .isEqualTo(Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
     }
 
     @Test
     public void testFormatPercentage_RoundTrue_RoundUpIfPossible() {
-        final String[] expectedPercentages =
-                {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_1, PERCENTAGE_1, PERCENTAGE_49,
-                        PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_50, PERCENTAGE_100};
+        final String[] expectedPercentages = {
+            PERCENTAGE_0,
+            PERCENTAGE_0,
+            PERCENTAGE_1,
+            PERCENTAGE_1,
+            PERCENTAGE_49,
+            PERCENTAGE_49,
+            PERCENTAGE_50,
+            PERCENTAGE_50,
+            PERCENTAGE_100
+        };
 
         for (int i = 0, size = TEST_PERCENTAGES.length; i < size; i++) {
             final String percentage = Utils.formatPercentage(TEST_PERCENTAGES[i], true);
@@ -132,9 +141,17 @@
 
     @Test
     public void testFormatPercentage_RoundFalse_NoRound() {
-        final String[] expectedPercentages =
-                {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_49,
-                        PERCENTAGE_49, PERCENTAGE_49, PERCENTAGE_50, PERCENTAGE_100};
+        final String[] expectedPercentages = {
+            PERCENTAGE_0,
+            PERCENTAGE_0,
+            PERCENTAGE_0,
+            PERCENTAGE_0,
+            PERCENTAGE_49,
+            PERCENTAGE_49,
+            PERCENTAGE_49,
+            PERCENTAGE_50,
+            PERCENTAGE_100
+        };
 
         for (int i = 0, size = TEST_PERCENTAGES.length; i < size; i++) {
             final String percentage = Utils.formatPercentage(TEST_PERCENTAGES[i], false);
@@ -146,7 +163,9 @@
     public void testGetDefaultStorageManagerDaysToRetain_storageManagerDaysToRetainUsesResources() {
         Resources resources = mock(Resources.class);
         when(resources.getInteger(
-                eq(com.android.internal.R.integer.config_storageManagerDaystoRetainDefault)))
+                        eq(
+                                com.android.internal.R.integer
+                                        .config_storageManagerDaystoRetainDefault)))
                 .thenReturn(60);
         assertThat(Utils.getDefaultStorageManagerDaysToRetain(resources)).isEqualTo(60);
     }
@@ -214,8 +233,10 @@
     public void isInService_voiceOutOfServiceDataInService_returnTrue() {
         when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
         when(mServiceState.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
-        when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
-                AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(mNetworkRegistrationInfo);
+        when(mServiceState.getNetworkRegistrationInfo(
+                        NetworkRegistrationInfo.DOMAIN_PS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN))
+                .thenReturn(mNetworkRegistrationInfo);
         when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
 
         assertThat(Utils.isInService(mServiceState)).isTrue();
@@ -224,8 +245,10 @@
     @Test
     public void isInService_voiceOutOfServiceDataInServiceOnIwLan_returnFalse() {
         when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-        when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
-                AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
+        when(mServiceState.getNetworkRegistrationInfo(
+                        NetworkRegistrationInfo.DOMAIN_PS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WLAN))
+                .thenReturn(mNetworkRegistrationInfo);
         when(mServiceState.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
         when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
 
@@ -235,8 +258,10 @@
     @Test
     public void isInService_voiceOutOfServiceDataNull_returnFalse() {
         when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-        when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
-                AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(null);
+        when(mServiceState.getNetworkRegistrationInfo(
+                        NetworkRegistrationInfo.DOMAIN_PS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN))
+                .thenReturn(null);
 
         assertThat(Utils.isInService(mServiceState)).isFalse();
     }
@@ -244,8 +269,10 @@
     @Test
     public void isInService_voiceOutOfServiceDataOutOfService_returnFalse() {
         when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-        when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
-                AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(mNetworkRegistrationInfo);
+        when(mServiceState.getNetworkRegistrationInfo(
+                        NetworkRegistrationInfo.DOMAIN_PS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN))
+                .thenReturn(mNetworkRegistrationInfo);
         when(mNetworkRegistrationInfo.isInService()).thenReturn(false);
 
         assertThat(Utils.isInService(mServiceState)).isFalse();
@@ -260,96 +287,106 @@
 
     @Test
     public void getCombinedServiceState_servicestateNull_returnOutOfService() {
-        assertThat(Utils.getCombinedServiceState(null)).isEqualTo(
-                ServiceState.STATE_OUT_OF_SERVICE);
+        assertThat(Utils.getCombinedServiceState(null))
+                .isEqualTo(ServiceState.STATE_OUT_OF_SERVICE);
     }
 
     @Test
     public void getCombinedServiceState_ServiceStatePowerOff_returnPowerOff() {
         when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_POWER_OFF);
 
-        assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
-                ServiceState.STATE_POWER_OFF);
+        assertThat(Utils.getCombinedServiceState(mServiceState))
+                .isEqualTo(ServiceState.STATE_POWER_OFF);
     }
 
     @Test
     public void getCombinedServiceState_voiceInService_returnInService() {
         when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
 
-        assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
-                ServiceState.STATE_IN_SERVICE);
+        assertThat(Utils.getCombinedServiceState(mServiceState))
+                .isEqualTo(ServiceState.STATE_IN_SERVICE);
     }
 
     @Test
     public void getCombinedServiceState_voiceOutOfServiceDataInService_returnInService() {
         when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-        when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
-                AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(mNetworkRegistrationInfo);
+        when(mServiceState.getNetworkRegistrationInfo(
+                        NetworkRegistrationInfo.DOMAIN_PS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN))
+                .thenReturn(mNetworkRegistrationInfo);
         when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
 
-        assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
-                ServiceState.STATE_IN_SERVICE);
+        assertThat(Utils.getCombinedServiceState(mServiceState))
+                .isEqualTo(ServiceState.STATE_IN_SERVICE);
     }
 
     @Test
     public void getCombinedServiceState_voiceOutOfServiceDataInServiceOnIwLan_returnOutOfService() {
         when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-        when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
-                AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
+        when(mServiceState.getNetworkRegistrationInfo(
+                        NetworkRegistrationInfo.DOMAIN_PS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WLAN))
+                .thenReturn(mNetworkRegistrationInfo);
         when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
 
-        assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
-                ServiceState.STATE_OUT_OF_SERVICE);
+        assertThat(Utils.getCombinedServiceState(mServiceState))
+                .isEqualTo(ServiceState.STATE_OUT_OF_SERVICE);
     }
 
     @Test
     public void getCombinedServiceState_voiceOutOfServiceDataOutOfService_returnOutOfService() {
         when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-        when(mServiceState.getDataRegistrationState()).thenReturn(
-                ServiceState.STATE_OUT_OF_SERVICE);
+        when(mServiceState.getDataRegistrationState())
+                .thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
 
-        assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
-                ServiceState.STATE_OUT_OF_SERVICE);
+        assertThat(Utils.getCombinedServiceState(mServiceState))
+                .isEqualTo(ServiceState.STATE_OUT_OF_SERVICE);
     }
 
     @Test
     public void getBatteryStatus_statusIsFull_returnFullString() {
-        final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100).putExtra(
-                BatteryManager.EXTRA_SCALE, 100);
+        final Intent intent =
+                new Intent()
+                        .putExtra(BatteryManager.EXTRA_LEVEL, 100)
+                        .putExtra(BatteryManager.EXTRA_SCALE, 100);
         final Resources resources = mContext.getResources();
 
-        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
-                resources.getString(R.string.battery_info_status_full));
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
+                .isEqualTo(resources.getString(R.string.battery_info_status_full));
     }
 
     @Test
     public void getBatteryStatus_statusIsFullAndUseCompactStatus_returnFullyChargedString() {
-        final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100).putExtra(
-                BatteryManager.EXTRA_SCALE, 100);
+        final Intent intent =
+                new Intent()
+                        .putExtra(BatteryManager.EXTRA_LEVEL, 100)
+                        .putExtra(BatteryManager.EXTRA_SCALE, 100);
         final Resources resources = mContext.getResources();
 
-        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
-                resources.getString(R.string.battery_info_status_full_charged));
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true))
+                .isEqualTo(resources.getString(R.string.battery_info_status_full_charged));
     }
 
     @Test
     public void getBatteryStatus_batteryLevelIs100_returnFullString() {
-        final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_STATUS,
-                BatteryManager.BATTERY_STATUS_FULL);
+        final Intent intent =
+                new Intent()
+                        .putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_FULL);
         final Resources resources = mContext.getResources();
 
-        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
-                resources.getString(R.string.battery_info_status_full));
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
+                .isEqualTo(resources.getString(R.string.battery_info_status_full));
     }
 
     @Test
     public void getBatteryStatus_batteryLevelIs100AndUseCompactStatus_returnFullyString() {
-        final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_STATUS,
-                BatteryManager.BATTERY_STATUS_FULL);
+        final Intent intent =
+                new Intent()
+                        .putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_FULL);
         final Resources resources = mContext.getResources();
 
-        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
-                resources.getString(R.string.battery_info_status_full_charged));
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true))
+                .isEqualTo(resources.getString(R.string.battery_info_status_full_charged));
     }
 
     @Test
@@ -359,8 +396,8 @@
         intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_USB);
         final Resources resources = mContext.getResources();
 
-        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
-                resources.getString(R.string.battery_info_status_charging));
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
+                .isEqualTo(resources.getString(R.string.battery_info_status_charging));
     }
 
     @Test
@@ -370,8 +407,8 @@
         intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_DOCK);
         final Resources resources = mContext.getResources();
 
-        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
-                resources.getString(R.string.battery_info_status_charging_dock));
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
+                .isEqualTo(resources.getString(R.string.battery_info_status_charging_dock));
     }
 
     @Test
@@ -381,8 +418,8 @@
         intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_WIRELESS);
         final Resources resources = mContext.getResources();
 
-        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
-                resources.getString(R.string.battery_info_status_charging_wireless));
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false))
+                .isEqualTo(resources.getString(R.string.battery_info_status_charging_wireless));
     }
 
     @Test
@@ -392,8 +429,8 @@
         intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_USB);
         final Resources resources = mContext.getResources();
 
-        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
-                resources.getString(R.string.battery_info_status_charging));
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true))
+                .isEqualTo(resources.getString(R.string.battery_info_status_charging));
     }
 
     @Test
@@ -403,8 +440,8 @@
         intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_WIRELESS);
         final Resources resources = mContext.getResources();
 
-        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo(
-                resources.getString(R.string.battery_info_status_charging));
+        assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true))
+                .isEqualTo(resources.getString(R.string.battery_info_status_charging));
     }
 
     @Test
@@ -503,12 +540,97 @@
     @Test
     public void containsIncompatibleChargers_disableWarning_returnFalse() {
         setupIncompatibleCharging();
-        Settings.Secure.putInt(mContext.getContentResolver(),
-                Utils.INCOMPATIBLE_CHARGER_WARNING_DISABLED, 1);
+        Settings.Secure.putInt(
+                mContext.getContentResolver(), Utils.INCOMPATIBLE_CHARGER_WARNING_DISABLED, 1);
 
         assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isFalse();
     }
 
+    @Test
+    public void shouldShowWirelessChargingNotification_neverSendNotification_returnTrue() {
+        updateWirelessChargingNotificationTimestamp(
+                mContext, WIRELESS_CHARGING_DEFAULT_TIMESTAMP, TAG);
+
+        assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isTrue();
+    }
+
+    @Test
+    public void shouldShowNotification_neverSendNotification_updateTimestampAndEnabledState() {
+        updateWirelessChargingNotificationTimestamp(
+                mContext, WIRELESS_CHARGING_DEFAULT_TIMESTAMP, TAG);
+
+        Utils.shouldShowWirelessChargingNotification(mContext, TAG);
+
+        assertThat(getWirelessChargingNotificationTimestamp())
+                .isNotEqualTo(WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
+        assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isTrue();
+    }
+
+    @Test
+    public void shouldShowWirelessChargingNotification_notificationDisabled_returnFalse() {
+        updateWirelessChargingNotificationTimestamp(mContext, CURRENT_TIMESTAMP, TAG);
+
+        assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isFalse();
+    }
+
+    @Test
+    public void shouldShowWirelessChargingNotification_withinTimeThreshold_returnFalse() {
+        updateWirelessChargingNotificationTimestamp(mContext, CURRENT_TIMESTAMP, TAG);
+
+        assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isFalse();
+    }
+
+    @Test
+    public void shouldShowWirelessChargingNotification_exceedTimeThreshold_returnTrue() {
+        final long monthAgo = Duration.ofDays(31).toMillis();
+        final long timestamp = CURRENT_TIMESTAMP - monthAgo;
+        updateWirelessChargingNotificationTimestamp(mContext, timestamp, TAG);
+
+        assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isTrue();
+    }
+
+    @Test
+    public void shouldShowNotification_exceedTimeThreshold_updateTimestampAndEnabledState() {
+        final long monthAgo = Duration.ofDays(31).toMillis();
+        final long timestamp = CURRENT_TIMESTAMP - monthAgo;
+        updateWirelessChargingNotificationTimestamp(mContext, timestamp, TAG);
+
+        Utils.shouldShowWirelessChargingNotification(mContext, TAG);
+
+        assertThat(getWirelessChargingNotificationTimestamp()).isNotEqualTo(timestamp);
+        assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isTrue();
+    }
+
+    @Test
+    public void updateWirelessChargingNotificationTimestamp_dismissForever_setMinValue() {
+        updateWirelessChargingNotificationTimestamp(mContext, Long.MIN_VALUE, TAG);
+
+        assertThat(getWirelessChargingNotificationTimestamp()).isEqualTo(Long.MIN_VALUE);
+    }
+
+    @Test
+    public void updateWirelessChargingNotificationTimestamp_notDismissForever_setTimestamp() {
+        updateWirelessChargingNotificationTimestamp(mContext, CURRENT_TIMESTAMP, TAG);
+
+        assertThat(getWirelessChargingNotificationTimestamp())
+                .isNotEqualTo(WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
+        assertThat(getWirelessChargingNotificationTimestamp()).isNotEqualTo(Long.MIN_VALUE);
+    }
+
+    @Test
+    public void shouldShowWirelessChargingWarningTip_enabled_returnTrue() {
+        Utils.updateWirelessChargingWarningEnabled(mContext, true, TAG);
+
+        assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isTrue();
+    }
+
+    @Test
+    public void shouldShowWirelessChargingWarningTip_disabled_returnFalse() {
+        Utils.updateWirelessChargingWarningEnabled(mContext, false, TAG);
+
+        assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isFalse();
+    }
+
     private void setupIncompatibleCharging() {
         setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY);
     }
@@ -520,6 +642,13 @@
         when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
         when(mUsbPort.supportsComplianceWarnings()).thenReturn(true);
         when(mUsbPortStatus.isConnected()).thenReturn(true);
-        when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{complianceWarningType});
+        when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[] {complianceWarningType});
+    }
+
+    private long getWirelessChargingNotificationTimestamp() {
+        return Settings.Secure.getLong(
+                mContext.getContentResolver(),
+                Utils.WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP,
+                WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
index 994c1ee..e32d880 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
@@ -16,6 +16,8 @@
 
 package com.android.settingslib.applications;
 
+import static android.content.pm.Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.fail;
@@ -24,12 +26,16 @@
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.graphics.drawable.Drawable;
+import android.os.Environment;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import com.android.settingslib.Utils;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -39,6 +45,8 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowPackageManager;
 
 import java.io.File;
 import java.lang.reflect.Field;
@@ -60,6 +68,10 @@
     private ApplicationInfo mAppInfo;
     private ApplicationsState.AppEntry mAppEntry;
     private ArrayList<ApplicationsState.AppEntry> mAppEntries;
+    private ShadowPackageManager mShadowPackageManager;
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Before
     public void setUp() {
@@ -70,6 +82,7 @@
         mAppEntry = createAppEntry(mAppInfo, /* id= */ 1);
         mAppEntries = new ArrayList<>(Arrays.asList(mAppEntry));
         doReturn(mIcon).when(mIcon).mutate();
+        mShadowPackageManager = Shadow.extract(mContext.getPackageManager());
     }
 
     @After
@@ -151,6 +164,32 @@
         assertThat(AppUtils.isAppInstalled(appEntry)).isFalse();
     }
 
+    @Test
+    public void isMainlineModule_hasApexPackageName_shouldCheckByPackageInfo() {
+        mSetFlagsRule.enableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = APP_PACKAGE_NAME;
+        packageInfo.setApexPackageName("com.test.apex.package");
+        mShadowPackageManager.installPackage(packageInfo);
+
+        assertThat(
+                AppUtils.isMainlineModule(mContext.getPackageManager(), APP_PACKAGE_NAME)).isTrue();
+    }
+
+    @Test
+    public void isMainlineModule_noApexPackageName_shouldCheckBySourceDirPath() {
+        mSetFlagsRule.disableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.sourceDir = Environment.getApexDirectory().getAbsolutePath();
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = APP_PACKAGE_NAME;
+        packageInfo.applicationInfo = applicationInfo;
+        mShadowPackageManager.installPackage(packageInfo);
+
+        assertThat(
+                AppUtils.isMainlineModule(mContext.getPackageManager(), APP_PACKAGE_NAME)).isTrue();
+    }
+
     private ApplicationsState.AppEntry createAppEntry(ApplicationInfo appInfo, int id) {
         ApplicationsState.AppEntry appEntry = new ApplicationsState.AppEntry(mContext, appInfo, id);
         appEntry.label = "label";
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index 34d8148..827d8fa 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.applications;
 
+import static android.content.pm.Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX;
 import static android.os.UserHandle.MU_ENABLED;
 import static android.os.UserHandle.USER_SYSTEM;
 
@@ -58,6 +59,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.text.TextUtils;
 import android.util.IconDrawableFactory;
 
@@ -70,6 +72,7 @@
 
 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;
@@ -89,6 +92,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.UUID;
@@ -137,6 +141,9 @@
     @Mock
     private IPackageManager mPackageManagerService;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Implements(value = IconDrawableFactory.class)
     public static class ShadowIconDrawableFactory {
 
@@ -169,7 +176,9 @@
         public List<ModuleInfo> getInstalledModules(int flags) {
             if (mInstalledModules.isEmpty()) {
                 for (String moduleName : mModuleNames) {
-                    mInstalledModules.add(createModuleInfo(moduleName));
+                    mInstalledModules.add(
+                            createModuleInfo(moduleName,
+                                    TextUtils.concat(moduleName, ".apex").toString()));
                 }
             }
             return mInstalledModules;
@@ -188,10 +197,11 @@
             return resolveInfos;
         }
 
-        private ModuleInfo createModuleInfo(String packageName) {
+        private ModuleInfo createModuleInfo(String packageName, String apexPackageName) {
             final ModuleInfo info = new ModuleInfo();
             info.setName(packageName);
             info.setPackageName(packageName);
+            info.setApkInApexPackageNames(Collections.singletonList(apexPackageName));
             // will treat any app with package name that contains "hidden" as hidden module
             info.setHidden(!TextUtils.isEmpty(packageName) && packageName.contains("hidden"));
             return info;
@@ -822,4 +832,32 @@
         assertThat(mApplicationsState.getEntry(PKG_1, /* userId= */ 0).info.packageName)
                 .isEqualTo(PKG_1);
     }
+
+    @Test
+    public void isHiddenModule_hasApkInApexInfo_shouldSupportHiddenApexPackage() {
+        mSetFlagsRule.enableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
+        ApplicationsState.sInstance = null;
+        mApplicationsState = ApplicationsState.getInstance(mApplication, mPackageManagerService);
+        String normalModulePackage = "test.module.1";
+        String hiddenModulePackage = "test.hidden.module.2";
+        String hiddenApexPackage = "test.hidden.module.2.apex";
+
+        assertThat(mApplicationsState.isHiddenModule(normalModulePackage)).isFalse();
+        assertThat(mApplicationsState.isHiddenModule(hiddenModulePackage)).isTrue();
+        assertThat(mApplicationsState.isHiddenModule(hiddenApexPackage)).isTrue();
+    }
+
+    @Test
+    public void isHiddenModule_noApkInApexInfo_onlySupportHiddenModule() {
+        mSetFlagsRule.disableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
+        ApplicationsState.sInstance = null;
+        mApplicationsState = ApplicationsState.getInstance(mApplication, mPackageManagerService);
+        String normalModulePackage = "test.module.1";
+        String hiddenModulePackage = "test.hidden.module.2";
+        String hiddenApexPackage = "test.hidden.module.2.apex";
+
+        assertThat(mApplicationsState.isHiddenModule(normalModulePackage)).isFalse();
+        assertThat(mApplicationsState.isHiddenModule(hiddenModulePackage)).isTrue();
+        assertThat(mApplicationsState.isHiddenModule(hiddenApexPackage)).isFalse();
+    }
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 8ad5f24..dc8116d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -261,6 +261,7 @@
         Settings.Secure.CREDENTIAL_SERVICE,
         Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
         Settings.Secure.EVEN_DIMMER_ACTIVATED,
-        Settings.Secure.EVEN_DIMMER_MIN_NITS
+        Settings.Secure.EVEN_DIMMER_MIN_NITS,
+        Settings.Secure.STYLUS_POINTER_ICON_ENABLED,
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index d854df38..fabdafc 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -416,5 +416,6 @@
         VALIDATORS.put(Secure.CREDENTIAL_SERVICE, CREDENTIAL_SERVICE_VALIDATOR);
         VALIDATORS.put(Secure.CREDENTIAL_SERVICE_PRIMARY, NULLABLE_COMPONENT_NAME_VALIDATOR);
         VALIDATORS.put(Secure.AUTOFILL_SERVICE, AUTOFILL_SERVICE_VALIDATOR);
+        VALIDATORS.put(Secure.STYLUS_POINTER_ICON_ENABLED, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index e4a762a..bc07836 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2601,6 +2601,9 @@
         p.end(soundsToken);
 
         dumpSetting(s, p,
+                Settings.Secure.STYLUS_POINTER_ICON_ENABLED,
+                SecureSettingsProto.STYLUS_POINTER_ICON_ENABLED);
+        dumpSetting(s, p,
                 Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED,
                 SecureSettingsProto.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED);
         dumpSetting(s, p,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index ae71cec..1c9e748 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -614,8 +614,8 @@
         final Iterator<Map.Entry<String, Setting>> iterator = mSettings.entrySet().iterator();
         int index = prefix.lastIndexOf('/');
         String namespace = index < 0 ? "" : prefix.substring(0, index);
-        Map<String, String> trunkFlagMap =
-                mNamespaceDefaults.get(namespace);
+        Map<String, String> trunkFlagMap = (mNamespaceDefaults == null)
+                ? null : mNamespaceDefaults.get(namespace);
         // Delete old keys with the prefix that are not part of the new set.
         // trunk flags will not be configured with restricted propagation
         // trunk flags will be explicitly set, so not removing them here
diff --git a/packages/SoundPicker/OWNERS b/packages/SoundPicker/OWNERS
deleted file mode 100644
index 5bf46e0..0000000
--- a/packages/SoundPicker/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Haptics team works on the SoundPicker
-include platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/packages/SoundPicker2/Android.bp b/packages/SoundPicker2/Android.bp
deleted file mode 100644
index f4d8bf2..0000000
--- a/packages/SoundPicker2/Android.bp
+++ /dev/null
@@ -1,46 +0,0 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_library {
-    name: "SoundPicker2Lib",
-    srcs: [
-        "src/**/*.java",
-    ],
-    resource_dirs: [
-        "res",
-    ],
-    static_libs: [
-        "androidx.appcompat_appcompat",
-        "hilt_android",
-        "guava",
-        "androidx.recyclerview_recyclerview",
-        "androidx-constraintlayout_constraintlayout",
-        "androidx.viewpager2_viewpager2",
-        "com.google.android.material_material",
-    ],
-}
-
-android_app {
-    name: "SoundPicker2",
-    defaults: ["platform_app_defaults"],
-    manifest: "AndroidManifest.xml",
-    static_libs: ["SoundPicker2Lib"],
-    platform_apis: true,
-    certificate: "media",
-    privileged: true,
-
-    optimize: {
-        enabled: true,
-        optimize: true,
-        shrink: true,
-        shrink_resources: true,
-        obfuscate: false,
-        proguard_compatibility: false,
-    },
-}
diff --git a/packages/SoundPicker2/AndroidManifest.xml b/packages/SoundPicker2/AndroidManifest.xml
deleted file mode 100644
index 934b003..0000000
--- a/packages/SoundPicker2/AndroidManifest.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.soundpicker"
-        android:sharedUserId="android.media">
-
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
-    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
-    <uses-permission android:name="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY" />
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
-
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
-
-    <application
-            android:name=".RingtonePickerApplication"
-            android:allowBackup="false"
-            android:label="@string/app_label"
-            android:theme="@style/Theme.AppCompat"
-            android:supportsRtl="true">
-        <receiver android:name="RingtoneReceiver"
-                android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/>
-            </intent-filter>
-        </receiver>
-
-        <service android:name="RingtoneOverlayService" />
-
-        <activity android:name="RingtonePickerActivity"
-                android:theme="@style/Theme.AppCompat.Dialog"
-                android:enabled="@*android:bool/config_defaultRingtonePickerEnabled"
-                android:excludeFromRecents="true"
-                android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.RINGTONE_PICKER" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.RINGTONE_PICKER_SOUND" />
-                <category android:name="android.intent.category.RINGTONE_PICKER_VIBRATION" />
-                <category android:name="android.intent.category.RINGTONE_PICKER_RINGTONE" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/packages/SoundPicker2/OWNERS b/packages/SoundPicker2/OWNERS
deleted file mode 100644
index 5bf46e0..0000000
--- a/packages/SoundPicker2/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Haptics team works on the SoundPicker
-include platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/packages/SoundPicker2/res/drawable/ic_add.xml b/packages/SoundPicker2/res/drawable/ic_add.xml
deleted file mode 100644
index 22b3fe9..0000000
--- a/packages/SoundPicker2/res/drawable/ic_add.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-    Copyright (C) 2016 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT 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="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="48.0"
-        android:viewportHeight="48.0">
-    <path
-        android:fillColor="?android:attr/colorAccent"
-        android:pathData="M38.0,26.0L26.0,26.0l0.0,12.0l-4.0,0.0L22.0,26.0L10.0,26.0l0.0,-4.0l12.0,0.0L22.0,10.0l4.0,0.0l0.0,12.0l12.0,0.0l0.0,4.0z"/>
-</vector>
\ No newline at end of file
diff --git a/packages/SoundPicker2/res/drawable/ic_add_padded.xml b/packages/SoundPicker2/res/drawable/ic_add_padded.xml
deleted file mode 100644
index c376867..0000000
--- a/packages/SoundPicker2/res/drawable/ic_add_padded.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!--
-    Copyright (C) 2017 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
-        android:drawable="@drawable/ic_add"
-        android:insetTop="4dp"
-        android:insetRight="4dp"
-        android:insetBottom="4dp"
-        android:insetLeft="4dp"/>
diff --git a/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml b/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml
deleted file mode 100644
index edfc0ab..0000000
--- a/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<!--
-     Currently, no file manager app on watch could handle ACTION_GET_CONTENT intent.
-     Make the visibility to "gone" to prevent failures.
- -->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/add_new_sound_text"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:minHeight="?android:attr/listPreferredItemHeightSmall"
-        android:textAppearance="?android:attr/textAppearanceMedium"
-        android:text="@null"
-        android:textColor="?android:attr/colorAccent"
-        android:gravity="center_vertical"
-        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-        android:drawableStart="@drawable/ic_add_padded"
-        android:drawablePadding="8dp"
-        android:ellipsize="marquee"
-        android:visibility="gone" />
diff --git a/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml b/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml
deleted file mode 100644
index ee29a37..0000000
--- a/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<com.android.soundpicker.CheckedListItem xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="wrap_content"
-    android:gravity="center_vertical"
-    android:background="?android:attr/selectableItemBackground"
-    >
-
-    <CheckedTextView
-        android:id="@+id/checked_text_view"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:minHeight="?android:attr/listPreferredItemHeightSmall"
-        android:textAppearance="?android:attr/textAppearanceMedium"
-        android:textColor="?android:attr/textColorAlertDialogListItem"
-        android:gravity="center_vertical"
-        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-        android:drawableStart="?android:attr/listChoiceIndicatorSingle"
-        android:drawablePadding="8dp"
-        android:ellipsize="marquee"
-        android:layout_toLeftOf="@+id/work_icon"
-        android:maxLines="3" />
-
-    <ImageView
-        android:id="@id/work_icon"
-        android:layout_width="18dp"
-        android:layout_height="18dp"
-        android:layout_alignParentRight="true"
-        android:layout_centerVertical="true"
-        android:scaleType="centerCrop"
-        android:layout_marginRight="20dp" />
-</com.android.soundpicker.CheckedListItem>
diff --git a/packages/SoundPicker2/res/layout/activity_ringtone_picker.xml b/packages/SoundPicker2/res/layout/activity_ringtone_picker.xml
deleted file mode 100644
index 6fc6080..0000000
--- a/packages/SoundPicker2/res/layout/activity_ringtone_picker.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2023 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:orientation="vertical"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"/>
\ No newline at end of file
diff --git a/packages/SoundPicker2/res/layout/add_new_sound_item.xml b/packages/SoundPicker2/res/layout/add_new_sound_item.xml
deleted file mode 100644
index 024b97e..0000000
--- a/packages/SoundPicker2/res/layout/add_new_sound_item.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2016 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT 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:layout_width="fill_parent"
-              android:layout_height="wrap_content"
-              android:gravity="center_vertical"
-              android:background="?android:attr/selectableItemBackground"
-              android:focusable="true"
-              android:clickable="true">
-
-    <ImageView
-        android:layout_width="24dp"
-        android:layout_height="24dp"
-        android:layout_alignParentRight="true"
-        android:layout_centerVertical="true"
-        android:scaleType="centerCrop"
-        android:layout_marginRight="24dp"
-        android:layout_marginLeft="24dp"
-        android:src="@drawable/ic_add"/>
-
-    <TextView
-        android:id="@+id/add_new_sound_text"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:minHeight="?android:attr/listPreferredItemHeightSmall"
-        android:text="@null"
-        android:textColor="?android:attr/colorAccent"
-        android:textAppearance="?android:attr/textAppearanceMedium"
-        android:maxLines="3"
-        android:gravity="center_vertical"
-        android:paddingEnd="?android:attr/dialogPreferredPadding"
-        android:drawablePadding="20dp"
-        android:ellipsize="marquee"/>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SoundPicker2/res/layout/fragment_ringtone_picker.xml b/packages/SoundPicker2/res/layout/fragment_ringtone_picker.xml
deleted file mode 100644
index 787f92e..0000000
--- a/packages/SoundPicker2/res/layout/fragment_ringtone_picker.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2023 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<androidx.recyclerview.widget.RecyclerView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/recycler_view"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-/>
\ No newline at end of file
diff --git a/packages/SoundPicker2/res/layout/fragment_tabbed_dialog.xml b/packages/SoundPicker2/res/layout/fragment_tabbed_dialog.xml
deleted file mode 100644
index 7efd911..0000000
--- a/packages/SoundPicker2/res/layout/fragment_tabbed_dialog.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2023 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:orientation="vertical"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent">
-    <com.google.android.material.tabs.TabLayout
-            android:id="@+id/tabLayout"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"/>
-    <androidx.viewpager2.widget.ViewPager2
-            android:id="@+id/masterViewPager"
-            android:paddingTop="12dp"
-            android:paddingBottom="12dp"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"/>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SoundPicker2/res/layout/radio_with_work_badge.xml b/packages/SoundPicker2/res/layout/radio_with_work_badge.xml
deleted file mode 100644
index 36ac93e..0000000
--- a/packages/SoundPicker2/res/layout/radio_with_work_badge.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<com.android.soundpicker.CheckedListItem
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="wrap_content"
-    android:gravity="center_vertical"
-    android:background="?android:attr/selectableItemBackground"
-    android:focusable="true"
-    android:clickable="true">
-
-    <CheckedTextView
-        android:id="@+id/checked_text_view"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:minHeight="?android:attr/listPreferredItemHeightSmall"
-        android:textAppearance="?android:attr/textAppearanceMedium"
-        android:textColor="?android:attr/textColorAlertDialogListItem"
-        android:gravity="center_vertical"
-        android:paddingStart="20dp"
-        android:paddingEnd="?android:attr/dialogPreferredPadding"
-        android:drawableStart="?android:attr/listChoiceIndicatorSingle"
-        android:drawablePadding="20dp"
-        android:ellipsize="marquee"
-        android:layout_toLeftOf="@+id/work_icon"
-        android:maxLines="3"/>
-
-    <ImageView
-        android:id="@id/work_icon"
-        android:layout_width="18dp"
-        android:layout_height="18dp"
-        android:layout_alignParentRight="true"
-        android:layout_centerVertical="true"
-        android:scaleType="centerCrop"
-        android:layout_marginRight="20dp"/>
-</com.android.soundpicker.CheckedListItem>
diff --git a/packages/SoundPicker2/res/raw/default_alarm_alert.ogg b/packages/SoundPicker2/res/raw/default_alarm_alert.ogg
deleted file mode 100644
index e69de29..0000000
--- a/packages/SoundPicker2/res/raw/default_alarm_alert.ogg
+++ /dev/null
diff --git a/packages/SoundPicker2/res/raw/default_notification_sound.ogg b/packages/SoundPicker2/res/raw/default_notification_sound.ogg
deleted file mode 100644
index e69de29..0000000
--- a/packages/SoundPicker2/res/raw/default_notification_sound.ogg
+++ /dev/null
diff --git a/packages/SoundPicker2/res/raw/default_ringtone.ogg b/packages/SoundPicker2/res/raw/default_ringtone.ogg
deleted file mode 100644
index e69de29..0000000
--- a/packages/SoundPicker2/res/raw/default_ringtone.ogg
+++ /dev/null
diff --git a/packages/SoundPicker2/res/values/config.xml b/packages/SoundPicker2/res/values/config.xml
deleted file mode 100644
index 4e237a2..0000000
--- a/packages/SoundPicker2/res/values/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<!-- These resources are around just to allow their values to be customized
-     for different hardware and product builds.  Do not translate.
-
-     NOTE: The naming convention is "config_camelCaseValue".  -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-    <!-- True if the ringtone picker should show the ok/cancel buttons. If it is not shown, the
-    ringtone will be automatically selected when the picker is closed. -->
-    <bool name="config_showOkCancelButtons">true</bool>
-</resources>
diff --git a/packages/SoundPicker2/res/values/strings.xml b/packages/SoundPicker2/res/values/strings.xml
deleted file mode 100644
index ab7b95a..0000000
--- a/packages/SoundPicker2/res/values/strings.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Choice in the ringtone picker.  If chosen, the default ringtone will be used. -->
-    <string name="ringtone_default">Default ringtone</string>
-
-    <!-- Choice in the notification sound picker.  If chosen, the default notification sound will be
-         used. -->
-    <string name="notification_sound_default">Default notification sound</string>
-
-    <!-- Choice in the alarm sound picker.  If chosen, the default alarm sound will be used. -->
-    <string name="alarm_sound_default">Default alarm sound</string>
-
-    <!-- Text for the RingtonePicker item that allows adding a new ringtone. -->
-    <string name="add_ringtone_text">Add ringtone</string>
-    <!-- Text for the RingtonePicker item that allows adding a new alarm. -->
-    <string name="add_alarm_text">Add alarm</string>
-    <!-- Text for the RingtonePicker item that allows adding a new notification. -->
-    <string name="add_notification_text">Add notification</string>
-    <!-- Text for the RingtonePicker item ContextMenu that allows deleting a custom ringtone. -->
-    <string name="delete_ringtone_text">Delete</string>
-    <!-- Text for the Toast displayed when adding a custom ringtone fails. -->
-    <string name="unable_to_add_ringtone">Unable to add custom ringtone</string>
-    <!-- Text for the Toast displayed when deleting a custom ringtone fails. -->
-    <string name="unable_to_delete_ringtone">Unable to delete custom ringtone</string>
-
-    <!-- Text for the name of the app. [CHAR LIMIT=12] -->
-    <string name="app_label">Sounds</string>
-
-    <string name="empty_list">The list is empty</string>
-    <string name="sound_page_title">Sound</string>
-    <string name="vibration_page_title">Vibration</string>
-</resources>
diff --git a/packages/SoundPicker2/res/values/styles.xml b/packages/SoundPicker2/res/values/styles.xml
deleted file mode 100644
index d22d9c4..0000000
--- a/packages/SoundPicker2/res/values/styles.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <style name="PickerDialogTheme" parent="@*android:style/Theme.DeviceDefault.Settings.Dialog">
-    </style>
-
-</resources>
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/BasePickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/BasePickerFragment.java
deleted file mode 100644
index 4fc2a86..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/BasePickerFragment.java
+++ /dev/null
@@ -1,312 +0,0 @@
-/*
- * 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.soundpicker;
-
-import android.app.Activity;
-import android.content.ContentProvider;
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.MediaStore;
-import android.util.Log;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.fragment.app.Fragment;
-import androidx.lifecycle.ViewModelProvider;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import dagger.hilt.android.AndroidEntryPoint;
-
-import java.util.Objects;
-
-/**
- * Base class for generic picker fragments.
- *
- * <p>This fragment displays a recycler view that is populated by a {@link RingtoneListViewAdapter}
- * with data provided by a {@link RingtoneListHandler}. Each item can be selected on click,
- * which also triggers a ringtone preview performed by the shared {@link RingtonePickerViewModel}.
- * The ringtone preview uses the selection state of all picker fragments (e.g. sound selected by
- * one fragment and vibration selected by another).
- */
-@AndroidEntryPoint(Fragment.class)
-public abstract class BasePickerFragment extends Hilt_BasePickerFragment implements
-        RingtoneListViewAdapter.Callbacks {
-
-    private static final String TAG = "BasePickerFragment";
-    private static final String COLUMN_LABEL = MediaStore.Audio.Media.TITLE;
-    private boolean mIsManagedProfile;
-    private Drawable mWorkIconDrawable;
-
-    protected RingtoneListViewAdapter mRingtoneListViewAdapter;
-    protected RecyclerView mRecyclerView;
-    protected RingtonePickerViewModel.Config mPickerConfig;
-    protected RingtonePickerViewModel mRingtonePickerViewModel;
-    protected RingtoneListHandler.Config mRingtoneListConfig;
-    protected RingtoneListHandler mRingtoneListHandler;
-
-    public BasePickerFragment() {
-        super(R.layout.fragment_ringtone_picker);
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-        mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
-                RingtonePickerViewModel.class);
-        mRingtoneListHandler = getRingtoneListHandler();
-        mRecyclerView = view.requireViewById(R.id.recycler_view);
-
-        mPickerConfig = mRingtonePickerViewModel.getPickerConfig();
-        mRingtoneListConfig = mRingtoneListHandler.getRingtoneListConfig();
-
-        mIsManagedProfile = UserManager.get(requireActivity()).isManagedProfile(
-                mPickerConfig.userId);
-
-        mRingtoneListViewAdapter = createRingtoneListViewAdapter();
-        mRecyclerView.setHasFixedSize(true);
-        mRecyclerView.setAdapter(mRingtoneListViewAdapter);
-        mRecyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
-        setSelectedItem(mRingtoneListHandler.getSelectedItemPosition());
-        prepareRecyclerView(mRecyclerView);
-    }
-
-    @Override
-    public boolean isWorkRingtone(int position) {
-        if (!mIsManagedProfile) {
-            return false;
-        }
-
-        /*
-         * Display the work icon if the ringtone belongs to a work profile. We
-         * can tell that a ringtone belongs to a work profile if the picker user
-         * is a managed profile, the ringtone Uri is in external storage, and
-         * either the uri has no user id or has the id of the picker user
-         */
-        Uri currentUri = mRingtoneListHandler.getRingtoneUri(position);
-        int uriUserId = ContentProvider.getUserIdFromUri(currentUri,
-                mPickerConfig.userId);
-        Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(currentUri);
-
-        return uriUserId == mPickerConfig.userId
-                && uriWithoutUserId.toString().startsWith(
-                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString());
-    }
-
-    @Override
-    public Drawable getWorkIconDrawable() {
-        if (mWorkIconDrawable == null) {
-            mWorkIconDrawable = requireActivity().getPackageManager()
-                    .getUserBadgeForDensityNoBackground(
-                            UserHandle.of(mPickerConfig.userId), /* density= */ -1);
-        }
-
-        return mWorkIconDrawable;
-    }
-
-    @Override
-    public void onRingtoneSelected(int position) {
-        setSelectedItem(position);
-
-        // In the buttonless (watch-only) version, preemptively set our result since
-        // we won't have another chance to do so before the activity closes.
-        if (!mPickerConfig.showOkCancelButtons) {
-            setSuccessResultWithSelectedRingtone();
-        }
-
-        // Play clip
-        mRingtonePickerViewModel.playRingtone();
-    }
-
-    @Override
-    public void onAddRingtoneSelected() {
-        addRingtoneAsync();
-    }
-
-    /**
-     * Sets up the list by adding fixed items to the top and bottom, if required. And sets the
-     * selected item in the list.
-     * @param recyclerView The recyclerview that contains the list of displayed items.
-     */
-    protected void prepareRecyclerView(@NonNull RecyclerView recyclerView) {
-        // Reset the static item count, as this method can be called multiple times
-        mRingtoneListHandler.resetFixedItems();
-
-        if (mRingtoneListConfig.hasDefaultItem) {
-            int defaultItemPos = addDefaultRingtoneItem();
-
-            if (getSelectedItem() < 0
-                    && RingtoneManager.isDefault(mRingtoneListConfig.initialSelectedUri)) {
-                setSelectedItem(defaultItemPos);
-            }
-        }
-
-        if (mRingtoneListConfig.hasSilentItem) {
-            int silentItemPos = addSilentItem();
-
-            // The 'Silent' item should use a null Uri
-            if (getSelectedItem() < 0
-                    && mRingtoneListConfig.initialSelectedUri == null) {
-                setSelectedItem(silentItemPos);
-            }
-        }
-
-        if (getSelectedItem() < 0) {
-            setSelectedItem(mRingtoneListHandler.getRingtonePosition(
-                    mRingtoneListConfig.initialSelectedUri));
-        }
-
-        // In the buttonless (watch-only) version, preemptively set our result since we won't
-        // have another chance to do so before the activity closes.
-        if (!mPickerConfig.showOkCancelButtons) {
-            setSuccessResultWithSelectedRingtone();
-        }
-
-        addNewRingtoneItem();
-
-        // Enable context menu in ringtone items
-        registerForContextMenu(recyclerView);
-    }
-
-    /**
-     * Returns the fragment's sound/vibration list handler.
-     * @return The ringtone list handler.
-     */
-    protected abstract RingtoneListHandler getRingtoneListHandler();
-
-    /**
-     * Starts the process to add a new ringtone to the list of ringtones asynchronously.
-     * Currently, only works for adding sound files.
-     */
-    protected abstract void addRingtoneAsync();
-
-    /**
-     * Adds an item to the end of the list that can be used to add new ringtones to the list.
-     * Currently, only works for adding sound files.
-     */
-    protected abstract void addNewRingtoneItem();
-
-    protected int getSelectedItem() {
-        return mRingtoneListHandler.getSelectedItemPosition();
-    }
-
-    /**
-     * Returns the selected URI to the caller activity.
-     */
-    protected void setSuccessResultWithSelectedRingtone() {
-        requireActivity().setResult(Activity.RESULT_OK,
-                new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI,
-                        mRingtonePickerViewModel.getSelectedRingtoneUri()));
-    }
-
-    /**
-     * Creates a ringtone recyclerview adapter using the ringtone manager cursor.
-     * @return The created RingtoneListViewAdapter.
-     */
-    protected RingtoneListViewAdapter createRingtoneListViewAdapter() {
-        LocalizedCursor cursor = new LocalizedCursor(
-                mRingtoneListHandler.getRingtoneCursor(), getResources(), COLUMN_LABEL);
-        return new RingtoneListViewAdapter(cursor, /* RingtoneListViewAdapterCallbacks= */ this);
-    }
-
-    /**
-     * Sets the selected item in the list and scroll to the position in the recyclerview.
-     * @param pos the position of the selected item in the list.
-     */
-    protected void setSelectedItem(int pos) {
-        Objects.requireNonNull(mRingtoneListViewAdapter);
-        mRingtoneListHandler.setSelectedItemPosition(pos);
-        mRingtoneListViewAdapter.setSelectedItem(pos);
-        mRingtoneListHandler.setSelectedItemId(mRingtoneListViewAdapter.getItemId(pos));
-        mRecyclerView.scrollToPosition(pos);
-    }
-
-    /**
-     * Adds a fixed item to the fixed items list . A fixed item is one that is not from
-     * the RingtoneManager.
-     *
-     * @param textResId The resource ID of the text for the item.
-     * @return The index of the inserted fixed item in the adapter.
-     */
-    protected int addFixedItem(int textResId) {
-        return mRingtoneListViewAdapter.addTitleForFixedItem(textResId);
-    }
-
-    /**
-     * Re-query RingtoneManager for the most recent set of installed ringtones. May move the
-     * selected item position to match the new position of the chosen ringtone.
-     * <p>
-     * This should only need to happen after adding or removing a ringtone.
-     */
-    protected void requeryForAdapter() {
-        mRingtonePickerViewModel.reinit();
-        // Refresh and set a new cursor, and closing the old one.
-        mRingtoneListViewAdapter = createRingtoneListViewAdapter();
-        mRecyclerView.setAdapter(mRingtoneListViewAdapter);
-        prepareRecyclerView(mRecyclerView);
-
-        // Update selected item location.
-        for (int i = 0; i < mRingtoneListViewAdapter.getItemCount(); i++) {
-            if (mRingtoneListViewAdapter.getItemId(i)
-                    == mRingtoneListHandler.getSelectedItemId()) {
-                setSelectedItem(i);
-                return;
-            }
-        }
-
-        // If selected item is still unknown, then set it to the default item, if available.
-        // If it's not available, then attempt to set it to the silent item in the list.
-        int selectedPosition = mRingtoneListHandler.getDefaultItemPosition();
-
-        if (selectedPosition < 0) {
-            selectedPosition = mRingtoneListHandler.getSilentItemPosition();
-        }
-
-        setSelectedItem(selectedPosition);
-    }
-
-    private int addDefaultRingtoneItem() {
-        int defaultItemPosInAdapter = addFixedItem(
-                RingtonePickerViewModel.getDefaultRingtoneItemTextByType(
-                        mPickerConfig.ringtoneType));
-        int defaultItemPosInListHandler = mRingtoneListHandler.addDefaultItem();
-
-        if (defaultItemPosInAdapter != defaultItemPosInListHandler) {
-            Log.wtf(TAG, "Default item position in adapter and list handler must match.");
-            return RingtoneListHandler.ITEM_POSITION_UNKNOWN;
-        }
-
-        return defaultItemPosInListHandler;
-    }
-
-    private int addSilentItem() {
-        int silentItemPosInAdapter = addFixedItem(com.android.internal.R.string.ringtone_silent);
-        int silentItemPosInListHandler = mRingtoneListHandler.addSilentItem();
-
-        if (silentItemPosInAdapter != silentItemPosInListHandler) {
-            Log.wtf(TAG, "Silent item position in adapter and list handler must match.");
-            return RingtoneListHandler.ITEM_POSITION_UNKNOWN;
-        }
-
-        return silentItemPosInListHandler;
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java b/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java
deleted file mode 100644
index 819ae98..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.soundpicker;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.Checkable;
-import android.widget.CheckedTextView;
-import android.widget.RelativeLayout;
-
-/**
- * The {@link CheckedListItem} is a layout item that represents a ringtone, and is used in
- * {@link RingtonePickerActivity}. It contains the ringtone's name, and a work badge to right of the
- * name if the ringtone belongs to a work profile.
- */
-public class CheckedListItem extends RelativeLayout implements Checkable {
-
-    public CheckedListItem(Context context) {
-        super(context);
-    }
-
-    public CheckedListItem(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public CheckedListItem(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    public CheckedListItem(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
-    @Override
-    public void setChecked(boolean checked) {
-        getCheckedTextView().setChecked(checked);
-    }
-
-    @Override
-    public boolean isChecked() {
-        return getCheckedTextView().isChecked();
-    }
-
-    @Override
-    public void toggle() {
-        getCheckedTextView().toggle();
-    }
-
-    private CheckedTextView getCheckedTextView() {
-        return (CheckedTextView) findViewById(R.id.checked_text_view);
-    }
-
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/ListeningExecutorServiceFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/ListeningExecutorServiceFactory.java
deleted file mode 100644
index afdbf05..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/ListeningExecutorServiceFactory.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.soundpicker;
-
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
-
-import java.util.concurrent.Executors;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * A factory class used to create {@link ListeningExecutorService}.
- */
-@Singleton
-public class ListeningExecutorServiceFactory {
-
-    @Inject
-    ListeningExecutorServiceFactory() {
-    }
-
-    /**
-     * Returns a single thread {@link ListeningExecutorService}.
-     *
-     */
-    public ListeningExecutorService createSingleThreadExecutor() {
-        return MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/LocalizedCursor.java b/packages/SoundPicker2/src/com/android/soundpicker/LocalizedCursor.java
deleted file mode 100644
index 83d04a3..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/LocalizedCursor.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * 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.soundpicker;
-
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.CursorWrapper;
-import android.util.Log;
-import android.util.TypedValue;
-
-import androidx.annotation.Nullable;
-
-import java.util.Locale;
-import java.util.regex.Pattern;
-
-/**
- * A cursor wrapper class mainly used to guarantee getting a ringtone title
- */
-final class LocalizedCursor extends CursorWrapper {
-
-    private static final String TAG = "LocalizedCursor";
-    private static final String SOUND_NAME_RES_PREFIX = "sound_name_";
-
-    private final int mTitleIndex;
-    private final Resources mResources;
-    private final Pattern mSanitizePattern;
-    private final String mNamePrefix;
-
-    LocalizedCursor(Cursor cursor, Resources resources, String columnLabel) {
-        super(cursor);
-        mTitleIndex = mCursor.getColumnIndex(columnLabel);
-        mResources = resources;
-        mSanitizePattern = Pattern.compile("[^a-zA-Z0-9]");
-        if (mTitleIndex == -1) {
-            Log.e(TAG, "No index for column " + columnLabel);
-            mNamePrefix = null;
-        } else {
-            mNamePrefix = buildNamePrefix(mResources);
-        }
-    }
-
-    /**
-     * Builds the prefix for the name of the resource to look up.
-     * The format is: "ResourcePackageName::ResourceTypeName/" (the type name is expected to be
-     * "string" but let's not hardcode it).
-     * Here we use an existing resource "notification_sound_default" which is always expected to be
-     * found.
-     *
-     * @param resources Application's resources
-     * @return the built name prefix, or null if failed to build.
-     */
-    @Nullable
-    private static String buildNamePrefix(Resources resources) {
-        try {
-            return String.format("%s:%s/%s",
-                    resources.getResourcePackageName(R.string.notification_sound_default),
-                    resources.getResourceTypeName(R.string.notification_sound_default),
-                    SOUND_NAME_RES_PREFIX);
-        } catch (Resources.NotFoundException e) {
-            Log.e(TAG, "Failed to build the prefix for the name of the resource.", e);
-        }
-
-        return null;
-    }
-
-    /**
-     * Process resource name to generate a valid resource name.
-     *
-     * @return a non-null String
-     */
-    private String sanitize(String input) {
-        if (input == null) {
-            return "";
-        }
-        return mSanitizePattern.matcher(input).replaceAll("_").toLowerCase(Locale.ROOT);
-    }
-
-    @Override
-    public String getString(int columnIndex) {
-        final String defaultName = mCursor.getString(columnIndex);
-        if ((columnIndex != mTitleIndex) || (mNamePrefix == null)) {
-            return defaultName;
-        }
-        TypedValue value = new TypedValue();
-        try {
-            // the name currently in the database is used to derive a name to match
-            // against resource names in this package
-            mResources.getValue(mNamePrefix + sanitize(defaultName), value,
-                    /* resolveRefs= */ false);
-        } catch (Resources.NotFoundException e) {
-            Log.d(TAG, "Failed to get localized string. Using default string instead.", e);
-            return defaultName;
-        }
-        if ((value != null) && (value.type == TypedValue.TYPE_STRING)) {
-            Log.d(TAG, String.format("Replacing name %s with %s",
-                    defaultName, value.string.toString()));
-            return value.string.toString();
-        } else {
-            Log.e(TAG, "Invalid value when looking up localized name, using " + defaultName);
-            return defaultName;
-        }
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneFactory.java
deleted file mode 100644
index 6817f53..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneFactory.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.soundpicker;
-
-import android.content.Context;
-import android.media.AudioAttributes;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-
-import dagger.hilt.android.qualifiers.ApplicationContext;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * A factory class used to create {@link Ringtone}.
- */
-@Singleton
-public class RingtoneFactory {
-
-    private final Context mApplicationContext;
-
-    @Inject
-    RingtoneFactory(@ApplicationContext Context applicationContext) {
-        mApplicationContext = applicationContext;
-    }
-
-    /**
-     * Returns a {@link Ringtone} built from the provided URI and audio attributes flags.
-     *
-     * @param uri The URI used to build the {@link Ringtone}.
-     * @param audioAttributesFlags A combination of audio attribute flags that affect the volume
-     *                             and settings when playing the ringtone.
-     * @return the built {@link Ringtone}.
-     */
-    public Ringtone create(Uri uri, int audioAttributesFlags) {
-        AudioAttributes audioAttributes = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
-                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-                .setFlags(audioAttributesFlags)
-                .build();
-        return RingtoneManager.getRingtone(mApplicationContext, uri,
-                /* volumeShaperConfig= */ null, audioAttributes);
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListHandler.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListHandler.java
deleted file mode 100644
index bb38e0e..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListHandler.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * 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.soundpicker;
-
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.Nullable;
-import android.database.Cursor;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import javax.inject.Inject;
-
-/**
- * Handles ringtone list state and actions. This includes keeping track of the selected item,
- * ringtone manager cursor and added items to the list.
- */
-public class RingtoneListHandler {
-
-    // TODO: We're using an empty URI instead of null, because null URIs still produce a sound,
-    //  while empty ones don't (Potentially this might be due to empty URIs being perceived as
-    //  malformed ones). We will switch to using the official silent URIs (SOUND_OFF, VIBRATION_OFF)
-    //  once they become available.
-    static final Uri SILENT_URI = Uri.EMPTY;
-    static final int ITEM_POSITION_UNKNOWN = -1;
-
-    private static final String TAG = "RingtoneListHandler";
-
-    /** The position in the list of the 'Silent' item. */
-    private int mSilentItemPosition = ITEM_POSITION_UNKNOWN;
-    /** The position in the list of the 'Default' item. */
-    private int mDefaultItemPosition = ITEM_POSITION_UNKNOWN;
-    /** The number of fixed items in the list. */
-    private int mFixedItemCount;
-    /**
-     * Stable ID for the ringtone that is currently selected (may be -1 if no ringtone is selected).
-     */
-    private long mSelectedItemId = -1;
-    private int mSelectedItemPosition = ITEM_POSITION_UNKNOWN;
-
-    private RingtoneManager mRingtoneManager;
-    private Config mRingtoneListConfig;
-    private Cursor mRingtoneCursor;
-
-    /**
-     * Holds immutable info on the ringtone list that is displayed.
-     */
-    static final class Config {
-        /**
-         * Whether this list has the 'Default' item.
-         */
-        public final boolean hasDefaultItem;
-        /**
-         * The Uri to play when the 'Default' item is clicked.
-         */
-        public final Uri uriForDefaultItem;
-        /**
-         * Whether this list has the 'Silent' item.
-         */
-        public final boolean hasSilentItem;
-        /**
-         * The initially selected uri in the list.
-         */
-        public final Uri initialSelectedUri;
-
-        Config(boolean hasDefaultItem, Uri uriForDefaultItem, boolean hasSilentItem,
-                Uri initialSelectedUri) {
-            this.hasDefaultItem = hasDefaultItem;
-            this.uriForDefaultItem = uriForDefaultItem;
-            this.hasSilentItem = hasSilentItem;
-            this.initialSelectedUri = initialSelectedUri;
-        }
-    }
-
-    @Inject
-    RingtoneListHandler() {
-    }
-
-    void init(@NonNull Config ringtoneListConfig,
-            @NonNull RingtoneManager ringtoneManager, @NonNull Cursor ringtoneCursor) {
-        mRingtoneManager = requireNonNull(ringtoneManager);
-        mRingtoneListConfig = requireNonNull(ringtoneListConfig);
-        mRingtoneCursor = requireNonNull(ringtoneCursor);
-    }
-
-    Config getRingtoneListConfig() {
-        return mRingtoneListConfig;
-    }
-
-    Cursor getRingtoneCursor() {
-        requireInitCalled();
-        return mRingtoneCursor;
-    }
-
-    Uri getRingtoneUri(int position) {
-        if (position < 0) {
-            Log.w(TAG, "Selected item position is unknown.");
-            // When the selected item is ITEM_POSITION_UNKNOWN, it is not the case we expected.
-            // We return SILENT_URI for this case.
-            return SILENT_URI;
-        } else if (position == mDefaultItemPosition) {
-            // Use the default Uri that they originally gave us.
-            return mRingtoneListConfig.uriForDefaultItem;
-        } else if (position == mSilentItemPosition) {
-            // Use SILENT_URI for the 'Silent' item.
-            return SILENT_URI;
-        } else {
-            requireInitCalled();
-            return mRingtoneManager.getRingtoneUri(mapListPositionToRingtonePosition(position));
-        }
-    }
-
-    int getRingtonePosition(Uri uri) {
-        requireInitCalled();
-        return mapRingtonePositionToListPosition(mRingtoneManager.getRingtonePosition(uri));
-    }
-
-    void resetFixedItems() {
-        mFixedItemCount = 0;
-        mDefaultItemPosition = ITEM_POSITION_UNKNOWN;
-        mSilentItemPosition = ITEM_POSITION_UNKNOWN;
-    }
-
-    int addDefaultItem() {
-        if (mDefaultItemPosition < 0) {
-            mDefaultItemPosition = addFixedItem();
-        }
-        return mDefaultItemPosition;
-    }
-
-    int getDefaultItemPosition() {
-        return mDefaultItemPosition;
-    }
-
-    int addSilentItem() {
-        if (mSilentItemPosition < 0) {
-            mSilentItemPosition = addFixedItem();
-        }
-        return mSilentItemPosition;
-    }
-
-    public int getSilentItemPosition() {
-        return mSilentItemPosition;
-    }
-
-    int getSelectedItemPosition() {
-        return mSelectedItemPosition;
-    }
-
-    void setSelectedItemPosition(int selectedItemPosition) {
-        mSelectedItemPosition = selectedItemPosition;
-    }
-
-    void setSelectedItemId(long selectedItemId) {
-        mSelectedItemId = selectedItemId;
-    }
-
-    long getSelectedItemId() {
-        return mSelectedItemId;
-    }
-
-    @Nullable
-    Uri getSelectedRingtoneUri() {
-        return getRingtoneUri(mSelectedItemPosition);
-    }
-
-    /**
-     * Maps the item position in the list, to its equivalent position in the RingtoneManager.
-     *
-     * @param itemPosition the position of item in the list.
-     * @return position of the item in the RingtoneManager.
-     */
-    private int mapListPositionToRingtonePosition(int itemPosition) {
-        // If the manager position is less than add items, then return that.
-        if (itemPosition < mFixedItemCount) return itemPosition;
-
-        return itemPosition - mFixedItemCount;
-    }
-
-    /**
-     * Maps the item position in the RingtoneManager, to its equivalent position in the list.
-     *
-     * @param itemPosition the position of the item in the RingtoneManager.
-     * @return position of the item in the list.
-     */
-    private int mapRingtonePositionToListPosition(int itemPosition) {
-        // If the manager position is less than add items, then return that.
-        if (itemPosition < 0) return itemPosition;
-
-        return itemPosition + mFixedItemCount;
-    }
-
-    /**
-     * Increments the number of added fixed items and returns the index of the newest added item.
-     * @return index of the newest added fixed item.
-     */
-    private int addFixedItem() {
-        return mFixedItemCount++;
-    }
-
-    private void requireInitCalled() {
-        requireNonNull(mRingtoneManager);
-        requireNonNull(mRingtoneCursor);
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListViewAdapter.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListViewAdapter.java
deleted file mode 100644
index 4ca8943..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListViewAdapter.java
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * 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.soundpicker;
-
-import static com.android.internal.widget.RecyclerView.NO_ID;
-
-import android.database.Cursor;
-import android.graphics.drawable.Drawable;
-import android.media.RingtoneManager;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CheckedTextView;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.StringRes;
-import androidx.recyclerview.widget.RecyclerView;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * The adapter presents a list of ringtones which may include fixed item in the list and an action
- * button at the end.
- *
- * The adapter handles three different types of items:
- * <ul>
- * <li>FIXED: Fixed items are items added to the top of the list. These items can not be modified
- * and their position will never change.
- * <li>DYNAMIC: Dynamic items are items from the ringtone manager. These items can be modified
- * and their position can change.
- * <li>FOOTER: A footer item is an added button to the end of the list. This item can be clicked
- * but not selected and its position will never change.
- * </ul>
- */
-final class RingtoneListViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
-
-    private static final int VIEW_TYPE_FIXED_ITEM = 0;
-    private static final int VIEW_TYPE_DYNAMIC_ITEM = 1;
-    private static final int VIEW_TYPE_ADD_RINGTONE_ITEM = 2;
-    private final Cursor mCursor;
-    private final List<Integer> mFixedItemTitles;
-    private final Callbacks mCallbacks;
-    private final int mRowIDColumn;
-    private int mSelectedItem = -1;
-    @StringRes private Integer mAddRingtoneItemTitle;
-
-    /** Provides callbacks for the adapter. */
-    interface Callbacks {
-        void onRingtoneSelected(int position);
-        void onAddRingtoneSelected();
-        boolean isWorkRingtone(int position);
-        Drawable getWorkIconDrawable();
-    }
-
-    RingtoneListViewAdapter(Cursor cursor,
-            Callbacks callbacks) {
-        mCursor = cursor;
-        mCallbacks = callbacks;
-        mFixedItemTitles = new ArrayList<>();
-        mRowIDColumn = mCursor != null ? mCursor.getColumnIndex("_id") : -1;
-        setHasStableIds(true);
-    }
-
-    void setSelectedItem(int position) {
-        notifyItemChanged(mSelectedItem);
-        mSelectedItem = position;
-        notifyItemChanged(mSelectedItem);
-    }
-
-    /**
-     * Adds title to the fixed items list and returns the index of the newest added item.
-     * @param textResId the title to add to the fixed items list.
-     * @return The index of the newest added item in the fixed items list.
-     */
-    int addTitleForFixedItem(@StringRes int textResId) {
-        mFixedItemTitles.add(textResId);
-        notifyItemInserted(mFixedItemTitles.size() - 1);
-        return mFixedItemTitles.size() - 1;
-    }
-
-    void addTitleForAddRingtoneItem(@StringRes int textResId) {
-        mAddRingtoneItemTitle = textResId;
-        notifyItemInserted(getItemCount() - 1);
-    }
-
-    @NotNull
-    @Override
-    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
-
-        if (viewType == VIEW_TYPE_FIXED_ITEM) {
-            View fixedItemView = inflater.inflate(
-                    com.android.internal.R.layout.select_dialog_singlechoice_material, parent,
-                    false);
-
-            return new FixedItemViewHolder(fixedItemView, mCallbacks);
-        }
-
-        if (viewType == VIEW_TYPE_ADD_RINGTONE_ITEM) {
-            View addRingtoneItemView = inflater.inflate(R.layout.add_new_sound_item, parent, false);
-
-            return new AddRingtoneItemViewHolder(addRingtoneItemView,
-                    mCallbacks);
-        }
-
-        View view = inflater.inflate(R.layout.radio_with_work_badge, parent, false);
-
-        return new DynamicItemViewHolder(view, mCallbacks);
-    }
-
-    @Override
-    public void onBindViewHolder(@NotNull RecyclerView.ViewHolder holder, int position) {
-        if (holder instanceof FixedItemViewHolder) {
-            FixedItemViewHolder viewHolder = (FixedItemViewHolder) holder;
-
-            viewHolder.onBind(mFixedItemTitles.get(position),
-                    /* isChecked= */ position == mSelectedItem);
-            return;
-        }
-        if (holder instanceof AddRingtoneItemViewHolder) {
-            AddRingtoneItemViewHolder viewHolder = (AddRingtoneItemViewHolder) holder;
-
-            viewHolder.onBind(mAddRingtoneItemTitle);
-            return;
-        }
-
-        if (!(holder instanceof DynamicItemViewHolder)) {
-            throw new IllegalArgumentException("holder type is not supported");
-        }
-
-        DynamicItemViewHolder viewHolder = (DynamicItemViewHolder) holder;
-        int pos = position - mFixedItemTitles.size();
-        if (!mCursor.moveToPosition(pos)) {
-            throw new IllegalStateException("Could not move cursor to position: " + pos);
-        }
-
-        Drawable workIcon = (mCallbacks != null)
-                && mCallbacks.isWorkRingtone(position)
-                ? mCallbacks.getWorkIconDrawable() : null;
-
-        viewHolder.onBind(mCursor.getString(RingtoneManager.TITLE_COLUMN_INDEX),
-                /* isChecked= */ position == mSelectedItem, workIcon);
-    }
-
-    @Override
-    public int getItemViewType(int position) {
-        if (!mFixedItemTitles.isEmpty() && position < mFixedItemTitles.size()) {
-            return VIEW_TYPE_FIXED_ITEM;
-        }
-        if (mAddRingtoneItemTitle != null && position == getItemCount() - 1) {
-            return VIEW_TYPE_ADD_RINGTONE_ITEM;
-        }
-
-        return VIEW_TYPE_DYNAMIC_ITEM;
-    }
-
-    @Override
-    public int getItemCount() {
-        int itemCount = mFixedItemTitles.size() + mCursor.getCount();
-
-        if (mAddRingtoneItemTitle != null) {
-            itemCount++;
-        }
-
-        return itemCount;
-    }
-
-    @Override
-    public long getItemId(int position) {
-        int itemViewType = getItemViewType(position);
-        if (itemViewType == VIEW_TYPE_FIXED_ITEM) {
-            // Since the item is a fixed item, then we can use the position as a stable ID
-            // since the order of the fixed items should never change.
-            return position;
-        }
-        if (itemViewType == VIEW_TYPE_DYNAMIC_ITEM && mCursor != null
-                && mCursor.moveToPosition(position - mFixedItemTitles.size())
-                && mRowIDColumn != -1) {
-            return mCursor.getLong(mRowIDColumn) + mFixedItemTitles.size();
-        }
-
-        // The position is either invalid or the item is the add ringtone item view, so no stable
-        // ID is returned. Add ringtone item view cannot be selected and only include an action
-        // buttons.
-        return NO_ID;
-    }
-
-    private static class DynamicItemViewHolder extends RecyclerView.ViewHolder {
-        private final CheckedTextView mTitleTextView;
-        private final ImageView mWorkIcon;
-
-        DynamicItemViewHolder(View itemView, Callbacks listener) {
-            super(itemView);
-
-            mTitleTextView = itemView.requireViewById(R.id.checked_text_view);
-            mWorkIcon = itemView.requireViewById(R.id.work_icon);
-            itemView.setOnClickListener(v -> listener.onRingtoneSelected(this.getLayoutPosition()));
-        }
-
-        void onBind(String title, boolean isChecked, Drawable workIcon) {
-            mTitleTextView.setText(title);
-            mTitleTextView.setChecked(isChecked);
-
-            if (workIcon == null) {
-                mWorkIcon.setVisibility(View.GONE);
-            } else {
-                mWorkIcon.setImageDrawable(workIcon);
-                mWorkIcon.setVisibility(View.VISIBLE);
-            }
-        }
-    }
-
-    private static class FixedItemViewHolder extends RecyclerView.ViewHolder {
-        private final CheckedTextView mTitleTextView;
-
-        FixedItemViewHolder(View itemView, Callbacks listener) {
-            super(itemView);
-
-            mTitleTextView = (CheckedTextView) itemView;
-            itemView.setOnClickListener(v -> listener.onRingtoneSelected(this.getLayoutPosition()));
-        }
-
-        void onBind(@StringRes int title, boolean isChecked) {
-            Objects.requireNonNull(mTitleTextView);
-
-            mTitleTextView.setText(title);
-            mTitleTextView.setChecked(isChecked);
-        }
-    }
-
-    private static class AddRingtoneItemViewHolder extends RecyclerView.ViewHolder {
-        private final TextView mTitleTextView;
-
-        AddRingtoneItemViewHolder(View itemView, Callbacks listener) {
-            super(itemView);
-
-            mTitleTextView = itemView.requireViewById(R.id.add_new_sound_text);
-            itemView.setOnClickListener(v -> listener.onAddRingtoneSelected());
-        }
-
-        void onBind(@StringRes int title) {
-            mTitleTextView.setText(title);
-        }
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneManagerFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneManagerFactory.java
deleted file mode 100644
index f08eb24..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneManagerFactory.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.soundpicker;
-
-import android.content.Context;
-import android.media.RingtoneManager;
-
-import dagger.hilt.android.qualifiers.ApplicationContext;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * A factory class used to create {@link RingtoneManager}.
- */
-@Singleton
-public class RingtoneManagerFactory {
-
-    private final Context mApplicationContext;
-
-    @Inject
-    RingtoneManagerFactory(@ApplicationContext Context applicationContext) {
-        mApplicationContext = applicationContext;
-    }
-
-    /**
-     * Creates a new {@link RingtoneManager} and returns it.
-     *
-     * @return a {@link RingtoneManager}
-     */
-    public RingtoneManager create() {
-        return new RingtoneManager(mApplicationContext, /* includeParentRingtones */ true);
-    }
-}
-
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java
deleted file mode 100644
index b94ebeb..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java
+++ /dev/null
@@ -1,113 +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.soundpicker;
-
-import android.app.Service;
-import android.content.Intent;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Environment;
-import android.os.FileUtils;
-import android.os.IBinder;
-import android.provider.MediaStore;
-import android.provider.Settings.System;
-import android.util.Log;
-
-import androidx.annotation.IdRes;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Service to copy and set customization of default sounds
- */
-public class RingtoneOverlayService extends Service {
-    private static final String TAG = "RingtoneOverlayService";
-    private static final boolean DEBUG = false;
-
-    @Override
-    public int onStartCommand(@Nullable final Intent intent, final int flags, final int startId) {
-        AsyncTask.execute(() -> {
-            updateRingtones();
-            stopSelf();
-        });
-
-        // Try again later if we are killed before we finish.
-        return Service.START_REDELIVER_INTENT;
-    }
-
-    @Override
-    public IBinder onBind(@Nullable final Intent intent) {
-        return null;
-    }
-
-    private void updateRingtones() {
-        copyResourceAndSetAsSound(R.raw.default_ringtone,
-                System.RINGTONE, Environment.DIRECTORY_RINGTONES);
-        copyResourceAndSetAsSound(R.raw.default_notification_sound,
-                System.NOTIFICATION_SOUND, Environment.DIRECTORY_NOTIFICATIONS);
-        copyResourceAndSetAsSound(R.raw.default_alarm_alert,
-                System.ALARM_ALERT, Environment.DIRECTORY_ALARMS);
-    }
-
-    /* If the resource contains any data, copy a resource to the file system, scan it, and set the
-     * file URI as the default for a sound. */
-    private void copyResourceAndSetAsSound(@IdRes final int id, @NonNull final String name,
-            @NonNull final String subPath) {
-        final File destDir = Environment.getExternalStoragePublicDirectory(subPath);
-        if (!destDir.exists() && !destDir.mkdirs()) {
-            Log.e(TAG, "can't create " + destDir.getAbsolutePath());
-            return;
-        }
-
-        final File dest = new File(destDir, "default_" + name + ".ogg");
-        try (
-                InputStream is = getResources().openRawResource(id);
-                FileOutputStream os = new FileOutputStream(dest);
-        ) {
-            if (is.available() > 0) {
-                FileUtils.copy(is, os);
-                final Uri uri = scanFile(dest);
-                if (uri != null) {
-                    set(name, uri);
-                }
-            } else {
-                // TODO Shall we remove any former copied resource in this case and unset
-                // the defaults if we use this event a second time to clear the data?
-                if (DEBUG) Log.d(TAG, "Resource for " + name + " has no overlay");
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "Unable to open resource for " + name + ": " + e);
-        }
-    }
-
-    private Uri scanFile(@NonNull final File file) {
-        return MediaStore.scanFile(getContentResolver(), file);
-    }
-
-    private void set(@NonNull final String name, @NonNull final Uri uri) {
-        final Uri settingUri = System.getUriFor(name);
-        RingtoneManager.setActualDefaultRingtoneUri(this,
-                RingtoneManager.getDefaultType(settingUri), uri);
-        System.putInt(getContentResolver(), name + "_set", 1);
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java
deleted file mode 100644
index 90a14f9..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.soundpicker;
-
-import android.content.Intent;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.util.Log;
-
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentTransaction;
-import androidx.lifecycle.ViewModelProvider;
-
-import dagger.hilt.android.AndroidEntryPoint;
-
-/**
- * The {@link RingtonePickerActivity} allows the user to choose one from all of the
- * available ringtones. The chosen ringtone's URI will be persisted as a string.
- *
- * @see RingtoneManager#ACTION_RINGTONE_PICKER
- */
-@AndroidEntryPoint(AppCompatActivity.class)
-public final class RingtonePickerActivity extends Hilt_RingtonePickerActivity {
-
-    private static final String TAG = "RingtonePickerActivity";
-    // TODO: Use the extra keys from RingtoneManager once they're added.
-    private static final String EXTRA_RINGTONE_PICKER_CATEGORY = "EXTRA_RINGTONE_PICKER_CATEGORY";
-    private static final String EXTRA_VIBRATION_SHOW_DEFAULT = "EXTRA_VIBRATION_SHOW_DEFAULT";
-    private static final String EXTRA_VIBRATION_DEFAULT_URI = "EXTRA_VIBRATION_DEFAULT_URI";
-    private static final String EXTRA_VIBRATION_SHOW_SILENT = "EXTRA_VIBRATION_SHOW_SILENT";
-    private static final String EXTRA_VIBRATION_EXISTING_URI = "EXTRA_VIBRATION_EXISTING_URI";
-    private static final boolean RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED = false;
-
-    private RingtonePickerViewModel mRingtonePickerViewModel;
-    private int mAttributesFlags;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_ringtone_picker);
-
-        mRingtonePickerViewModel = new ViewModelProvider(this).get(RingtonePickerViewModel.class);
-
-        Intent intent = getIntent();
-        /**
-         * Id of the user to which the ringtone picker should list the ringtones
-         */
-        int pickerUserId = UserHandle.myUserId();
-
-        // Get the types of ringtones to show
-        int ringtoneType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE,
-                RingtonePickerViewModel.RINGTONE_TYPE_UNKNOWN);
-
-        // AudioAttributes flags
-        mAttributesFlags |= intent.getIntExtra(
-                RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
-                0 /*defaultValue == no flags*/);
-
-        boolean showOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons);
-
-        String title = intent.getStringExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
-        if (title == null) {
-            title = getString(RingtonePickerViewModel.getTitleByType(ringtoneType));
-        }
-        String ringtonePickerCategory = intent.getStringExtra(EXTRA_RINGTONE_PICKER_CATEGORY);
-        RingtonePickerViewModel.PickerType pickerType = mapCategoryToPickerType(
-                ringtonePickerCategory);
-
-        RingtoneListHandler.Config soundListConfig = getSoundListConfig(pickerType, intent,
-                ringtoneType);
-        RingtoneListHandler.Config vibrationListConfig = getVibrationListConfig(pickerType, intent);
-
-        RingtonePickerViewModel.Config pickerConfig =
-                new RingtonePickerViewModel.Config(title, pickerUserId, ringtoneType,
-                        showOkCancelButtons, mAttributesFlags, pickerType);
-
-        mRingtonePickerViewModel.init(pickerConfig, soundListConfig, vibrationListConfig);
-
-        if (savedInstanceState == null) {
-            TabbedDialogFragment dialogFragment = new TabbedDialogFragment();
-
-            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
-            Fragment prev = getSupportFragmentManager().findFragmentByTag(TabbedDialogFragment.TAG);
-            if (prev != null) {
-                ft.remove(prev);
-            }
-            ft.addToBackStack(null);
-            dialogFragment.show(ft, TabbedDialogFragment.TAG);
-        }
-
-        // The volume keys will control the stream that we are choosing a ringtone for
-        setVolumeControlStream(mRingtonePickerViewModel.getRingtoneStreamType());
-    }
-
-    private RingtoneListHandler.Config getSoundListConfig(
-            RingtonePickerViewModel.PickerType pickerType, Intent intent, int ringtoneType) {
-        if (pickerType != RingtonePickerViewModel.PickerType.SOUND_PICKER
-                && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
-            // This ringtone picker does not require a sound picker.
-            return null;
-        }
-
-        // Get whether to show the 'Default' sound item, and the URI to play when it's clicked
-        boolean hasDefaultSoundItem =
-                intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
-
-        // The Uri to play when the 'Default' sound item is clicked.
-        Uri uriForDefaultSoundItem =
-                intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
-        if (uriForDefaultSoundItem == null) {
-            uriForDefaultSoundItem = RingtonePickerViewModel.getDefaultItemUriByType(ringtoneType);
-        }
-
-        // Get whether this list has the 'Silent' sound item.
-        boolean hasSilentSoundItem =
-                intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
-
-        // AudioAttributes flags
-        mAttributesFlags |= intent.getIntExtra(
-                RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
-                0 /*defaultValue == no flags*/);
-
-        // Get the sound URI whose list item should have a checkmark
-        Uri existingSoundUri = intent
-                .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
-
-        return new RingtoneListHandler.Config(hasDefaultSoundItem,
-                uriForDefaultSoundItem, hasSilentSoundItem, existingSoundUri);
-    }
-
-    private RingtoneListHandler.Config getVibrationListConfig(
-            RingtonePickerViewModel.PickerType pickerType, Intent intent) {
-        if (pickerType != RingtonePickerViewModel.PickerType.VIBRATION_PICKER
-                && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
-            // This ringtone picker does not require a vibration picker.
-            return null;
-        }
-
-        // Get whether to show the 'Default' vibration item, and the URI to play when it's clicked
-        boolean hasDefaultVibrationItem =
-                intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_DEFAULT, false);
-
-        // The Uri to play when the 'Default' vibration item is clicked.
-        Uri uriForDefaultVibrationItem = intent.getParcelableExtra(EXTRA_VIBRATION_DEFAULT_URI);
-
-        // Get whether this list has the 'Silent' vibration item.
-        boolean hasSilentVibrationItem =
-                intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_SILENT, true);
-
-        // Get the vibration URI whose list item should have a checkmark
-        Uri existingVibrationUri = intent.getParcelableExtra(EXTRA_VIBRATION_EXISTING_URI);
-
-        return new RingtoneListHandler.Config(
-                hasDefaultVibrationItem, uriForDefaultVibrationItem, hasSilentVibrationItem,
-                existingVibrationUri);
-    }
-
-    @Override
-    public void onDestroy() {
-        mRingtonePickerViewModel.cancelPendingAsyncTasks();
-        super.onDestroy();
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-        mRingtonePickerViewModel.onStop(isChangingConfigurations());
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-        mRingtonePickerViewModel.onPause(isChangingConfigurations());
-    }
-
-    /**
-     * Maps the ringtone picker category to the appropriate PickerType.
-     * If the category is null or the feature is still not released, then it defaults to sound
-     * picker.
-     *
-     * @param category the ringtone picker category.
-     * @return the corresponding picker type.
-     */
-    private static RingtonePickerViewModel.PickerType mapCategoryToPickerType(String category) {
-        if (category == null || !RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED) {
-            return RingtonePickerViewModel.PickerType.SOUND_PICKER;
-        }
-
-        switch (category) {
-            case "android.intent.category.RINGTONE_PICKER_RINGTONE":
-                return RingtonePickerViewModel.PickerType.RINGTONE_PICKER;
-            case "android.intent.category.RINGTONE_PICKER_SOUND":
-                return RingtonePickerViewModel.PickerType.SOUND_PICKER;
-            case "android.intent.category.RINGTONE_PICKER_VIBRATION":
-                return RingtonePickerViewModel.PickerType.VIBRATION_PICKER;
-            default:
-                Log.w(TAG, "Unrecognized category: " + category + ". Defaulting to sound picker.");
-                return RingtonePickerViewModel.PickerType.SOUND_PICKER;
-        }
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerViewModel.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerViewModel.java
deleted file mode 100644
index 2c09711..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerViewModel.java
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- * 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.soundpicker;
-
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.Nullable;
-import android.annotation.StringRes;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.provider.Settings;
-
-import androidx.annotation.NonNull;
-import androidx.lifecycle.ViewModel;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-
-import dagger.hilt.android.lifecycle.HiltViewModel;
-
-import java.io.IOException;
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-
-/**
- * A view model which holds immutable info about the picker state and means to retrieve and play
- * currently selected ringtones.
- */
-@HiltViewModel
-public final class RingtonePickerViewModel extends ViewModel {
-
-    static final int RINGTONE_TYPE_UNKNOWN = -1;
-
-    /**
-     * Keep the currently playing ringtone around when changing orientation, so that it
-     * can be stopped later, after the activity is recreated.
-     */
-    @VisibleForTesting
-    static Ringtone sPlayingRingtone;
-
-    private static final String TAG = "RingtonePickerViewModel";
-
-    private final RingtoneManagerFactory mRingtoneManagerFactory;
-    private final RingtoneFactory mRingtoneFactory;
-    private final RingtoneListHandler mSoundListHandler;
-    private final RingtoneListHandler mVibrationListHandler;
-    private final ListeningExecutorService mListeningExecutorService;
-
-    private RingtoneManager mRingtoneManager;
-
-    /**
-     * The ringtone that's currently playing.
-     */
-    private Ringtone mCurrentRingtone;
-
-    private Config mPickerConfig;
-
-    private ListenableFuture<Uri> mAddCustomRingtoneFuture;
-
-    public enum PickerType {
-        RINGTONE_PICKER,
-        SOUND_PICKER,
-        VIBRATION_PICKER
-    }
-
-    /**
-     * Holds immutable info on the picker that should be displayed.
-     */
-    static final class Config {
-        public final String title;
-        /**
-         * Id of the user to which the ringtone picker should list the ringtones.
-         */
-        public final int userId;
-        /**
-         * Ringtone type.
-         */
-        public final int ringtoneType;
-        /**
-         * AudioAttributes flags.
-         */
-        public final int audioAttributesFlags;
-        /**
-         * In the buttonless (watch-only) version we don't show the OK/Cancel buttons.
-         */
-        public final boolean showOkCancelButtons;
-
-        public final PickerType mPickerType;
-
-        Config(String title, int userId, int ringtoneType, boolean showOkCancelButtons,
-                int audioAttributesFlags, PickerType pickerType) {
-            this.title = title;
-            this.userId = userId;
-            this.ringtoneType = ringtoneType;
-            this.showOkCancelButtons = showOkCancelButtons;
-            this.audioAttributesFlags = audioAttributesFlags;
-            this.mPickerType = pickerType;
-        }
-    }
-
-    @Inject
-    RingtonePickerViewModel(RingtoneManagerFactory ringtoneManagerFactory,
-            RingtoneFactory ringtoneFactory,
-            ListeningExecutorServiceFactory listeningExecutorServiceFactory,
-            RingtoneListHandler soundListHandler,
-            RingtoneListHandler vibrationListHandler) {
-        mRingtoneManagerFactory = ringtoneManagerFactory;
-        mRingtoneFactory = ringtoneFactory;
-        mListeningExecutorService = listeningExecutorServiceFactory.createSingleThreadExecutor();
-        mSoundListHandler = soundListHandler;
-        mVibrationListHandler = vibrationListHandler;
-    }
-
-    @StringRes
-    static int getTitleByType(int ringtoneType) {
-        switch (ringtoneType) {
-            case RingtoneManager.TYPE_ALARM:
-                return com.android.internal.R.string.ringtone_picker_title_alarm;
-            case RingtoneManager.TYPE_NOTIFICATION:
-                return com.android.internal.R.string.ringtone_picker_title_notification;
-            default:
-                return com.android.internal.R.string.ringtone_picker_title;
-        }
-    }
-
-    static Uri getDefaultItemUriByType(int ringtoneType) {
-        switch (ringtoneType) {
-            case RingtoneManager.TYPE_ALARM:
-                return Settings.System.DEFAULT_ALARM_ALERT_URI;
-            case RingtoneManager.TYPE_NOTIFICATION:
-                return Settings.System.DEFAULT_NOTIFICATION_URI;
-            default:
-                return Settings.System.DEFAULT_RINGTONE_URI;
-        }
-    }
-
-    @StringRes
-    static int getAddNewItemTextByType(int ringtoneType) {
-        switch (ringtoneType) {
-            case RingtoneManager.TYPE_ALARM:
-                return R.string.add_alarm_text;
-            case RingtoneManager.TYPE_NOTIFICATION:
-                return R.string.add_notification_text;
-            default:
-                return R.string.add_ringtone_text;
-        }
-    }
-
-    @StringRes
-    static int getDefaultRingtoneItemTextByType(int ringtoneType) {
-        switch (ringtoneType) {
-            case RingtoneManager.TYPE_ALARM:
-                return R.string.alarm_sound_default;
-            case RingtoneManager.TYPE_NOTIFICATION:
-                return R.string.notification_sound_default;
-            default:
-                return R.string.ringtone_default;
-        }
-    }
-
-    void init(@NonNull Config pickerConfig,
-            RingtoneListHandler.Config soundListConfig,
-            RingtoneListHandler.Config vibrationListConfig) {
-        mRingtoneManager = mRingtoneManagerFactory.create();
-        mPickerConfig = pickerConfig;
-        if (mPickerConfig.ringtoneType != RINGTONE_TYPE_UNKNOWN) {
-            mRingtoneManager.setType(mPickerConfig.ringtoneType);
-        }
-        if (soundListConfig != null) {
-            mSoundListHandler.init(soundListConfig, mRingtoneManager,
-                    mRingtoneManager.getCursor());
-        }
-        if (vibrationListConfig != null) {
-            // TODO: Switch to the vibration cursor, once the API is made available.
-            mVibrationListHandler.init(vibrationListConfig, mRingtoneManager,
-                    mRingtoneManager.getCursor());
-        }
-    }
-
-    /**
-     * Re-initializes the view model which is required after updating any of the picker lists.
-     * This could happen when adding a custom ringtone.
-     */
-    void reinit() {
-        init(mPickerConfig, mSoundListHandler.getRingtoneListConfig(),
-                mVibrationListHandler.getRingtoneListConfig());
-    }
-
-    @NonNull
-    Config getPickerConfig() {
-        requireInitCalled();
-        return mPickerConfig;
-    }
-
-    @NonNull
-    RingtoneListHandler getSoundListHandler() {
-        return mSoundListHandler;
-    }
-
-    @NonNull
-    RingtoneListHandler getVibrationListHandler() {
-        return mVibrationListHandler;
-    }
-
-    /**
-     * Combined the currently selected sound and vibration URIs and returns a unified URI. If the
-     * picker does not show either sound or vibration, that portion of the URI will be null.
-     *
-     * Currently only the sound URI is returned, since we don't have the API to retrieve vibrations
-     * yet.
-     * @return Combined sound and vibration URI.
-     */
-    Uri getSelectedRingtoneUri() {
-        // TODO: Combine sound and vibration URIs before returning.
-        return mSoundListHandler.getSelectedRingtoneUri();
-    }
-
-    int getRingtoneStreamType() {
-        requireInitCalled();
-        return mRingtoneManager.inferStreamType();
-    }
-
-    void onPause(boolean isChangingConfigurations) {
-        if (!isChangingConfigurations) {
-            stopAnyPlayingRingtone();
-        }
-    }
-
-    void onStop(boolean isChangingConfigurations) {
-        if (isChangingConfigurations) {
-            saveAnyPlayingRingtone();
-        } else {
-            stopAnyPlayingRingtone();
-        }
-    }
-
-    /**
-     * Plays a ringtone which is created using the currently selected sound and vibration URIs. If
-     * this is a sound or vibration only picker, then the other portion of the URI will be empty
-     * and should not affect the played ringtone.
-     *
-     * Currently, we only use the sound URI to create the ringtone, since we still don't have the
-     * API to retrieve the available vibrations list.
-     */
-    void playRingtone() {
-        requireInitCalled();
-        stopAnyPlayingRingtone();
-
-        mCurrentRingtone = mRingtoneFactory.create(getSelectedRingtoneUri(),
-                mPickerConfig.audioAttributesFlags);
-
-        if (mCurrentRingtone != null) {
-            mCurrentRingtone.play();
-        }
-    }
-
-    /**
-     * Cancels all pending async tasks.
-     */
-    void cancelPendingAsyncTasks() {
-        if (mAddCustomRingtoneFuture != null && !mAddCustomRingtoneFuture.isDone()) {
-            mAddCustomRingtoneFuture.cancel(/* mayInterruptIfRunning= */ true);
-        }
-    }
-
-    /**
-     * Adds an audio file to the list of ringtones asynchronously.
-     * Any previous async tasks are canceled before start the new one.
-     *
-     * @param uri      Uri of the file to be added as ringtone. Must be a media file.
-     * @param type     The type of the ringtone to be added.
-     * @param callback The callback to invoke when the task is completed.
-     * @param executor The executor to run the callback on when the task completes.
-     */
-    void addSoundRingtoneAsync(Uri uri, int type, FutureCallback<Uri> callback, Executor executor) {
-        // Cancel any currently running add ringtone tasks before starting a new one
-        cancelPendingAsyncTasks();
-        mAddCustomRingtoneFuture = mListeningExecutorService.submit(
-                () -> addRingtone(uri, type));
-        Futures.addCallback(mAddCustomRingtoneFuture, callback, executor);
-    }
-
-    /**
-     * Adds an audio file to the list of ringtones.
-     *
-     * @param uri  Uri of the file to be added as ringtone. Must be a media file.
-     * @param type The type of the ringtone to be added.
-     * @return The Uri of the installed ringtone, which may be the {@code uri} if it
-     * is already in ringtone storage. Or null if it failed to add the audio file.
-     */
-    @Nullable
-    private Uri addRingtone(Uri uri, int type) throws IOException {
-        requireInitCalled();
-        return mRingtoneManager.addCustomExternalRingtone(uri, type);
-    }
-
-    private void saveAnyPlayingRingtone() {
-        if (mCurrentRingtone != null && mCurrentRingtone.isPlaying()) {
-            sPlayingRingtone = mCurrentRingtone;
-        }
-        mCurrentRingtone = null;
-    }
-
-    private void stopAnyPlayingRingtone() {
-        if (sPlayingRingtone != null && sPlayingRingtone.isPlaying()) {
-            sPlayingRingtone.stop();
-        }
-        sPlayingRingtone = null;
-
-        if (mCurrentRingtone != null && mCurrentRingtone.isPlaying()) {
-            mCurrentRingtone.stop();
-        }
-        mCurrentRingtone = null;
-    }
-
-    private void requireInitCalled() {
-        requireNonNull(mRingtoneManager);
-        requireNonNull(mPickerConfig);
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java
deleted file mode 100644
index 6a34936..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.soundpicker;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-public class RingtoneReceiver extends BroadcastReceiver {
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        final String action = intent.getAction();
-        if (Intent.ACTION_DEVICE_CUSTOMIZATION_READY.equals(action)) {
-            initResourceRingtones(context);
-        }
-    }
-
-    private void initResourceRingtones(Context context) {
-        context.startService(
-                new Intent(context, RingtoneOverlayService.class));
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/SoundPickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/SoundPickerFragment.java
deleted file mode 100644
index a37191f..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/SoundPickerFragment.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * 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.soundpicker;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Environment;
-import android.util.Log;
-import android.view.View;
-import android.widget.Toast;
-
-import androidx.activity.result.ActivityResult;
-import androidx.activity.result.ActivityResultCallback;
-import androidx.activity.result.ActivityResultLauncher;
-import androidx.activity.result.contract.ActivityResultContracts;
-import androidx.core.content.ContextCompat;
-import androidx.lifecycle.ViewModelProvider;
-
-import com.google.common.util.concurrent.FutureCallback;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * A fragment that displays a picker used to select sound or silent. It also includes the
- * ability to add custom sounds.
- */
-public class SoundPickerFragment extends BasePickerFragment {
-
-    private static final String TAG = "SoundPickerFragment";
-
-    private final FutureCallback<Uri> mAddCustomRingtoneCallback = new FutureCallback<>() {
-        @Override
-        public void onSuccess(Uri ringtoneUri) {
-            requeryForAdapter();
-        }
-
-        @Override
-        public void onFailure(Throwable throwable) {
-            Log.e(TAG, "Failed to add custom ringtone.", throwable);
-            // Ringtone was not added, display error Toast
-            Toast.makeText(requireActivity().getApplicationContext(),
-                    R.string.unable_to_add_ringtone, Toast.LENGTH_SHORT).show();
-        }
-    };
-
-    ActivityResultLauncher<Intent> mActivityResultLauncher = registerForActivityResult(
-            new ActivityResultContracts.StartActivityForResult(),
-            new ActivityResultCallback<ActivityResult>() {
-                @Override
-                public void onActivityResult(ActivityResult result) {
-                    if (result.getResultCode() == Activity.RESULT_OK) {
-                        // There are no request codes
-                        Intent data = result.getData();
-                        mRingtonePickerViewModel.addSoundRingtoneAsync(data.getData(),
-                                mPickerConfig.ringtoneType,
-                                mAddCustomRingtoneCallback,
-                                // Causes the callback to be executed on the main thread.
-                                ContextCompat.getMainExecutor(
-                                        requireActivity().getApplicationContext()));
-                    }
-                }
-            });
-
-    @Override
-    public void onViewCreated(@NotNull View view, Bundle savedInstanceState) {
-        mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
-                RingtonePickerViewModel.class);
-        super.onViewCreated(view, savedInstanceState);
-    }
-
-    @Override
-    protected RingtoneListHandler getRingtoneListHandler() {
-        return mRingtonePickerViewModel.getSoundListHandler();
-    }
-
-    @Override
-    protected void addRingtoneAsync() {
-        // The "Add new ringtone" item was clicked. Start a file picker intent to
-        // select only audio files (MIME type "audio/*")
-        final Intent chooseFile = getMediaFilePickerIntent();
-        mActivityResultLauncher.launch(chooseFile);
-    }
-
-    @Override
-    protected void addNewRingtoneItem() {
-        // If external storage is available, add a button to install sounds from storage.
-        if (resolvesMediaFilePicker()
-                && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
-            mRingtoneListViewAdapter.addTitleForAddRingtoneItem(
-                    RingtonePickerViewModel.getAddNewItemTextByType(mPickerConfig.ringtoneType));
-        }
-    }
-
-    private boolean resolvesMediaFilePicker() {
-        return getMediaFilePickerIntent().resolveActivity(requireActivity().getPackageManager())
-                != null;
-    }
-
-    private Intent getMediaFilePickerIntent() {
-        final Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
-        chooseFile.setType("audio/*");
-        chooseFile.putExtra(Intent.EXTRA_MIME_TYPES,
-                new String[]{"audio/*", "application/ogg"});
-        return chooseFile;
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/TabbedDialogFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/TabbedDialogFragment.java
deleted file mode 100644
index 50ea9d7..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/TabbedDialogFragment.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * 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.soundpicker;
-
-import static android.app.Activity.RESULT_CANCELED;
-
-import android.app.Activity;
-import android.app.Dialog;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.media.RingtoneManager;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.DialogFragment;
-import androidx.lifecycle.ViewModelProvider;
-import androidx.viewpager2.widget.ViewPager2;
-
-import com.google.android.material.tabs.TabLayout;
-import com.google.android.material.tabs.TabLayoutMediator;
-
-import dagger.hilt.android.AndroidEntryPoint;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * A dialog fragment with a sound and/or vibration tab based on the picker type.
- * <ul>
- * <li> Ringtone Pickers will display both sound and vibration tabs.
- * <li> Sound Pickers will only display the sound tab.
- * <li> Vibration Pickers will only display the vibration tab.
- * </ul>
- */
-@AndroidEntryPoint(DialogFragment.class)
-public class TabbedDialogFragment extends Hilt_TabbedDialogFragment {
-
-    static final String TAG = "TabbedDialogFragment";
-
-    private RingtonePickerViewModel mRingtonePickerViewModel;
-
-    private final ViewPager2.OnPageChangeCallback mOnPageChangeCallback =
-            new ViewPager2.OnPageChangeCallback() {
-                @Override
-                public void onPageScrollStateChanged(int state) {
-                    super.onPageScrollStateChanged(state);
-                    if (state == ViewPager2.SCROLL_STATE_IDLE) {
-                        mRingtonePickerViewModel.onStop(/* isChangingConfigurations= */ false);
-                    }
-                }
-            };
-
-    @Override
-    public void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
-                RingtonePickerViewModel.class);
-    }
-
-    @NonNull
-    @Override
-    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
-        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity(),
-                android.R.style.ThemeOverlay_Material_Dialog)
-                .setTitle(mRingtonePickerViewModel.getPickerConfig().title);
-        // Do not show OK/Cancel buttons in the buttonless (watch-only) version.
-        if (mRingtonePickerViewModel.getPickerConfig().showOkCancelButtons) {
-            dialogBuilder
-                    .setPositiveButton(getString(com.android.internal.R.string.ok),
-                            (dialog, whichButton) -> {
-                                setSuccessResultWithSelectedRingtone();
-                                requireActivity().finish();
-                            })
-                    .setNegativeButton(getString(com.android.internal.R.string.cancel),
-                            (dialog, whichButton) -> {
-                                requireActivity().setResult(RESULT_CANCELED);
-                                requireActivity().finish();
-                            });
-        }
-
-        View view = buildTabbedView(requireActivity().getLayoutInflater());
-        dialogBuilder.setView(view);
-
-        return dialogBuilder.create();
-    }
-
-    @Override
-    public void onCancel(@NonNull @NotNull DialogInterface dialog) {
-        super.onCancel(dialog);
-        if (!requireActivity().isChangingConfigurations()) {
-            requireActivity().finish();
-        }
-    }
-
-    @Override
-    public void onDismiss(@NonNull @NotNull DialogInterface dialog) {
-        super.onDismiss(dialog);
-        if (!requireActivity().isChangingConfigurations()) {
-            requireActivity().finish();
-        }
-    }
-
-    private void setSuccessResultWithSelectedRingtone() {
-        requireActivity().setResult(Activity.RESULT_OK,
-                new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI,
-                        mRingtonePickerViewModel.getSelectedRingtoneUri()));
-    }
-
-    /**
-     * Inflates the tabbed layout view and adds the required fragments. If there's only one
-     * fragment to display, then the tab area is hidden.
-     * @param inflater The LayoutInflater that is used to inflate the tabbed view.
-     * @return The tabbed view.
-     */
-    private View buildTabbedView(@NonNull LayoutInflater inflater) {
-        View view = inflater.inflate(R.layout.fragment_tabbed_dialog, null, false);
-        TabLayout tabLayout = view.requireViewById(R.id.tabLayout);
-        ViewPager2 viewPager = view.requireViewById(R.id.masterViewPager);
-
-        ViewPagerAdapter adapter = new ViewPagerAdapter(requireActivity());
-        addFragments(adapter);
-
-        if (adapter.getItemCount() == 1) {
-            // Hide the tab area since there's only one fragment to display.
-            tabLayout.setVisibility(View.GONE);
-        }
-
-        viewPager.setAdapter(adapter);
-        viewPager.registerOnPageChangeCallback(mOnPageChangeCallback);
-        new TabLayoutMediator(tabLayout, viewPager,
-                (tab, position) -> tab.setText(adapter.getTitle(position))).attach();
-
-        return view;
-    }
-
-    /**
-     * Adds the appropriate fragments to the adapter based on the PickerType.
-     *
-     * @param adapter The adapter to add the fragments to.
-     */
-    private void addFragments(ViewPagerAdapter adapter) {
-        switch (mRingtonePickerViewModel.getPickerConfig().mPickerType) {
-            case RINGTONE_PICKER:
-                adapter.addFragment(getString(R.string.sound_page_title),
-                        new SoundPickerFragment());
-                adapter.addFragment(getString(R.string.vibration_page_title),
-                        new VibrationPickerFragment());
-                break;
-            case SOUND_PICKER:
-                adapter.addFragment(getString(R.string.sound_page_title),
-                        new SoundPickerFragment());
-                break;
-            case VIBRATION_PICKER:
-                adapter.addFragment(getString(R.string.vibration_page_title),
-                        new VibrationPickerFragment());
-                break;
-            default:
-                adapter.addFragment(getString(R.string.sound_page_title),
-                        new SoundPickerFragment());
-                break;
-        }
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/VibrationPickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/VibrationPickerFragment.java
deleted file mode 100644
index 7412c19..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/VibrationPickerFragment.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.soundpicker;
-
-import android.os.Bundle;
-import android.view.View;
-
-import androidx.lifecycle.ViewModelProvider;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * A fragment that displays a picker used to select vibration or silent (no vibration).
- */
-public class VibrationPickerFragment extends BasePickerFragment {
-
-    @Override
-    public void onViewCreated(@NotNull View view, Bundle savedInstanceState) {
-        mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
-                RingtonePickerViewModel.class);
-        super.onViewCreated(view, savedInstanceState);
-    }
-
-    @Override
-    protected RingtoneListHandler getRingtoneListHandler() {
-        return mRingtonePickerViewModel.getVibrationListHandler();
-    }
-
-    @Override
-    protected void addRingtoneAsync() {
-        // no-op
-    }
-
-    @Override
-    protected void addNewRingtoneItem() {
-        // no-op
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/ViewPagerAdapter.java b/packages/SoundPicker2/src/com/android/soundpicker/ViewPagerAdapter.java
deleted file mode 100644
index 179068e..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/ViewPagerAdapter.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.soundpicker;
-
-import androidx.annotation.NonNull;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentActivity;
-import androidx.viewpager2.adapter.FragmentStateAdapter;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * An adapter used to populate pages inside a ViewPager.
- */
-public class ViewPagerAdapter extends FragmentStateAdapter {
-
-    private final List<Fragment> mFragments = new ArrayList<>();
-    private final List<String> mTitles = new ArrayList<>();
-
-    public ViewPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
-        super(fragmentActivity);
-    }
-
-    /**
-     * Adds a fragment and page title to the adapter.
-     * @param title the title of the page in the ViewPager.
-     * @param fragment the fragment that will be inflated on this page.
-     */
-    public void addFragment(String title, Fragment fragment) {
-        mTitles.add(title);
-        mFragments.add(fragment);
-    }
-
-    /**
-     * Returns the title of the requested page.
-     * @param position the position of the page in the Viewpager.
-     * @return The title of the requested page.
-     */
-    public String getTitle(int position) {
-        return mTitles.get(position);
-    }
-
-    @NonNull
-    @Override
-    public Fragment createFragment(int position) {
-        return Objects.requireNonNull(mFragments.get(position),
-                "Could not find a fragment using position: " + position);
-    }
-
-    @Override
-    public int getItemCount() {
-        return mFragments.size();
-    }
-}
diff --git a/packages/SoundPicker2/tests/Android.bp b/packages/SoundPicker2/tests/Android.bp
deleted file mode 100644
index d88d442..0000000
--- a/packages/SoundPicker2/tests/Android.bp
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2023, The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test {
-    name: "SoundPicker2Tests",
-    certificate: "platform",
-    libs: [
-        "android.test.runner",
-        "android.test.base",
-    ],
-    static_libs: [
-        "androidx.test.core",
-        "androidx.test.rules",
-        "androidx.test.ext.junit",
-        "androidx.test.ext.truth",
-        "mockito-target-minus-junit4",
-        "guava-android-testlib",
-        "SoundPicker2Lib",
-    ],
-    srcs: [
-        "src/**/*.java",
-    ],
-}
diff --git a/packages/SoundPicker2/tests/AndroidManifest.xml b/packages/SoundPicker2/tests/AndroidManifest.xml
deleted file mode 100644
index 295aeb1..0000000
--- a/packages/SoundPicker2/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.soundpicker.tests">
-
-    <application android:debuggable="true">
-        <uses-library android:name="android.test.runner" />
-    </application>
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.soundpicker.tests"
-        android:label="Sound picker tests">
-    </instrumentation>
-</manifest>
diff --git a/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java b/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java
deleted file mode 100644
index 80e71e200..0000000
--- a/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * 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.soundpicker;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.when;
-
-import android.database.Cursor;
-import android.media.RingtoneManager;
-import android.net.Uri;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-public class RingtoneListHandlerTest {
-
-    private static final Uri DEFAULT_URI = Uri.parse("media://custom/ringtone/default_uri");
-    private static final Uri RINGTONE_URI = Uri.parse("media://custom/ringtone/uri");
-    private static final int SILENT_RINGTONE_POSITION = 0;
-    private static final int DEFAULT_RINGTONE_POSITION = 1;
-    private static final int RINGTONE_POSITION = 2;
-
-    @Mock
-    private RingtoneManager mMockRingtoneManager;
-    @Mock
-    private Cursor mMockCursor;
-
-    private RingtoneListHandler mRingtoneListHandler;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        RingtoneListHandler.Config mRingtoneListConfig = createRingtoneListConfig();
-
-        mRingtoneListHandler = new RingtoneListHandler();
-
-        // Add silent and default options to the list.
-        mRingtoneListHandler.addSilentItem();
-        mRingtoneListHandler.addDefaultItem();
-
-        mRingtoneListHandler.init(mRingtoneListConfig, mMockRingtoneManager, mMockCursor);
-    }
-
-    @Test
-    public void testGetRingtoneCursor_returnsTheCorrectRingtoneCursor() {
-        assertThat(mRingtoneListHandler.getRingtoneCursor()).isEqualTo(mMockCursor);
-    }
-
-    @Test
-    public void testGetRingtoneUri_returnsTheCorrectRingtoneUri() {
-        Uri expectedUri = RINGTONE_URI;
-        when(mMockRingtoneManager.getRingtoneUri(eq(0))).thenReturn(expectedUri);
-
-        // Request 3rd item from list.
-        Uri actualUri = mRingtoneListHandler.getRingtoneUri(RINGTONE_POSITION);
-        assertThat(actualUri).isEqualTo(expectedUri);
-    }
-
-    @Test
-    public void testGetRingtoneUri_withSelectedItemUnknown_returnsTheCorrectRingtoneUri() {
-        Uri uri = mRingtoneListHandler.getRingtoneUri(RingtoneListHandler.ITEM_POSITION_UNKNOWN);
-        assertThat(uri).isEqualTo(RingtoneListHandler.SILENT_URI);
-    }
-
-    @Test
-    public void testGetRingtoneUri_withSelectedItemDefaultPosition_returnsTheCorrectRingtoneUri() {
-        Uri actualUri = mRingtoneListHandler.getRingtoneUri(DEFAULT_RINGTONE_POSITION);
-        assertThat(actualUri).isEqualTo(DEFAULT_URI);
-    }
-
-    @Test
-    public void testGetRingtoneUri_withSelectedItemSilentPosition_returnsTheCorrectRingtoneUri() {
-        Uri uri = mRingtoneListHandler.getRingtoneUri(SILENT_RINGTONE_POSITION);
-        assertThat(uri).isEqualTo(RingtoneListHandler.SILENT_URI);
-    }
-
-    @Test
-    public void testGetCurrentlySelectedRingtoneUri_returnsTheCorrectRingtoneUri() {
-        mRingtoneListHandler.setSelectedItemPosition(RingtoneListHandler.ITEM_POSITION_UNKNOWN);
-        Uri actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
-        assertThat(actualUri).isEqualTo(RingtoneListHandler.SILENT_URI);
-
-        mRingtoneListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
-        actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
-        assertThat(actualUri).isEqualTo(DEFAULT_URI);
-
-        mRingtoneListHandler.setSelectedItemPosition(SILENT_RINGTONE_POSITION);
-        actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
-        assertThat(actualUri).isEqualTo(RingtoneListHandler.SILENT_URI);
-
-        when(mMockRingtoneManager.getRingtoneUri(eq(0))).thenReturn(RINGTONE_URI);
-        mRingtoneListHandler.setSelectedItemPosition(RINGTONE_POSITION);
-        actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
-        assertThat(actualUri).isEqualTo(RINGTONE_URI);
-    }
-
-    @Test
-    public void testGetRingtonePosition_returnsTheCorrectRingtonePosition() {
-        when(mMockRingtoneManager.getRingtonePosition(RINGTONE_URI)).thenReturn(0);
-
-        int actualPosition = mRingtoneListHandler.getRingtonePosition(RINGTONE_URI);
-
-        assertThat(actualPosition).isEqualTo(RINGTONE_POSITION);
-
-    }
-
-    @Test
-    public void testFixedItems_onlyAddsItemsOnceAndInOrder() {
-        // Clear fixed items before testing the add methods.
-        mRingtoneListHandler.resetFixedItems();
-
-        assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
-                RingtoneListHandler.ITEM_POSITION_UNKNOWN);
-        assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
-                RingtoneListHandler.ITEM_POSITION_UNKNOWN);
-
-        mRingtoneListHandler.addSilentItem();
-        mRingtoneListHandler.addDefaultItem();
-        mRingtoneListHandler.addSilentItem();
-        mRingtoneListHandler.addDefaultItem();
-
-        assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
-                SILENT_RINGTONE_POSITION);
-        assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
-                DEFAULT_RINGTONE_POSITION);
-    }
-
-    @Test
-    public void testResetFixedItems_resetsSilentAndDefaultItemPositions() {
-        assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
-                SILENT_RINGTONE_POSITION);
-        assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
-                DEFAULT_RINGTONE_POSITION);
-
-        mRingtoneListHandler.resetFixedItems();
-
-        assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
-                RingtoneListHandler.ITEM_POSITION_UNKNOWN);
-        assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
-                RingtoneListHandler.ITEM_POSITION_UNKNOWN);
-    }
-
-    private RingtoneListHandler.Config createRingtoneListConfig() {
-        return new RingtoneListHandler.Config(/* hasDefaultItem= */ true,
-                /* uriForDefaultItem= */ DEFAULT_URI, /* hasSilentItem= */ true,
-                /* existingUri= */ DEFAULT_URI);
-    }
-}
diff --git a/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java b/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
deleted file mode 100644
index cde6c76..0000000
--- a/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
+++ /dev/null
@@ -1,534 +0,0 @@
-/*
- * 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.soundpicker;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.database.Cursor;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.provider.Settings;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.testing.TestingExecutors;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.IOException;
-import java.util.concurrent.ExecutorService;
-
-@RunWith(AndroidJUnit4.class)
-public class RingtonePickerViewModelTest {
-
-    private static final Uri DEFAULT_URI = Uri.parse("media://custom/ringtone/default_uri");
-    private static final Uri RINGTONE_URI = Uri.parse("media://custom/ringtone/uri");
-    private static final int RINGTONE_TYPE_UNKNOWN = -1;
-    private static final int DEFAULT_RINGTONE_POSITION = 1;
-
-    @Mock
-    private RingtoneManagerFactory mMockRingtoneManagerFactory;
-    @Mock
-    private RingtoneFactory mMockRingtoneFactory;
-    @Mock
-    private RingtoneManager mMockRingtoneManager;
-    @Mock
-    private ListeningExecutorServiceFactory mMockListeningExecutorServiceFactory;
-    @Mock
-    private Cursor mMockCursor;
-
-    private RingtoneListHandler mSoundListHandler;
-    private RingtoneListHandler mVibrationListHandler;
-    private ExecutorService mMainThreadExecutor;
-    private ListeningExecutorService mBackgroundThreadExecutor;
-    private Ringtone mMockDefaultRingtone;
-    private Ringtone mMockRingtone;
-    private RingtonePickerViewModel mViewModel;
-    private RingtoneListHandler.Config mSoundListConfig;
-    private RingtoneListHandler.Config mVibrationListConfig;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mSoundListHandler = new RingtoneListHandler();
-        mVibrationListHandler = new RingtoneListHandler();
-        mSoundListConfig = createRingtoneListConfig();
-        mVibrationListConfig = createRingtoneListConfig();
-        mMockDefaultRingtone = createMockRingtone();
-        mMockRingtone = createMockRingtone();
-        when(mMockRingtoneManagerFactory.create()).thenReturn(mMockRingtoneManager);
-        when(mMockRingtoneFactory.create(DEFAULT_URI,
-                AudioAttributes.FLAG_AUDIBILITY_ENFORCED)).thenReturn(mMockDefaultRingtone);
-        when(mMockRingtoneManager.getRingtoneUri(anyInt())).thenReturn(RINGTONE_URI);
-        when(mMockRingtoneManager.getCursor()).thenReturn(mMockCursor);
-        mMainThreadExecutor = TestingExecutors.sameThreadScheduledExecutor();
-        mBackgroundThreadExecutor = TestingExecutors.sameThreadScheduledExecutor();
-        when(mMockListeningExecutorServiceFactory.createSingleThreadExecutor()).thenReturn(
-                mBackgroundThreadExecutor);
-
-        mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
-                mMockListeningExecutorServiceFactory, mSoundListHandler,
-                mVibrationListHandler);
-
-        // Add silent and default options to the sound list.
-        mSoundListHandler.addSilentItem();
-        mSoundListHandler.addDefaultItem();
-
-        // Add silent and default options to the vibration list.
-        mVibrationListHandler.addSilentItem();
-        mVibrationListHandler.addDefaultItem();
-
-        mSoundListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
-        mVibrationListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
-    }
-
-    @After
-    public void teardown() {
-        if (mMainThreadExecutor != null && !mMainThreadExecutor.isShutdown()) {
-            mMainThreadExecutor.shutdown();
-        }
-        if (mBackgroundThreadExecutor != null && !mBackgroundThreadExecutor.isShutdown()) {
-            mBackgroundThreadExecutor.shutdown();
-        }
-    }
-
-    @Test
-    public void testInitRingtoneManager_whenTypeIsUnknown_createManagerButDoNotSetType() {
-        mViewModel.init(createPickerConfig(RINGTONE_TYPE_UNKNOWN), mSoundListConfig,
-                mVibrationListConfig);
-
-        verify(mMockRingtoneManagerFactory).create();
-        verify(mMockRingtoneManager, never()).setType(anyInt());
-        assertNotNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
-        assertNotNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
-    }
-
-    @Test
-    public void testInitRingtoneManager_whenTypeIsNotUnknown_createManagerAndSetType() {
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_NOTIFICATION), mSoundListConfig,
-                mVibrationListConfig);
-
-        verify(mMockRingtoneManagerFactory).create();
-        verify(mMockRingtoneManager).setType(RingtoneManager.TYPE_NOTIFICATION);
-        assertNotNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
-        assertNotNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
-    }
-
-    @Test
-    public void testInitRingtoneManager_bothListConfigsAreNull_onlyRecreateRingtoneManager() {
-        mViewModel.init(
-                createPickerConfig(RingtoneManager.TYPE_NOTIFICATION),
-                /* soundListConfig= */ null, /* vibrationListConfig= */ null);
-
-        verify(mMockRingtoneManagerFactory).create();
-        verify(mMockRingtoneManager).setType(RingtoneManager.TYPE_NOTIFICATION);
-        assertNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
-        assertNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
-    }
-
-    @Test
-    public void testReinitialize_bothListConfigsInitialized_recreateManagerAndReinitHandlers() {
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_NOTIFICATION), mSoundListConfig,
-                mVibrationListConfig);
-        mViewModel.reinit();
-
-        verify(mMockRingtoneManagerFactory, times(2)).create();
-        verify(mMockRingtoneManager, times(2)).setType(RingtoneManager.TYPE_NOTIFICATION);
-        assertNotNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
-        assertNotNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
-    }
-
-    @Test
-    public void testReinitialize_bothListConfigsAlreadyNull_onlyRecreateRingtoneManager() {
-        mViewModel.init(
-                createPickerConfig(RingtoneManager.TYPE_NOTIFICATION),
-                /* soundListConfig= */ null, /* vibrationListConfig= */ null);
-        mViewModel.reinit();
-
-        verify(mMockRingtoneManagerFactory, times(2)).create();
-        verify(mMockRingtoneManager, times(2)).setType(RingtoneManager.TYPE_NOTIFICATION);
-        assertNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
-        assertNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
-    }
-
-    @Test
-    public void testGetStreamType_returnsTheCorrectStreamType() {
-        when(mMockRingtoneManager.inferStreamType()).thenReturn(AudioManager.STREAM_ALARM);
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-        assertEquals(mViewModel.getRingtoneStreamType(), AudioManager.STREAM_ALARM);
-    }
-
-    @Test
-    public void testOnPause_withChangingConfigurationTrue_doNotStopPlayingRingtone() {
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-        mViewModel.playRingtone();
-        verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
-        mViewModel.onPause(/* isChangingConfigurations= */ true);
-        verify(mMockDefaultRingtone, never()).stop();
-    }
-
-    @Test
-    public void testOnPause_withChangingConfigurationFalse_stopPlayingRingtone() {
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-        mViewModel.playRingtone();
-        verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
-        mViewModel.onPause(/* isChangingConfigurations= */ false);
-        verify(mMockDefaultRingtone).stop();
-    }
-
-    @Test
-    public void testOnViewModelRecreated_previousRingtoneCanStillBeStopped() {
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-        Ringtone mockRingtone1 = createMockRingtone();
-        Ringtone mockRingtone2 = createMockRingtone();
-
-        when(mMockRingtoneFactory.create(any(), anyInt())).thenReturn(mockRingtone1, mockRingtone2);
-        mViewModel.playRingtone();
-        verifyRingtonePlayCalledAndMockPlayingState(mockRingtone1);
-        // Fake a scenario where the activity is destroyed and recreated due to a config change.
-        // This will result in a new view model getting created.
-        mViewModel.onStop(/* isChangingConfigurations= */ true);
-        verify(mockRingtone1, never()).stop();
-        mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
-                mMockListeningExecutorServiceFactory, mSoundListHandler,
-                mVibrationListHandler);
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-        mViewModel.playRingtone();
-        verifyRingtonePlayCalledAndMockPlayingState(mockRingtone2);
-        verify(mockRingtone1).stop();
-        verify(mockRingtone2, never()).stop();
-    }
-
-    @Test
-    public void testOnStop_withChangingConfigurationTrueAndDefaultRingtonePlaying_saveRingtone() {
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-        mViewModel.playRingtone();
-        verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
-        mViewModel.onStop(/* isChangingConfigurations= */ true);
-        assertEquals(RingtonePickerViewModel.sPlayingRingtone, mMockDefaultRingtone);
-    }
-
-    @Test
-    public void testOnStop_withChangingConfigurationTrueAndCurrentRingtonePlaying_saveRingtone() {
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-        mViewModel.playRingtone();
-        verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
-        mViewModel.onStop(/* isChangingConfigurations= */ true);
-        assertEquals(RingtonePickerViewModel.sPlayingRingtone, mMockDefaultRingtone);
-    }
-
-    @Test
-    public void testOnStop_withChangingConfigurationTrueAndNoPlayingRingtone_saveNothing() {
-        mViewModel.onStop(/* isChangingConfigurations= */ true);
-        assertNull(RingtonePickerViewModel.sPlayingRingtone);
-    }
-
-    @Test
-    public void testOnStop_withChangingConfigurationFalse_stopPlayingRingtone() {
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-
-        mViewModel.playRingtone();
-        verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
-        mViewModel.onStop(/* isChangingConfigurations= */ false);
-        verify(mMockDefaultRingtone).stop();
-    }
-
-    @Test
-    public void testGetCurrentlySelectedRingtoneUri_returnsTheCorrectRingtoneUri() {
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-
-        assertEquals(DEFAULT_URI, mViewModel.getSelectedRingtoneUri());
-    }
-
-    @Test
-    public void testPlayRingtone_playTheCorrectRingtone() {
-        mSoundListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-
-        mViewModel.playRingtone();
-        verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
-    }
-
-    @Test
-    public void testPlayRingtone_stopsPreviouslyRunningRingtone() {
-        // Start playing the first ringtone
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-        mViewModel.playRingtone();
-        verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
-        // Start playing the second ringtone
-        when(mMockRingtoneFactory.create(DEFAULT_URI,
-                AudioAttributes.FLAG_AUDIBILITY_ENFORCED)).thenReturn(mMockRingtone);
-        mViewModel.playRingtone();
-        verifyRingtonePlayCalledAndMockPlayingState(mMockRingtone);
-
-        verify(mMockDefaultRingtone).stop();
-    }
-
-    @Test
-    public void testDefaultItemUri_withNotificationIntent_returnDefaultNotificationUri() {
-        Uri uri = RingtonePickerViewModel.getDefaultItemUriByType(
-                RingtoneManager.TYPE_NOTIFICATION);
-        assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, uri);
-    }
-
-    @Test
-    public void testDefaultItemUri_withAlarmIntent_returnDefaultAlarmUri() {
-        Uri uri = RingtonePickerViewModel.getDefaultItemUriByType(RingtoneManager.TYPE_ALARM);
-        assertEquals(Settings.System.DEFAULT_ALARM_ALERT_URI, uri);
-    }
-
-    @Test
-    public void testDefaultItemUri_withRingtoneIntent_returnDefaultRingtoneUri() {
-        Uri uri = RingtonePickerViewModel.getDefaultItemUriByType(RingtoneManager.TYPE_RINGTONE);
-        assertEquals(Settings.System.DEFAULT_RINGTONE_URI, uri);
-    }
-
-    @Test
-    public void testDefaultItemUri_withInvalidRingtoneType_returnDefaultRingtoneUri() {
-        Uri uri = RingtonePickerViewModel.getDefaultItemUriByType(-1);
-        assertEquals(Settings.System.DEFAULT_RINGTONE_URI, uri);
-    }
-
-    @Test
-    public void testTitle_withNotificationRingtoneType_returnRingtoneNotificationTitle() {
-        int title = RingtonePickerViewModel.getTitleByType(RingtoneManager.TYPE_NOTIFICATION);
-        assertEquals(com.android.internal.R.string.ringtone_picker_title_notification, title);
-    }
-
-    @Test
-    public void testTitle_withAlarmRingtoneType_returnRingtoneAlarmTitle() {
-        int title = RingtonePickerViewModel.getTitleByType(RingtoneManager.TYPE_ALARM);
-        assertEquals(com.android.internal.R.string.ringtone_picker_title_alarm, title);
-    }
-
-    @Test
-    public void testTitle_withInvalidRingtoneType_returnDefaultRingtoneTitle() {
-        int title = RingtonePickerViewModel.getTitleByType(/*ringtoneType= */ -1);
-        assertEquals(com.android.internal.R.string.ringtone_picker_title, title);
-    }
-
-    @Test
-    public void testAddNewItemText_withAlarmType_returnAlarmAddItemText() {
-        int addNewItemTextResId = RingtonePickerViewModel.getAddNewItemTextByType(
-                RingtoneManager.TYPE_ALARM);
-        assertEquals(R.string.add_alarm_text, addNewItemTextResId);
-    }
-
-    @Test
-    public void testAddNewItemText_withNotificationType_returnNotificationAddItemText() {
-        int addNewItemTextResId = RingtonePickerViewModel.getAddNewItemTextByType(
-                RingtoneManager.TYPE_NOTIFICATION);
-        assertEquals(R.string.add_notification_text, addNewItemTextResId);
-    }
-
-    @Test
-    public void testAddNewItemText_withRingtoneType_returnRingtoneAddItemText() {
-        int addNewItemTextResId = RingtonePickerViewModel.getAddNewItemTextByType(
-                RingtoneManager.TYPE_RINGTONE);
-        assertEquals(R.string.add_ringtone_text, addNewItemTextResId);
-    }
-
-    @Test
-    public void testAddNewItemText_withInvalidType_returnRingtoneAddItemText() {
-        int addNewItemTextResId = RingtonePickerViewModel.getAddNewItemTextByType(-1);
-        assertEquals(R.string.add_ringtone_text, addNewItemTextResId);
-    }
-
-    @Test
-    public void testDefaultItemText_withNotificationType_returnNotificationDefaultItemText() {
-        int defaultRingtoneItemText = RingtonePickerViewModel.getDefaultRingtoneItemTextByType(
-                RingtoneManager.TYPE_NOTIFICATION);
-        assertEquals(R.string.notification_sound_default, defaultRingtoneItemText);
-    }
-
-    @Test
-    public void testDefaultItemText_withAlarmType_returnAlarmDefaultItemText() {
-        int defaultRingtoneItemText = RingtonePickerViewModel.getDefaultRingtoneItemTextByType(
-                RingtoneManager.TYPE_NOTIFICATION);
-        assertEquals(R.string.notification_sound_default, defaultRingtoneItemText);
-    }
-
-    @Test
-    public void testDefaultItemText_withRingtoneType_returnRingtoneDefaultItemText() {
-        int defaultRingtoneItemText = RingtonePickerViewModel.getDefaultRingtoneItemTextByType(
-                RingtoneManager.TYPE_RINGTONE);
-        assertEquals(R.string.ringtone_default, defaultRingtoneItemText);
-    }
-
-    @Test
-    public void testDefaultItemText_withInvalidType_returnRingtoneDefaultItemText() {
-        int defaultRingtoneItemText = RingtonePickerViewModel.getDefaultRingtoneItemTextByType(-1);
-        assertEquals(R.string.ringtone_default, defaultRingtoneItemText);
-    }
-
-    @Test
-    public void testCancelPendingAsyncTasks_correctlyCancelsPendingTasks()
-            throws IOException {
-        FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
-
-        when(mMockListeningExecutorServiceFactory.createSingleThreadExecutor()).thenReturn(
-                TestingExecutors.noOpScheduledExecutor());
-
-        mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
-                mMockListeningExecutorServiceFactory, mSoundListHandler,
-                mVibrationListHandler);
-        mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
-                mockCallback, mMainThreadExecutor);
-        verify(mockCallback, never()).onFailure(any());
-        // Calling cancelPendingAsyncTasks should cancel the pending task. Cancelling an async
-        // task invokes the onFailure method in the callable.
-        mViewModel.cancelPendingAsyncTasks();
-        verify(mockCallback).onFailure(any());
-        verify(mockCallback, never()).onSuccess(any());
-
-    }
-
-    @Test
-    public void testAddRingtoneAsync_cancelPreviousTaskBeforeStartingNewOne()
-            throws IOException {
-        FutureCallback<Uri> mockCallback1 = mock(FutureCallback.class);
-        FutureCallback<Uri> mockCallback2 = mock(FutureCallback.class);
-
-        when(mMockListeningExecutorServiceFactory.createSingleThreadExecutor()).thenReturn(
-                TestingExecutors.noOpScheduledExecutor());
-
-        mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
-                mMockListeningExecutorServiceFactory, mSoundListHandler,
-                mVibrationListHandler);
-        mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
-                mockCallback1, mMainThreadExecutor);
-        verify(mockCallback1, never()).onFailure(any());
-        // We call addRingtoneAsync again to cancel the previous task and start a new one.
-        // Cancelling an async task invokes the onFailure method in the callable.
-        mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
-                mockCallback2, mMainThreadExecutor);
-        verify(mockCallback1).onFailure(any());
-        verify(mockCallback1, never()).onSuccess(any());
-        verifyNoMoreInteractions(mockCallback2);
-    }
-
-    @Test
-    public void testAddRingtoneAsync_whenAddRingtoneIsSuccessful_successCallbackIsInvoked()
-            throws IOException {
-        Uri expectedUri = DEFAULT_URI;
-        FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
-
-        when(mMockRingtoneManager.addCustomExternalRingtone(DEFAULT_URI,
-                RingtoneManager.TYPE_NOTIFICATION)).thenReturn(expectedUri);
-
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-
-        mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
-                mockCallback, mMainThreadExecutor);
-
-        verify(mockCallback).onSuccess(expectedUri);
-        verify(mockCallback, never()).onFailure(any());
-    }
-
-    @Test
-    public void testAddRingtoneAsync_whenAddRingtoneFailed_failureCallbackIsInvoked()
-            throws IOException {
-        FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
-
-        when(mMockRingtoneManager.addCustomExternalRingtone(any(), anyInt())).thenThrow(
-                IOException.class);
-
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-
-        mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
-                mockCallback, mMainThreadExecutor);
-
-        verify(mockCallback).onFailure(any(IOException.class));
-        verify(mockCallback, never()).onSuccess(any());
-    }
-
-    private Ringtone createMockRingtone() {
-        Ringtone mockRingtone = mock(Ringtone.class);
-        when(mockRingtone.getAudioAttributes()).thenReturn(
-                audioAttributes(AudioAttributes.USAGE_NOTIFICATION_RINGTONE, 0));
-
-        return mockRingtone;
-    }
-
-    private void verifyRingtonePlayCalledAndMockPlayingState(Ringtone ringtone) {
-        verify(ringtone).play();
-        when(ringtone.isPlaying()).thenReturn(true);
-    }
-
-    private static AudioAttributes audioAttributes(int audioUsage, int flags) {
-        return new AudioAttributes.Builder()
-                .setUsage(audioUsage)
-                .setFlags(flags)
-                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-                .build();
-    }
-
-    private RingtonePickerViewModel.Config createPickerConfig(int ringtoneType,
-            int audioAttributes) {
-        return new RingtonePickerViewModel.Config("Phone ringtone", /* userId= */ 1,
-                ringtoneType, /* showOkCancelButtons= */ true,
-                audioAttributes, RingtonePickerViewModel.PickerType.RINGTONE_PICKER);
-    }
-
-    private RingtonePickerViewModel.Config createPickerConfig(int ringtoneType) {
-        return new RingtonePickerViewModel.Config("Phone ringtone", /* userId= */ 1,
-                ringtoneType, /* showOkCancelButtons= */ true,
-                AudioAttributes.FLAG_AUDIBILITY_ENFORCED,
-                RingtonePickerViewModel.PickerType.RINGTONE_PICKER);
-    }
-
-    private RingtoneListHandler.Config createRingtoneListConfig() {
-        return new RingtoneListHandler.Config(/* hasDefaultItem= */ true,
-                /* uriForDefaultItem= */ DEFAULT_URI, /* hasSilentItem= */ true,
-                /* existingUri= */ Uri.parse(""));
-    }
-}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 8194055..4973caf 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -53,12 +53,12 @@
  */
 class ActivityLaunchAnimator(
     /** The animator used when animating a View into an app. */
-    private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR,
+    private val transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
 
     /** The animator used when animating a Dialog into an app. */
     // TODO(b/218989950): Remove this animator and instead set the duration of the dim fade out to
     // TIMINGS.contentBeforeFadeOutDuration.
-    private val dialogToAppAnimator: LaunchAnimator = DEFAULT_DIALOG_TO_APP_ANIMATOR,
+    private val dialogToAppAnimator: TransitionAnimator = DEFAULT_DIALOG_TO_APP_ANIMATOR,
 
     /**
      * Whether we should disable the WindowManager timeout. This should be set to true in tests
@@ -71,7 +71,7 @@
         /** The timings when animating a View into an app. */
         @JvmField
         val TIMINGS =
-            LaunchAnimator.Timings(
+            TransitionAnimator.Timings(
                 totalDuration = 500L,
                 contentBeforeFadeOutDelay = 0L,
                 contentBeforeFadeOutDuration = 150L,
@@ -89,7 +89,7 @@
 
         /** The interpolators when animating a View or a dialog into an app. */
         val INTERPOLATORS =
-            LaunchAnimator.Interpolators(
+            TransitionAnimator.Interpolators(
                 positionInterpolator = Interpolators.EMPHASIZED,
                 positionXInterpolator = Interpolators.EMPHASIZED_COMPLEMENT,
                 contentBeforeFadeOutInterpolator = Interpolators.LINEAR_OUT_SLOW_IN,
@@ -99,8 +99,9 @@
         // TODO(b/288507023): Remove this flag.
         @JvmField val DEBUG_LAUNCH_ANIMATION = Build.IS_DEBUGGABLE
 
-        private val DEFAULT_LAUNCH_ANIMATOR = LaunchAnimator(TIMINGS, INTERPOLATORS)
-        private val DEFAULT_DIALOG_TO_APP_ANIMATOR = LaunchAnimator(DIALOG_TIMINGS, INTERPOLATORS)
+        private val DEFAULT_TRANSITION_ANIMATOR = TransitionAnimator(TIMINGS, INTERPOLATORS)
+        private val DEFAULT_DIALOG_TO_APP_ANIMATOR =
+            TransitionAnimator(DIALOG_TIMINGS, INTERPOLATORS)
 
         /** Durations & interpolators for the navigation bar fading in & out. */
         private const val ANIMATION_DURATION_NAV_FADE_IN = 266L
@@ -154,7 +155,7 @@
      * Start an intent and animate the opening window. The intent will be started by running
      * [intentStarter], which should use the provided [RemoteAnimationAdapter] and return the launch
      * result. [controller] is responsible from animating the view from which the intent was started
-     * in [Controller.onLaunchAnimationProgress]. No animation will start if there is no window
+     * in [Controller.onTransitionAnimationProgress]. No animation will start if there is no window
      * opening.
      *
      * If [controller] is null or [animate] is false, then the intent will be started and no
@@ -255,7 +256,7 @@
 
     private fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) {
         if (Looper.myLooper() != Looper.getMainLooper()) {
-            this.launchContainer.context.mainExecutor.execute {
+            this.transitionContainer.context.mainExecutor.execute {
                 callOnIntentStartedOnMainThread(willAnimate)
             }
         } else {
@@ -306,14 +307,14 @@
     @VisibleForTesting
     fun createRunner(controller: Controller): Runner {
         // Make sure we use the modified timings when animating a dialog into an app.
-        val launchAnimator =
+        val transitionAnimator =
             if (controller.isDialogLaunch) {
                 dialogToAppAnimator
             } else {
-                launchAnimator
+                transitionAnimator
             }
 
-        return Runner(controller, callback!!, launchAnimator, lifecycleListener)
+        return Runner(controller, callback!!, transitionAnimator, lifecycleListener)
     }
 
     interface PendingIntentStarter {
@@ -364,7 +365,7 @@
      *
      * Note that all callbacks (onXXX methods) are all called on the main thread.
      */
-    interface Controller : LaunchAnimator.Controller {
+    interface Controller : TransitionAnimator.Controller {
         companion object {
             /**
              * Return a [Controller] that will animate and expand [view] into the opening window.
@@ -427,9 +428,9 @@
         fun onIntentStarted(willAnimate: Boolean) {}
 
         /**
-         * The animation was cancelled. Note that [onLaunchAnimationEnd] will still be called after
-         * this if the animation was already started, i.e. if [onLaunchAnimationStart] was called
-         * before the cancellation.
+         * The animation was cancelled. Note that [onTransitionAnimationEnd] will still be called
+         * after this if the animation was already started, i.e. if [onTransitionAnimationStart] was
+         * called before the cancellation.
          *
          * If this launch animation affected the occlusion state of the keyguard, WM will provide us
          * with [newKeyguardOccludedState] so that we can set the occluded state appropriately.
@@ -475,11 +476,11 @@
         controller: Controller,
         callback: Callback,
         /** The animator to use to animate the window launch. */
-        launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR,
+        transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
         /** Listener for animation lifecycle events. */
         listener: Listener? = null
     ) : IRemoteAnimationRunner.Stub() {
-        private val context = controller.launchContainer.context
+        private val context = controller.transitionContainer.context
 
         // This is being passed across IPC boundaries and cycles (through PendingIntentRecords,
         // etc.) are possible. So we need to make sure we drop any references that might
@@ -492,7 +493,7 @@
                     controller,
                     callback,
                     DelegatingAnimationCompletionListener(listener, this::dispose),
-                    launchAnimator,
+                    transitionAnimator,
                     disableWmTimeout
                 )
         }
@@ -543,7 +544,7 @@
         /** Listener for animation lifecycle events. */
         private val listener: Listener? = null,
         /** The animator to use to animate the window launch. */
-        private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR,
+        private val transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
 
         /**
          * Whether we should disable the WindowManager timeout. This should be set to true in tests
@@ -552,10 +553,10 @@
         // TODO(b/301385865): Remove this flag.
         disableWmTimeout: Boolean = false,
     ) : RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> {
-        private val launchContainer = controller.launchContainer
-        private val context = launchContainer.context
+        private val transitionContainer = controller.transitionContainer
+        private val context = transitionContainer.context
         private val transactionApplierView =
-            controller.openingWindowSyncView ?: controller.launchContainer
+            controller.openingWindowSyncView ?: controller.transitionContainer
         private val transactionApplier = SyncRtSurfaceTransactionApplier(transactionApplierView)
         private val timeoutHandler =
             if (!disableWmTimeout) {
@@ -570,7 +571,7 @@
         private var windowCropF = RectF()
         private var timedOut = false
         private var cancelled = false
-        private var animation: LaunchAnimator.Animation? = null
+        private var animation: TransitionAnimator.Animation? = null
 
         /**
          * A timeout to cancel the launch animation if the remote animation is not started or
@@ -660,7 +661,7 @@
             nonApps: Array<out RemoteAnimationTarget>?,
             iCallback: IRemoteAnimationFinishedCallback?
         ) {
-            if (LaunchAnimator.DEBUG) {
+            if (TransitionAnimator.DEBUG) {
                 Log.d(TAG, "Remote animation started")
             }
 
@@ -687,7 +688,7 @@
 
             val windowBounds = window.screenSpaceBounds
             val endState =
-                LaunchAnimator.State(
+                TransitionAnimator.State(
                     top = windowBounds.top,
                     bottom = windowBounds.bottom,
                     left = windowBounds.left,
@@ -699,7 +700,7 @@
             // TODO(b/184121838): We should somehow get the top and bottom radius of the window
             // instead of recomputing isExpandingFullyAbove here.
             val isExpandingFullyAbove =
-                launchAnimator.isExpandingFullyAbove(controller.launchContainer, endState)
+                transitionAnimator.isExpandingFullyAbove(controller.transitionContainer, endState)
             val endRadius =
                 if (isExpandingFullyAbove) {
                     // Most of the time, expanding fully above the root view means expanding in full
@@ -718,7 +719,7 @@
             val delegate = this.controller
             val controller =
                 object : Controller by delegate {
-                    override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+                    override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
                         listener?.onLaunchAnimationStart()
 
                         if (DEBUG_LAUNCH_ANIMATION) {
@@ -728,10 +729,10 @@
                                     "$isExpandingFullyAbove) [controller=$delegate]"
                             )
                         }
-                        delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+                        delegate.onTransitionAnimationStart(isExpandingFullyAbove)
                     }
 
-                    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+                    override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
                         listener?.onLaunchAnimationEnd()
                         iCallback?.invoke()
 
@@ -742,11 +743,11 @@
                                     "$isExpandingFullyAbove) [controller=$delegate]"
                             )
                         }
-                        delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+                        delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
                     }
 
-                    override fun onLaunchAnimationProgress(
-                        state: LaunchAnimator.State,
+                    override fun onTransitionAnimationProgress(
+                        state: TransitionAnimator.State,
                         progress: Float,
                         linearProgress: Float
                     ) {
@@ -758,12 +759,12 @@
                         navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
 
                         listener?.onLaunchAnimationProgress(linearProgress)
-                        delegate.onLaunchAnimationProgress(state, progress, linearProgress)
+                        delegate.onTransitionAnimationProgress(state, progress, linearProgress)
                     }
                 }
 
             animation =
-                launchAnimator.startAnimation(
+                transitionAnimator.startAnimation(
                     controller,
                     endState,
                     windowBackgroundColor,
@@ -774,7 +775,7 @@
 
         private fun applyStateToWindow(
             window: RemoteAnimationTarget,
-            state: LaunchAnimator.State,
+            state: TransitionAnimator.State,
             linearProgress: Float,
         ) {
             if (transactionApplierView.viewRootImpl == null || !window.leash.isValid) {
@@ -825,7 +826,7 @@
             val alpha =
                 if (controller.isBelowAnimatingWindow) {
                     val windowProgress =
-                        LaunchAnimator.getProgress(
+                        TransitionAnimator.getProgress(
                             TIMINGS,
                             linearProgress,
                             TIMINGS.contentAfterFadeInDelay,
@@ -857,7 +858,7 @@
 
         private fun applyStateToNavigationBar(
             navigationBar: RemoteAnimationTarget,
-            state: LaunchAnimator.State,
+            state: TransitionAnimator.State,
             linearProgress: Float
         ) {
             if (transactionApplierView.viewRootImpl == null || !navigationBar.leash.isValid) {
@@ -868,7 +869,7 @@
             }
 
             val fadeInProgress =
-                LaunchAnimator.getProgress(
+                TransitionAnimator.getProgress(
                     TIMINGS,
                     linearProgress,
                     ANIMATION_DELAY_NAV_FADE_IN,
@@ -890,7 +891,7 @@
                     .withVisibility(true)
             } else {
                 val fadeOutProgress =
-                    LaunchAnimator.getProgress(
+                    TransitionAnimator.getProgress(
                         TIMINGS,
                         linearProgress,
                         0,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 168039e..9a36960 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -58,7 +58,7 @@
     private val callback: Callback,
     private val interactionJankMonitor: InteractionJankMonitor,
     private val featureFlags: AnimationFeatureFlags,
-    private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS),
+    private val transitionAnimator: TransitionAnimator = TransitionAnimator(TIMINGS, INTERPOLATORS),
     private val isForTesting: Boolean = false,
 ) {
     private companion object {
@@ -108,21 +108,21 @@
         fun stopDrawingInOverlay()
 
         /**
-         * Create the [LaunchAnimator.Controller] that will be called to animate the source
+         * Create the [TransitionAnimator.Controller] that will be called to animate the source
          * controlled by this [Controller] during the dialog launch animation.
          *
          * At the end of this animation, the source should *not* be visible anymore (until the
          * dialog is closed and is animated back into the source).
          */
-        fun createLaunchController(): LaunchAnimator.Controller
+        fun createTransitionController(): TransitionAnimator.Controller
 
         /**
-         * Create the [LaunchAnimator.Controller] that will be called to animate the source
+         * Create the [TransitionAnimator.Controller] that will be called to animate the source
          * controlled by this [Controller] during the dialog exit animation.
          *
          * At the end of this animation, the source should be visible again.
          */
-        fun createExitController(): LaunchAnimator.Controller
+        fun createExitController(): TransitionAnimator.Controller
 
         /**
          * Whether we should animate the dialog back into the source when it is dismissed. If this
@@ -270,7 +270,7 @@
 
         val animatedDialog =
             AnimatedDialog(
-                launchAnimator = launchAnimator,
+                transitionAnimator = transitionAnimator,
                 callback = callback,
                 interactionJankMonitor = interactionJankMonitor,
                 controller = controller,
@@ -406,8 +406,8 @@
                 dialog.dismiss()
             }
 
-            override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
-                controller.onLaunchAnimationStart(isExpandingFullyAbove)
+            override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
+                controller.onTransitionAnimationStart(isExpandingFullyAbove)
 
                 // Make sure the dialog is not dismissed during the animation.
                 disableDialogDismiss()
@@ -420,8 +420,8 @@
                 dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
             }
 
-            override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
-                controller.onLaunchAnimationEnd(isExpandingFullyAbove)
+            override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+                controller.onTransitionAnimationEnd(isExpandingFullyAbove)
 
                 // Hide the dialog then dismiss it to instantly dismiss it without playing the
                 // animation.
@@ -492,7 +492,7 @@
 data class DialogCuj(@CujType val cujType: Int, val tag: String? = null)
 
 private class AnimatedDialog(
-    private val launchAnimator: LaunchAnimator,
+    private val transitionAnimator: TransitionAnimator,
     private val callback: DialogLaunchAnimator.Callback,
     private val interactionJankMonitor: InteractionJankMonitor,
 
@@ -892,7 +892,7 @@
         // Create 2 controllers to animate both the dialog and the source.
         val startController =
             if (isLaunching) {
-                controller.createLaunchController()
+                controller.createTransitionController()
             } else {
                 GhostedViewLaunchAnimatorController(dialogContentWithBackground!!)
             }
@@ -902,34 +902,34 @@
             } else {
                 controller.createExitController()
             }
-        startController.launchContainer = decorView
-        endController.launchContainer = decorView
+        startController.transitionContainer = decorView
+        endController.transitionContainer = decorView
 
         val endState = endController.createAnimatorState()
         val controller =
-            object : LaunchAnimator.Controller {
-                override var launchContainer: ViewGroup
-                    get() = startController.launchContainer
+            object : TransitionAnimator.Controller {
+                override var transitionContainer: ViewGroup
+                    get() = startController.transitionContainer
                     set(value) {
-                        startController.launchContainer = value
-                        endController.launchContainer = value
+                        startController.transitionContainer = value
+                        endController.transitionContainer = value
                     }
 
-                override fun createAnimatorState(): LaunchAnimator.State {
+                override fun createAnimatorState(): TransitionAnimator.State {
                     return startController.createAnimatorState()
                 }
 
-                override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+                override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
                     // During launch, onLaunchAnimationStart will be used to remove the temporary
                     // touch surface ghost so it is important to call this before calling
                     // onLaunchAnimationStart on the controller (which will create its own ghost).
                     onLaunchAnimationStart()
 
-                    startController.onLaunchAnimationStart(isExpandingFullyAbove)
-                    endController.onLaunchAnimationStart(isExpandingFullyAbove)
+                    startController.onTransitionAnimationStart(isExpandingFullyAbove)
+                    endController.onTransitionAnimationStart(isExpandingFullyAbove)
                 }
 
-                override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+                override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
                     // onLaunchAnimationEnd is called by an Animator at the end of the animation,
                     // on a Choreographer animation tick. The following calls will move the animated
                     // content from the dialog overlay back to its original position, and this
@@ -943,23 +943,23 @@
                     // that the move of the content back to its original window will be reflected in
                     // the next frame right after [onLaunchAnimationEnd] is called.
                     dialog.context.mainExecutor.execute {
-                        startController.onLaunchAnimationEnd(isExpandingFullyAbove)
-                        endController.onLaunchAnimationEnd(isExpandingFullyAbove)
+                        startController.onTransitionAnimationEnd(isExpandingFullyAbove)
+                        endController.onTransitionAnimationEnd(isExpandingFullyAbove)
 
                         onLaunchAnimationEnd()
                     }
                 }
 
-                override fun onLaunchAnimationProgress(
-                    state: LaunchAnimator.State,
+                override fun onTransitionAnimationProgress(
+                    state: TransitionAnimator.State,
                     progress: Float,
                     linearProgress: Float
                 ) {
-                    startController.onLaunchAnimationProgress(state, progress, linearProgress)
+                    startController.onTransitionAnimationProgress(state, progress, linearProgress)
 
                     // The end view is visible only iff the starting view is not visible.
                     state.visible = !state.visible
-                    endController.onLaunchAnimationProgress(state, progress, linearProgress)
+                    endController.onTransitionAnimationProgress(state, progress, linearProgress)
 
                     // If the dialog content is complex, its dimension might change during the
                     // launch animation. The animation end position might also change during the
@@ -973,7 +973,7 @@
                 }
             }
 
-        launchAnimator.startAnimation(controller, endState, originalDialogBackgroundColor)
+        transitionAnimator.startAnimation(controller, endState, originalDialogBackgroundColor)
     }
 
     private fun shouldAnimateDialogIntoSource(): Boolean {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 055252b..03f10f9 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -66,11 +66,11 @@
 ) : ActivityLaunchAnimator.Controller {
 
     /** The container to which we will add the ghost view and expanding background. */
-    override var launchContainer = ghostedView.rootView as ViewGroup
-    private val launchContainerOverlay: ViewGroupOverlay
-        get() = launchContainer.overlay
+    override var transitionContainer = ghostedView.rootView as ViewGroup
+    private val transitionContainerOverlay: ViewGroupOverlay
+        get() = transitionContainer.overlay
 
-    private val launchContainerLocation = IntArray(2)
+    private val transitionContainerLocation = IntArray(2)
 
     /** The ghost view that is drawn and animated instead of the ghosted view. */
     private var ghostView: GhostView? = null
@@ -78,8 +78,8 @@
     private val ghostViewMatrix = Matrix()
 
     /**
-     * The expanding background view that will be added to [launchContainer] (below [ghostView]) and
-     * animate.
+     * The expanding background view that will be added to [transitionContainer] (below [ghostView])
+     * and animate.
      */
     private var backgroundView: FrameLayout? = null
 
@@ -92,7 +92,7 @@
     private var startBackgroundAlpha: Int = 0xFF
 
     private val ghostedViewLocation = IntArray(2)
-    private val ghostedViewState = LaunchAnimator.State()
+    private val ghostedViewState = TransitionAnimator.State()
 
     /**
      * The background of the [ghostedView]. This background will be used to draw the background of
@@ -175,9 +175,9 @@
         return radius * ghostedView.scaleX
     }
 
-    override fun createAnimatorState(): LaunchAnimator.State {
+    override fun createAnimatorState(): TransitionAnimator.State {
         val state =
-            LaunchAnimator.State(
+            TransitionAnimator.State(
                 topCornerRadius = getCurrentTopCornerRadius(),
                 bottomCornerRadius = getCurrentBottomCornerRadius()
             )
@@ -185,7 +185,7 @@
         return state
     }
 
-    fun fillGhostedViewState(state: LaunchAnimator.State) {
+    fun fillGhostedViewState(state: TransitionAnimator.State) {
         // For the animation we are interested in the area that has a non transparent background,
         // so we have to take the optical insets into account.
         ghostedView.getLocationOnScreen(ghostedViewLocation)
@@ -200,7 +200,7 @@
                 insets.right
     }
 
-    override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+    override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
         if (ghostedView.parent !is ViewGroup) {
             // This should usually not happen, but let's make sure we don't crash if the view was
             // detached right before we started the animation.
@@ -209,7 +209,7 @@
         }
 
         backgroundView =
-            FrameLayout(launchContainer.context).also { launchContainerOverlay.add(it) }
+            FrameLayout(transitionContainer.context).also { transitionContainerOverlay.add(it) }
 
         // We wrap the ghosted view background and use it to draw the expandable background. Its
         // alpha will be set to 0 as soon as we start drawing the expanding background.
@@ -225,7 +225,7 @@
 
         // Create a ghost of the view that will be moving and fading out. This allows to fade out
         // the content before fading out the background.
-        ghostView = GhostView.addGhost(ghostedView, launchContainer)
+        ghostView = GhostView.addGhost(ghostedView, transitionContainer)
 
         // [GhostView.addGhost], the result of which is our [ghostView], creates a [GhostView], and
         // adds it first to a [FrameLayout] container. It then adds _that_ container to an
@@ -244,8 +244,8 @@
         cujType?.let { interactionJankMonitor.begin(ghostedView, it) }
     }
 
-    override fun onLaunchAnimationProgress(
-        state: LaunchAnimator.State,
+    override fun onTransitionAnimationProgress(
+        state: TransitionAnimator.State,
         progress: Float,
         linearProgress: Float
     ) {
@@ -287,15 +287,15 @@
         if (ghostedView.parent is ViewGroup) {
             // Recalculate the matrix in case the ghosted view moved. We ensure that the ghosted
             // view is still attached to a ViewGroup, otherwise calculateMatrix will throw.
-            GhostView.calculateMatrix(ghostedView, launchContainer, ghostViewMatrix)
+            GhostView.calculateMatrix(ghostedView, transitionContainer, ghostViewMatrix)
         }
 
-        launchContainer.getLocationOnScreen(launchContainerLocation)
+        transitionContainer.getLocationOnScreen(transitionContainerLocation)
         ghostViewMatrix.postScale(
             scale,
             scale,
-            ghostedViewState.centerX - launchContainerLocation[0],
-            ghostedViewState.centerY - launchContainerLocation[1]
+            ghostedViewState.centerX - transitionContainerLocation[0],
+            ghostedViewState.centerY - transitionContainerLocation[1]
         )
         ghostViewMatrix.postTranslate(
             (leftChange + rightChange) / 2f,
@@ -310,10 +310,10 @@
         val rightWithInsets = state.right + insets.right
         val bottomWithInsets = state.bottom + insets.bottom
 
-        backgroundView.top = topWithInsets - launchContainerLocation[1]
-        backgroundView.bottom = bottomWithInsets - launchContainerLocation[1]
-        backgroundView.left = leftWithInsets - launchContainerLocation[0]
-        backgroundView.right = rightWithInsets - launchContainerLocation[0]
+        backgroundView.top = topWithInsets - transitionContainerLocation[1]
+        backgroundView.bottom = bottomWithInsets - transitionContainerLocation[1]
+        backgroundView.left = leftWithInsets - transitionContainerLocation[0]
+        backgroundView.right = rightWithInsets - transitionContainerLocation[0]
 
         val backgroundDrawable = backgroundDrawable!!
         backgroundDrawable.wrapped?.let {
@@ -321,7 +321,7 @@
         }
     }
 
-    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+    override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
         if (ghostView == null) {
             // We didn't actually run the animation.
             return
@@ -332,7 +332,7 @@
         backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
 
         GhostView.removeGhost(ghostedView)
-        backgroundView?.let { launchContainerOverlay.remove(it) }
+        backgroundView?.let { transitionContainerOverlay.remove(it) }
 
         if (ghostedView is LaunchableView) {
             // Restore the ghosted view visibility.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
similarity index 79%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
rename to packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index d6eba2e..5e4276c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -31,17 +31,18 @@
 import com.android.app.animation.Interpolators.LINEAR
 import kotlin.math.roundToInt
 
-private const val TAG = "LaunchAnimator"
+private const val TAG = "TransitionAnimator"
 
-/** A base class to animate a window launch (activity or dialog) from a view . */
-class LaunchAnimator(private val timings: Timings, private val interpolators: Interpolators) {
+/** A base class to animate a window (activity or dialog) launch to or return from a view . */
+class TransitionAnimator(private val timings: Timings, private val interpolators: Interpolators) {
     companion object {
         internal const val DEBUG = false
         private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
 
         /**
-         * Given the [linearProgress] of a launch animation, return the linear progress of the
-         * sub-animation starting [delay] ms after the launch animation and that lasts [duration].
+         * Given the [linearProgress] of a transition animation, return the linear progress of the
+         * sub-animation starting [delay] ms after the transition animation and that lasts
+         * [duration].
          */
         @JvmStatic
         fun getProgress(
@@ -58,7 +59,7 @@
         }
     }
 
-    private val launchContainerLocation = IntArray(2)
+    private val transitionContainerLocation = IntArray(2)
     private val cornerRadii = FloatArray(8)
 
     /**
@@ -73,7 +74,7 @@
          *
          * This will be used to:
          * - Get the associated [Context].
-         * - Compute whether we are expanding fully above the launch container.
+         * - Compute whether we are expanding fully above the transition container.
          * - Get to overlay to which we initially put the window background layer, until the opening
          *   window is made visible (see [openingWindowSyncView]).
          *
@@ -81,7 +82,7 @@
          * inside a different location, for instance to ensure correct layering during the
          * animation.
          */
-        var launchContainer: ViewGroup
+        var transitionContainer: ViewGroup
 
         /**
          * The [View] with which the opening app window should be synchronized with once it starts
@@ -90,7 +91,7 @@
          * We will also move the window background layer to this view's overlay once the opening
          * window is visible.
          *
-         * If null, this will default to [launchContainer].
+         * If null, this will default to [transitionContainer].
          */
         val openingWindowSyncView: View?
             get() = null
@@ -99,7 +100,7 @@
          * Return the [State] of the view that will be animated. We will animate from this state to
          * the final window state.
          *
-         * Note: This state will be mutated and passed to [onLaunchAnimationProgress] during the
+         * Note: This state will be mutated and passed to [onTransitionAnimationProgress] during the
          * animation.
          */
         fun createAnimatorState(): State
@@ -107,22 +108,22 @@
         /**
          * The animation started. This is typically used to initialize any additional resource
          * needed for the animation. [isExpandingFullyAbove] will be true if the window is expanding
-         * fully above the [launchContainer].
+         * fully above the [transitionContainer].
          */
-        fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {}
+        fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {}
 
         /** The animation made progress and the expandable view [state] should be updated. */
-        fun onLaunchAnimationProgress(state: State, progress: Float, linearProgress: Float) {}
+        fun onTransitionAnimationProgress(state: State, progress: Float, linearProgress: Float) {}
 
         /**
-         * The animation ended. This will be called *if and only if* [onLaunchAnimationStart] was
-         * called previously. This is typically used to clean up the resources initialized when the
-         * animation was started.
+         * The animation ended. This will be called *if and only if* [onTransitionAnimationStart]
+         * was called previously. This is typically used to clean up the resources initialized when
+         * the animation was started.
          */
-        fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {}
+        fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {}
     }
 
-    /** The state of an expandable view during a [LaunchAnimator] animation. */
+    /** The state of an expandable view during a [TransitionAnimator] animation. */
     open class State(
         /** The position of the view in screen space coordinates. */
         var top: Int = 0,
@@ -198,13 +199,13 @@
     )
 
     /**
-     * Start a launch animation controlled by [controller] towards [endState]. An intermediary layer
-     * with [windowBackgroundColor] will fade in then (optionally) fade out above the expanding
-     * view, and should be the same background color as the opening (or closing) window.
+     * Start a transition animation controlled by [controller] towards [endState]. An intermediary
+     * layer with [windowBackgroundColor] will fade in then (optionally) fade out above the
+     * expanding view, and should be the same background color as the opening (or closing) window.
      *
      * If [fadeOutWindowBackgroundLayer] is true, then this intermediary layer will fade out during
      * the second half of the animation, and will have SRC blending mode (ultimately punching a hole
-     * in the [launch container][Controller.launchContainer]) iff [drawHole] is true.
+     * in the [transition container][Controller.transitionContainer]) iff [drawHole] is true.
      */
     fun startAnimation(
         controller: Controller,
@@ -251,13 +252,13 @@
             }
         }
 
-        val launchContainer = controller.launchContainer
-        val isExpandingFullyAbove = isExpandingFullyAbove(launchContainer, endState)
+        val transitionContainer = controller.transitionContainer
+        val isExpandingFullyAbove = isExpandingFullyAbove(transitionContainer, endState)
 
         // 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
         // to hide the expanding view, then we fade it out with SRC mode to draw a hole in the
-        // launch container and reveal the opening window.
+        // transition container and reveal the opening window.
         val windowBackgroundLayer =
             GradientDrawable().apply {
                 setColor(windowBackgroundColor)
@@ -275,9 +276,9 @@
         val openingWindowSyncViewOverlay = openingWindowSyncView?.overlay
         val moveBackgroundLayerWhenAppIsVisible =
             openingWindowSyncView != null &&
-                openingWindowSyncView.viewRootImpl != controller.launchContainer.viewRootImpl
+                openingWindowSyncView.viewRootImpl != controller.transitionContainer.viewRootImpl
 
-        val launchContainerOverlay = launchContainer.overlay
+        val transitionContainerOverlay = transitionContainer.overlay
         var cancelled = false
         var movedBackgroundLayer = false
 
@@ -287,20 +288,20 @@
                     if (DEBUG) {
                         Log.d(TAG, "Animation started")
                     }
-                    controller.onLaunchAnimationStart(isExpandingFullyAbove)
+                    controller.onTransitionAnimationStart(isExpandingFullyAbove)
 
-                    // Add the drawable to the launch container overlay. Overlays always draw
+                    // Add the drawable to the transition container overlay. Overlays always draw
                     // drawables after views, so we know that it will be drawn above any view added
                     // by the controller.
-                    launchContainerOverlay.add(windowBackgroundLayer)
+                    transitionContainerOverlay.add(windowBackgroundLayer)
                 }
 
                 override fun onAnimationEnd(animation: Animator) {
                     if (DEBUG) {
                         Log.d(TAG, "Animation ended")
                     }
-                    controller.onLaunchAnimationEnd(isExpandingFullyAbove)
-                    launchContainerOverlay.remove(windowBackgroundLayer)
+                    controller.onTransitionAnimationEnd(isExpandingFullyAbove)
+                    transitionContainerOverlay.remove(windowBackgroundLayer)
 
                     if (moveBackgroundLayerWhenAppIsVisible) {
                         openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
@@ -353,17 +354,21 @@
                 // in its new container.
                 movedBackgroundLayer = true
 
-                launchContainerOverlay.remove(windowBackgroundLayer)
+                transitionContainerOverlay.remove(windowBackgroundLayer)
                 openingWindowSyncViewOverlay!!.add(windowBackgroundLayer)
 
-                ViewRootSync.synchronizeNextDraw(launchContainer, openingWindowSyncView, then = {})
+                ViewRootSync.synchronizeNextDraw(
+                    transitionContainer,
+                    openingWindowSyncView,
+                    then = {}
+                )
             }
 
             val container =
                 if (movedBackgroundLayer) {
                     openingWindowSyncView!!
                 } else {
-                    controller.launchContainer
+                    controller.transitionContainer
                 }
 
             applyStateToWindowBackgroundLayer(
@@ -374,7 +379,7 @@
                 fadeOutWindowBackgroundLayer,
                 drawHole
             )
-            controller.onLaunchAnimationProgress(state, progress, linearProgress)
+            controller.onTransitionAnimationProgress(state, progress, linearProgress)
         }
 
         animator.start()
@@ -386,30 +391,30 @@
         }
     }
 
-    /** Return whether we are expanding fully above the [launchContainer]. */
-    internal fun isExpandingFullyAbove(launchContainer: View, endState: State): Boolean {
-        launchContainer.getLocationOnScreen(launchContainerLocation)
-        return endState.top <= launchContainerLocation[1] &&
-            endState.bottom >= launchContainerLocation[1] + launchContainer.height &&
-            endState.left <= launchContainerLocation[0] &&
-            endState.right >= launchContainerLocation[0] + launchContainer.width
+    /** Return whether we are expanding fully above the [transitionContainer]. */
+    internal fun isExpandingFullyAbove(transitionContainer: View, endState: State): Boolean {
+        transitionContainer.getLocationOnScreen(transitionContainerLocation)
+        return endState.top <= transitionContainerLocation[1] &&
+            endState.bottom >= transitionContainerLocation[1] + transitionContainer.height &&
+            endState.left <= transitionContainerLocation[0] &&
+            endState.right >= transitionContainerLocation[0] + transitionContainer.width
     }
 
     private fun applyStateToWindowBackgroundLayer(
         drawable: GradientDrawable,
         state: State,
         linearProgress: Float,
-        launchContainer: View,
+        transitionContainer: View,
         fadeOutWindowBackgroundLayer: Boolean,
         drawHole: Boolean
     ) {
         // Update position.
-        launchContainer.getLocationOnScreen(launchContainerLocation)
+        transitionContainer.getLocationOnScreen(transitionContainerLocation)
         drawable.setBounds(
-            state.left - launchContainerLocation[0],
-            state.top - launchContainerLocation[1],
-            state.right - launchContainerLocation[0],
-            state.bottom - launchContainerLocation[1]
+            state.left - transitionContainerLocation[0],
+            state.top - transitionContainerLocation[1],
+            state.right - transitionContainerLocation[0],
+            state.bottom - transitionContainerLocation[1]
         )
 
         // Update radius.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
index 1290f00..e2a29ab 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
@@ -68,19 +68,19 @@
         }
     }
 
-    override fun createLaunchController(): LaunchAnimator.Controller {
+    override fun createTransitionController(): TransitionAnimator.Controller {
         val delegate = GhostedViewLaunchAnimatorController(source)
-        return object : LaunchAnimator.Controller by delegate {
-            override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+        return object : TransitionAnimator.Controller by delegate {
+            override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
                 // Remove the temporary ghost added by [startDrawingInOverlayOf]. Another
                 // ghost (that ghosts only the source content, and not its background) will
                 // be added right after this by the delegate and will be animated.
                 GhostView.removeGhost(source)
-                delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+                delegate.onTransitionAnimationStart(isExpandingFullyAbove)
             }
 
-            override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
-                delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+            override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+                delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
 
                 // At this point the view visibility is restored by the delegate, so we delay the
                 // visibility changes again and make it invisible while the dialog is shown.
@@ -94,7 +94,7 @@
         }
     }
 
-    override fun createExitController(): LaunchAnimator.Controller {
+    override fun createExitController(): TransitionAnimator.Controller {
         return GhostedViewLaunchAnimatorController(source)
     }
 
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
index ac1ef15..8eb2f2e 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
@@ -75,7 +75,7 @@
 import androidx.lifecycle.setViewTreeLifecycleOwner
 import androidx.lifecycle.setViewTreeViewModelStoreOwner
 import com.android.systemui.animation.Expandable
-import com.android.systemui.animation.LaunchAnimator
+import com.android.systemui.animation.TransitionAnimator
 import kotlin.math.max
 import kotlin.math.min
 
@@ -301,7 +301,7 @@
 private fun AnimatedContentInOverlay(
     color: Color,
     sizeInOriginalLayout: Size,
-    animatorState: State<LaunchAnimator.State?>,
+    animatorState: State<TransitionAnimator.State?>,
     overlay: ViewGroupOverlay,
     controller: ExpandableControllerImpl,
     content: @Composable (Expandable) -> Unit,
@@ -407,7 +407,7 @@
 
 internal fun measureAndLayoutComposeViewInOverlay(
     view: View,
-    state: LaunchAnimator.State,
+    state: TransitionAnimator.State,
 ) {
     val exactWidth = state.width
     val exactHeight = state.height
@@ -449,7 +449,7 @@
 }
 
 private fun ContentDrawScope.drawBackground(
-    animatorState: LaunchAnimator.State,
+    animatorState: TransitionAnimator.State,
     color: Color,
     border: BorderStroke?,
 ) {
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
index 0e7694e..1020263 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
@@ -44,7 +44,7 @@
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.animation.Expandable
-import com.android.systemui.animation.LaunchAnimator
+import com.android.systemui.animation.TransitionAnimator
 import kotlin.math.roundToInt
 
 /** A controller that can control animated launches from an [Expandable]. */
@@ -70,7 +70,7 @@
     val layoutDirection = LocalLayoutDirection.current
 
     // The current animation state, if we are currently animating a dialog or activity.
-    val animatorState = remember { mutableStateOf<LaunchAnimator.State?>(null) }
+    val animatorState = remember { mutableStateOf<TransitionAnimator.State?>(null) }
 
     // Whether a dialog controlled by this ExpandableController is currently showing.
     val isDialogShowing = remember { mutableStateOf(false) }
@@ -123,7 +123,7 @@
     internal val borderStroke: BorderStroke?,
     internal val composeViewRoot: View,
     internal val density: Density,
-    internal val animatorState: MutableState<LaunchAnimator.State?>,
+    internal val animatorState: MutableState<TransitionAnimator.State?>,
     internal val isDialogShowing: MutableState<Boolean>,
     internal val overlay: MutableState<ViewGroupOverlay?>,
     internal val currentComposeViewInOverlay: MutableState<View?>,
@@ -153,32 +153,32 @@
         }
 
     /**
-     * Create a [LaunchAnimator.Controller] that is going to be used to drive an activity or dialog
-     * animation. This controller will:
+     * Create a [TransitionAnimator.Controller] that is going to be used to drive an activity or
+     * dialog animation. This controller will:
      * 1. Compute the start/end animation state using [boundsInComposeViewRoot] and the location of
      *    composeViewRoot on the screen.
      * 2. Update [animatorState] with the current animation state if we are animating, or null
      *    otherwise.
      */
-    private fun launchController(): LaunchAnimator.Controller {
-        return object : LaunchAnimator.Controller {
+    private fun transitionController(): TransitionAnimator.Controller {
+        return object : TransitionAnimator.Controller {
             private val rootLocationOnScreen = intArrayOf(0, 0)
 
-            override var launchContainer: ViewGroup = composeViewRoot.rootView as ViewGroup
+            override var transitionContainer: ViewGroup = composeViewRoot.rootView as ViewGroup
 
-            override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+            override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
                 animatorState.value = null
             }
 
-            override fun onLaunchAnimationProgress(
-                state: LaunchAnimator.State,
+            override fun onTransitionAnimationProgress(
+                state: TransitionAnimator.State,
                 progress: Float,
                 linearProgress: Float
             ) {
                 // We copy state given that it's always the same object that is mutated by
                 // ActivityLaunchAnimator.
                 animatorState.value =
-                    LaunchAnimator.State(
+                    TransitionAnimator.State(
                             state.top,
                             state.bottom,
                             state.left,
@@ -195,7 +195,7 @@
                 }
             }
 
-            override fun createAnimatorState(): LaunchAnimator.State {
+            override fun createAnimatorState(): TransitionAnimator.State {
                 val boundsInRoot = boundsInComposeViewRoot.value
                 val outline =
                     shape.createOutline(
@@ -236,7 +236,7 @@
                     }
 
                 val rootLocation = rootLocationOnScreen()
-                return LaunchAnimator.State(
+                return TransitionAnimator.State(
                     top = rootLocation.y.roundToInt(),
                     bottom = (rootLocation.y + boundsInRoot.height).roundToInt(),
                     left = rootLocation.x.roundToInt(),
@@ -258,17 +258,18 @@
 
     /** Create an [ActivityLaunchAnimator.Controller] that can be used to animate activities. */
     private fun activityController(cujType: Int?): ActivityLaunchAnimator.Controller {
-        val delegate = launchController()
-        return object : ActivityLaunchAnimator.Controller, LaunchAnimator.Controller by delegate {
-            override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
-                delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+        val delegate = transitionController()
+        return object :
+            ActivityLaunchAnimator.Controller, TransitionAnimator.Controller by delegate {
+            override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
+                delegate.onTransitionAnimationStart(isExpandingFullyAbove)
                 overlay.value = composeViewRoot.rootView.overlay as ViewGroupOverlay
                 cujType?.let { InteractionJankMonitor.getInstance().begin(composeViewRoot, it) }
             }
 
-            override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+            override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
                 cujType?.let { InteractionJankMonitor.getInstance().end(it) }
-                delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+                delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
                 overlay.value = null
             }
         }
@@ -293,11 +294,11 @@
                 }
             }
 
-            override fun createLaunchController(): LaunchAnimator.Controller {
-                val delegate = launchController()
-                return object : LaunchAnimator.Controller by delegate {
-                    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
-                        delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+            override fun createTransitionController(): TransitionAnimator.Controller {
+                val delegate = transitionController()
+                return object : TransitionAnimator.Controller by delegate {
+                    override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+                        delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
 
                         // Make sure we don't draw this expandable when the dialog is showing.
                         isDialogShowing.value = true
@@ -305,11 +306,11 @@
                 }
             }
 
-            override fun createExitController(): LaunchAnimator.Controller {
-                val delegate = launchController()
-                return object : LaunchAnimator.Controller by delegate {
-                    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
-                        delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+            override fun createExitController(): TransitionAnimator.Controller {
+                val delegate = transitionController()
+                return object : TransitionAnimator.Controller by delegate {
+                    override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+                        delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
                         isDialogShowing.value = false
                     }
                 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Color.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Color.kt
new file mode 100644
index 0000000..64b9f2d
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Color.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.common.ui.compose
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.ui.graphics.Color
+import com.android.compose.theme.colorAttr
+
+/** Resolves [com.android.systemui.common.shared.model.Color] into [Color] */
+@Composable
+@ReadOnlyComposable
+fun com.android.systemui.common.shared.model.Color.toColor(): Color {
+    return when (this) {
+        is com.android.systemui.common.shared.model.Color.Attribute -> colorAttr(attribute)
+        is com.android.systemui.common.shared.model.Color.Loaded -> Color(color)
+    }
+}
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 6a8da10..f387021 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
@@ -19,11 +19,6 @@
 import android.content.Context
 import android.view.ViewGroup
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.dagger.SysUISingleton
@@ -45,7 +40,6 @@
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.DisposableHandle
 
 @SysUISingleton
 class NotificationSection
@@ -53,74 +47,54 @@
 constructor(
     @Application private val context: Context,
     private val viewModel: NotificationsPlaceholderViewModel,
-    private val controller: NotificationStackScrollLayoutController,
-    private val sceneContainerFlags: SceneContainerFlags,
-    private val sharedNotificationContainer: SharedNotificationContainer,
-    private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
-    private val stackScrollLayout: NotificationStackScrollLayout,
-    private val notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel,
-    private val ambientState: AmbientState,
-    private val notificationStackSizeCalculator: NotificationStackSizeCalculator,
+    controller: NotificationStackScrollLayoutController,
+    sceneContainerFlags: SceneContainerFlags,
+    sharedNotificationContainer: SharedNotificationContainer,
+    sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
+    stackScrollLayout: NotificationStackScrollLayout,
+    notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel,
+    ambientState: AmbientState,
+    notificationStackSizeCalculator: NotificationStackSizeCalculator,
     @Main private val mainDispatcher: CoroutineDispatcher,
 ) {
-    @Composable
-    fun SceneScope.Notifications(modifier: Modifier = Modifier) {
-        if (KeyguardShadeMigrationNssl.isUnexpectedlyInLegacyMode()) {
+
+    init {
+        if (!KeyguardShadeMigrationNssl.isUnexpectedlyInLegacyMode()) {
             // This scene container section moves the NSSL to the SharedNotificationContainer.
             // This also requires that SharedNotificationContainer gets moved to the
             // SceneWindowRootView by the SceneWindowRootViewBinder. Prior to Scene Container,
             // but when the KeyguardShadeMigrationNssl flag is enabled, NSSL is moved into this
             // container by the NotificationStackScrollLayoutSection.
-            return
-        }
-
-        var isBound by remember { mutableStateOf(false) }
-
-        DisposableEffect(Unit) {
-            val disposableHandles: MutableList<DisposableHandle> = mutableListOf()
-
             // Ensure stackScrollLayout is a child of sharedNotificationContainer.
+
             if (stackScrollLayout.parent != sharedNotificationContainer) {
                 (stackScrollLayout.parent as? ViewGroup)?.removeView(stackScrollLayout)
                 sharedNotificationContainer.addNotificationStackScrollLayout(stackScrollLayout)
             }
 
-            disposableHandles.add(
-                SharedNotificationContainerBinder.bind(
-                    sharedNotificationContainer,
-                    sharedNotificationContainerViewModel,
-                    sceneContainerFlags,
-                    controller,
-                    notificationStackSizeCalculator,
-                    mainDispatcher,
-                )
+            SharedNotificationContainerBinder.bind(
+                sharedNotificationContainer,
+                sharedNotificationContainerViewModel,
+                sceneContainerFlags,
+                controller,
+                notificationStackSizeCalculator,
+                mainDispatcher,
             )
 
             if (sceneContainerFlags.flexiNotifsEnabled()) {
-                disposableHandles.add(
-                    NotificationStackAppearanceViewBinder.bind(
-                        context,
-                        sharedNotificationContainer,
-                        notificationStackAppearanceViewModel,
-                        ambientState,
-                        controller,
-                    )
+                NotificationStackAppearanceViewBinder.bind(
+                    context,
+                    sharedNotificationContainer,
+                    notificationStackAppearanceViewModel,
+                    ambientState,
+                    controller,
                 )
             }
-
-            isBound = true
-
-            onDispose {
-                disposableHandles.forEach { it.dispose() }
-                disposableHandles.clear()
-                isBound = false
-            }
         }
+    }
 
-        if (!isBound) {
-            return
-        }
-
+    @Composable
+    fun SceneScope.Notifications(modifier: Modifier = Modifier) {
         NotificationStack(
             viewModel = viewModel,
             modifier = modifier,
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 3fb8254..ef6ae2e 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
@@ -19,6 +19,7 @@
 
 import android.util.Log
 import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.scrollBy
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.WindowInsets
@@ -61,11 +62,11 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.lerp
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.NestedScrollBehavior
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.height
-import com.android.compose.ui.util.lerp
 import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
 import com.android.systemui.notifications.ui.composable.Notifications.Form
 import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_CORNER_RADIUS
@@ -140,6 +141,8 @@
 ) {
     val density = LocalDensity.current
     val screenCornerRadius = LocalScreenCornerRadius.current
+    val scrollState = rememberScrollState()
+    val syntheticScroll = viewModel.syntheticScroll.collectAsState(0f)
     val expansionFraction by viewModel.expandFraction.collectAsState(0f)
 
     val navBarHeight =
@@ -180,11 +183,28 @@
 
     // if contentHeight drops below minimum visible scrim height while scrim is
     // expanded, reset scrim offset.
-    LaunchedEffect(contentHeight, screenHeight, maxScrimTop, scrimOffset) {
+    LaunchedEffect(contentHeight, scrimOffset) {
         snapshotFlow { contentHeight.value < minVisibleScrimHeight() && scrimOffset.value < 0f }
             .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.value = 0f }
     }
 
+    // if we receive scroll delta from NSSL, offset the scrim and placeholder accordingly.
+    LaunchedEffect(syntheticScroll, scrimOffset, scrollState) {
+        snapshotFlow { syntheticScroll.value }
+            .collect { delta ->
+                val minOffset = minScrimOffset()
+                if (scrimOffset.value > minOffset) {
+                    val remainingDelta = (minOffset - (scrimOffset.value - delta)).coerceAtLeast(0f)
+                    scrimOffset.value = (scrimOffset.value - delta).coerceAtLeast(minOffset)
+                    if (remainingDelta > 0f) {
+                        scrollState.scrollBy(remainingDelta)
+                    }
+                } else {
+                    scrollState.scrollTo(delta.roundToInt())
+                }
+            }
+    }
+
     Box(
         modifier =
             modifier
@@ -260,7 +280,7 @@
                                 )
                             }
                         )
-                        .verticalScroll(rememberScrollState())
+                        .verticalScroll(scrollState)
                         .fillMaxWidth()
                         .height { (contentHeight.value + navBarHeight).roundToInt() },
             )
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index c027c49..de8f2ec 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -18,21 +18,21 @@
 
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.defaultMinSize
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.layout
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
 import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.MovableElementScenePicker
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.TransitionState
+import com.android.compose.modifiers.thenIf
 import com.android.compose.theme.colorAttr
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Companion.Collapsing
@@ -44,9 +44,16 @@
 import com.android.systemui.scene.ui.composable.Shade
 
 object QuickSettings {
+    private val SCENES =
+        setOf(
+            QuickSettingsSceneKey,
+            Shade,
+        )
+
     object Elements {
         // TODO RENAME
-        val Content = ElementKey("QuickSettingsContent")
+        val Content =
+            ElementKey("QuickSettingsContent", scenePicker = MovableElementScenePicker(SCENES))
         val CollapsedGrid = ElementKey("QuickSettingsCollapsedGrid")
         val FooterActions = ElementKey("QuickSettingsFooterActions")
     }
@@ -86,14 +93,22 @@
  */
 @Composable
 fun SceneScope.QuickSettings(
-    modifier: Modifier = Modifier,
     qsSceneAdapter: QSSceneAdapter,
+    heightProvider: () -> Int,
+    modifier: Modifier = Modifier,
 ) {
     val contentState = stateForQuickSettingsContent()
 
     MovableElement(
         key = QuickSettings.Elements.Content,
-        modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 300.dp)
+        modifier =
+            modifier.fillMaxWidth().layout { measurable, constraints ->
+                val placeable = measurable.measure(constraints)
+                // Use the height of the correct view based on the scene it is being composed in
+                val height = heightProvider()
+
+                layout(placeable.width, height) { placeable.placeRelative(0, 0) }
+            }
     ) {
         content { QuickSettingsContent(qsSceneAdapter = qsSceneAdapter, contentState) }
     }
@@ -118,15 +133,7 @@
         qsView?.let { view ->
             Box(
                 modifier =
-                    modifier
-                        .fillMaxWidth()
-                        .then(
-                            if (isCustomizing) {
-                                Modifier.fillMaxHeight()
-                            } else {
-                                Modifier.wrapContentHeight()
-                            }
-                        )
+                    modifier.fillMaxWidth().thenIf(isCustomizing) { Modifier.fillMaxHeight() }
             ) {
                 AndroidView(
                     modifier = Modifier.fillMaxWidth().background(colorAttr(R.attr.underSurface)),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 969dec3..1cbc992 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -213,8 +213,9 @@
                     Spacer(modifier = Modifier.height(16.dp))
                     // This view has its own horizontal padding
                     QuickSettings(
-                        modifier = Modifier.sysuiResTag("expanded_qs_scroll_view"),
                         viewModel.qsSceneAdapter,
+                        { viewModel.qsSceneAdapter.qsHeight },
+                        modifier = Modifier.sysuiResTag("expanded_qs_scroll_view"),
                     )
                 }
             }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index 9f9e1f5..da1b417 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -40,6 +40,7 @@
 import com.android.compose.animation.scene.Swipe
 import com.android.compose.animation.scene.SwipeDirection
 import com.android.compose.animation.scene.UserAction as SceneTransitionUserAction
+import com.android.compose.animation.scene.UserActionResult
 import com.android.compose.animation.scene.observableTransitionState
 import com.android.compose.animation.scene.updateSceneTransitionLayoutState
 import com.android.systemui.ribbon.ui.composable.BottomRightCornerRibbon
@@ -168,7 +169,7 @@
 private fun toTransitionModels(
     userAction: UserAction,
     sceneModel: SceneModel,
-): Pair<SceneTransitionUserAction, SceneTransitionSceneKey> {
+): Pair<SceneTransitionUserAction, UserActionResult> {
     return userAction.toTransitionUserAction() to sceneModel.key.toTransitionSceneKey()
 }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 677df7e..cac35cb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -189,8 +189,8 @@
                                     )
                             )
                             QuickSettings(
-                                modifier = Modifier.height(130.dp),
                                 viewModel.qsSceneAdapter,
+                                { viewModel.qsSceneAdapter.qqsHeight },
                             )
 
                             if (viewModel.isMediaVisible()) {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index b26194f..37d763b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -30,7 +30,8 @@
 import androidx.compose.ui.graphics.lerp
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.lerp
-import com.android.compose.ui.util.lerp
+import androidx.compose.ui.util.fastCoerceIn
+import androidx.compose.ui.util.lerp
 
 /**
  * A [State] whose [value] is animated.
@@ -282,7 +283,7 @@
                 } else {
                     val progress =
                         if (canOverflow) transition.progress
-                        else transition.progress.coerceIn(0f, 1f)
+                        else transition.progress.fastCoerceIn(0f, 1f)
                     lerp(fromValue, toValue, progress)
                 }
             } else fromValue ?: toValue
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 e40f6b6..828e34d 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
@@ -39,6 +39,8 @@
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.round
+import androidx.compose.ui.util.fastCoerceIn
+import androidx.compose.ui.util.lerp
 import com.android.compose.animation.scene.transformation.PropertyTransformation
 import com.android.compose.animation.scene.transformation.SharedElementTransformation
 import com.android.compose.ui.util.lerp
@@ -362,7 +364,7 @@
             isSpecified = { true },
             ::lerp,
         )
-        .coerceIn(0f, 1f)
+        .fastCoerceIn(0f, 1f)
 }
 
 @OptIn(ExperimentalComposeUiApi::class)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
index 2596d4a..9770399 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
@@ -44,7 +44,7 @@
 class SceneKey(
     debugName: String,
     identity: Any = Object(),
-) : Key(debugName, identity), UserActionResult {
+) : Key(debugName, identity) {
     @VisibleForTesting
     // TODO(b/240432457): Make internal once PlatformComposeSceneTransitionLayoutTestsUtils can
     // access internal members.
@@ -53,11 +53,6 @@
     /** The unique [ElementKey] identifying this scene's root element. */
     val rootElementKey = ElementKey(debugName, identity)
 
-    // Implementation of [UserActionResult].
-    override val toScene: SceneKey = this
-    override val transitionKey: TransitionKey? = null
-    override val distance: UserActionDistance? = null
-
     override fun toString(): String {
         return "SceneKey(debugName=$debugName)"
     }
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 529fc03..3ff869b 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
@@ -283,21 +283,28 @@
             }
 
             onDragStart(drag.position, overSlop, pressed.size)
-            onDrag(drag, overSlop)
 
-            val successful =
-                when (orientation) {
-                    Orientation.Horizontal ->
-                        horizontalDrag(drag.id) {
-                            onDrag(it, it.positionChange().x)
-                            it.consume()
-                        }
-                    Orientation.Vertical ->
-                        verticalDrag(drag.id) {
-                            onDrag(it, it.positionChange().y)
-                            it.consume()
-                        }
-                }
+            val successful: Boolean
+            try {
+                onDrag(drag, overSlop)
+
+                successful =
+                    when (orientation) {
+                        Orientation.Horizontal ->
+                            horizontalDrag(drag.id) {
+                                onDrag(it, it.positionChange().x)
+                                it.consume()
+                            }
+                        Orientation.Vertical ->
+                            verticalDrag(drag.id) {
+                                onDrag(it, it.positionChange().y)
+                                it.consume()
+                            }
+                    }
+            } catch (t: Throwable) {
+                onDragCancel()
+                throw t
+            }
 
             if (successful) {
                 onDragEnd()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index 23af5ac..b3d2bc9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -701,27 +701,19 @@
                         gestureHandler.shouldImmediatelyIntercept(startedPosition = null)
                 if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false
 
-                val swipeTransition = gestureHandler.swipeTransition
-                val progress = swipeTransition.progress
                 val threshold = layoutImpl.transitionInterceptionThreshold
-                fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold
-
-                // The transition is always between 0 and 1. If it is close to either of these
-                // intervals, we want to go directly to the TransitionState.Idle.
-                // The progress value can go beyond this range in the case of overscroll.
-                val shouldSnapToIdle = isProgressCloseTo(0f) || isProgressCloseTo(1f)
-                if (shouldSnapToIdle) {
-                    swipeTransition.cancelOffsetAnimation()
-                    layoutState.finishTransition(swipeTransition, swipeTransition.currentScene)
+                val hasSnappedToIdle = layoutState.snapToIdleIfClose(threshold)
+                if (hasSnappedToIdle) {
+                    // If the current swipe transition is closed to 0f or 1f, then we want to
+                    // interrupt the transition (snapping it to Idle) and scroll the list.
+                    return@PriorityNestedScrollConnection false
                 }
 
-                // Start only if we cannot consume this event
-                val canStart = !shouldSnapToIdle
-                if (canStart) {
-                    isIntercepting = true
-                }
-
-                canStart
+                // 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
+                // transition.
+                isIntercepting = true
+                true
             },
             canStartPostScroll = { offsetAvailable, offsetBeforeStart ->
                 val behavior: NestedScrollBehavior =
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 d904c8b..e1f8a09 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
@@ -332,7 +332,11 @@
 @Stable @ElementDsl interface MovableElementContentScope : BaseSceneScope, ElementBoxScope
 
 /** An action performed by the user. */
-sealed interface UserAction
+sealed interface UserAction {
+    infix fun to(scene: SceneKey): Pair<UserAction, UserActionResult> {
+        return this to UserActionResult(toScene = scene)
+    }
+}
 
 /** The user navigated back, either using a gesture or by triggering a KEYCODE_BACK event. */
 data object Back : UserAction
@@ -385,65 +389,26 @@
     ): SwipeSource?
 }
 
-/**
- * The result of performing a [UserAction].
- *
- * Note: [UserActionResult] is implemented by [SceneKey], so you can also use scene keys directly
- * when defining your [UserActionResult]s.
- *
- * ```
- * SceneTransitionLayout(...) {
- *     scene(
- *         Scenes.Foo,
- *         userActions =
- *             mapOf(
- *                 Swipe.Right to Scene.Bar,
- *                 Swipe.Down to Scene.Doe,
- *             )
- *         )
- *     ) { ... }
- * }
- * ```
- */
-interface UserActionResult {
+/** The result of performing a [UserAction]. */
+class UserActionResult(
     /** The scene we should be transitioning to during the [UserAction]. */
-    val toScene: SceneKey
-
-    /** The key of the transition that should be used. */
-    val transitionKey: TransitionKey?
+    val toScene: SceneKey,
 
     /**
      * The distance the action takes to animate from 0% to 100%.
      *
      * If `null`, a default distance will be used that depends on the [UserAction] performed.
      */
-    val distance: UserActionDistance?
-}
+    val distance: UserActionDistance? = null,
 
-/** Create a [UserActionResult] to [toScene] with the given [distance] and [transitionKey]. */
-fun UserActionResult(
-    toScene: SceneKey,
-    distance: UserActionDistance? = null,
-    transitionKey: TransitionKey? = null,
-): UserActionResult {
-    return object : UserActionResult {
-        override val toScene: SceneKey = toScene
-        override val transitionKey: TransitionKey? = transitionKey
-        override val distance: UserActionDistance? = distance
-    }
-}
-
-/** Create a [UserActionResult] to [toScene] with the given fixed [distance] and [transitionKey]. */
-fun UserActionResult(
-    toScene: SceneKey,
-    distance: Dp,
-    transitionKey: TransitionKey? = null,
-): UserActionResult {
-    return UserActionResult(
-        toScene = toScene,
-        distance = FixedDistance(distance),
-        transitionKey = transitionKey,
-    )
+    /** The key of the transition that should be used. */
+    val transitionKey: TransitionKey? = null,
+) {
+    constructor(
+        toScene: SceneKey,
+        distance: Dp,
+        transitionKey: TransitionKey? = null,
+    ) : this(toScene, FixedDistance(distance), transitionKey)
 }
 
 interface UserActionDistance {
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 aee6f9e..a8da551 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
@@ -24,6 +24,11 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
+import androidx.compose.ui.util.fastFilter
+import androidx.compose.ui.util.fastForEach
+import com.android.compose.animation.scene.transition.link.LinkedTransition
+import com.android.compose.animation.scene.transition.link.StateLink
+import kotlin.math.absoluteValue
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.Channel
 
@@ -100,8 +105,9 @@
 fun MutableSceneTransitionLayoutState(
     initialScene: SceneKey,
     transitions: SceneTransitions = SceneTransitions.Empty,
+    stateLinks: List<StateLink> = emptyList(),
 ): MutableSceneTransitionLayoutState {
-    return MutableSceneTransitionLayoutStateImpl(initialScene, transitions)
+    return MutableSceneTransitionLayoutStateImpl(initialScene, transitions, stateLinks)
 }
 
 /**
@@ -120,9 +126,12 @@
     currentScene: SceneKey,
     onChangeScene: (SceneKey) -> Unit,
     transitions: SceneTransitions = SceneTransitions.Empty,
+    stateLinks: List<StateLink> = emptyList(),
 ): SceneTransitionLayoutState {
-    return remember { HoistedSceneTransitionLayoutScene(currentScene, transitions, onChangeScene) }
-        .apply { update(currentScene, onChangeScene, transitions) }
+    return remember {
+            HoistedSceneTransitionLayoutScene(currentScene, transitions, onChangeScene, stateLinks)
+        }
+        .apply { update(currentScene, onChangeScene, transitions, stateLinks) }
 }
 
 @Stable
@@ -183,8 +192,10 @@
     }
 }
 
-internal abstract class BaseSceneTransitionLayoutState(initialScene: SceneKey) :
-    SceneTransitionLayoutState {
+internal abstract class BaseSceneTransitionLayoutState(
+    initialScene: SceneKey,
+    protected var stateLinks: List<StateLink>,
+) : SceneTransitionLayoutState {
     override var transitionState: TransitionState by
         mutableStateOf(TransitionState.Idle(initialScene))
         protected set
@@ -195,6 +206,8 @@
      */
     internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty
 
+    private val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>()
+
     /**
      * Called when the [current scene][TransitionState.currentScene] should be changed to [scene].
      *
@@ -223,19 +236,94 @@
             transitions
                 .transitionSpec(transition.fromScene, transition.toScene, key = transitionKey)
                 .transformationSpec()
-
+        cancelActiveTransitionLinks()
+        setupTransitionLinks(transition)
         transitionState = transition
     }
 
+    private fun cancelActiveTransitionLinks() {
+        for ((link, linkedTransition) in activeTransitionLinks) {
+            link.target.finishTransition(linkedTransition, linkedTransition.currentScene)
+        }
+        activeTransitionLinks.clear()
+    }
+
+    private fun setupTransitionLinks(transitionState: TransitionState) {
+        if (transitionState !is TransitionState.Transition) return
+        stateLinks.fastForEach { stateLink ->
+            val matchingLinks =
+                stateLink.transitionLinks.fastFilter { it.isMatchingLink(transitionState) }
+            if (matchingLinks.isEmpty()) return@fastForEach
+            if (matchingLinks.size > 1) error("More than one link matched.")
+
+            val targetCurrentScene = stateLink.target.transitionState.currentScene
+            val matchingLink = matchingLinks[0]
+
+            if (!matchingLink.targetIsInValidState(targetCurrentScene)) return@fastForEach
+
+            val linkedTransition =
+                LinkedTransition(
+                    originalTransition = transitionState,
+                    fromScene = targetCurrentScene,
+                    toScene = matchingLink.targetTo,
+                )
+
+            stateLink.target.startTransition(linkedTransition, matchingLink.targetTransitionKey)
+            activeTransitionLinks[stateLink] = linkedTransition
+        }
+    }
+
     /**
      * Notify that [transition] was finished and that we should settle to [idleScene]. This will do
      * nothing if [transition] was interrupted since it was started.
      */
     internal fun finishTransition(transition: TransitionState.Transition, idleScene: SceneKey) {
+        resolveActiveTransitionLinks(idleScene)
         if (transitionState == transition) {
             transitionState = TransitionState.Idle(idleScene)
         }
     }
+
+    private fun resolveActiveTransitionLinks(idleScene: SceneKey) {
+        val previousTransition = this.transitionState as? TransitionState.Transition ?: return
+        for ((link, linkedTransition) in activeTransitionLinks) {
+            if (previousTransition.fromScene == idleScene) {
+                // The transition ended by arriving at the fromScene, move link to Idle(fromScene).
+                link.target.finishTransition(linkedTransition, linkedTransition.fromScene)
+            } else if (previousTransition.toScene == idleScene) {
+                // The transition ended by arriving at the toScene, move link to Idle(toScene).
+                link.target.finishTransition(linkedTransition, linkedTransition.toScene)
+            } else {
+                // The transition was interrupted by something else, we reset to initial state.
+                link.target.finishTransition(linkedTransition, linkedTransition.fromScene)
+            }
+        }
+        activeTransitionLinks.clear()
+    }
+
+    /**
+     * Check if a transition is in progress. If the progress value is near 0 or 1, immediately snap
+     * to the closest scene.
+     *
+     * @return true if snapped to the closest scene.
+     */
+    internal fun snapToIdleIfClose(threshold: Float): Boolean {
+        val transition = currentTransition ?: return false
+        val progress = transition.progress
+        fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold
+
+        return when {
+            isProgressCloseTo(0f) -> {
+                finishTransition(transition, transition.fromScene)
+                true
+            }
+            isProgressCloseTo(1f) -> {
+                finishTransition(transition, transition.toScene)
+                true
+            }
+            else -> false
+        }
+    }
 }
 
 /**
@@ -246,7 +334,8 @@
     initialScene: SceneKey,
     override var transitions: SceneTransitions,
     private var changeScene: (SceneKey) -> Unit,
-) : BaseSceneTransitionLayoutState(initialScene) {
+    stateLinks: List<StateLink> = emptyList(),
+) : BaseSceneTransitionLayoutState(initialScene, stateLinks) {
     private val targetSceneChannel = Channel<SceneKey>(Channel.CONFLATED)
 
     override fun CoroutineScope.onChangeScene(scene: SceneKey) = changeScene(scene)
@@ -256,10 +345,12 @@
         currentScene: SceneKey,
         onChangeScene: (SceneKey) -> Unit,
         transitions: SceneTransitions,
+        stateLinks: List<StateLink>,
     ) {
         SideEffect {
             this.changeScene = onChangeScene
             this.transitions = transitions
+            this.stateLinks = stateLinks
 
             targetSceneChannel.trySend(currentScene)
         }
@@ -283,7 +374,8 @@
 internal class MutableSceneTransitionLayoutStateImpl(
     initialScene: SceneKey,
     override var transitions: SceneTransitions,
-) : MutableSceneTransitionLayoutState, BaseSceneTransitionLayoutState(initialScene) {
+    stateLinks: List<StateLink> = emptyList(),
+) : MutableSceneTransitionLayoutState, BaseSceneTransitionLayoutState(initialScene, stateLinks) {
     override fun setTargetScene(
         targetScene: SceneKey,
         coroutineScope: CoroutineScope,
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 04254fb..603f7ba 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
@@ -16,6 +16,9 @@
 
 package com.android.compose.animation.scene.transformation
 
+import androidx.compose.ui.util.fastCoerceAtLeast
+import androidx.compose.ui.util.fastCoerceAtMost
+import androidx.compose.ui.util.fastCoerceIn
 import com.android.compose.animation.scene.Element
 import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.Scene
@@ -106,10 +109,10 @@
     fun progress(transitionProgress: Float): Float {
         return when {
             start.isSpecified() && end.isSpecified() ->
-                ((transitionProgress - start) / (end - start)).coerceIn(0f, 1f)
+                ((transitionProgress - start) / (end - start)).fastCoerceIn(0f, 1f)
             !start.isSpecified() && !end.isSpecified() -> transitionProgress
-            end.isSpecified() -> (transitionProgress / end).coerceAtMost(1f)
-            else -> ((transitionProgress - start) / (1f - start)).coerceAtLeast(0f)
+            end.isSpecified() -> (transitionProgress / end).fastCoerceAtMost(1f)
+            else -> ((transitionProgress - start) / (1f - start)).fastCoerceAtLeast(0f)
         }
     }
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
new file mode 100644
index 0000000..33b57b2
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.compose.animation.scene.transition.link
+
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionState
+
+/** A linked transition which is driven by a [originalTransition]. */
+internal class LinkedTransition(
+    private val originalTransition: TransitionState.Transition,
+    fromScene: SceneKey,
+    toScene: SceneKey,
+) : TransitionState.Transition(fromScene, toScene) {
+
+    override val currentScene: SceneKey
+        get() {
+            return when (originalTransition.currentScene) {
+                originalTransition.fromScene -> fromScene
+                originalTransition.toScene -> toScene
+                else -> error("Original currentScene is neither FromScene nor ToScene")
+            }
+        }
+
+    override val isInitiatedByUserInput: Boolean
+        get() = originalTransition.isInitiatedByUserInput
+
+    override val isUserInputOngoing: Boolean
+        get() = originalTransition.isUserInputOngoing
+
+    override val progress: Float
+        get() = originalTransition.progress
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
new file mode 100644
index 0000000..6c29946
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/StateLink.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.compose.animation.scene.transition.link
+
+import com.android.compose.animation.scene.BaseSceneTransitionLayoutState
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayoutState
+import com.android.compose.animation.scene.TransitionKey
+import com.android.compose.animation.scene.TransitionState
+
+/** A link between a source (implicit) and [target] `SceneTransitionLayoutState`. */
+class StateLink(target: SceneTransitionLayoutState, val transitionLinks: List<TransitionLink>) {
+
+    internal val target = target as BaseSceneTransitionLayoutState
+
+    /**
+     * Links two transitions (source and target) together.
+     *
+     * `null` can be passed to indicate that any SceneKey should match. e.g. passing `null`, `null`,
+     * `null`, `SceneA` means that any transition at the source will trigger a transition in the
+     * target to `SceneA` from any current scene.
+     */
+    class TransitionLink(
+        val sourceFrom: SceneKey?,
+        val sourceTo: SceneKey?,
+        val targetFrom: SceneKey?,
+        val targetTo: SceneKey,
+        val targetTransitionKey: TransitionKey? = null,
+    ) {
+        init {
+            if (
+                (sourceFrom != null && sourceFrom == sourceTo) ||
+                    (targetFrom != null && targetFrom == targetTo)
+            )
+                error("From and To can't be the same")
+        }
+
+        internal fun isMatchingLink(transition: TransitionState.Transition): Boolean {
+            return (sourceFrom == null || sourceFrom == transition.fromScene) &&
+                (sourceTo == null || sourceTo == transition.toScene)
+        }
+
+        internal fun targetIsInValidState(targetCurrentScene: SceneKey): Boolean {
+            return (targetFrom == null || targetFrom == targetCurrentScene) &&
+                targetTo != targetCurrentScene
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
index 13747b7..e78ab29 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt
@@ -20,24 +20,8 @@
 import androidx.compose.ui.geometry.isSpecified
 import androidx.compose.ui.geometry.lerp
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.util.lerp
 import com.android.compose.animation.scene.Scale
-import kotlin.math.roundToInt
-import kotlin.math.roundToLong
-
-/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
-fun lerp(start: Float, stop: Float, fraction: Float): Float {
-    return (1 - fraction) * start + fraction * stop
-}
-
-/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
-fun lerp(start: Int, stop: Int, fraction: Float): Int {
-    return start + ((stop - start) * fraction.toDouble()).roundToInt()
-}
-
-/** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
-fun lerp(start: Long, stop: Long, fraction: Float): Long {
-    return start + ((stop - start) * fraction.toDouble()).roundToLong()
-}
 
 /** Linearly interpolate between [start] and [stop] with [fraction] fraction between them. */
 fun lerp(start: IntSize, stop: IntSize, fraction: Float): IntSize {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
index a116501..e8854cf 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -30,8 +30,8 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.lerp
+import androidx.compose.ui.util.lerp
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.compose.ui.util.lerp
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertThrows
 import org.junit.Rule
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
new file mode 100644
index 0000000..cd99d05
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.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.compose.animation.scene
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performTouchInput
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class MultiPointerDraggableTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun cancellingPointerCallsOnDragStopped() {
+        val size = 200f
+        val middle = Offset(size / 2f, size / 2f)
+
+        var enabled by mutableStateOf(false)
+        var started = false
+        var dragged = false
+        var stopped = false
+
+        var touchSlop = 0f
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            Box(
+                Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+                    .multiPointerDraggable(
+                        orientation = Orientation.Vertical,
+                        enabled = { enabled },
+                        startDragImmediately = { false },
+                        onDragStarted = { _, _, _ -> started = true },
+                        onDragDelta = { _ -> dragged = true },
+                        onDragStopped = { stopped = true },
+                    )
+            )
+        }
+
+        fun startDraggingDown() {
+            rule.onRoot().performTouchInput {
+                down(middle)
+                moveBy(Offset(0f, touchSlop))
+            }
+        }
+
+        fun releaseFinger() {
+            rule.onRoot().performTouchInput { up() }
+        }
+
+        // Swiping down does nothing because enabled is false.
+        startDraggingDown()
+        assertThat(started).isFalse()
+        assertThat(dragged).isFalse()
+        assertThat(stopped).isFalse()
+        releaseFinger()
+
+        // Enable the draggable and swipe down. This should both call onDragStarted() and
+        // onDragDelta().
+        enabled = true
+        rule.waitForIdle()
+        startDraggingDown()
+        assertThat(started).isTrue()
+        assertThat(dragged).isTrue()
+        assertThat(stopped).isFalse()
+
+        // Disable the pointer input. This should call onDragStopped() even if didn't release the
+        // finger yet.
+        enabled = false
+        rule.waitForIdle()
+        assertThat(started).isTrue()
+        assertThat(dragged).isTrue()
+        assertThat(stopped).isTrue()
+    }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index 2dc94a4..dacbdb4 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -54,12 +54,8 @@
         private val layoutState =
             MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
 
-        val mutableUserActionsA: MutableMap<UserAction, SceneKey> =
-            mutableMapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
-
-        val mutableUserActionsB: MutableMap<UserAction, SceneKey> =
-            mutableMapOf(Swipe.Up to SceneC, Swipe.Down to SceneA)
-
+        val mutableUserActionsA = mutableMapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
+        val mutableUserActionsB = mutableMapOf(Swipe.Up to SceneC, Swipe.Down to SceneA)
         private val scenesBuilder: SceneTransitionLayoutScope.() -> Unit = {
             scene(
                 key = SceneA,
@@ -507,7 +503,7 @@
         onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
         assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
 
-        mutableUserActionsA[Swipe.Up] = SceneC
+        mutableUserActionsA[Swipe.Up] = UserActionResult(SceneC)
         onDelta(pixels = up(fractionOfScreen = 0.1f))
         // target stays B even though UserActions changed
         assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.2f)
@@ -524,7 +520,7 @@
         onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
         assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
 
-        mutableUserActionsA[Swipe.Up] = SceneC
+        mutableUserActionsA[Swipe.Up] = UserActionResult(SceneC)
         onDelta(pixels = up(fractionOfScreen = 0.1f))
         onDragStopped(velocity = down(fractionOfScreen = 0.1f))
 
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index c61917d..f81a7f2 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -18,10 +18,14 @@
 
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
+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.transition.link.StateLink
 import com.android.compose.test.runMonotonicClockTest
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineStart
-import kotlinx.coroutines.cancel
 import kotlinx.coroutines.launch
 import org.junit.Rule
 import org.junit.Test
@@ -31,93 +35,241 @@
 class SceneTransitionLayoutStateTest {
     @get:Rule val rule = createComposeRule()
 
+    class TestableTransition(
+        fromScene: SceneKey,
+        toScene: SceneKey,
+    ) : TransitionState.Transition(fromScene, toScene) {
+        override var currentScene: SceneKey = fromScene
+        override var progress: Float = 0.0f
+        override var isInitiatedByUserInput: Boolean = false
+        override var isUserInputOngoing: Boolean = false
+    }
+
     @Test
     fun isTransitioningTo_idle() {
-        val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
 
         assertThat(state.isTransitioning()).isFalse()
-        assertThat(state.isTransitioning(from = TestScenes.SceneA)).isFalse()
-        assertThat(state.isTransitioning(to = TestScenes.SceneB)).isFalse()
-        assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB))
-            .isFalse()
+        assertThat(state.isTransitioning(from = SceneA)).isFalse()
+        assertThat(state.isTransitioning(to = SceneB)).isFalse()
+        assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isFalse()
     }
 
     @Test
     fun isTransitioningTo_transition() {
-        val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
-        state.startTransition(
-            transition(from = TestScenes.SceneA, to = TestScenes.SceneB),
-            transitionKey = null
-        )
+        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
+        state.startTransition(transition(from = SceneA, to = SceneB), transitionKey = null)
 
         assertThat(state.isTransitioning()).isTrue()
-        assertThat(state.isTransitioning(from = TestScenes.SceneA)).isTrue()
-        assertThat(state.isTransitioning(from = TestScenes.SceneB)).isFalse()
-        assertThat(state.isTransitioning(to = TestScenes.SceneB)).isTrue()
-        assertThat(state.isTransitioning(to = TestScenes.SceneA)).isFalse()
-        assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue()
+        assertThat(state.isTransitioning(from = SceneA)).isTrue()
+        assertThat(state.isTransitioning(from = SceneB)).isFalse()
+        assertThat(state.isTransitioning(to = SceneB)).isTrue()
+        assertThat(state.isTransitioning(to = SceneA)).isFalse()
+        assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isTrue()
     }
 
     @Test
     fun setTargetScene_idleToSameScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
-        assertThat(state.setTargetScene(TestScenes.SceneA, coroutineScope = this)).isNull()
+        val state = MutableSceneTransitionLayoutState(SceneA)
+        assertThat(state.setTargetScene(SceneA, coroutineScope = this)).isNull()
     }
 
     @Test
     fun setTargetScene_idleToDifferentScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
-        val transition = state.setTargetScene(TestScenes.SceneB, coroutineScope = this)
+        val state = MutableSceneTransitionLayoutState(SceneA)
+        val transition = state.setTargetScene(SceneB, coroutineScope = this)
         assertThat(transition).isNotNull()
         assertThat(state.transitionState).isEqualTo(transition)
 
         testScheduler.advanceUntilIdle()
-        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
+        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
     }
 
     @Test
     fun setTargetScene_transitionToSameScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
-        assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
-        assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNull()
+        val state = MutableSceneTransitionLayoutState(SceneA)
+        assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
+        assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNull()
         testScheduler.advanceUntilIdle()
-        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
+        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
     }
 
     @Test
     fun setTargetScene_transitionToDifferentScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
-        assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
-        assertThat(state.setTargetScene(TestScenes.SceneC, coroutineScope = this)).isNotNull()
+        val state = MutableSceneTransitionLayoutState(SceneA)
+        assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
+        assertThat(state.setTargetScene(SceneC, coroutineScope = this)).isNotNull()
         testScheduler.advanceUntilIdle()
-        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneC))
+        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneC))
     }
 
     @Test
     fun setTargetScene_transitionToOriginalScene() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
-        assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
+        val state = MutableSceneTransitionLayoutState(SceneA)
+        assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
 
         // Progress is 0f, so we don't animate at all and directly snap back to A.
-        assertThat(state.setTargetScene(TestScenes.SceneA, coroutineScope = this)).isNull()
-        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneA))
+        assertThat(state.setTargetScene(SceneA, coroutineScope = this)).isNull()
+        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA))
     }
 
     @Test
     fun setTargetScene_coroutineScopeCancelled() = runMonotonicClockTest {
-        val state = MutableSceneTransitionLayoutState(TestScenes.SceneA)
+        val state = MutableSceneTransitionLayoutState(SceneA)
 
         lateinit var transition: TransitionState.Transition
         val job =
             launch(start = CoroutineStart.UNDISPATCHED) {
-                transition = state.setTargetScene(TestScenes.SceneB, coroutineScope = this)!!
+                transition = state.setTargetScene(SceneB, coroutineScope = this)!!
             }
         assertThat(state.transitionState).isEqualTo(transition)
 
         // Cancelling the scope/job still sets the state to Idle(targetScene).
         job.cancel()
         testScheduler.advanceUntilIdle()
-        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
+        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+    }
+
+    private fun setupLinkedStates(
+        parentInitialScene: SceneKey = SceneC,
+        childInitialScene: SceneKey = SceneA,
+        sourceFrom: SceneKey? = SceneA,
+        sourceTo: SceneKey? = SceneB,
+        targetFrom: SceneKey? = SceneC,
+        targetTo: SceneKey = SceneD
+    ): Pair<BaseSceneTransitionLayoutState, BaseSceneTransitionLayoutState> {
+        val parentState = MutableSceneTransitionLayoutState(parentInitialScene)
+        val link =
+            listOf(
+                StateLink(
+                    parentState,
+                    listOf(StateLink.TransitionLink(sourceFrom, sourceTo, targetFrom, targetTo))
+                )
+            )
+        val childState = MutableSceneTransitionLayoutState(childInitialScene, stateLinks = link)
+        return Pair(
+            parentState as BaseSceneTransitionLayoutState,
+            childState as BaseSceneTransitionLayoutState
+        )
+    }
+
+    @Test
+    fun linkedTransition_startsLinkAndFinishesLinkInToState() {
+        val (parentState, childState) = setupLinkedStates()
+
+        val childTransition = TestableTransition(SceneA, SceneB)
+
+        childState.startTransition(childTransition, null)
+        assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+        assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
+
+        childState.finishTransition(childTransition, SceneB)
+        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
+    }
+
+    @Test
+    fun linkedTransition_transitiveLink() {
+        val parentParentState =
+            MutableSceneTransitionLayoutState(SceneB) as BaseSceneTransitionLayoutState
+        val parentLink =
+            listOf(
+                StateLink(
+                    parentParentState,
+                    listOf(StateLink.TransitionLink(SceneC, SceneD, SceneB, SceneC))
+                )
+            )
+        val parentState =
+            MutableSceneTransitionLayoutState(SceneC, stateLinks = parentLink)
+                as BaseSceneTransitionLayoutState
+        val link =
+            listOf(
+                StateLink(
+                    parentState,
+                    listOf(StateLink.TransitionLink(SceneA, SceneB, SceneC, SceneD))
+                )
+            )
+        val childState =
+            MutableSceneTransitionLayoutState(SceneA, stateLinks = link)
+                as BaseSceneTransitionLayoutState
+
+        val childTransition = TestableTransition(SceneA, SceneB)
+
+        childState.startTransition(childTransition, null)
+        assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+        assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
+        assertThat(parentParentState.isTransitioning(SceneB, SceneC)).isTrue()
+
+        childState.finishTransition(childTransition, SceneB)
+        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
+        assertThat(parentParentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+    }
+
+    @Test
+    fun linkedTransition_linkProgressIsEqual() {
+        val (parentState, childState) = setupLinkedStates()
+
+        val childTransition = TestableTransition(SceneA, SceneB)
+
+        childState.startTransition(childTransition, null)
+        assertThat(parentState.currentTransition?.progress).isEqualTo(0f)
+
+        childTransition.progress = .5f
+        assertThat(parentState.currentTransition?.progress).isEqualTo(.5f)
+    }
+
+    @Test
+    fun linkedTransition_reverseTransitionIsNotLinked() {
+        val (parentState, childState) = setupLinkedStates()
+
+        val childTransition = TestableTransition(SceneB, SceneA)
+
+        childState.startTransition(childTransition, null)
+        assertThat(childState.isTransitioning(SceneB, SceneA)).isTrue()
+        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+
+        childState.finishTransition(childTransition, SceneB)
+        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+    }
+
+    @Test
+    fun linkedTransition_startsLinkAndFinishesLinkInFromState() {
+        val (parentState, childState) = setupLinkedStates()
+
+        val childTransition = TestableTransition(SceneA, SceneB)
+        childState.startTransition(childTransition, null)
+
+        childState.finishTransition(childTransition, SceneA)
+        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA))
+        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+    }
+
+    @Test
+    fun linkedTransition_startsLinkAndFinishesLinkInUnknownState() {
+        val (parentState, childState) = setupLinkedStates()
+
+        val childTransition = TestableTransition(SceneA, SceneB)
+        childState.startTransition(childTransition, null)
+
+        childState.finishTransition(childTransition, SceneD)
+        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
+        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+    }
+
+    @Test
+    fun linkedTransition_startsLinkButLinkedStateIsTakenOver() {
+        val (parentState, childState) = setupLinkedStates()
+
+        val childTransition = TestableTransition(SceneA, SceneB)
+        val parentTransition = TestableTransition(SceneC, SceneA)
+        childState.startTransition(childTransition, null)
+        parentState.startTransition(parentTransition, null)
+
+        childState.finishTransition(childTransition, SceneB)
+        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+        assertThat(parentState.transitionState).isEqualTo(parentTransition)
     }
 
     @Test
@@ -125,11 +277,11 @@
         val transitionkey = TransitionKey(debugName = "foo")
         val state =
             MutableSceneTransitionLayoutState(
-                TestScenes.SceneA,
+                SceneA,
                 transitions =
                     transitions {
-                        from(TestScenes.SceneA, to = TestScenes.SceneB) { fade(TestElements.Foo) }
-                        from(TestScenes.SceneA, to = TestScenes.SceneB, key = transitionkey) {
+                        from(SceneA, to = SceneB) { fade(TestElements.Foo) }
+                        from(SceneA, to = SceneB, key = transitionkey) {
                             fade(TestElements.Foo)
                             fade(TestElements.Bar)
                         }
@@ -138,19 +290,19 @@
                 as MutableSceneTransitionLayoutStateImpl
 
         // Default transition from A to B.
-        assertThat(state.setTargetScene(TestScenes.SceneB, coroutineScope = this)).isNotNull()
+        assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
         assertThat(state.transformationSpec.transformations).hasSize(1)
 
         // Go back to A.
-        state.setTargetScene(TestScenes.SceneA, coroutineScope = this)
+        state.setTargetScene(SceneA, coroutineScope = this)
         testScheduler.advanceUntilIdle()
         assertThat(state.currentTransition).isNull()
-        assertThat(state.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
+        assertThat(state.transitionState.currentScene).isEqualTo(SceneA)
 
         // Specific transition from A to B.
         assertThat(
                 state.setTargetScene(
-                    TestScenes.SceneB,
+                    SceneB,
                     coroutineScope = this,
                     transitionKey = transitionkey,
                 )
@@ -158,4 +310,83 @@
             .isNotNull()
         assertThat(state.transformationSpec.transformations).hasSize(2)
     }
+
+    @Test
+    fun snapToIdleIfClose_snapToStart() = runMonotonicClockTest {
+        val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+        state.startTransition(
+            transition(from = TestScenes.SceneA, to = TestScenes.SceneB, progress = { 0.2f }),
+            transitionKey = null
+        )
+        assertThat(state.isTransitioning()).isTrue()
+
+        // Ignore the request if the progress is not close to 0 or 1, using the threshold.
+        assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
+        assertThat(state.isTransitioning()).isTrue()
+
+        // Go to the initial scene if it is close to 0.
+        assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
+        assertThat(state.isTransitioning()).isFalse()
+        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneA))
+    }
+
+    @Test
+    fun snapToIdleIfClose_snapToEnd() = runMonotonicClockTest {
+        val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+        state.startTransition(
+            transition(from = TestScenes.SceneA, to = TestScenes.SceneB, progress = { 0.8f }),
+            transitionKey = null
+        )
+        assertThat(state.isTransitioning()).isTrue()
+
+        // Ignore the request if the progress is not close to 0 or 1, using the threshold.
+        assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
+        assertThat(state.isTransitioning()).isTrue()
+
+        // Go to the final scene if it is close to 1.
+        assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
+        assertThat(state.isTransitioning()).isFalse()
+        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB))
+    }
+
+    @Test
+    fun linkedTransition_fuzzyLinksAreMatchedAndStarted() {
+        val (parentState, childState) = setupLinkedStates(SceneC, SceneA, null, null, null, SceneD)
+        val childTransition = TestableTransition(SceneA, SceneB)
+
+        childState.startTransition(childTransition, null)
+        assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+        assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
+
+        childState.finishTransition(childTransition, SceneB)
+        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneB))
+        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneD))
+    }
+
+    @Test
+    fun linkedTransition_fuzzyLinksAreMatchedAndResetToProperPreviousScene() {
+        val (parentState, childState) =
+            setupLinkedStates(SceneC, SceneA, SceneA, null, null, SceneD)
+
+        val childTransition = TestableTransition(SceneA, SceneB)
+
+        childState.startTransition(childTransition, null)
+        assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+        assertThat(parentState.isTransitioning(SceneC, SceneD)).isTrue()
+
+        childState.finishTransition(childTransition, SceneA)
+        assertThat(childState.transitionState).isEqualTo(TransitionState.Idle(SceneA))
+        assertThat(parentState.transitionState).isEqualTo(TransitionState.Idle(SceneC))
+    }
+
+    @Test
+    fun linkedTransition_fuzzyLinksAreNotMatched() {
+        val (parentState, childState) =
+            setupLinkedStates(SceneC, SceneA, SceneB, null, SceneC, SceneD)
+        val childTransition = TestableTransition(SceneA, SceneB)
+
+        childState.startTransition(childTransition, null)
+        assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
+        assertThat(parentState.isTransitioning(SceneC, SceneD)).isFalse()
+    }
 }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 001e3a5..54c7a08 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -49,7 +49,7 @@
  * existing lockscreen clock.
  */
 class DefaultClockController(
-    ctx: Context,
+    private val ctx: Context,
     private val layoutInflater: LayoutInflater,
     private val resources: Resources,
     private val settings: ClockSettings?,
@@ -121,7 +121,11 @@
         protected var targetRegion: Rect? = null
 
         override val config = ClockFaceConfig()
-        override val layout = DefaultClockFaceLayout(view)
+        override val layout =
+            DefaultClockFaceLayout(view).apply {
+                views[0].id =
+                    resources.getIdentifier("lockscreen_clock_view", "id", ctx.packageName)
+            }
 
         override var animations: DefaultClockAnimations = DefaultClockAnimations(view, 0f, 0f)
             internal set
@@ -188,7 +192,11 @@
         seedColor: Int?,
         messageBuffer: MessageBuffer?,
     ) : DefaultClockFaceController(view, seedColor, messageBuffer) {
-        override val layout = DefaultClockFaceLayout(view)
+        override val layout =
+            DefaultClockFaceLayout(view).apply {
+                views[0].id =
+                    resources.getIdentifier("lockscreen_clock_view_large", "id", ctx.packageName)
+            }
         override val config =
             ClockFaceConfig(hasCustomPositionUpdatedAnimation = hasStepClockAnimation)
 
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 754d5dc..2a87452 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
@@ -27,7 +27,11 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.withContext
 
-/** Defines interface for classes that can provide access to data from [Settings.Secure]. */
+/**
+ * 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]
+ */
 interface SecureSettingsRepository {
 
     /** Returns a [Flow] tracking the value of a setting as an [Int]. */
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index 1519021..c6327ff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -24,11 +24,13 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor
 import com.android.systemui.Flags as AconfigFlags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.DevicePostureController
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -90,6 +92,8 @@
         whenever(keyguardPasswordView.findViewById<ImageView>(R.id.switch_ime_button))
             .thenReturn(mock(ImageView::class.java))
         `when`(keyguardPasswordView.resources).thenReturn(context.resources)
+
+        val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
         val fakeFeatureFlags = FakeFeatureFlags()
         fakeFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
         mSetFlagsRule.enableFlags(AconfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES)
@@ -111,6 +115,7 @@
                 postureController,
                 fakeFeatureFlags,
                 mSelectedUserInteractor,
+                keyguardKeyboardInteractor,
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index c4bcb53..f86342c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -31,6 +31,7 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.classifier.FalsingCollectorFake;
@@ -102,13 +103,15 @@
                 .thenReturn(mOkButton);
 
         when(mPinBasedInputView.getResources()).thenReturn(getContext().getResources());
+        KeyguardKeyboardInteractor keyguardKeyboardInteractor =
+                new KeyguardKeyboardInteractor(new FakeKeyboardRepository());
         FakeFeatureFlags featureFlags = new FakeFeatureFlags();
         mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_REVAMPED_BOUNCER_MESSAGES);
         mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
                 mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
                 mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
                 mEmergencyButtonController, mFalsingCollector, featureFlags,
-                mSelectedUserInteractor, new FakeKeyboardRepository()) {
+                mSelectedUserInteractor, keyguardKeyboardInteractor) {
             @Override
             public void onResume(int reason) {
                 super.onResume(reason);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 5f932f4..e8a43ac 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -36,6 +36,7 @@
 import com.android.internal.widget.LockPatternUtils
 import com.android.keyguard.KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor
 import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate
@@ -51,6 +52,7 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
@@ -202,6 +204,7 @@
         whenever(keyguardStateController.canDismissLockScreen()).thenReturn(true)
         whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(true)
 
+        val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
         featureFlags = FakeFeatureFlags()
         featureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
         featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
@@ -232,6 +235,7 @@
                 postureController,
                 featureFlags,
                 mSelectedUserInteractor,
+                keyguardKeyboardInteractor,
             )
 
         kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekableSliderHapticPluginTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekableSliderHapticPluginTest.kt
index ea766f8..805b4a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekableSliderHapticPluginTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekableSliderHapticPluginTest.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.fakeSystemClock
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
@@ -75,7 +74,7 @@
     fun start_afterStop_startsTheTrackingAgain() = runOnStartedPlugin {
         // WHEN the plugin is restarted
         plugin.stop()
-        plugin.start()
+        plugin.startInScope(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // THEN the tracking begins again
         assertThat(plugin.isTracking).isTrue()
@@ -131,22 +130,21 @@
     private fun runOnStartedPlugin(test: suspend TestScope.() -> Unit) =
         with(kosmos) {
             testScope.runTest {
-                createPlugin(this, UnconfinedTestDispatcher(testScheduler))
-                // GIVEN that the plugin is started
-                plugin.start()
+                val pluginScope = CoroutineScope(UnconfinedTestDispatcher(testScheduler))
+                createPlugin()
+                // GIVEN that the plugin is started in a test scope
+                plugin.startInScope(pluginScope)
 
                 // THEN run the test
                 test()
             }
         }
 
-    private fun createPlugin(scope: CoroutineScope, dispatcher: CoroutineDispatcher) {
+    private fun createPlugin() {
         plugin =
             SeekableSliderHapticPlugin(
                 vibratorHelper,
                 kosmos.fakeSystemClock,
-                dispatcher,
-                scope,
             )
     }
 
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 0543bc2..d52696a 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
@@ -20,6 +20,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -68,6 +69,8 @@
 
     @Before
     fun setUp() {
+        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+
         MockitoAnnotations.initMocks(this)
         whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow)
         kosmos.burnInInteractor = burnInInteractor
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 6cc680b..c23ec22 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -30,6 +30,8 @@
 import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+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.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -57,7 +59,10 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class KeyguardRootViewModelTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
+    private val kosmos =
+        testKosmos().apply {
+            fakeFeatureFlagsClassic.apply { set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) }
+        }
     private val testScope = kosmos.testScope
     private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
     private val keyguardInteractor = kosmos.keyguardInteractor
@@ -111,6 +116,23 @@
         }
 
     @Test
+    fun iconContainer_isNotVisible_onKeyguard_dontShowWhenGoneToAodTransitionRunning() =
+        testScope.runTest {
+            val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
+            runCurrent()
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.AOD,
+                testScope,
+            )
+            whenever(screenOffAnimationController.shouldShowAodIconsWhenShade()).thenReturn(false)
+            runCurrent()
+
+            assertThat(isVisible?.value).isFalse()
+            assertThat(isVisible?.isAnimating).isFalse()
+        }
+
+    @Test
     fun iconContainer_isVisible_bypassEnabled() =
         testScope.runTest {
             val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
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 b60f483..63fb67d 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
@@ -50,7 +50,8 @@
 
     @Test
     fun mapsDisabledDataToInactiveState() {
-        val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(false))
+        val tileState: QSTileState =
+            mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(false))
 
         val actualActivationState = tileState.activationState
 
@@ -59,7 +60,8 @@
 
     @Test
     fun mapsEnabledDataToActiveState() {
-        val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(true))
+        val tileState: QSTileState =
+            mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(true))
 
         val actualActivationState = tileState.activationState
         assertEquals(QSTileState.ActivationState.ACTIVE, actualActivationState)
@@ -67,7 +69,8 @@
 
     @Test
     fun mapsEnabledDataToOnIconState() {
-        val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(true))
+        val tileState: QSTileState =
+            mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(true))
 
         val expectedIcon =
             Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_on)!!, null)
@@ -77,7 +80,8 @@
 
     @Test
     fun mapsDisabledDataToOffIconState() {
-        val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(false))
+        val tileState: QSTileState =
+            mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(false))
 
         val expectedIcon =
             Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
@@ -86,11 +90,32 @@
     }
 
     @Test
-    fun supportsOnlyClickAction() {
+    fun mapsUnavailableDataToOffIconState() {
+        val tileState: QSTileState =
+            mapper.map(qsTileConfig, FlashlightTileModel.FlashlightTemporarilyUnavailable)
+
+        val expectedIcon =
+            Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
+        val actualIcon = tileState.icon()
+        assertThat(actualIcon).isEqualTo(expectedIcon)
+    }
+
+    @Test
+    fun supportClickActionWhenAvailable() {
         val dontCare = true
-        val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(dontCare))
+        val tileState: QSTileState =
+            mapper.map(qsTileConfig, FlashlightTileModel.FlashlightAvailable(dontCare))
 
         val supportedActions = tileState.supportedActions
         assertThat(supportedActions).containsExactly(QSTileState.UserAction.CLICK)
     }
+
+    @Test
+    fun doesNotSupportClickActionWhenUnavailable() {
+        val tileState: QSTileState =
+            mapper.map(qsTileConfig, FlashlightTileModel.FlashlightTemporarilyUnavailable)
+
+        val supportedActions = tileState.supportedActions
+        assertThat(supportedActions).isEmpty()
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
index a9e39354..c5a8c70 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
@@ -70,8 +70,7 @@
     }
 
     @Test
-    fun dataMatchesController() = runTest {
-        controller.setFlashlight(false)
+    fun isEnabledDataMatchesControllerWhenAvailable() = runTest {
         val flowValues: List<FlashlightTileModel> by
             collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
 
@@ -81,8 +80,35 @@
         controller.setFlashlight(false)
         runCurrent()
 
-        assertThat(flowValues.size).isEqualTo(3)
-        assertThat(flowValues.map { it.isEnabled }).containsExactly(false, true, false).inOrder()
+        assertThat(flowValues.size).isEqualTo(4) // 2 from setup(), 2 from this test
+        assertThat(
+                flowValues.filterIsInstance<FlashlightTileModel.FlashlightAvailable>().map {
+                    it.isEnabled
+                }
+            )
+            .containsExactly(false, false, true, false)
+            .inOrder()
+    }
+
+    /**
+     * Simulates the scenario of changes in flashlight tile availability when camera is initially
+     * closed, then opened, and closed again.
+     */
+    @Test
+    fun availabilityDataMatchesControllerAvailability() = runTest {
+        val flowValues: List<FlashlightTileModel> by
+            collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
+
+        runCurrent()
+        controller.onFlashlightAvailabilityChanged(false)
+        runCurrent()
+        controller.onFlashlightAvailabilityChanged(true)
+        runCurrent()
+
+        assertThat(flowValues.size).isEqualTo(4) // 2 from setup + 2 from this test
+        assertThat(flowValues.map { it is FlashlightTileModel.FlashlightAvailable })
+            .containsExactly(true, true, false, true)
+            .inOrder()
     }
 
     private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt
index 28d43b3..1f19c98 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt
@@ -29,7 +29,9 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.Mock
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 
 @SmallTest
@@ -51,7 +53,7 @@
         assumeFalse(ActivityManager.isUserAMonkey())
         val stateBeforeClick = false
 
-        underTest.handleInput(click(FlashlightTileModel(stateBeforeClick)))
+        underTest.handleInput(click(FlashlightTileModel.FlashlightAvailable(stateBeforeClick)))
 
         verify(controller).setFlashlight(!stateBeforeClick)
     }
@@ -61,8 +63,17 @@
         assumeFalse(ActivityManager.isUserAMonkey())
         val stateBeforeClick = true
 
-        underTest.handleInput(click(FlashlightTileModel(stateBeforeClick)))
+        underTest.handleInput(click(FlashlightTileModel.FlashlightAvailable(stateBeforeClick)))
 
         verify(controller).setFlashlight(!stateBeforeClick)
     }
+
+    @Test
+    fun handleClickWhenUnavailable() = runTest {
+        assumeFalse(ActivityManager.isUserAMonkey())
+
+        underTest.handleInput(click(FlashlightTileModel.FlashlightTemporarilyUnavailable))
+
+        verify(controller, never()).setFlashlight(anyBoolean())
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 42200a3..51f8b11 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -61,7 +61,7 @@
     private val sceneInteractor by lazy { kosmos.sceneInteractor }
     private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
     private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
-    private val qsFlexiglassAdapter = FakeQSSceneAdapter { mock() }
+    private val qsFlexiglassAdapter = FakeQSSceneAdapter({ mock() })
     private val footerActionsViewModel = mock<FooterActionsViewModel>()
     private val footerActionsViewModelFactory =
         mock<FooterActionsViewModel.Factory> {
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 189ba7b..9d3f0d6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -265,6 +265,7 @@
                 authenticationInteractor = dagger.Lazy { kosmos.authenticationInteractor },
                 windowController = mock(),
                 deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
+                centralSurfaces = mock(),
             )
         startable.start()
 
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 12dbf11..34c5173 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
@@ -18,6 +18,7 @@
 
 package com.android.systemui.scene.domain.startable
 
+import android.app.StatusBarManager
 import android.os.PowerManager
 import android.platform.test.annotations.EnableFlags
 import android.view.Display
@@ -48,6 +49,7 @@
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
 import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
 import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
@@ -65,6 +67,7 @@
 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.Mockito.clearInvocations
 import org.mockito.Mockito.never
@@ -78,6 +81,7 @@
 class SceneContainerStartableTest : SysuiTestCase() {
 
     @Mock private lateinit var windowController: NotificationShadeWindowController
+    @Mock private lateinit var centralSurfaces: CentralSurfaces
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
@@ -115,6 +119,7 @@
                 authenticationInteractor = dagger.Lazy { authenticationInteractor },
                 windowController = windowController,
                 deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
+                centralSurfaces = centralSurfaces,
             )
     }
 
@@ -763,6 +768,227 @@
             verify(windowController, times(2)).setNotificationShadeFocusable(false)
         }
 
+    @Test
+    fun hydrateInteractionState_whileLocked() =
+        testScope.runTest {
+            val transitionStateFlow =
+                prepareState(
+                    initialSceneKey = SceneKey.Lockscreen,
+                )
+            underTest.start()
+            runCurrent()
+            verify(centralSurfaces).setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true)
+
+            clearInvocations(centralSurfaces)
+            emulateSceneTransition(
+                transitionStateFlow = transitionStateFlow,
+                toScene = SceneKey.Bouncer,
+                verifyBeforeTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyDuringTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyAfterTransition = {
+                    verify(centralSurfaces)
+                        .setInteracting(
+                            StatusBarManager.WINDOW_STATUS_BAR,
+                            false,
+                        )
+                },
+            )
+
+            clearInvocations(centralSurfaces)
+            emulateSceneTransition(
+                transitionStateFlow = transitionStateFlow,
+                toScene = SceneKey.Lockscreen,
+                verifyBeforeTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyDuringTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyAfterTransition = {
+                    verify(centralSurfaces)
+                        .setInteracting(
+                            StatusBarManager.WINDOW_STATUS_BAR,
+                            true,
+                        )
+                },
+            )
+
+            clearInvocations(centralSurfaces)
+            emulateSceneTransition(
+                transitionStateFlow = transitionStateFlow,
+                toScene = SceneKey.Shade,
+                verifyBeforeTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyDuringTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyAfterTransition = {
+                    verify(centralSurfaces)
+                        .setInteracting(
+                            StatusBarManager.WINDOW_STATUS_BAR,
+                            false,
+                        )
+                },
+            )
+
+            clearInvocations(centralSurfaces)
+            emulateSceneTransition(
+                transitionStateFlow = transitionStateFlow,
+                toScene = SceneKey.Lockscreen,
+                verifyBeforeTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyDuringTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyAfterTransition = {
+                    verify(centralSurfaces)
+                        .setInteracting(
+                            StatusBarManager.WINDOW_STATUS_BAR,
+                            true,
+                        )
+                },
+            )
+
+            clearInvocations(centralSurfaces)
+            emulateSceneTransition(
+                transitionStateFlow = transitionStateFlow,
+                toScene = SceneKey.QuickSettings,
+                verifyBeforeTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyDuringTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyAfterTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+            )
+        }
+
+    @Test
+    fun hydrateInteractionState_whileUnlocked() =
+        testScope.runTest {
+            val transitionStateFlow =
+                prepareState(
+                    isDeviceUnlocked = true,
+                    initialSceneKey = SceneKey.Gone,
+                )
+            underTest.start()
+            verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+
+            clearInvocations(centralSurfaces)
+            emulateSceneTransition(
+                transitionStateFlow = transitionStateFlow,
+                toScene = SceneKey.Bouncer,
+                verifyBeforeTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyDuringTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyAfterTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+            )
+
+            clearInvocations(centralSurfaces)
+            emulateSceneTransition(
+                transitionStateFlow = transitionStateFlow,
+                toScene = SceneKey.Lockscreen,
+                verifyBeforeTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyDuringTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyAfterTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+            )
+
+            clearInvocations(centralSurfaces)
+            emulateSceneTransition(
+                transitionStateFlow = transitionStateFlow,
+                toScene = SceneKey.Shade,
+                verifyBeforeTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyDuringTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyAfterTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+            )
+
+            clearInvocations(centralSurfaces)
+            emulateSceneTransition(
+                transitionStateFlow = transitionStateFlow,
+                toScene = SceneKey.Lockscreen,
+                verifyBeforeTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyDuringTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyAfterTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+            )
+
+            clearInvocations(centralSurfaces)
+            emulateSceneTransition(
+                transitionStateFlow = transitionStateFlow,
+                toScene = SceneKey.QuickSettings,
+                verifyBeforeTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyDuringTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+                verifyAfterTransition = {
+                    verify(centralSurfaces, never()).setInteracting(anyInt(), anyBoolean())
+                },
+            )
+        }
+
+    private fun TestScope.emulateSceneTransition(
+        transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
+        toScene: SceneKey,
+        verifyBeforeTransition: (() -> Unit)? = null,
+        verifyDuringTransition: (() -> Unit)? = null,
+        verifyAfterTransition: (() -> Unit)? = null,
+    ) {
+        val fromScene = sceneInteractor.desiredScene.value.key
+        sceneInteractor.changeScene(SceneModel(toScene), "reason")
+        runCurrent()
+        verifyBeforeTransition?.invoke()
+
+        transitionStateFlow.value =
+            ObservableTransitionState.Transition(
+                fromScene = fromScene,
+                toScene = toScene,
+                progress = flowOf(0.5f),
+                isInitiatedByUserInput = true,
+                isUserInputOngoing = flowOf(true),
+            )
+        runCurrent()
+        verifyDuringTransition?.invoke()
+
+        transitionStateFlow.value =
+            ObservableTransitionState.Idle(
+                scene = toScene,
+            )
+        runCurrent()
+        verifyAfterTransition?.invoke()
+    }
+
     private fun TestScope.prepareState(
         isDeviceUnlocked: Boolean = false,
         isBypassEnabled: Boolean = false,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
new file mode 100644
index 0000000..d3c6598
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.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.shade.domain.interactor
+
+import android.content.applicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.shared.recents.utilities.Utilities
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadeBackActionInteractorImplTest : SysuiTestCase() {
+    val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+    val testScope = kosmos.testScope
+    val sceneInteractor = kosmos.sceneInteractor
+    val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
+    val underTest = kosmos.shadeBackActionInteractor
+
+    @Before
+    fun ignoreSplitShade() {
+        Assume.assumeFalse(Utilities.isLargeScreen(kosmos.applicationContext))
+    }
+
+    @Test
+    fun animateCollapseQs_notOnQs() =
+        testScope.runTest {
+            setScene(SceneKey.Shade)
+            underTest.animateCollapseQs(true)
+            runCurrent()
+            assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Shade)
+        }
+
+    @Test
+    fun animateCollapseQs_fullyCollapse_entered() =
+        testScope.runTest {
+            enterDevice()
+            setScene(SceneKey.QuickSettings)
+            underTest.animateCollapseQs(true)
+            runCurrent()
+            assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Gone)
+        }
+
+    @Test
+    fun animateCollapseQs_fullyCollapse_locked() =
+        testScope.runTest {
+            deviceEntryRepository.setUnlocked(false)
+            setScene(SceneKey.QuickSettings)
+            underTest.animateCollapseQs(true)
+            runCurrent()
+            assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun animateCollapseQs_notFullyCollapse() =
+        testScope.runTest {
+            setScene(SceneKey.QuickSettings)
+            underTest.animateCollapseQs(false)
+            runCurrent()
+            assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Shade)
+        }
+
+    private fun enterDevice() {
+        deviceEntryRepository.setUnlocked(true)
+        testScope.runCurrent()
+        setScene(SceneKey.Gone)
+    }
+
+    private fun setScene(key: SceneKey) {
+        sceneInteractor.changeScene(SceneModel(key), "test")
+        sceneInteractor.setTransitionState(
+            MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+        )
+        testScope.runCurrent()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 5ef095f..f1f5dc3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -82,7 +82,7 @@
             scope = testScope.backgroundScope,
         )
 
-    private val qsFlexiglassAdapter = FakeQSSceneAdapter { mock() }
+    private val qsFlexiglassAdapter = FakeQSSceneAdapter({ mock() })
 
     private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel
 
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 4436be7..fd7a7f3 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
@@ -116,6 +116,8 @@
 
     /** Custom constraints to apply to Lockscreen ConstraintLayout. */
     fun applyConstraints(constraints: ConstraintSet): ConstraintSet
+
+    fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet
 }
 
 /** A ClockFaceLayout that applies the default lockscreen layout to a single view */
@@ -131,6 +133,10 @@
         }
         return constraints
     }
+
+    override fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet {
+        return constraints
+    }
 }
 
 /** Events that should call when various rendering parameters change */
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_input_method_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_input_method_background.xml
new file mode 100644
index 0000000..ad22894
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/bouncer_input_method_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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true">
+        <shape android:shape="oval">
+            <stroke android:width="3dp" android:color="@color/bouncer_password_focus_color"/>
+        </shape>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_password_view_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_password_view_background.xml
new file mode 100644
index 0000000..8c2b036
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/bouncer_password_view_background.xml
@@ -0,0 +1,34 @@
+<?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">
+    <item android:state_focused="true">
+        <shape android:shape="rectangle">
+            <corners android:radius="16dp" />
+            <stroke android:width="3dp"
+                android:color="@color/bouncer_password_focus_color" />
+            <padding android:bottom="8dp" android:left="8dp" android:right="8dp" android:top="8dp"/>
+        </shape>
+    </item>
+    <item>
+        <inset android:insetLeft="-4dp"
+            android:insetRight="-4dp"
+            android:insetTop="-4dp">
+            <shape android:shape="rectangle">
+                <stroke android:width="3dp" android:color="@color/bouncer_password_focus_color"/>
+            </shape>
+        </inset>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_pin_view_focused_background.xml
similarity index 100%
rename from packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml
rename to packages/SystemUI/res-keyguard/drawable/bouncer_pin_view_focused_background.xml
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
index 2fc1d2e..909d4fc 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -54,7 +54,7 @@
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:contentDescription="@string/keyguard_accessibility_password"
-             android:gravity="center_horizontal"
+             android:gravity="center"
              android:singleLine="true"
              android:textStyle="normal"
              android:inputType="textPassword"
@@ -68,14 +68,14 @@
          <ImageView android:id="@+id/switch_ime_button"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
-             android:layout_marginBottom="12dp"
              android:src="@drawable/ic_lockscreen_ime"
              android:contentDescription="@string/accessibility_ime_switch_button"
              android:clickable="true"
-             android:padding="8dip"
+             android:layout_marginRight="8dp"
+             android:padding="12dip"
              android:tint="?android:attr/textColorPrimary"
              android:layout_gravity="end|center_vertical"
-             android:background="?android:attr/selectableItemBackground"
+             android:background="@drawable/bouncer_input_method_background"
              android:visibility="gone"
              />
        </FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index ddad1e3..e853f02 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -28,8 +28,14 @@
     <!-- Width for the keyguard pin input field -->
     <dimen name="keyguard_pin_field_width">292dp</dimen>
 
-    <!-- Width for the keyguard pin input field -->
-    <dimen name="keyguard_pin_field_height">48dp</dimen>
+    <!-- height for the keyguard pin input field -->
+    <dimen name="keyguard_pin_field_height">56dp</dimen>
+
+    <!-- height for the keyguard password input field -->
+    <dimen name="keyguard_password_field_height">56dp</dimen>
+
+    <!-- width for the keyguard password input field -->
+    <dimen name="keyguard_password_field_width">276dp</dimen>
 
     <!-- Height of the sliding KeyguardSecurityContainer
          (includes 2x keyguard_security_view_top_margin) -->
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 4789a22..c43e394 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -76,7 +76,7 @@
     </style>
     <style name="Widget.TextView.Password" parent="@android:style/Widget.TextView">
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
-        <item name="android:background">@drawable/bouncer_password_text_view_focused_background</item>
+        <item name="android:background">@drawable/bouncer_pin_view_focused_background</item>
         <item name="android:gravity">center</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
diff --git a/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml b/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml
index dec9930..a80d3b4 100644
--- a/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml
+++ b/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml
@@ -20,6 +20,7 @@
     android:height="24dp"
     android:viewportWidth="24.0"
     android:viewportHeight="24.0"
+    android:alpha="0.3"
     >
     <path
         android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 51012a4..cc31754 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -174,7 +174,7 @@
     <dimen name="status_bar_clock_size">14sp</dimen>
 
     <!-- The starting padding for the clock in the status bar. -->
-    <dimen name="status_bar_clock_starting_padding">7dp</dimen>
+    <dimen name="status_bar_clock_starting_padding">4dp</dimen>
 
     <!-- The end padding for the clock in the status bar. -->
     <dimen name="status_bar_clock_end_padding">0dp</dimen>
@@ -395,7 +395,7 @@
     <dimen name="status_bar_icon_horizontal_margin">0sp</dimen>
 
     <!-- the padding on the start of the statusbar -->
-    <dimen name="status_bar_padding_start">8dp</dimen>
+    <dimen name="status_bar_padding_start">4dp</dimen>
 
     <!-- the padding on the end of the statusbar -->
     <dimen name="status_bar_padding_end">4dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f0cbe7a..15688c5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1599,6 +1599,15 @@
     <!-- Accessibility label for hotspot icon [CHAR LIMIT=NONE] -->
     <string name="accessibility_status_bar_hotspot">Hotspot</string>
 
+    <!-- Accessibility label for no satellite connection [CHAR LIMIT=NONE] -->
+    <string name="accessibility_status_bar_satellite_no_connection">Satellite, no connection</string>
+    <!-- Accessibility label for poor satellite connection [CHAR LIMIT=NONE] -->
+    <string name="accessibility_status_bar_satellite_poor_connection">Satellite, poor connection</string>
+    <!-- Accessibility label for good satellite connection [CHAR LIMIT=NONE] -->
+    <string name="accessibility_status_bar_satellite_good_connection">Satellite, good connection</string>
+    <!-- Accessibility label for available satellite connection [CHAR LIMIT=NONE] -->
+    <string name="accessibility_status_bar_satellite_available">Satellite, connection available</string>
+
     <!-- Accessibility label for managed profile icon (not shown on screen) [CHAR LIMIT=NONE] -->
     <string name="accessibility_managed_profile">Work profile</string>
 
@@ -3337,6 +3346,6 @@
     <string name="keyboard_backlight_value">Level %1$d of %2$d</string>
     <!-- Label for home control panel [CHAR LIMIT=30] -->
     <string name="home_controls_dream_label">Home Controls</string>
-    <!-- Description for home control panel [CHAR LIMIT=50] -->
+    <!-- Description for home control panel [CHAR LIMIT=67] -->
     <string name="home_controls_dream_description">Quickly access your home controls as a screensaver</string>
 </resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index efd8f7f..458a21c5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -30,6 +30,7 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
 import com.android.systemui.Flags;
 import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
 import com.android.systemui.bouncer.ui.BouncerMessageView;
@@ -37,7 +38,6 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.keyboard.data.repository.KeyboardRepository;
 import com.android.systemui.log.BouncerLogger;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.DevicePostureController;
@@ -211,7 +211,7 @@
         private final FeatureFlags mFeatureFlags;
         private final SelectedUserInteractor mSelectedUserInteractor;
         private final UiEventLogger mUiEventLogger;
-        private final KeyboardRepository mKeyboardRepository;
+        private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
 
         @Inject
         public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -226,7 +226,7 @@
                 KeyguardViewController keyguardViewController,
                 FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor,
                 UiEventLogger uiEventLogger,
-                KeyboardRepository keyboardRepository) {
+                KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
             mLockPatternUtils = lockPatternUtils;
             mLatencyTracker = latencyTracker;
@@ -243,7 +243,7 @@
             mFeatureFlags = featureFlags;
             mSelectedUserInteractor = selectedUserInteractor;
             mUiEventLogger = uiEventLogger;
-            mKeyboardRepository = keyboardRepository;
+            mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
         }
 
         /** Create a new {@link KeyguardInputViewController}. */
@@ -265,14 +265,15 @@
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mInputMethodManager, emergencyButtonController, mMainExecutor, mResources,
                         mFalsingCollector, mKeyguardViewController,
-                        mDevicePostureController, mFeatureFlags, mSelectedUserInteractor);
+                        mDevicePostureController, mFeatureFlags, mSelectedUserInteractor,
+                        mKeyguardKeyboardInteractor);
             } else if (keyguardInputView instanceof KeyguardPINView) {
                 return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mLiftToActivateListener, emergencyButtonController, mFalsingCollector,
                         mDevicePostureController, mFeatureFlags, mSelectedUserInteractor,
-                        mUiEventLogger, mKeyboardRepository
+                        mUiEventLogger, mKeyguardKeyboardInteractor
                 );
             } else if (keyguardInputView instanceof KeyguardSimPinView) {
                 return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
@@ -280,14 +281,15 @@
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
                         emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
-                        mKeyboardRepository);
+                        mKeyguardKeyboardInteractor);
             } else if (keyguardInputView instanceof KeyguardSimPukView) {
                 return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
                         emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
-                        mKeyboardRepository);
+                        mKeyguardKeyboardInteractor
+                );
             }
 
             throw new RuntimeException("Unable to find controller for " + keyguardInputView);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 3d8aaaf..7473e0c6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -17,8 +17,10 @@
 package com.android.keyguard;
 
 import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
 import android.text.Editable;
 import android.text.InputType;
@@ -27,6 +29,7 @@
 import android.text.method.TextKeyListener;
 import android.view.KeyEvent;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.ViewGroup.MarginLayoutParams;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodInfo;
@@ -39,6 +42,8 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
+import com.android.systemui.Flags;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
@@ -52,6 +57,7 @@
 public class KeyguardPasswordViewController
         extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> {
 
+    private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
     private final KeyguardSecurityCallback mKeyguardSecurityCallback;
     private final DevicePostureController mPostureController;
     private final DevicePostureController.Callback mPostureCallback = posture ->
@@ -60,6 +66,8 @@
     private final DelayableExecutor mMainExecutor;
     private final KeyguardViewController mKeyguardViewController;
     private final boolean mShowImeAtScreenOn;
+    private Drawable mDefaultPasswordFieldBackground;
+    private Drawable mFocusedPasswordFieldBackground;
     private EditText mPasswordEntry;
     private ImageView mSwitchImeButton;
     private boolean mPaused;
@@ -121,7 +129,8 @@
             KeyguardViewController keyguardViewController,
             DevicePostureController postureController,
             FeatureFlags featureFlags,
-            SelectedUserInteractor selectedUserInteractor) {
+            SelectedUserInteractor selectedUserInteractor,
+            KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, falsingCollector,
                 emergencyButtonController, featureFlags, selectedUserInteractor);
@@ -130,11 +139,15 @@
         mPostureController = postureController;
         mMainExecutor = mainExecutor;
         mKeyguardViewController = keyguardViewController;
+        mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
         if (featureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)) {
             view.setIsLockScreenLandscapeEnabled();
         }
         mShowImeAtScreenOn = resources.getBoolean(R.bool.kg_show_ime_at_screen_on);
         mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
+        mDefaultPasswordFieldBackground = mPasswordEntry.getBackground();
+        mFocusedPasswordFieldBackground = getResources().getDrawable(
+                R.drawable.bouncer_password_view_background);
         mSwitchImeButton = mView.findViewById(R.id.switch_ime_button);
     }
 
@@ -175,6 +188,27 @@
 
         // If there's more than one IME, enable the IME switcher button
         updateSwitchImeButton();
+
+        if (Flags.pinInputFieldStyledFocusState()) {
+            collectFlow(mPasswordEntry,
+                    mKeyguardKeyboardInteractor.isAnyKeyboardConnected(),
+                    this::setPasswordFieldFocusBackground);
+
+            ViewGroup.LayoutParams layoutParams = mPasswordEntry.getLayoutParams();
+            layoutParams.height = (int) getResources()
+                    .getDimension(R.dimen.keyguard_password_field_height);
+            layoutParams.width = (int) getResources()
+                    .getDimension(R.dimen.keyguard_password_field_width);
+        }
+
+    }
+
+    private void setPasswordFieldFocusBackground(boolean isAnyKeyboardConnected) {
+        if (isAnyKeyboardConnected) {
+            mPasswordEntry.setBackground(mFocusedPasswordFieldBackground);
+        } else {
+            mPasswordEntry.setBackground(mDefaultPasswordFieldBackground);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index 60dd568..476497d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -16,8 +16,8 @@
 
 package com.android.keyguard;
 
-import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 import static com.android.systemui.Flags.pinInputFieldStyledFocusState;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.StateListDrawable;
@@ -32,9 +32,9 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.keyboard.data.repository.KeyboardRepository;
 import com.android.systemui.res.R;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
@@ -43,7 +43,7 @@
 
     private final LiftToActivateListener mLiftToActivateListener;
     private final FalsingCollector mFalsingCollector;
-    private final KeyboardRepository mKeyboardRepository;
+    private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
     protected PasswordTextView mPasswordEntry;
 
     private final OnKeyListener mOnKeyListener = (v, keyCode, event) -> {
@@ -75,13 +75,13 @@
             FalsingCollector falsingCollector,
             FeatureFlags featureFlags,
             SelectedUserInteractor selectedUserInteractor,
-            KeyboardRepository keyboardRepository) {
+            KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, falsingCollector,
                 emergencyButtonController, featureFlags, selectedUserInteractor);
         mLiftToActivateListener = liftToActivateListener;
         mFalsingCollector = falsingCollector;
-        mKeyboardRepository = keyboardRepository;
+        mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
         mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
     }
 
@@ -132,7 +132,7 @@
             okButton.setOnHoverListener(mLiftToActivateListener);
         }
         if (pinInputFieldStyledFocusState()) {
-            collectFlow(mPasswordEntry, mKeyboardRepository.isAnyKeyboardConnected(),
+            collectFlow(mPasswordEntry, mKeyguardKeyboardInteractor.isAnyKeyboardConnected(),
                     this::setKeyboardBasedFocusOutline);
 
             /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index b958f55..f4cda02 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -25,10 +25,10 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.keyboard.data.repository.KeyboardRepository;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -61,11 +61,11 @@
             FalsingCollector falsingCollector,
             DevicePostureController postureController, FeatureFlags featureFlags,
             SelectedUserInteractor selectedUserInteractor, UiEventLogger uiEventLogger,
-            KeyboardRepository keyboardRepository) {
+            KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
                 emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
-                keyboardRepository);
+                keyguardKeyboardInteractor);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mPostureController = postureController;
         mLockPatternUtils = lockPatternUtils;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 1cdcbd0..558679e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -42,9 +42,9 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.keyboard.data.repository.KeyboardRepository;
 import com.android.systemui.res.R;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
@@ -94,11 +94,12 @@
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
             TelephonyManager telephonyManager, FalsingCollector falsingCollector,
             EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
-            SelectedUserInteractor selectedUserInteractor, KeyboardRepository keyboardRepository) {
+            SelectedUserInteractor selectedUserInteractor,
+            KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
                 emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
-                keyboardRepository);
+                keyguardKeyboardInteractor);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mTelephonyManager = telephonyManager;
         mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index f019d61..cb1c4b30 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -37,9 +37,9 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.keyboard.data.repository.KeyboardRepository;
 import com.android.systemui.res.R;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
@@ -91,11 +91,12 @@
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
             TelephonyManager telephonyManager, FalsingCollector falsingCollector,
             EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
-            SelectedUserInteractor selectedUserInteractor, KeyboardRepository keyboardRepository) {
+            SelectedUserInteractor selectedUserInteractor,
+            KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
                 emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
-                keyboardRepository);
+                keyguardKeyboardInteractor);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mTelephonyManager = telephonyManager;
         mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index ef65144..9ebae90 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -109,8 +109,12 @@
                 animProps.setDelay(0).setDuration(160);
                 log("goingToFullShade && !keyguardFadingAway");
             }
-            PropertyAnimator.setProperty(
-                    mView, AnimatableProperty.ALPHA, 0f, animProps, true /* animate */);
+            if (KeyguardShadeMigrationNssl.isEnabled()) {
+                log("Using LockscreenToGoneTransition 1");
+            } else {
+                PropertyAnimator.setProperty(
+                        mView, AnimatableProperty.ALPHA, 0f, animProps, true /* animate */);
+            }
         } else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) {
             mView.setVisibility(View.VISIBLE);
             mKeyguardViewVisibilityAnimating = true;
@@ -179,9 +183,13 @@
                 mView.setVisibility(View.VISIBLE);
             }
         } else {
-            log("Direct set Visibility to GONE");
-            mView.setVisibility(View.GONE);
-            mView.setAlpha(1f);
+            if (KeyguardShadeMigrationNssl.isEnabled()) {
+                log("Using LockscreenToGoneTransition 2");
+            } else {
+                log("Direct set Visibility to GONE");
+                mView.setVisibility(View.GONE);
+                mView.setAlpha(1f);
+            }
         }
 
         mLastOccludedState = isOccluded;
diff --git a/packages/SystemUI/src/com/android/keyguard/domain/interactor/KeyguardKeyboardInteractor.kt b/packages/SystemUI/src/com/android/keyguard/domain/interactor/KeyguardKeyboardInteractor.kt
new file mode 100644
index 0000000..c39d3e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/domain/interactor/KeyguardKeyboardInteractor.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyboard.data.repository.KeyboardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class KeyguardKeyboardInteractor @Inject constructor(keyboardRepository: KeyboardRepository) {
+    val isAnyKeyboardConnected: Flow<Boolean> = keyboardRepository.isAnyKeyboardConnected
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt b/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt
index 7a560e8..29df49b5 100644
--- a/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt
+++ b/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt
@@ -29,26 +29,28 @@
 import javax.inject.Inject
 
 /**
- * Coordinates screen on/turning on animations for the KeyguardViewMediator. Specifically for
- * screen on events, this will invoke the onDrawn Runnable after all tasks have completed. This
- * should route back to the [com.android.systemui.keyguard.KeyguardService], which informs
- * the system_server that keyguard has drawn.
+ * Coordinates screen on/turning on animations for the KeyguardViewMediator. Specifically for screen
+ * on events, this will invoke the onDrawn Runnable after all tasks have completed. This should
+ * route back to the [com.android.systemui.keyguard.KeyguardService], which informs the
+ * system_server that keyguard has drawn.
  */
 @SysUISingleton
-class ScreenOnCoordinator @Inject constructor(
+class ScreenOnCoordinator
+@Inject
+constructor(
     unfoldComponent: Optional<SysUIUnfoldComponent>,
-    @Main private val mainHandler: Handler
+    @Main private val mainHandler: Handler,
 ) {
 
-    private val unfoldLightRevealAnimation = unfoldComponent.map(
-        SysUIUnfoldComponent::getUnfoldLightRevealOverlayAnimation).getOrNull()
-    private val foldAodAnimationController = unfoldComponent.map(
-        SysUIUnfoldComponent::getFoldAodAnimationController).getOrNull()
+    private val foldAodAnimationController =
+        unfoldComponent.map(SysUIUnfoldComponent::getFoldAodAnimationController).getOrNull()
+    private val fullScreenLightRevealAnimations =
+        unfoldComponent.map(SysUIUnfoldComponent::getFullScreenLightRevealAnimations).getOrNull()
     private val pendingTasks = PendingTasksContainer()
 
     /**
-     * When turning on, registers tasks that may need to run before invoking [onDrawn].
-     * This is called on a binder thread from [com.android.systemui.keyguard.KeyguardService].
+     * When turning on, registers tasks that may need to run before invoking [onDrawn]. This is
+     * called on a binder thread from [com.android.systemui.keyguard.KeyguardService].
      */
     @BinderThread
     fun onScreenTurningOn(onDrawn: Runnable) {
@@ -56,8 +58,10 @@
 
         pendingTasks.reset()
 
-        unfoldLightRevealAnimation?.onScreenTurningOn(pendingTasks.registerTask("unfold-reveal"))
         foldAodAnimationController?.onScreenTurningOn(pendingTasks.registerTask("fold-to-aod"))
+        fullScreenLightRevealAnimations?.forEach {
+            it.onScreenTurningOn(pendingTasks.registerTask(it::class.java.simpleName))
+        }
 
         pendingTasks.onTasksComplete {
             if (Flags.enableBackgroundKeyguardOndrawnCallback()) {
@@ -71,8 +75,8 @@
     }
 
     /**
-     * Called when screen is fully turned on and screen on blocker is removed.
-     * This is called on a binder thread from [com.android.systemui.keyguard.KeyguardService].
+     * Called when screen is fully turned on and screen on blocker is removed. This is called on a
+     * binder thread from [com.android.systemui.keyguard.KeyguardService].
      */
     @BinderThread
     fun onScreenTurnedOn() {
diff --git a/packages/SystemUI/src/com/android/systemui/CoreStartable.java b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
index 4c9782c..39e1c41 100644
--- a/packages/SystemUI/src/com/android/systemui/CoreStartable.java
+++ b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
@@ -36,7 +36,7 @@
  * If your CoreStartable depends on different CoreStartables starting before it, use a
  * {@link com.android.systemui.startable.Dependencies} annotation to list out those dependencies.
  *
- * @see SystemUIApplication#startServicesIfNeeded()
+ * @see SystemUIApplication#startSystemUserServicesIfNeeded()
  */
 public interface CoreStartable extends Dumpable {
 
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
index e88aaf01..aab0b1e9 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
@@ -22,7 +22,6 @@
 import android.content.ContentProvider
 import android.content.Context
 import android.content.Intent
-import android.util.Log
 import androidx.core.app.AppComponentFactory
 import com.android.systemui.dagger.ContextComponentHelper
 import com.android.systemui.dagger.SysUIComponent
@@ -91,7 +90,8 @@
         return app
     }
 
-    @UsesReflection(KeepTarget(instanceOfClassConstant = SysUIComponent::class, methodName = "inject"))
+    @UsesReflection(
+            KeepTarget(instanceOfClassConstant = SysUIComponent::class, methodName = "inject"))
     override fun instantiateProviderCompat(cl: ClassLoader, className: String): ContentProvider {
         val contentProvider = super.instantiateProviderCompat(cl, className)
         if (contentProvider is ContextInitializer) {
@@ -103,11 +103,12 @@
                         .getMethod("inject", contentProvider.javaClass)
                     injectMethod.invoke(rootComponent, contentProvider)
                 } catch (e: NoSuchMethodException) {
-                    Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e)
+                    throw RuntimeException("No injector for class: " + contentProvider.javaClass, e)
                 } catch (e: IllegalAccessException) {
-                    Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e)
+                    throw RuntimeException("Injector inaccessible for class: " +
+                            contentProvider.javaClass, e)
                 } catch (e: InvocationTargetException) {
-                    Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e)
+                    throw RuntimeException("Error while injecting: " + contentProvider.javaClass, e)
                 }
                 initializer
             }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 8aae206..15ef61e 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -42,6 +42,7 @@
 import com.android.systemui.dagger.GlobalRootComponent;
 import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.process.ProcessWrapper;
 import com.android.systemui.res.R;
 import com.android.systemui.startable.Dependencies;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -77,6 +78,7 @@
     private SystemUIAppComponentFactoryBase.ContextAvailableCallback mContextAvailableCallback;
     private SysUIComponent mSysUIComponent;
     private SystemUIInitializer mInitializer;
+    private ProcessWrapper mProcessWrapper;
 
     public SystemUIApplication() {
         super();
@@ -115,6 +117,7 @@
         // Enable Looper trace points.
         // This allows us to see Handler callbacks on traces.
         rootComponent.getMainLooper().setTraceTag(Trace.TRACE_TAG_APP);
+        mProcessWrapper = rootComponent.getProcessWrapper();
 
         // Set the application theme that is inherited by all services. Note that setting the
         // application theme in the manifest does only work for activities. Keep this in sync with
@@ -132,7 +135,7 @@
             View.setTraceLayoutSteps(true);
         }
 
-        if (rootComponent.getProcessWrapper().isSystemUser()) {
+        if (mProcessWrapper.isSystemUser()) {
             IntentFilter bootCompletedFilter = new
                     IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED);
             bootCompletedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
@@ -199,7 +202,11 @@
      * <p>This method must only be called from the main thread.</p>
      */
 
-    public void startServicesIfNeeded() {
+    public void startSystemUserServicesIfNeeded() {
+        if (!mProcessWrapper.isSystemUser()) {
+            Log.wtf(TAG, "Tried starting SystemUser services on non-SystemUser");
+            return;  // Per-user startables are handled in #startSystemUserServicesIfNeeded.
+        }
         final String vendorComponent = mInitializer.getVendorComponent(getResources());
 
         // Sort the startables so that we get a deterministic ordering.
@@ -219,6 +226,9 @@
      * <p>This method must only be called from the main thread.</p>
      */
     void startSecondaryUserServicesIfNeeded() {
+        if (mProcessWrapper.isSystemUser()) {
+            return;  // Per-user startables are handled in #startSystemUserServicesIfNeeded.
+        }
         // Sort the startables so that we get a deterministic ordering.
         Map<Class<?>, Provider<CoreStartable>> sortedStartables = new TreeMap<>(
                 Comparator.comparing(Class::getName));
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index 872b005..1a9b01f 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -22,10 +22,10 @@
 import android.os.HandlerThread;
 import android.util.Log;
 
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.GlobalRootComponent;
 import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dagger.WMComponent;
+import com.android.systemui.res.R;
 import com.android.systemui.util.InitializationChecker;
 import com.android.wm.shell.dagger.WMShellConcurrencyModule;
 import com.android.wm.shell.keyguard.KeyguardTransitions;
@@ -124,9 +124,6 @@
                     .setDesktopMode(Optional.ofNullable(null));
         }
         mSysUIComponent = builder.build();
-        if (initializeComponents) {
-            mSysUIComponent.init();
-        }
 
         // Every other part of our codebase currently relies on Dependency, so we
         // really need to ensure the Dependency gets initialized early on.
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java b/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java
index f4ec6f7..407f764 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java
@@ -19,12 +19,31 @@
 import android.app.Service;
 import android.content.Intent;
 import android.os.IBinder;
+import android.util.Log;
+
+import com.android.systemui.process.ProcessWrapper;
+
+import javax.inject.Inject;
 
 public class SystemUISecondaryUserService extends Service {
 
+    private static final String TAG = "SysUISecondaryService";
+
+    private final ProcessWrapper mProcessWrapper;
+
+    @Inject
+    SystemUISecondaryUserService(ProcessWrapper processWrapper) {
+        mProcessWrapper = processWrapper;
+    }
+
     @Override
     public void onCreate() {
         super.onCreate();
+        if (mProcessWrapper.isSystemUser()) {
+            Log.w(TAG, "SecondaryServices started for System User. Stopping it.");
+            stopSelf();
+            return;
+        }
         ((SystemUIApplication) getApplication()).startSecondaryUserServicesIfNeeded();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 76c2282..b26be0c 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -77,7 +77,7 @@
         super.onCreate();
 
         // Start all of SystemUI
-        ((SystemUIApplication) getApplication()).startServicesIfNeeded();
+        ((SystemUIApplication) getApplication()).startSystemUserServicesIfNeeded();
 
         // Finish initializing dump logic
         mLogBufferFreezer.attach(mBroadcastDispatcher);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
index bf121fb..e57323b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
@@ -26,6 +26,7 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Rect;
+import android.text.Layout;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewTreeObserver;
@@ -162,6 +163,7 @@
         mTextView.setPadding(/* left= */ 0, textPadding, /* right= */ 0, textPadding);
         mTextView.setTextSize(COMPLEX_UNIT_PX, textSize);
         mTextView.setTextColor(textColor);
+        mTextView.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL);
 
         final ColorStateList colorAccent = Utils.getColorAccent(getContext());
         mUndoButton.setText(res.getString(R.string.accessibility_floating_button_undo));
diff --git a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
index 6076f32..5bd7e54 100644
--- a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
@@ -29,7 +29,7 @@
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
 import com.android.systemui.shade.QuickSettingsController
 import com.android.systemui.shade.ShadeController
-import com.android.systemui.shade.ShadeViewController
+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.phone.StatusBarKeyguardViewManager
@@ -64,8 +64,8 @@
                 }
 
                 override fun onBackProgressed(backEvent: BackEvent) {
-                    if (shouldBackBeHandled() && shadeViewController.canBeCollapsed()) {
-                        shadeViewController.onBackProgressed(backEvent.progress)
+                    if (shouldBackBeHandled() && shadeBackActionInteractor.canBeCollapsed()) {
+                        shadeBackActionInteractor.onBackProgressed(backEvent.progress)
                     }
                 }
             }
@@ -77,12 +77,12 @@
         get() =
             notificationShadeWindowController.windowRootView?.viewRootImpl?.onBackInvokedDispatcher
 
-    private lateinit var shadeViewController: ShadeViewController
+    private lateinit var shadeBackActionInteractor: ShadeBackActionInteractor
     private lateinit var qsController: QuickSettingsController
 
-    fun setup(qsController: QuickSettingsController, svController: ShadeViewController) {
+    fun setup(qsController: QuickSettingsController, svController: ShadeBackActionInteractor) {
         this.qsController = qsController
-        this.shadeViewController = svController
+        this.shadeBackActionInteractor = svController
     }
 
     override fun start() {
@@ -114,16 +114,16 @@
             return true
         }
         if (qsController.expanded) {
-            shadeViewController.animateCollapseQs(false)
+            shadeBackActionInteractor.animateCollapseQs(false)
             return true
         }
-        if (shadeViewController.closeUserSwitcherIfOpen()) {
+        if (shadeBackActionInteractor.closeUserSwitcherIfOpen()) {
             return true
         }
         if (shouldBackBeHandled()) {
-            if (shadeViewController.canBeCollapsed()) {
+            if (shadeBackActionInteractor.canBeCollapsed()) {
                 // this is the Shade dismiss animation, so make sure QQS closes when it ends.
-                shadeViewController.onBackPressed()
+                shadeBackActionInteractor.onBackPressed()
                 shadeController.animateCollapseShade()
             }
             return true
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
index 792a7ef..c8fb044 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
@@ -17,31 +17,20 @@
 package com.android.systemui.biometrics.data.repository
 
 import android.content.Context
-import android.hardware.devicestate.DeviceStateManager
-import android.hardware.display.DisplayManager
-import android.hardware.display.DisplayManager.DisplayListener
-import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-import android.os.Handler
 import android.util.Size
 import android.view.DisplayInfo
-import com.android.app.tracing.traceSection
-import com.android.internal.util.ArrayUtils
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.biometrics.shared.model.toDisplayRotation
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-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 java.util.concurrent.Executor
+import com.android.systemui.display.data.repository.DeviceStateRepository
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.REAR_DISPLAY
+import com.android.systemui.display.data.repository.DisplayRepository
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
@@ -66,52 +55,23 @@
     val currentDisplaySize: StateFlow<Size>
 }
 
-// TODO(b/296211844): This class could directly use DeviceStateRepository and DisplayRepository
-// instead.
 @SysUISingleton
 class DisplayStateRepositoryImpl
 @Inject
 constructor(
-    @Application applicationScope: CoroutineScope,
+    @Background backgroundScope: CoroutineScope,
     @Application val context: Context,
-    deviceStateManager: DeviceStateManager,
-    displayManager: DisplayManager,
-    @Main handler: Handler,
-    @Background backgroundExecutor: Executor,
-    @Background backgroundDispatcher: CoroutineDispatcher,
+    deviceStateRepository: DeviceStateRepository,
+    displayRepository: DisplayRepository,
 ) : DisplayStateRepository {
     override val isReverseDefaultRotation =
         context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
 
     override val isInRearDisplayMode: StateFlow<Boolean> =
-        conflatedCallbackFlow {
-                val sendRearDisplayStateUpdate = { state: Boolean ->
-                    trySendWithFailureLogging(
-                        state,
-                        TAG,
-                        "Error sending rear display state update to $state"
-                    )
-                }
-
-                val callback =
-                    DeviceStateManager.DeviceStateCallback { state ->
-                        val isInRearDisplayMode =
-                            ArrayUtils.contains(
-                                context.resources.getIntArray(
-                                    com.android.internal.R.array.config_rearDisplayDeviceStates
-                                ),
-                                state
-                            )
-                        sendRearDisplayStateUpdate(isInRearDisplayMode)
-                    }
-
-                sendRearDisplayStateUpdate(false)
-                deviceStateManager.registerCallback(backgroundExecutor, callback)
-                awaitClose { deviceStateManager.unregisterCallback(callback) }
-            }
-            .flowOn(backgroundDispatcher)
+        deviceStateRepository.state
+            .map { it == REAR_DISPLAY }
             .stateIn(
-                applicationScope,
+                backgroundScope,
                 started = SharingStarted.Eagerly,
                 initialValue = false,
             )
@@ -123,37 +83,10 @@
     }
 
     private val currentDisplayInfo: StateFlow<DisplayInfo> =
-        conflatedCallbackFlow {
-                val callback =
-                    object : DisplayListener {
-                        override fun onDisplayRemoved(displayId: Int) {}
-
-                        override fun onDisplayAdded(displayId: Int) {}
-
-                        override fun onDisplayChanged(displayId: Int) {
-                            traceSection(
-                                "DisplayStateRepository" +
-                                    ".currentRotationDisplayListener#onDisplayChanged"
-                            ) {
-                                val displayInfo = getDisplayInfo()
-                                trySendWithFailureLogging(
-                                    displayInfo,
-                                    TAG,
-                                    "Error sending displayInfo to $displayInfo"
-                                )
-                            }
-                        }
-                    }
-                displayManager.registerDisplayListener(
-                    callback,
-                    handler,
-                    EVENT_FLAG_DISPLAY_CHANGED
-                )
-                awaitClose { displayManager.unregisterDisplayListener(callback) }
-            }
-            .flowOn(backgroundDispatcher)
+        displayRepository.displayChangeEvent
+            .map { getDisplayInfo() }
             .stateIn(
-                applicationScope,
+                backgroundScope,
                 started = SharingStarted.Eagerly,
                 initialValue = getDisplayInfo(),
             )
@@ -170,7 +103,7 @@
         currentDisplayInfo
             .map { rotationToDisplayRotation(it.rotation) }
             .stateIn(
-                applicationScope,
+                backgroundScope,
                 started = SharingStarted.WhileSubscribed(),
                 initialValue = rotationToDisplayRotation(currentDisplayInfo.value.rotation)
             )
@@ -179,7 +112,7 @@
         currentDisplayInfo
             .map { Size(it.naturalWidth, it.naturalHeight) }
             .stateIn(
-                applicationScope,
+                backgroundScope,
                 started = SharingStarted.WhileSubscribed(),
                 initialValue =
                     Size(
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/Color.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Color.kt
new file mode 100644
index 0000000..d235c95
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Color.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.common.shared.model
+
+import android.annotation.AttrRes
+import android.annotation.ColorInt
+
+/**
+ * Models a color that can be either a specific [Color.Loaded] value or a resolvable theme
+ * [Color.Attribute]
+ */
+sealed interface Color {
+
+    data class Loaded(@ColorInt val color: Int) : Color
+
+    data class Attribute(@AttrRes val attribute: Int) : Color
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
index 12be32c..964eb6f 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
@@ -24,17 +24,12 @@
 import androidx.annotation.LayoutRes
 import com.android.settingslib.Utils
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.onDensityOrFontScaleChanged
 import com.android.systemui.statusbar.policy.onThemeChanged
 import com.android.systemui.util.kotlin.emitOnStart
-import com.android.systemui.util.view.bindLatest
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 
@@ -91,46 +86,3 @@
             .map { layoutInflater.inflate(id, root, attachToRoot) as T }
     }
 }
-
-/**
- * Perform an inflation right away, then re-inflate whenever the device configuration changes, and
- * call [onInflate] on the resulting view each time. Disposes of the [DisposableHandle] returned by
- * [onInflate] when done.
- *
- * This never completes unless cancelled, it just suspends and waits for updates. It runs on a
- * background thread using [backgroundDispatcher].
- *
- * For parameters [resource], [root] and [attachToRoot], see [LayoutInflater.inflate].
- *
- * An example use-case of this is when a view needs to be re-inflated whenever a configuration
- * change occurs, which would require the ViewBinder to then re-bind the new view. For example, the
- * code in the parent view's binder would look like:
- * ```
- * parentView.repeatWhenAttached {
- *     configurationState
- *         .reinflateAndBindLatest(
- *             R.layout.my_layout,
- *             parentView,
- *             attachToRoot = false,
- *             coroutineScope = lifecycleScope,
- *             configurationController.onThemeChanged,
- *         ) { view: ChildView ->
- *             ChildViewBinder.bind(view, childViewModel)
- *         }
- * }
- * ```
- *
- * In turn, the bind method (passed through [onInflate]) uses [repeatWhenAttached], which returns a
- * [DisposableHandle].
- */
-suspend fun <T : View> ConfigurationState.reinflateAndBindLatest(
-    @LayoutRes resource: Int,
-    root: ViewGroup?,
-    attachToRoot: Boolean,
-    backgroundDispatcher: CoroutineDispatcher,
-    onInflate: (T) -> DisposableHandle?,
-) {
-    inflateLayout<T>(resource, root, attachToRoot)
-        .flowOn(backgroundDispatcher)
-        .bindLatest(onInflate)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index dd186d6..f7bc5cdc 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -26,10 +26,13 @@
 import com.android.systemui.ScreenDecorationsModule;
 import com.android.systemui.accessibility.SystemActionsModule;
 import com.android.systemui.battery.BatterySaverModule;
+import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerImpl;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.media.dagger.MediaModule;
+import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
+import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.navigationbar.NavigationBarControllerModule;
 import com.android.systemui.navigationbar.gestural.GestureModule;
 import com.android.systemui.plugins.qs.QSFactory;
@@ -63,6 +66,7 @@
 import com.android.systemui.statusbar.policy.SensorPrivacyController;
 import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
 import com.android.systemui.toast.ToastModule;
+import com.android.systemui.unfold.SysUIUnfoldStartableModule;
 import com.android.systemui.unfold.UnfoldTransitionModule;
 import com.android.systemui.volume.dagger.VolumeModule;
 import com.android.systemui.wallpapers.dagger.WallpaperModule;
@@ -92,12 +96,15 @@
         AospPolicyModule.class,
         BatterySaverModule.class,
         CollapsedStatusBarFragmentStartableModule.class,
+        ConnectingDisplayViewModel.StartableModule.class,
         GestureModule.class,
         HeadsUpModule.class,
         KeyboardShortcutsModule.class,
         MediaModule.class,
+        MediaMuteAwaitConnectionCli.StartableModule.class,
         MultiUserUtilsModule.class,
         NavigationBarControllerModule.class,
+        NearbyMediaDevicesManager.StartableModule.class,
         PowerModule.class,
         QSModule.class,
         RearDisplayModule.class,
@@ -108,6 +115,7 @@
         ShadeModule.class,
         StartCentralSurfacesModule.class,
         SceneContainerFrameworkModule.class,
+        SysUIUnfoldStartableModule.class,
         UnfoldTransitionModule.Startables.class,
         ToastModule.class,
         VolumeModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index e7b8773..3b0c281 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -19,25 +19,15 @@
 import com.android.systemui.BootCompleteCacheImpl;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.Dependency;
-import com.android.systemui.Flags;
 import com.android.systemui.InitController;
 import com.android.systemui.SystemUIAppComponentFactoryBase;
 import com.android.systemui.dagger.qualifiers.PerUser;
-import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
-import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
-import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.people.PeopleProvider;
 import com.android.systemui.statusbar.NotificationInsetsModule;
 import com.android.systemui.statusbar.QsFrameTranslateModule;
 import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.unfold.FoldStateLogger;
-import com.android.systemui.unfold.FoldStateLoggingProvider;
-import com.android.systemui.unfold.SysUIUnfoldComponent;
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
-import com.android.systemui.unfold.dagger.UnfoldBg;
-import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder;
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.desktopmode.DesktopMode;
@@ -126,42 +116,6 @@
     }
 
     /**
-     * Initializes all the SysUI components.
-     */
-    default void init() {
-        // Initialize components that have no direct tie to the dagger dependency graph,
-        // but are critical to this component's operation
-        getSysUIUnfoldComponent()
-                .ifPresent(
-                        c -> {
-                            c.getUnfoldLightRevealOverlayAnimation().init();
-                            c.getUnfoldTransitionWallpaperController().init();
-                            c.getUnfoldHapticsPlayer();
-                            c.getNaturalRotationUnfoldProgressProvider().init();
-                            c.getUnfoldLatencyTracker().init();
-                        });
-        // No init method needed, just needs to be gotten so that it's created.
-        getMediaMuteAwaitConnectionCli();
-        getNearbyMediaDevicesManager();
-        getConnectingDisplayViewModel().init();
-        getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
-        getFoldStateLogger().ifPresent(FoldStateLogger::init);
-
-        Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider;
-
-        if (Flags.unfoldAnimationBackgroundProgress()) {
-            unfoldTransitionProgressProvider = getBgUnfoldTransitionProgressProvider();
-        } else {
-            unfoldTransitionProgressProvider = getUnfoldTransitionProgressProvider();
-        }
-        unfoldTransitionProgressProvider
-                .ifPresent(
-                        (progressProvider) ->
-                                getUnfoldTransitionProgressForwarder()
-                                        .ifPresent(progressProvider::addCallback));
-    }
-
-    /**
      * Provides a BootCompleteCache.
      */
     @SysUISingleton
@@ -180,37 +134,6 @@
     ContextComponentHelper getContextComponentHelper();
 
     /**
-     * Creates a UnfoldTransitionProgressProvider that calculates progress in the background.
-     */
-    @SysUISingleton
-    @UnfoldBg
-    Optional<UnfoldTransitionProgressProvider> getBgUnfoldTransitionProgressProvider();
-
-    /**
-     * Creates a UnfoldTransitionProgressProvider that calculates progress in the main thread.
-     */
-    @SysUISingleton
-    Optional<UnfoldTransitionProgressProvider> getUnfoldTransitionProgressProvider();
-
-    /**
-     * Creates a UnfoldTransitionProgressForwarder.
-     */
-    @SysUISingleton
-    Optional<UnfoldTransitionProgressForwarder> getUnfoldTransitionProgressForwarder();
-
-    /**
-     * Creates a FoldStateLoggingProvider.
-     */
-    @SysUISingleton
-    Optional<FoldStateLoggingProvider> getFoldStateLoggingProvider();
-
-    /**
-     * Creates a FoldStateLogger.
-     */
-    @SysUISingleton
-    Optional<FoldStateLogger> getFoldStateLogger();
-
-    /**
      * Main dependency providing module.
      */
     @SysUISingleton
@@ -227,22 +150,6 @@
     InitController getInitController();
 
     /**
-     * For devices with a hinge: access objects within this component
-     */
-    Optional<SysUIUnfoldComponent> getSysUIUnfoldComponent();
-
-    /** */
-    MediaMuteAwaitConnectionCli getMediaMuteAwaitConnectionCli();
-
-    /** */
-    NearbyMediaDevicesManager getNearbyMediaDevicesManager();
-
-    /**
-     * Creates a ConnectingDisplayViewModel
-     */
-    ConnectingDisplayViewModel getConnectingDisplayViewModel();
-
-    /**
      * Returns {@link CoreStartable}s that should be started with the application.
      */
     Map<Class<?>, Provider<CoreStartable>> getStartables();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 28fd9a9..1720de8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -17,6 +17,7 @@
 package com.android.systemui.dagger;
 
 import android.app.INotificationManager;
+import android.app.Service;
 import android.content.Context;
 import android.service.dreams.IDreamManager;
 
@@ -28,6 +29,7 @@
 import com.android.systemui.BootCompleteCache;
 import com.android.systemui.BootCompleteCacheImpl;
 import com.android.systemui.CameraProtectionModule;
+import com.android.systemui.SystemUISecondaryUserService;
 import com.android.systemui.accessibility.AccessibilityModule;
 import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule;
 import com.android.systemui.appops.dagger.AppOpsModule;
@@ -150,6 +152,8 @@
 import dagger.BindsOptionalOf;
 import dagger.Module;
 import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
 
 import java.util.Collections;
 import java.util.Optional;
@@ -384,4 +388,9 @@
     @Binds
     abstract LargeScreenShadeInterpolator largeScreensShadeInterpolator(
             LargeScreenShadeInterpolatorImpl impl);
+
+    @Binds
+    @IntoMap
+    @ClassKey(SystemUISecondaryUserService.class)
+    abstract Service bindsSystemUISecondaryUserService(SystemUISecondaryUserService service);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
index 684627b..2461c26 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
@@ -39,4 +39,6 @@
     /** Provide the current status of fingerprint authentication. */
     val authenticationStatus: Flow<FingerprintAuthenticationStatus> =
         repository.authenticationStatus
+
+    val isLockedOut: Flow<Boolean> = repository.isLockedOut
 }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index 98130eb..cf91e14 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -36,7 +36,6 @@
 import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
 import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.log.FaceAuthenticationLogger
@@ -78,7 +77,7 @@
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val faceAuthenticationLogger: FaceAuthenticationLogger,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-    private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
+    private val deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
     private val userRepository: UserRepository,
     private val facePropertyRepository: FacePropertyRepository,
     private val faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig,
@@ -149,14 +148,24 @@
             }
             .launchIn(applicationScope)
 
-        deviceEntryFingerprintAuthRepository.isLockedOut
-            .onEach {
-                if (it) {
+        deviceEntryFingerprintAuthInteractor.isLockedOut
+            .sample(biometricSettingsRepository.isFaceAuthEnrolledAndEnabled, ::Pair)
+            .filter { (_, faceEnabledAndEnrolled) ->
+                // We don't care about this if face auth is not enabled.
+                faceEnabledAndEnrolled
+            }
+            .map { (fpLockedOut, _) -> fpLockedOut }
+            .sample(userRepository.selectedUser, ::Pair)
+            .onEach { (fpLockedOut, currentUser) ->
+                if (fpLockedOut) {
                     faceAuthenticationLogger.faceLockedOut("Fingerprint locked out")
-                    // We don't care about this if face auth is not enabled.
                     if (isFaceAuthEnabledAndEnrolled()) {
                         repository.setLockedOut(true)
                     }
+                } else {
+                    // Fingerprint is not locked out anymore, revert face lockout state back to
+                    // previous value.
+                    resetLockedOutState(currentUser.userInfo.id)
                 }
             }
             .launchIn(applicationScope)
@@ -169,10 +178,7 @@
                 val wasSwitching = previous.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
                 val isSwitching = curr.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
                 if (wasSwitching && !isSwitching) {
-                    val lockoutMode = facePropertyRepository.getLockoutMode(curr.userInfo.id)
-                    repository.setLockedOut(
-                        lockoutMode == LockoutMode.PERMANENT || lockoutMode == LockoutMode.TIMED
-                    )
+                    resetLockedOutState(curr.userInfo.id)
                     yield()
                     runFaceAuth(
                         FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING,
@@ -185,6 +191,13 @@
             .launchIn(applicationScope)
     }
 
+    private suspend fun resetLockedOutState(currentUserId: Int) {
+        val lockoutMode = facePropertyRepository.getLockoutMode(currentUserId)
+        repository.setLockedOut(
+            lockoutMode == LockoutMode.PERMANENT || lockoutMode == LockoutMode.TIMED
+        )
+    }
+
     override fun onSwipeUpOnBouncer() {
         runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
index 10aa703..190062c 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
@@ -18,6 +18,7 @@
 import android.app.Dialog
 import android.content.Context
 import com.android.server.policy.feature.flags.Flags
+import com.android.systemui.CoreStartable
 import com.android.systemui.biometrics.Utils
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -26,6 +27,10 @@
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
 import com.android.systemui.display.ui.view.MirroringConfirmationDialog
 import com.android.systemui.statusbar.policy.ConfigurationController
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -47,12 +52,12 @@
     @Application private val scope: CoroutineScope,
     @Background private val bgDispatcher: CoroutineDispatcher,
     private val configurationController: ConfigurationController,
-) {
+) : CoreStartable {
 
     private var dialog: Dialog? = null
 
     /** Starts listening for pending displays. */
-    fun init() {
+    override fun start() {
         val pendingDisplayFlow = connectedDisplayInteractor.pendingDisplay
         val concurrentDisplaysInProgessFlow =
             if (Flags.enableDualDisplayBlocking()) {
@@ -96,4 +101,12 @@
         dialog?.hide()
         dialog = null
     }
+
+    @Module
+    interface StartableModule {
+        @Binds
+        @IntoMap
+        @ClassKey(ConnectingDisplayViewModel::class)
+        fun bindsConnectingDisplayViewModel(impl: ConnectingDisplayViewModel): CoreStartable
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/HapticSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/HapticSliderViewBinder.kt
new file mode 100644
index 0000000..304fdd6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/HapticSliderViewBinder.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.haptics.slider
+
+import android.view.View
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.awaitCancellation
+
+object HapticSliderViewBinder {
+    /**
+     * Binds a [SeekableSliderHapticPlugin] to a [View]. The binded view should be a
+     * [android.widget.SeekBar] or a container of a [android.widget.SeekBar]
+     */
+    @JvmStatic
+    fun bind(view: View?, plugin: SeekableSliderHapticPlugin) {
+        view?.repeatWhenAttached {
+            plugin.startInScope(lifecycleScope)
+            try {
+                awaitCancellation()
+            } finally {
+                plugin.stop()
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
index 58fb6a9..931a869 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
@@ -20,11 +20,8 @@
 import android.view.VelocityTracker
 import android.widget.SeekBar
 import androidx.annotation.VisibleForTesting
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.delay
@@ -43,10 +40,8 @@
 constructor(
     vibratorHelper: VibratorHelper,
     systemClock: SystemClock,
-    @Main private val mainDispatcher: CoroutineDispatcher,
-    @Application private val applicationScope: CoroutineScope,
     sliderHapticFeedbackConfig: SliderHapticFeedbackConfig = SliderHapticFeedbackConfig(),
-    sliderTrackerConfig: SeekableSliderTrackerConfig = SeekableSliderTrackerConfig(),
+    private val sliderTrackerConfig: SeekableSliderTrackerConfig = SeekableSliderTrackerConfig(),
 ) {
 
     private val velocityTracker = VelocityTracker.obtain()
@@ -61,19 +56,15 @@
             systemClock,
         )
 
-    private val sliderTracker =
-        SeekableSliderTracker(
-            sliderHapticFeedbackProvider,
-            sliderEventProducer,
-            mainDispatcher,
-            sliderTrackerConfig,
-        )
+    private var sliderTracker: SeekableSliderTracker? = null
+
+    private var pluginScope: CoroutineScope? = null
 
     val isTracking: Boolean
-        get() = sliderTracker.isTracking
+        get() = sliderTracker?.isTracking == true
 
-    val trackerState: SliderState
-        get() = sliderTracker.currentState
+    val trackerState: SliderState?
+        get() = sliderTracker?.currentState
 
     /**
      * A waiting [Job] for a timer that estimates the key-up event when a key-down event is
@@ -89,14 +80,20 @@
         get() = keyUpJob != null && keyUpJob?.isActive == true
 
     /**
-     * Start the plugin.
-     *
-     * This starts the tracking of slider states, events and triggering of haptic feedback.
+     * Specify the scope for the plugin's operations and start the slider tracker in this scope.
+     * This also involves the key-up timer job.
      */
-    fun start() {
-        if (!isTracking) {
-            sliderTracker.startTracking()
-        }
+    fun startInScope(scope: CoroutineScope) {
+        if (sliderTracker != null) stop()
+        sliderTracker =
+            SeekableSliderTracker(
+                sliderHapticFeedbackProvider,
+                sliderEventProducer,
+                scope,
+                sliderTrackerConfig,
+            )
+        pluginScope = scope
+        sliderTracker?.startTracking()
     }
 
     /**
@@ -104,7 +101,7 @@
      *
      * This stops the tracking of slider states, events and triggers of haptic feedback.
      */
-    fun stop() = sliderTracker.stopTracking()
+    fun stop() = sliderTracker?.stopTracking()
 
     /** React to a touch event */
     fun onTouchEvent(event: MotionEvent?) {
@@ -147,9 +144,9 @@
     /**
      * An external key was pressed (e.g., a volume key).
      *
-     * This event is used to estimate the key-up event based on by running a timer as a waiting
-     * coroutine in the [keyUpTimerScope]. A key-up event in a slider corresponds to an onArrowUp
-     * event. Therefore, [onArrowUp] must be called after the timeout.
+     * This event is used to estimate the key-up event based on a running a timer as a waiting
+     * coroutine in the [pluginScope]. A key-up event in a slider corresponds to an onArrowUp event.
+     * Therefore, [onArrowUp] must be called after the timeout.
      */
     fun onKeyDown() {
         if (!isTracking) return
@@ -159,7 +156,7 @@
             keyUpJob?.cancel()
         }
         keyUpJob =
-            applicationScope.launch {
+            pluginScope?.launch {
                 delay(KEY_UP_TIMEOUT)
                 onArrowUp()
             }
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
index 10098fa..0af3038 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
@@ -17,9 +17,7 @@
 package com.android.systemui.haptics.slider
 
 import androidx.annotation.VisibleForTesting
-import com.android.systemui.dagger.qualifiers.Main
 import kotlin.math.abs
-import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.delay
@@ -31,21 +29,20 @@
  *
  * The tracker runs a state machine to execute actions on touch-based events typical of a seekable
  * slider such as [android.widget.SeekBar]. Coroutines responsible for running the state machine,
- * collecting slider events and maintaining waiting states are run on the main thread via the
- * [com.android.systemui.dagger.qualifiers.Main] coroutine dispatcher.
+ * collecting slider events and maintaining waiting states are run on the provided [CoroutineScope].
  *
  * @param[sliderStateListener] Listener of the slider state.
  * @param[sliderEventProducer] Producer of slider events arising from the slider.
- * @param[mainDispatcher] [CoroutineDispatcher] used to launch coroutines for the collection of
- *   slider events and the launch of timer jobs.
+ * @param[trackerScope] [CoroutineScope] used to launch coroutines for the collection of slider
+ *   events and the launch of timer jobs.
  * @property[config] Configuration parameters of the slider tracker.
  */
 class SeekableSliderTracker(
     sliderStateListener: SliderStateListener,
     sliderEventProducer: SliderEventProducer,
-    @Main mainDispatcher: CoroutineDispatcher,
+    trackerScope: CoroutineScope,
     private val config: SeekableSliderTrackerConfig = SeekableSliderTrackerConfig(),
-) : SliderTracker(CoroutineScope(mainDispatcher), sliderStateListener, sliderEventProducer) {
+) : SliderTracker(trackerScope, sliderStateListener, sliderEventProducer) {
 
     // History of the latest progress collected from slider events
     private var latestProgress = 0f
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 ec29bd6..89cdd25 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
@@ -32,19 +32,13 @@
 import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL
 import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
 import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
-import com.android.systemui.user.data.repository.UserRepository
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
 import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
 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.onEach
-import kotlinx.coroutines.flow.onStart
 import javax.inject.Inject
 
 interface StickyKeysRepository {
@@ -53,14 +47,12 @@
 }
 
 @SysUISingleton
-@OptIn(ExperimentalCoroutinesApi::class)
 class StickyKeysRepositoryImpl
 @Inject
 constructor(
     private val inputManager: InputManager,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
-    private val secureSettings: SecureSettings,
-    userRepository: UserRepository,
+    secureSettingsRepository: UserAwareSecureSettingsRepository,
     private val stickyKeysLogger: StickyKeysLogger,
 ) : StickyKeysRepository {
 
@@ -78,25 +70,10 @@
             .flowOn(backgroundDispatcher)
 
     override val settingEnabled: Flow<Boolean> =
-        userRepository.selectedUserInfo
-            .flatMapLatest { stickyKeySettingObserver(it.id) }
-            .flowOn(backgroundDispatcher)
-
-    private fun stickyKeySettingObserver(userId: Int): Flow<Boolean> {
-        return secureSettings
-            .observerFlow(userId, SETTING_KEY)
-            .onStart { emit(Unit) }
-            .map { isSettingEnabledForCurrentUser(userId) }
-            .distinctUntilChanged()
+        secureSettingsRepository
+            .boolSettingForActiveUser(SETTING_KEY, defaultValue = false)
             .onEach { stickyKeysLogger.logNewSettingValue(it) }
-    }
-
-    private fun isSettingEnabledForCurrentUser(userId: Int) =
-        secureSettings.getIntForUser(
-            /* name= */ SETTING_KEY,
-            /* default= */ 0,
-            /* userHandle= */ userId
-        ) != 0
+            .flowOn(backgroundDispatcher)
 
     private fun toStickyKeysMap(state: StickyModifierState): LinkedHashMap<ModifierKey, Locked> {
         val keys = linkedMapOf<ModifierKey, Locked>()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 4cabd70..2e233d8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -327,7 +327,7 @@
 
     @Override
     public void onCreate() {
-        ((SystemUIApplication) getApplication()).startServicesIfNeeded();
+        ((SystemUIApplication) getApplication()).startSystemUserServicesIfNeeded();
 
         if (mShellTransitions == null || !Transitions.ENABLE_SHELL_TRANSITIONS) {
             RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 4766a84..39bbf07 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -131,7 +131,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.animation.LaunchAnimator;
+import com.android.systemui.animation.TransitionAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -689,17 +689,18 @@
                             } else {
                                 resetStateLocked();
                             }
-                        }
-                        if (simState == TelephonyManager.SIM_STATE_ABSENT) {
-                            // MVNO SIMs can become transiently NOT_READY when switching networks,
-                            // so we should only lock when they are ABSENT.
-                            if (lastSimStateWasLocked) {
-                                if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to ABSENT when the "
-                                        + "previous state was locked. Reset the state.");
+                        } else {
+                            if (lastSimStateWasLocked && mShowing) {
+                                if (DEBUG_SIM_STATES) {
+                                    Log.d(TAG, "SIM moved to "
+                                            + "NOT_READY/ABSENT/UNKNOWN when the previous state "
+                                            + "was locked. Reset the state.");
+                                }
                                 resetStateLocked();
                             }
-                            mSimWasLocked.append(slotId, false);
                         }
+
+                        mSimWasLocked.append(slotId, false);
                     }
                     break;
                 case TelephonyManager.SIM_STATE_PIN_REQUIRED:
@@ -959,7 +960,7 @@
     final ActivityLaunchAnimator.Controller mOccludeAnimationController =
             new ActivityLaunchAnimator.Controller() {
                 @Override
-                public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {
+                public void onTransitionAnimationStart(boolean isExpandingFullyAbove) {
                     mOccludeAnimationPlaying = true;
                     mScrimControllerLazy.get().setOccludeAnimationPlaying(true);
                 }
@@ -976,7 +977,7 @@
                 }
 
                 @Override
-                public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
+                public void onTransitionAnimationEnd(boolean launchIsFullScreen) {
                     if (launchIsFullScreen) {
                         mShadeController.get().instantCollapseShade();
                     }
@@ -993,13 +994,13 @@
 
                 @NonNull
                 @Override
-                public ViewGroup getLaunchContainer() {
+                public ViewGroup getTransitionContainer() {
                     return ((ViewGroup) mKeyguardViewControllerLazy.get()
                             .getViewRootImpl().getView());
                 }
 
                 @Override
-                public void setLaunchContainer(@NonNull ViewGroup launchContainer) {
+                public void setTransitionContainer(@NonNull ViewGroup transitionContainer) {
                     // No-op, launch container is always the shade.
                     Log.wtf(TAG, "Someone tried to change the launch container for the "
                             + "ActivityLaunchAnimator, which should never happen.");
@@ -1007,9 +1008,9 @@
 
                 @NonNull
                 @Override
-                public LaunchAnimator.State createAnimatorState() {
-                    final int fullWidth = getLaunchContainer().getWidth();
-                    final int fullHeight = getLaunchContainer().getHeight();
+                public TransitionAnimator.State createAnimatorState() {
+                    final int fullWidth = getTransitionContainer().getWidth();
+                    final int fullHeight = getTransitionContainer().getHeight();
 
                     if (mUpdateMonitor.isSecureCameraLaunchedOverKeyguard()) {
                         final float initialHeight = fullHeight / 3f;
@@ -1017,7 +1018,7 @@
 
                         // Start the animation near the power button, at one-third size, since the
                         // camera was launched from the power button.
-                        return new LaunchAnimator.State(
+                        return new TransitionAnimator.State(
                                 (int) (mPowerButtonY - initialHeight / 2f) /* top */,
                                 (int) (mPowerButtonY + initialHeight / 2f) /* bottom */,
                                 (int) (fullWidth - initialWidth) /* left */,
@@ -1029,7 +1030,7 @@
 
                         // Start the animation in the center of the screen, scaled down to half
                         // size.
-                        return new LaunchAnimator.State(
+                        return new TransitionAnimator.State(
                                 (int) (fullHeight - initialHeight) / 2,
                                 (int) (initialHeight + (fullHeight - initialHeight) / 2),
                                 (int) (fullWidth - initialWidth) / 2,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
index 5e3779a..59288a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
@@ -60,6 +60,8 @@
 
     val currentClock: StateFlow<ClockController?>
 
+    val previewClock: StateFlow<ClockController>
+
     val clockEventController: ClockEventController
     fun setClockSize(@ClockSize size: Int)
 }
@@ -120,6 +122,15 @@
                 initialValue = clockRegistry.createCurrentClock()
             )
 
+    override val previewClock: StateFlow<ClockController> =
+        currentClockId
+            .map { clockRegistry.createCurrentClock() }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = clockRegistry.createCurrentClock()
+            )
+
     @VisibleForTesting
     suspend fun getClockSize(): SettingsClockSize {
         return withContext(backgroundDispatcher) {
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 cc1cf91..7ae70a9 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
@@ -74,6 +74,7 @@
                 BurnInModel(translationX, translationY, burnInHelperWrapper.burnInScale())
             }
             .distinctUntilChanged()
+            .stateIn(scope, SharingStarted.Lazily, BurnInModel())
 
     /**
      * Use for max burn-in offsets that are NOT specified in pixels. This flow will recalculate the
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index a97c152..0cf74a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -24,15 +24,15 @@
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.util.kotlin.Utils.Companion.sample
-import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
+import com.android.systemui.util.kotlin.sample as sampleUtil
 import com.android.wm.shell.animation.Interpolators
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.FlowPreview
+import kotlinx.coroutines.flow.sample
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -62,14 +62,17 @@
         listenForTransitionToCamera(scope, keyguardInteractor)
     }
 
+    @FlowPreview
     private fun listenForAlternateBouncerToLockscreenHubAodOrDozing() {
         scope.launch {
             keyguardInteractor.alternateBouncerShowing
                 // Add a slight delay, as alternateBouncer and primaryBouncer showing event changes
                 // will arrive with a small gap in time. This prevents a transition to LOCKSCREEN
                 // happening prematurely.
-                .onEach { delay(50) }
-                .sample(
+                // This should eventually be removed in favor of
+                // [KeyguardTransitionInteractor#startDismissKeyguardTransition]
+                .sample(150L)
+                .sampleCombine(
                     keyguardInteractor.primaryBouncerShowing,
                     startedKeyguardTransitionStep,
                     powerInteractor.isAwake,
@@ -111,19 +114,20 @@
 
     private fun listenForAlternateBouncerToGone() {
         scope.launch {
-            keyguardInteractor.isKeyguardGoingAway.sample(finishedKeyguardState, ::Pair).collect {
-                (isKeyguardGoingAway, keyguardState) ->
-                if (isKeyguardGoingAway && keyguardState == KeyguardState.ALTERNATE_BOUNCER) {
-                    startTransitionTo(KeyguardState.GONE)
+            keyguardInteractor.isKeyguardGoingAway
+                .sampleUtil(finishedKeyguardState, ::Pair)
+                .collect { (isKeyguardGoingAway, keyguardState) ->
+                    if (isKeyguardGoingAway && keyguardState == KeyguardState.ALTERNATE_BOUNCER) {
+                        startTransitionTo(KeyguardState.GONE)
+                    }
                 }
-            }
         }
     }
 
     private fun listenForAlternateBouncerToPrimaryBouncer() {
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
-                .sample(startedKeyguardTransitionStep, ::Pair)
+                .sampleUtil(startedKeyguardTransitionStep, ::Pair)
                 .collect { (isPrimaryBouncerShowing, startedKeyguardState) ->
                     if (
                         isPrimaryBouncerShowing &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 8fa33ee7..e0b5c0e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -21,18 +21,18 @@
 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.KeyguardWmStateRefactor
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
-import com.android.systemui.util.kotlin.Utils.Companion.toTriple
+import com.android.systemui.util.kotlin.Utils.Companion.sample
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -85,18 +85,21 @@
             keyguardInteractor
                 .dozeTransitionTo(DozeStateModel.FINISH)
                 .sample(
-                    combine(
-                        startedKeyguardTransitionStep,
-                        keyguardInteractor.isKeyguardOccluded,
-                        ::Pair
-                    ),
-                    ::toTriple
+                    startedKeyguardTransitionStep,
+                    keyguardInteractor.isKeyguardOccluded,
+                    keyguardInteractor.biometricUnlockState,
                 )
-                .collect { (_, lastStartedStep, occluded) ->
-                    if (lastStartedStep.to == KeyguardState.AOD && !occluded) {
+                .collect { (_, lastStartedStep, occluded, biometricUnlockState) ->
+                    if (
+                        lastStartedStep.to == KeyguardState.AOD &&
+                            !occluded &&
+                            !isWakeAndUnlock(biometricUnlockState)
+                    ) {
                         val modeOnCanceled =
                             if (lastStartedStep.from == KeyguardState.LOCKSCREEN) {
                                 TransitionModeOnCanceled.REVERSE
+                            } else if (lastStartedStep.from == KeyguardState.GONE) {
+                                TransitionModeOnCanceled.RESET
                             } else {
                                 TransitionModeOnCanceled.LAST_VALUE
                             }
@@ -126,15 +129,29 @@
     }
 
     private fun listenForAodToGone() {
+        if (KeyguardWmStateRefactor.isEnabled) {
+            return
+        }
+
         scope.launch {
             keyguardInteractor.biometricUnlockState.sample(finishedKeyguardState, ::Pair).collect {
                 (biometricUnlockState, keyguardState) ->
+                KeyguardWmStateRefactor.assertInLegacyMode()
                 if (keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)) {
                     startTransitionTo(KeyguardState.GONE)
                 }
             }
         }
     }
+
+    /**
+     * Dismisses AOD and transitions to GONE. This is called whenever authentication occurs while on
+     * AOD.
+     */
+    fun dismissAod() {
+        scope.launch { startTransitionTo(KeyguardState.GONE) }
+    }
+
     override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
         return ValueAnimator().apply {
             interpolator = Interpolators.LINEAR
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 7477624..6b85a63 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -68,11 +68,11 @@
         scope.launch {
             keyguardInteractor.isKeyguardShowing
                 .sample(
-                    startedKeyguardTransitionStep,
+                    currentKeyguardState,
                     communalInteractor.isIdleOnCommunal,
                 )
-                .collect { (isKeyguardShowing, lastStartedStep, isIdleOnCommunal) ->
-                    if (isKeyguardShowing && lastStartedStep.to == KeyguardState.GONE) {
+                .collect { (isKeyguardShowing, currentState, isIdleOnCommunal) ->
+                    if (isKeyguardShowing && currentState == KeyguardState.GONE) {
                         val to =
                             if (isIdleOnCommunal) {
                                 KeyguardState.GLANCEABLE_HUB
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index 356c408..196770a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -44,6 +44,8 @@
 
     val currentClock: StateFlow<ClockController?> = keyguardClockRepository.currentClock
 
+    val previewClock: StateFlow<ClockController> = keyguardClockRepository.previewClock
+
     var clock: ClockController? by keyguardClockRepository.clockEventController::clock
 
     val clockSize: StateFlow<Int> = keyguardClockRepository.clockSize
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
index 8784723..c496a6e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor.Companion.isSurfaceVisible
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
 import com.android.systemui.util.kotlin.toPx
 import dagger.Lazy
 import javax.inject.Inject
@@ -44,6 +45,7 @@
     transitionInteractor: KeyguardTransitionInteractor,
     inWindowLauncherUnlockAnimationInteractor: Lazy<InWindowLauncherUnlockAnimationInteractor>,
     swipeToDismissInteractor: SwipeToDismissInteractor,
+    notificationLaunchInteractor: NotificationLaunchAnimationInteractor,
 ) {
     /**
      * The view params to use for the surface. These params describe the alpha/translation values to
@@ -53,10 +55,20 @@
         combine(
                 transitionInteractor.startedKeyguardTransitionStep,
                 transitionInteractor.currentKeyguardState,
-            ) { startedStep, currentState ->
+                notificationLaunchInteractor.isLaunchAnimationRunning,
+            ) { startedStep, currentState, notifAnimationRunning ->
                 // If we're in transition to GONE, special unlock animation params apply.
                 if (startedStep.to == KeyguardState.GONE && currentState != KeyguardState.GONE) {
-                    if (inWindowLauncherUnlockAnimationInteractor.get().isLauncherUnderneath()) {
+                    if (notifAnimationRunning) {
+                        // If the notification launch animation is running, leave the alpha at 0f.
+                        // The ActivityLaunchAnimator will morph it from the notification at the
+                        // appropriate time.
+                        return@combine KeyguardSurfaceBehindModel(
+                            alpha = 0f,
+                        )
+                    } else if (
+                        inWindowLauncherUnlockAnimationInteractor.get().isLauncherUnderneath()
+                    ) {
                         // The Launcher icons have their own translation/alpha animations during the
                         // in-window animation. We'll just make the surface visible and let Launcher
                         // do its thing.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index b43ab5e..310f13d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -60,6 +60,7 @@
     private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
     private val fromPrimaryBouncerTransitionInteractor:
         dagger.Lazy<FromPrimaryBouncerTransitionInteractor>,
+    private val fromAodTransitionInteractor: dagger.Lazy<FromAodTransitionInteractor>,
 ) {
     private val TAG = this::class.simpleName
 
@@ -346,6 +347,7 @@
         when (val startedState = startedKeyguardState.replayCache.last()) {
             LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
             PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
+            AOD -> fromAodTransitionInteractor.get().dismissAod()
             else ->
                 Log.e(
                     "KeyguardTransitionInteractor",
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 19d00cf..c7f262a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -42,7 +42,7 @@
     private val lightRevealScrimRepository: LightRevealScrimRepository,
     @Application private val scope: CoroutineScope,
     private val scrimLogger: ScrimLogger,
-    powerInteractor: PowerInteractor,
+    private val powerInteractor: PowerInteractor,
 ) {
 
     init {
@@ -83,11 +83,13 @@
             // (invisible) jank. However, we need to still pass through 1f and 0f to ensure that the
             // correct end states are respected even if the screen turned off (or was still off)
             // when the animation finished
-            powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_OFF ||
-                it == 1f ||
-                it == 0f
+            screenIsShowingContent() || it == 1f || it == 0f
         }
 
+    private fun screenIsShowingContent() =
+        powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_OFF &&
+            powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_TURNING_ON
+
     companion object {
 
         /**
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 5c2df45..3ccbdba 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
@@ -60,6 +60,7 @@
     // The following are MutableSharedFlows, and do not require flowOn
     val startedKeyguardState = transitionInteractor.startedKeyguardState
     val finishedKeyguardState = transitionInteractor.finishedKeyguardState
+    val currentKeyguardState = transitionInteractor.currentKeyguardState
 
     suspend fun startTransitionTo(
         toState: KeyguardState,
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 49af664..b81793e 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
@@ -19,6 +19,8 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
+import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -26,7 +28,6 @@
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
-import javax.inject.Inject
 
 @SysUISingleton
 class WindowManagerLockscreenVisibilityInteractor
@@ -37,6 +38,7 @@
     surfaceBehindInteractor: KeyguardSurfaceBehindInteractor,
     fromLockscreenInteractor: FromLockscreenTransitionInteractor,
     fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
+    notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
 ) {
     private val defaultSurfaceBehindVisibility =
         transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible)
@@ -72,8 +74,7 @@
      */
     @OptIn(ExperimentalCoroutinesApi::class)
     val surfaceBehindVisibility: Flow<Boolean> =
-        transitionInteractor
-                .isInTransitionToAnyState
+        transitionInteractor.isInTransitionToAnyState
             .flatMapLatest { isInTransition ->
                 if (!isInTransition) {
                     defaultSurfaceBehindVisibility
@@ -99,12 +100,16 @@
         combine(
                 transitionInteractor.isInTransitionToState(KeyguardState.GONE),
                 transitionInteractor.finishedKeyguardState,
-                surfaceBehindInteractor.isAnimatingSurface
-            ) { isInTransitionToGone, finishedState, isAnimatingSurface ->
+                surfaceBehindInteractor.isAnimatingSurface,
+                notificationLaunchAnimationInteractor.isLaunchAnimationRunning,
+            ) { isInTransitionToGone, finishedState, isAnimatingSurface, notifLaunchRunning ->
+                // Using the animation if we're animating it directly, or if the
+                // ActivityLaunchAnimator is in the process of animating it.
+                val animationsRunning = isAnimatingSurface || notifLaunchRunning
                 // We may still be animating the surface after the keyguard is fully GONE, since
                 // some animations (like the translation spring) are not tied directly to the
                 // transition step amount.
-                isInTransitionToGone || (finishedState == KeyguardState.GONE && isAnimatingSurface)
+                isInTransitionToGone || (finishedState == KeyguardState.GONE && animationsRunning)
             }
             .distinctUntilChanged()
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 00b7989..8b278cd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -112,6 +112,38 @@
             interpolator: Interpolator = LINEAR,
             name: String? = null
         ): Flow<Float> {
+            return sharedFlowWithState(
+                    duration = duration,
+                    onStep = onStep,
+                    startTime = startTime,
+                    onStart = onStart,
+                    onCancel = onCancel,
+                    onFinish = onFinish,
+                    interpolator = interpolator,
+                    name = name,
+                )
+                .mapNotNull { stateToValue -> stateToValue.value }
+        }
+
+        /**
+         * Transitions will occur over a [transitionDuration] with [TransitionStep]s being emitted
+         * in the range of [0, 1]. View animations should begin and end within a subset of this
+         * range. This function maps the [startTime] and [duration] into [0, 1], when this subset is
+         * valid.
+         *
+         * Will return a [StateToValue], which encompasses the calculated value as well as the
+         * transitionState that is associated with it.
+         */
+        fun sharedFlowWithState(
+            duration: Duration,
+            onStep: (Float) -> Float,
+            startTime: Duration = 0.milliseconds,
+            onStart: (() -> Unit)? = null,
+            onCancel: (() -> Float)? = null,
+            onFinish: (() -> Float)? = null,
+            interpolator: Interpolator = LINEAR,
+            name: String? = null
+        ): Flow<StateToValue> {
             if (!duration.isPositive()) {
                 throw IllegalArgumentException("duration must be a positive number: $duration")
             }
@@ -164,7 +196,6 @@
                         .also { logger.logTransitionStep(name, step, it.value) }
                 }
                 .distinctUntilChanged()
-                .mapNotNull { stateToValue -> stateToValue.value }
         }
 
         /**
@@ -174,9 +205,9 @@
             return sharedFlow(duration = 1.milliseconds, onStep = { value }, onFinish = { value })
         }
     }
-
-    data class StateToValue(
-        val transitionState: TransitionState,
-        val value: Float?,
-    )
 }
+
+data class StateToValue(
+    val transitionState: TransitionState = TransitionState.FINISHED,
+    val value: Float? = 0f,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index f0e89f9..62a6e8b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -18,7 +18,6 @@
 
 import android.transition.TransitionManager
 import android.transition.TransitionSet
-import android.util.Log
 import android.view.View.INVISIBLE
 import androidx.annotation.VisibleForTesting
 import androidx.constraintlayout.helper.widget.Layer
@@ -36,7 +35,6 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.clocks.ClockController
-import com.android.systemui.res.R
 import com.android.systemui.shared.clocks.DEFAULT_CLOCK_ID
 import kotlinx.coroutines.launch
 
@@ -78,10 +76,6 @@
                 launch {
                     if (!migrateClocksToBlueprint()) return@launch
                     viewModel.clockShouldBeCentered.collect { clockShouldBeCentered ->
-                        Log.d(
-                            "ClockViewBinder",
-                            "Sherry clockShouldBeCentered $clockShouldBeCentered"
-                        )
                         viewModel.clock?.let {
                             // Weather clock also has hasCustomPositionUpdatedAnimation as true
                             // TODO(b/323020908): remove ID check
@@ -169,16 +163,12 @@
         rootView: ConstraintLayout,
     ) {
         clockController?.let { clock ->
-            clock.smallClock.layout.views[0].id = R.id.lockscreen_clock_view
-            if (clock.largeClock.layout.views.size == 1) {
-                clock.largeClock.layout.views[0].id = R.id.lockscreen_clock_view_large
-            }
-            // small clock should either be a single view or container with id
-            // `lockscreen_clock_view`
             clock.smallClock.layout.views.forEach {
                 rootView.addView(it).apply { it.visibility = INVISIBLE }
             }
-            clock.largeClock.layout.views.forEach { rootView.addView(it) }
+            clock.largeClock.layout.views.forEach {
+                rootView.addView(it).apply { it.visibility = INVISIBLE }
+            }
         }
     }
     fun applyConstraints(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index 1b5b329..b56c998 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -17,12 +17,35 @@
 
 package com.android.systemui.keyguard.ui.binder
 
+import android.content.Context
 import android.view.View
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
 import androidx.core.view.isVisible
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.keyguard.ClockEventController
+import com.android.systemui.customization.R as customizationR
+import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRenderer
+import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection.Companion.getDimen
+import com.android.systemui.keyguard.ui.view.layout.sections.setVisibility
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.res.R
+import com.android.systemui.util.Utils
+import kotlin.reflect.KSuspendFunction1
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
 
 /** Binder for the small clock view, large clock view. */
 object KeyguardPreviewClockViewBinder {
@@ -45,4 +68,129 @@
             }
         }
     }
+
+    @JvmStatic
+    fun bind(
+        context: Context,
+        rootView: ConstraintLayout,
+        viewModel: KeyguardPreviewClockViewModel,
+        clockEventController: ClockEventController,
+        updateClockAppearance: KSuspendFunction1<ClockController, Unit>
+    ) {
+        rootView.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    combine(viewModel.selectedClockSize, viewModel.previewClock) { _, clock ->
+                            clock
+                        }
+                        .collect { previewClock ->
+                            viewModel.lastClock?.let { lastClock ->
+                                (lastClock.largeClock.layout.views +
+                                        lastClock.smallClock.layout.views)
+                                    .forEach { rootView.removeView(it) }
+                            }
+                            viewModel.lastClock = previewClock
+                            clockEventController.clock = previewClock
+                            updateClockAppearance(previewClock)
+
+                            if (viewModel.shouldHighlightSelectedAffordance) {
+                                (previewClock.largeClock.layout.views +
+                                        previewClock.smallClock.layout.views)
+                                    .forEach { it.alpha = KeyguardPreviewRenderer.DIM_ALPHA }
+                            }
+                            previewClock.largeClock.layout.views.forEach {
+                                (it.parent as? ViewGroup)?.removeView(it)
+                                rootView.addView(it)
+                            }
+
+                            previewClock.smallClock.layout.views.forEach {
+                                (it.parent as? ViewGroup)?.removeView(it)
+                                rootView.addView(it)
+                            }
+                            applyPreviewConstraints(context, rootView, viewModel)
+                        }
+                }
+            }
+        }
+    }
+
+    private fun applyClockDefaultConstraints(context: Context, constraints: ConstraintSet) {
+        constraints.apply {
+            constrainWidth(R.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT)
+            constrainHeight(R.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT)
+            val largeClockTopMargin =
+                context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
+                    context.resources.getDimensionPixelSize(
+                        customizationR.dimen.small_clock_padding_top
+                    ) +
+                    context.resources.getDimensionPixelSize(
+                        R.dimen.keyguard_smartspace_top_offset
+                    ) +
+                    getDimen(context, DATE_WEATHER_VIEW_HEIGHT) +
+                    getDimen(context, ENHANCED_SMARTSPACE_HEIGHT)
+            connect(R.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
+            connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START)
+            connect(
+                R.id.lockscreen_clock_view_large,
+                ConstraintSet.END,
+                PARENT_ID,
+                ConstraintSet.END
+            )
+
+            connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.lock_icon_view, TOP)
+            constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT)
+            constrainHeight(
+                R.id.lockscreen_clock_view,
+                context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height)
+            )
+            connect(
+                R.id.lockscreen_clock_view,
+                START,
+                PARENT_ID,
+                START,
+                context.resources.getDimensionPixelSize(customizationR.dimen.clock_padding_start) +
+                    context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+            )
+            val smallClockTopMargin =
+                context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
+                    Utils.getStatusBarHeaderHeightKeyguard(context)
+            connect(R.id.lockscreen_clock_view, TOP, PARENT_ID, TOP, smallClockTopMargin)
+        }
+    }
+
+    private fun applyPreviewConstraints(
+        context: Context,
+        rootView: ConstraintLayout,
+        viewModel: KeyguardPreviewClockViewModel
+    ) {
+        val cs = ConstraintSet().apply { clone(rootView) }
+        val clock = viewModel.previewClock.value
+        applyClockDefaultConstraints(context, cs)
+        clock.largeClock.layout.applyPreviewConstraints(cs)
+        clock.smallClock.layout.applyPreviewConstraints(cs)
+
+        // When selectedClockSize is the initial value, make both clocks invisible to avoid
+        // flickering
+        val largeClockVisibility =
+            when (viewModel.selectedClockSize.value) {
+                SettingsClockSize.DYNAMIC -> VISIBLE
+                SettingsClockSize.SMALL -> INVISIBLE
+                null -> INVISIBLE
+            }
+        val smallClockVisibility =
+            when (viewModel.selectedClockSize.value) {
+                SettingsClockSize.DYNAMIC -> INVISIBLE
+                SettingsClockSize.SMALL -> VISIBLE
+                null -> INVISIBLE
+            }
+
+        cs.apply {
+            setVisibility(clock.largeClock.layout.views, largeClockVisibility)
+            setVisibility(clock.smallClock.layout.views, smallClockVisibility)
+        }
+        cs.applyTo(rootView)
+    }
+
+    private const val DATE_WEATHER_VIEW_HEIGHT = "date_weather_view_height"
+    private const val ENHANCED_SMARTSPACE_HEIGHT = "enhanced_smartspace_height"
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 789d30f..9e7c70d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -145,9 +145,7 @@
                         launch {
                             viewModel.burnInLayerVisibility.collect { visibility ->
                                 childViews[burnInLayerId]?.visibility = visibility
-                                // Reset alpha only for the icons, as they currently have their
-                                // own animator
-                                childViews[aodNotificationIconContainerId]?.alpha = 0f
+                                childViews[aodNotificationIconContainerId]?.visibility = visibility
                             }
                         }
 
@@ -313,6 +311,12 @@
             }
         }
 
+        if (KeyguardShadeMigrationNssl.isEnabled) {
+            burnInParams.update { current ->
+                current.copy(translationY = { childViews[burnInLayerId]?.translationY })
+            }
+        }
+
         onLayoutChangeListener = OnLayoutChange(viewModel, burnInParams)
         view.addOnLayoutChangeListener(onLayoutChangeListener)
 
@@ -435,11 +439,17 @@
             }
         when {
             !isVisible.isAnimating -> {
-                alpha = 1f
                 if (!KeyguardShadeMigrationNssl.isEnabled) {
                     translationY = 0f
                 }
-                visibility = if (isVisible.value) View.VISIBLE else View.INVISIBLE
+                visibility =
+                    if (isVisible.value) {
+                        alpha = 1f
+                        View.VISIBLE
+                    } else {
+                        alpha = 0f
+                        View.INVISIBLE
+                    }
             }
             newAodTransition() -> {
                 animateInIconTranslation()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index a0c0095..c14917b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -46,6 +46,7 @@
 import com.android.keyguard.ClockEventController
 import com.android.keyguard.KeyguardClockSwitch
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
+import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.animation.view.LaunchableImageView
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.broadcast.BroadcastDispatcher
@@ -197,6 +198,9 @@
                 shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
             )
         }
+        if (migrateClocksToBlueprint()) {
+            clockViewModel.shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance
+        }
         runBlocking(mainDispatcher) {
             host =
                 SurfaceControlViewHost(
@@ -396,11 +400,21 @@
 
         if (!shouldHideClock) {
             setUpClock(previewContext, rootView)
-            KeyguardPreviewClockViewBinder.bind(
-                largeClockHostView,
-                smallClockHostView,
-                clockViewModel,
-            )
+            if (migrateClocksToBlueprint()) {
+                KeyguardPreviewClockViewBinder.bind(
+                    context,
+                    keyguardRootView,
+                    clockViewModel,
+                    clockController,
+                    ::updateClockAppearance
+                )
+            } else {
+                KeyguardPreviewClockViewBinder.bind(
+                    largeClockHostView,
+                    smallClockHostView,
+                    clockViewModel,
+                )
+            }
         }
 
         setUpSmartspace(previewContext, rootView)
@@ -474,55 +488,61 @@
 
     private fun setUpClock(previewContext: Context, parentView: ViewGroup) {
         val resources = parentView.resources
-        largeClockHostView = FrameLayout(previewContext)
-        largeClockHostView.layoutParams =
-            FrameLayout.LayoutParams(
-                FrameLayout.LayoutParams.MATCH_PARENT,
-                FrameLayout.LayoutParams.MATCH_PARENT,
-            )
-        parentView.addView(largeClockHostView)
-        largeClockHostView.isInvisible = true
+        if (!migrateClocksToBlueprint()) {
+            largeClockHostView = FrameLayout(previewContext)
+            largeClockHostView.layoutParams =
+                FrameLayout.LayoutParams(
+                    FrameLayout.LayoutParams.MATCH_PARENT,
+                    FrameLayout.LayoutParams.MATCH_PARENT,
+                )
+            largeClockHostView.isInvisible = true
+            parentView.addView(largeClockHostView)
 
-        smallClockHostView = FrameLayout(previewContext)
-        val layoutParams =
-            FrameLayout.LayoutParams(
-                FrameLayout.LayoutParams.WRAP_CONTENT,
-                resources.getDimensionPixelSize(
-                    com.android.systemui.customization.R.dimen.small_clock_height
+            smallClockHostView = FrameLayout(previewContext)
+            val layoutParams =
+                FrameLayout.LayoutParams(
+                    FrameLayout.LayoutParams.WRAP_CONTENT,
+                    resources.getDimensionPixelSize(
+                        com.android.systemui.customization.R.dimen.small_clock_height
+                    )
                 )
+            layoutParams.topMargin =
+                KeyguardPreviewSmartspaceViewModel.getStatusBarHeight(resources) +
+                    resources.getDimensionPixelSize(
+                        com.android.systemui.customization.R.dimen.small_clock_padding_top
+                    )
+            smallClockHostView.layoutParams = layoutParams
+            smallClockHostView.setPaddingRelative(
+                /* start = */ resources.getDimensionPixelSize(
+                    com.android.systemui.customization.R.dimen.clock_padding_start
+                ),
+                /* top = */ 0,
+                /* end = */ 0,
+                /* bottom = */ 0
             )
-        layoutParams.topMargin =
-            KeyguardPreviewSmartspaceViewModel.getStatusBarHeight(resources) +
-                resources.getDimensionPixelSize(
-                    com.android.systemui.customization.R.dimen.small_clock_padding_top
-                )
-        smallClockHostView.layoutParams = layoutParams
-        smallClockHostView.setPaddingRelative(
-            resources.getDimensionPixelSize(
-                com.android.systemui.customization.R.dimen.clock_padding_start
-            ),
-            0,
-            0,
-            0
-        )
-        smallClockHostView.clipChildren = false
-        parentView.addView(smallClockHostView)
-        smallClockHostView.isInvisible = true
+            smallClockHostView.clipChildren = false
+            parentView.addView(smallClockHostView)
+            smallClockHostView.isInvisible = true
+        }
 
         // TODO (b/283465254): Move the listeners to KeyguardClockRepository
-        val clockChangeListener =
-            object : ClockRegistry.ClockChangeListener {
-                override fun onCurrentClockChanged() {
-                    onClockChanged()
+        if (!migrateClocksToBlueprint()) {
+            val clockChangeListener =
+                object : ClockRegistry.ClockChangeListener {
+                    override fun onCurrentClockChanged() {
+                        onClockChanged()
+                    }
                 }
-            }
-        clockRegistry.registerClockChangeListener(clockChangeListener)
-        disposables.add(
-            DisposableHandle { clockRegistry.unregisterClockChangeListener(clockChangeListener) }
-        )
+            clockRegistry.registerClockChangeListener(clockChangeListener)
+            disposables.add(
+                DisposableHandle {
+                    clockRegistry.unregisterClockChangeListener(clockChangeListener)
+                }
+            )
 
-        clockController.registerListeners(parentView)
-        disposables.add(DisposableHandle { clockController.unregisterListeners() })
+            clockController.registerListeners(parentView)
+            disposables.add(DisposableHandle { clockController.unregisterListeners() })
+        }
 
         val receiver =
             object : BroadcastReceiver() {
@@ -542,50 +562,61 @@
         )
         disposables.add(DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) })
 
-        val layoutChangeListener =
-            View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
-                if (clockController.clock !is DefaultClockController) {
-                    clockController.clock
-                        ?.largeClock
-                        ?.events
-                        ?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView))
-                    clockController.clock
-                        ?.smallClock
-                        ?.events
-                        ?.onTargetRegionChanged(KeyguardClockSwitch.getSmallClockRegion(parentView))
+        if (!migrateClocksToBlueprint()) {
+            val layoutChangeListener =
+                View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
+                    if (clockController.clock !is DefaultClockController) {
+                        clockController.clock
+                            ?.largeClock
+                            ?.events
+                            ?.onTargetRegionChanged(
+                                KeyguardClockSwitch.getLargeClockRegion(parentView)
+                            )
+                        clockController.clock
+                            ?.smallClock
+                            ?.events
+                            ?.onTargetRegionChanged(
+                                KeyguardClockSwitch.getSmallClockRegion(parentView)
+                            )
+                    }
                 }
-            }
-        parentView.addOnLayoutChangeListener(layoutChangeListener)
-        disposables.add(
-            DisposableHandle { parentView.removeOnLayoutChangeListener(layoutChangeListener) }
-        )
+            parentView.addOnLayoutChangeListener(layoutChangeListener)
+            disposables.add(
+                DisposableHandle { parentView.removeOnLayoutChangeListener(layoutChangeListener) }
+            )
+        }
 
         onClockChanged()
     }
 
+    private suspend fun updateClockAppearance(clock: ClockController) {
+        clockController.clock = clock
+        val colors = wallpaperColors
+        if (clockRegistry.seedColor == null && colors != null) {
+            // Seed color null means users do not override any color on the clock. The default
+            // color will need to use wallpaper's extracted color and consider if the
+            // wallpaper's color is dark or light.
+            val style = themeStyle ?: fetchThemeStyleFromSetting().also { themeStyle = it }
+            val wallpaperColorScheme = ColorScheme(colors, darkTheme = false, style)
+            val lightClockColor = wallpaperColorScheme.accent1.s100
+            val darkClockColor = wallpaperColorScheme.accent2.s600
+
+            // Note that when [wallpaperColors] is null, isWallpaperDark is true.
+            val isWallpaperDark: Boolean =
+                (colors.colorHints.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) == 0
+            clock.events.onSeedColorChanged(
+                if (isWallpaperDark) lightClockColor else darkClockColor
+            )
+        }
+    }
     private fun onClockChanged() {
+        if (migrateClocksToBlueprint()) {
+            return
+        }
         coroutineScope.launch {
             val clock = clockRegistry.createCurrentClock()
             clockController.clock = clock
-
-            val colors = wallpaperColors
-            if (clockRegistry.seedColor == null && colors != null) {
-                // Seed color null means users do not override any color on the clock. The default
-                // color will need to use wallpaper's extracted color and consider if the
-                // wallpaper's color is dark or light.
-                val style = themeStyle ?: fetchThemeStyleFromSetting().also { themeStyle = it }
-                val wallpaperColorScheme = ColorScheme(colors, darkTheme = false, style)
-                val lightClockColor = wallpaperColorScheme.accent1.s100
-                val darkClockColor = wallpaperColorScheme.accent2.s600
-
-                // Note that when [wallpaperColors] is null, isWallpaperDark is true.
-                val isWallpaperDark: Boolean =
-                    (colors.colorHints.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) == 0
-                clock.events.onSeedColorChanged(
-                    if (isWallpaperDark) lightClockColor else darkClockColor
-                )
-            }
-
+            updateClockAppearance(clock)
             updateLargeClock(clock)
             updateSmallClock(clock)
         }
@@ -626,6 +657,9 @@
     }
 
     private fun updateLargeClock(clock: ClockController) {
+        if (migrateClocksToBlueprint()) {
+            return
+        }
         clock.largeClock.events.onTargetRegionChanged(
             KeyguardClockSwitch.getLargeClockRegion(largeClockHostView)
         )
@@ -637,6 +671,9 @@
     }
 
     private fun updateSmallClock(clock: ClockController) {
+        if (migrateClocksToBlueprint()) {
+            return
+        }
         clock.smallClock.events.onTargetRegionChanged(
             KeyguardClockSwitch.getSmallClockRegion(smallClockHostView)
         )
@@ -656,6 +693,6 @@
         private const val KEY_DISPLAY_ID = "display_id"
         private const val KEY_COLORS = "wallpaper_colors"
 
-        private const val DIM_ALPHA = 0.3f
+        const val DIM_ALPHA = 0.3f
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index b1178f5..631b342 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -201,13 +201,16 @@
     }
 
     private fun getDimen(name: String): Int {
-        val res = context.packageManager.getResourcesForApplication(context.packageName)
-        val id = res.getIdentifier(name, "dimen", context.packageName)
-        return res.getDimensionPixelSize(id)
+        return getDimen(context, name)
     }
 
     companion object {
         private const val DATE_WEATHER_VIEW_HEIGHT = "date_weather_view_height"
         private const val ENHANCED_SMARTSPACE_HEIGHT = "enhanced_smartspace_height"
+        fun getDimen(context: Context, name: String): Int {
+            val res = context.packageManager.getResourcesForApplication(context.packageName)
+            val id = res.getIdentifier(name, "dimen", context.packageName)
+            return res.getDimensionPixelSize(id)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
index 3737e6f..d26356e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 
@@ -46,6 +47,14 @@
             to = KeyguardState.GONE,
         )
 
+    val lockscreenAlpha: Flow<Float> =
+        transitionAnimation.sharedFlow(
+            duration = 200.milliseconds,
+            onStep = { 1 - it },
+            onFinish = { 0f },
+            onCancel = { 1f },
+        )
+
     /** Scrim alpha values */
     val scrimAlpha: Flow<ScrimAlpha> =
         bouncerToGoneFlows.scrimAlpha(TO_GONE_DURATION, ALTERNATE_BOUNCER)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
index 9cf3c95..d4ea728 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
@@ -28,6 +28,7 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
 
 /** Models UI state for the alpha of the AOD (always-on display). */
 @SysUISingleton
@@ -42,13 +43,15 @@
     /** The alpha level for the entire lockscreen while in AOD. */
     val alpha: Flow<Float> =
         combine(
-                keyguardTransitionInteractor.currentKeyguardState,
+                keyguardTransitionInteractor.transitionValue(KeyguardState.GONE).onStart {
+                    emit(0f)
+                },
                 merge(
                     keyguardInteractor.keyguardAlpha,
                     occludedToLockscreenTransitionViewModel.lockscreenAlpha,
                 )
-            ) { currentKeyguardState, alpha ->
-                if (currentKeyguardState == KeyguardState.GONE) {
+            ) { transitionToGone, alpha ->
+                if (transitionToGone == 1f) {
                     // Ensures content is not visible when in GONE state
                     0f
                 } else {
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 780e323..8110de2 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
@@ -28,6 +28,13 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.ui.StateToValue
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.res.R
 import javax.inject.Inject
@@ -55,6 +62,7 @@
     private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
+    private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
     private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
     private val keyguardClockViewModel: KeyguardClockViewModel,
 ) {
@@ -80,21 +88,22 @@
                     burnIn(params).map { it.translationY.toFloat() }.onStart { emit(0f) },
                     goneToAodTransitionViewModel
                         .enterFromTopTranslationY(enterFromTopAmount)
-                        .onStart { emit(0f) },
+                        .onStart { emit(StateToValue()) },
                     occludedToLockscreenTransitionViewModel.lockscreenTranslationY.onStart {
                         emit(0f)
                     },
-                ) {
-                    keyguardTransitionY,
-                    burnInTranslationY,
-                    goneToAodTransitionTranslationY,
-                    occludedToLockscreenTransitionTranslationY ->
-
-                    // All values need to be combined for a smooth translation
-                    keyguardTransitionY +
-                        burnInTranslationY +
-                        goneToAodTransitionTranslationY +
-                        occludedToLockscreenTransitionTranslationY
+                    aodToLockscreenTransitionViewModel.translationY(params.translationY).onStart {
+                        emit(StateToValue())
+                    },
+                ) { keyguardTranslationY, burnInY, goneToAod, occludedToLockscreen, aodToLockscreen
+                    ->
+                    if (isInTransition(aodToLockscreen.transitionState)) {
+                        aodToLockscreen.value ?: 0f
+                    } else if (isInTransition(goneToAod.transitionState)) {
+                        (goneToAod.value ?: 0f) + burnInY
+                    } else {
+                        burnInY + occludedToLockscreen + keyguardTranslationY
+                    }
                 }
             }
             .distinctUntilChanged()
@@ -112,12 +121,19 @@
         }
     }
 
+    private fun isInTransition(state: TransitionState): Boolean {
+        return state == STARTED || state == RUNNING
+    }
+
     private fun burnIn(
         params: BurnInParameters,
     ): Flow<BurnInModel> {
         return combine(
             merge(
-                    keyguardTransitionInteractor.goneToAodTransition.map { it.value },
+                    keyguardTransitionInteractor.transition(GONE, AOD).map { it.value },
+                    keyguardTransitionInteractor.transition(ALTERNATE_BOUNCER, AOD).map {
+                        it.value
+                    },
                     keyguardTransitionInteractor.dozeAmountTransition.map { it.value },
                 )
                 .map { dozeAmount -> Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount) },
@@ -179,6 +195,8 @@
     val topInset: Int = 0,
     /** Status view top, without translation added in */
     val statusViewTop: Int = 0,
+    /** The current y translation of the view */
+    val translationY: () -> Float? = { null }
 )
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
index 266fd02..6d1d3cb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
@@ -16,11 +16,14 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.util.MathUtils
+import com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
 import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.StateToValue
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -48,6 +51,22 @@
             to = KeyguardState.LOCKSCREEN,
         )
 
+    /**
+     * Begin the transition from wherever the y-translation value is currently. This helps ensure a
+     * smooth transition if a transition in canceled.
+     */
+    fun translationY(currentTranslationY: () -> Float?): Flow<StateToValue> {
+        var startValue = 0f
+        return transitionAnimation.sharedFlowWithState(
+            duration = 500.milliseconds,
+            onStart = {
+                startValue = currentTranslationY() ?: 0f
+                startValue
+            },
+            onStep = { MathUtils.lerp(startValue, 0f, FAST_OUT_SLOW_IN.getInterpolation(it)) },
+        )
+    }
+
     /** Ensure alpha is set to be visible */
     val lockscreenAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
index f5e6135..85885b0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_AOD_DURATION
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.StateToValue
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -48,8 +49,8 @@
         )
 
     /** y-translation from the top of the screen for AOD */
-    fun enterFromTopTranslationY(translatePx: Int): Flow<Float> {
-        return transitionAnimation.sharedFlow(
+    fun enterFromTopTranslationY(translatePx: Int): Flow<StateToValue> {
+        return transitionAnimation.sharedFlowWithState(
             startTime = 600.milliseconds,
             duration = 500.milliseconds,
             onStart = { translatePx },
@@ -63,8 +64,8 @@
     /** alpha animation upon entering AOD */
     val enterFromTopAnimationAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
-            startTime = 600.milliseconds,
-            duration = 500.milliseconds,
+            startTime = 700.milliseconds,
+            duration = 400.milliseconds,
             onStart = { 0f },
             onStep = { it },
             onFinish = { 1f },
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt
index 5301302..f95a8a7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockViewModel.kt
@@ -20,9 +20,14 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.plugins.clocks.ClockController
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
 
 /** View model for the small clock view, large clock view. */
 class KeyguardPreviewClockViewModel
@@ -30,11 +35,24 @@
 constructor(
     @Application private val context: Context,
     interactor: KeyguardClockInteractor,
+    @Application private val applicationScope: CoroutineScope,
 ) {
 
+    var shouldHighlightSelectedAffordance: Boolean = false
     val isLargeClockVisible: Flow<Boolean> =
         interactor.selectedClockSize.map { it == SettingsClockSize.DYNAMIC }
 
     val isSmallClockVisible: Flow<Boolean> =
         interactor.selectedClockSize.map { it == SettingsClockSize.SMALL }
+
+    var lastClock: ClockController? = null
+
+    val previewClock: StateFlow<ClockController> = interactor.previewClock
+
+    val selectedClockSize: StateFlow<SettingsClockSize?> =
+        interactor.selectedClockSize.stateIn(
+            scope = applicationScope,
+            started = SharingStarted.WhileSubscribed(),
+            initialValue = null
+        )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 709e184..ec13228 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -30,6 +30,8 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
@@ -48,6 +50,7 @@
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
@@ -61,6 +64,9 @@
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
     aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+    lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel,
+    alternateBouncerToGoneTransitionViewModel: AlternateBouncerToGoneTransitionViewModel,
+    primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
     lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
     glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
     screenOffAnimationController: ScreenOffAnimationController,
@@ -75,6 +81,12 @@
 
     val goneToAodTransition = keyguardTransitionInteractor.transition(from = GONE, to = AOD)
 
+    private val goneToAodTransitionRunning: Flow<Boolean> =
+        goneToAodTransition
+            .map { it.transitionState == STARTED || it.transitionState == RUNNING }
+            .onStart { emit(false) }
+            .distinctUntilChanged()
+
     /** Last point that the root view was tapped */
     val lastRootViewTapPosition: Flow<Point?> = keyguardInteractor.lastRootViewTapPosition
 
@@ -92,10 +104,15 @@
     val alpha: Flow<Float> =
         combine(
                 communalInteractor.isIdleOnCommunal,
+                // The transitions are mutually exclusive, so they are safe to merge to get the last
+                // value emitted by any of them. Do not add flows that cannot make this guarantee.
                 merge(
                     aodAlphaViewModel.alpha,
                     lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha,
                     glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
+                    lockscreenToGoneTransitionViewModel.lockscreenAlpha,
+                    primaryBouncerToGoneTransitionViewModel.lockscreenAlpha,
+                    alternateBouncerToGoneTransitionViewModel.lockscreenAlpha,
                 )
             ) { isIdleOnCommunal, alpha ->
                 if (isIdleOnCommunal) {
@@ -130,6 +147,7 @@
     /** Is the notification icon container visible? */
     val isNotifIconContainerVisible: Flow<AnimatedValue<Boolean>> =
         combine(
+                goneToAodTransitionRunning,
                 keyguardTransitionInteractor.finishedKeyguardState.map {
                     KeyguardState.lockscreenVisibleInState(it)
                 },
@@ -137,6 +155,7 @@
                 areNotifsFullyHiddenAnimated(),
                 isPulseExpandingAnimated(),
             ) {
+                goneToAodTransitionRunning: Boolean,
                 onKeyguard: Boolean,
                 isBypassEnabled: Boolean,
                 notifsFullyHidden: AnimatedValue<Boolean>,
@@ -146,7 +165,9 @@
                     // Hide the AOD icons if we're not in the KEYGUARD state unless the screen off
                     // animation is playing, in which case we want them to be visible if we're
                     // animating in the AOD UI and will be switching to KEYGUARD shortly.
-                    !onKeyguard && !screenOffAnimationController.shouldShowAodIconsWhenShade() ->
+                    goneToAodTransitionRunning ||
+                        (!onKeyguard &&
+                            !screenOffAnimationController.shouldShowAodIconsWhenShade()) ->
                         AnimatedValue.NotAnimating(false)
                     else ->
                         zip(notifsFullyHidden, pulseExpanding) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
index a26ef07..d981650 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
@@ -46,12 +46,14 @@
 
     val shortcutsAlpha: Flow<Float> =
         transitionAnimation.sharedFlow(
-            duration = 250.milliseconds,
+            duration = 200.milliseconds,
             onStep = { 1 - it },
             onFinish = { 0f },
             onCancel = { 1f },
         )
 
+    val lockscreenAlpha: Flow<Float> = shortcutsAlpha
+
     override val deviceEntryParentViewAlpha: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(0f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index 7a488365..3ab0420 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -16,9 +16,6 @@
 
 package com.android.systemui.media;
 
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -37,8 +34,6 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
-import android.os.VibrationEffect;
-import android.os.vibrator.Flags;
 import android.provider.MediaStore;
 import android.util.Log;
 
@@ -58,7 +53,7 @@
 @SysUISingleton
 public class RingtonePlayer implements CoreStartable {
     private static final String TAG = "RingtonePlayer";
-    private static final boolean LOGD = true;
+    private static final boolean LOGD = false;
     private final Context mContext;
 
     // TODO: support Uri switching under same IBinder
@@ -91,11 +86,20 @@
      */
     private class Client implements IBinder.DeathRecipient {
         private final IBinder mToken;
-        private Ringtone mRingtone;
+        private final Ringtone mRingtone;
 
-        Client(@NonNull IBinder token, @NonNull Ringtone ringtone) {
-            mToken = requireNonNull(token);
-            mRingtone = requireNonNull(ringtone);
+        public Client(IBinder token, Uri uri, UserHandle user, AudioAttributes aa) {
+            this(token, uri, user, aa, null);
+        }
+
+        Client(IBinder token, Uri uri, UserHandle user, AudioAttributes aa,
+                @Nullable VolumeShaper.Configuration volumeShaperConfig) {
+            mToken = token;
+
+            mRingtone = new Ringtone(getContextForUser(user), false);
+            mRingtone.setAudioAttributesField(aa);
+            mRingtone.setUri(uri, volumeShaperConfig);
+            mRingtone.createLocalMediaPlayer();
         }
 
         @Override
@@ -112,48 +116,24 @@
         @Override
         public void play(IBinder token, Uri uri, AudioAttributes aa, float volume, boolean looping)
                 throws RemoteException {
-            if (Flags.hapticsCustomizationRingtoneV2Enabled()) {
-                playRemoteRingtone(token, uri, aa, true, Ringtone.MEDIA_SOUND,
-                        null, volume, looping, /* hapticGenerator= */ false,
-                        null);
-            } else {
-                playWithVolumeShaping(token, uri, aa, volume, looping, null);
-            }
+            playWithVolumeShaping(token, uri, aa, volume, looping, null);
         }
-
         @Override
-        public void playWithVolumeShaping(
-                IBinder token, Uri uri, AudioAttributes aa, float volume,
+        public void playWithVolumeShaping(IBinder token, Uri uri, AudioAttributes aa, float volume,
                 boolean looping, @Nullable VolumeShaper.Configuration volumeShaperConfig)
                 throws RemoteException {
             if (LOGD) {
-                Log.d(TAG, "playWithVolumeShaping(token=" + token + ", uri=" + uri + ", uid="
+                Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid="
                         + Binder.getCallingUid() + ")");
             }
             Client client;
             synchronized (mClients) {
                 client = mClients.get(token);
-            }
-            // Don't hold the lock while constructing the ringtone, since it can be slow. The caller
-            // shouldn't call play on the same ringtone from 2 threads, so this shouldn't race and
-            // waste the build.
-            if (client == null) {
-                final UserHandle user = Binder.getCallingUserHandle();
-                Ringtone ringtone = Ringtone.createV1WithCustomAudioAttributes(
-                        getContextForUser(user), aa, uri, volumeShaperConfig,
-                        /* allowRemote= */ false);
-                synchronized (mClients) {
-                    client = mClients.get(token);
-                    if (client == null) {
-                        client = new Client(token, ringtone);
-                        token.linkToDeath(client, 0);
-                        mClients.put(token, client);
-                        ringtone = null;  // "owned" by the client now.
-                    }
-                }
-                // Clean up ringtone if it was abandoned (a client already existed).
-                if (ringtone != null) {
-                    ringtone.stop();
+                if (client == null) {
+                    final UserHandle user = Binder.getCallingUserHandle();
+                    client = new Client(token, uri, user, aa, volumeShaperConfig);
+                    token.linkToDeath(client, 0);
+                    mClients.put(token, client);
                 }
             }
             client.mRingtone.setLooping(looping);
@@ -162,54 +142,6 @@
         }
 
         @Override
-        public void playRemoteRingtone(IBinder token, Uri uri, AudioAttributes aa,
-                boolean useExactAudioAttributes,
-                @Ringtone.RingtoneMedia int enabledMedia, @Nullable VibrationEffect vibrationEffect,
-                float volume,
-                boolean looping, boolean isHapticGeneratorEnabled,
-                @Nullable VolumeShaper.Configuration volumeShaperConfig)
-                throws RemoteException {
-            if (LOGD) {
-                Log.d(TAG, "playRemoteRingtone(token=" + token + ", uri=" + uri + ", uid="
-                        + Binder.getCallingUid() + ")");
-            }
-
-            // Don't hold the lock while constructing the ringtone, since it can be slow. The caller
-            // shouldn't call play on the same ringtone from 2 threads, so this shouldn't race and
-            // waste the build.
-            Client client;
-            synchronized (mClients) {
-                client = mClients.get(token);
-            }
-            if (client == null) {
-                final UserHandle user = Binder.getCallingUserHandle();
-                Ringtone ringtone = new Ringtone.Builder(getContextForUser(user), enabledMedia, aa)
-                        .setLocalOnly()
-                        .setUri(uri)
-                        .setLooping(looping)
-                        .setInitialSoundVolume(volume)
-                        .setUseExactAudioAttributes(useExactAudioAttributes)
-                        .setEnableHapticGenerator(isHapticGeneratorEnabled)
-                        .setVibrationEffect(vibrationEffect)
-                        .setVolumeShaperConfig(volumeShaperConfig)
-                        .build();
-                if (ringtone == null) {
-                    return;
-                }
-                synchronized (mClients) {
-                    client = mClients.get(token);
-                    if (client == null) {
-                        client = new Client(token, ringtone);
-                        token.linkToDeath(client, 0);
-                        mClients.put(token, client);
-                    }
-                }
-            }
-            // Ensure the client is initialized outside the all-clients lock, as it can be slow.
-            client.mRingtone.play();
-        }
-
-        @Override
         public void stop(IBinder token) {
             if (LOGD) Log.d(TAG, "stop(token=" + token + ")");
             Client client;
@@ -235,10 +167,10 @@
                 return false;
             }
         }
+
         @Override
         public void setPlaybackProperties(IBinder token, float volume, boolean looping,
-                                          boolean hapticGeneratorEnabled) {
-            // RingtoneV1-exclusive path.
+                boolean hapticGeneratorEnabled) {
             Client client;
             synchronized (mClients) {
                 client = mClients.get(token);
@@ -252,39 +184,6 @@
         }
 
         @Override
-        public void setHapticGeneratorEnabled(IBinder token, boolean hapticGeneratorEnabled) {
-            Client client;
-            synchronized (mClients) {
-                client = mClients.get(token);
-            }
-            if (client != null) {
-                client.mRingtone.setHapticGeneratorEnabled(hapticGeneratorEnabled);
-            }
-        }
-
-        @Override
-        public void setLooping(IBinder token, boolean looping) {
-            Client client;
-            synchronized (mClients) {
-                client = mClients.get(token);
-            }
-            if (client != null) {
-                client.mRingtone.setLooping(looping);
-            }
-        }
-
-        @Override
-        public void setVolume(IBinder token, float volume) {
-            Client client;
-            synchronized (mClients) {
-                client = mClients.get(token);
-            }
-            if (client != null) {
-                client.mRingtone.setVolume(volume);
-            }
-        }
-
-        @Override
         public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa,
                 float volume) {
             if (LOGD) Log.d(TAG, "playAsync(uri=" + uri + ", user=" + user + ")");
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
index 3f4f347..8d918e7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
@@ -22,9 +22,12 @@
 import androidx.annotation.UiThread
 import androidx.lifecycle.Observer
 import com.android.app.animation.Interpolators
+import com.android.app.tracing.TraceStateLogger
 import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.res.R
 import com.android.systemui.media.controls.ui.SquigglyProgress
+import com.android.systemui.res.R
+
+private const val TAG = "SeekBarObserver"
 
 /**
  * Observer for changes from SeekBarViewModel.
@@ -39,6 +42,10 @@
         @JvmStatic val RESET_ANIMATION_THRESHOLD_MS: Int = 250
     }
 
+    // Trace state loggers for playing and listening states of progress bar.
+    private val playingStateLogger = TraceStateLogger("$TAG#playing")
+    private val listeningStateLogger = TraceStateLogger("$TAG#listening")
+
     val seekBarEnabledMaxHeight =
         holder.seekBar.context.resources.getDimensionPixelSize(
             R.dimen.qs_media_enabled_seekbar_height
@@ -103,9 +110,13 @@
             return
         }
 
+        playingStateLogger.log("${data.playing}")
+        listeningStateLogger.log("${data.listening}")
+
         holder.seekBar.thumb.alpha = if (data.seekAvailable) 255 else 0
         holder.seekBar.isEnabled = data.seekAvailable
-        progressDrawable?.animate = data.playing && !data.scrubbing && animationEnabled
+        progressDrawable?.animate =
+            data.playing && !data.scrubbing && animationEnabled && data.listening
         progressDrawable?.transitionEnabled = !data.seekAvailable
 
         if (holder.seekBar.maxHeight != seekBarEnabledMaxHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
index a91917a..40a9b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
@@ -84,7 +84,16 @@
     @Background private val bgExecutor: RepeatableExecutor,
     private val falsingManager: FalsingManager,
 ) {
-    private var _data = Progress(false, false, false, false, null, 0)
+    private var _data =
+        Progress(
+            enabled = false,
+            seekAvailable = false,
+            playing = false,
+            scrubbing = false,
+            elapsedTime = null,
+            duration = 0,
+            listening = false
+        )
         set(value) {
             val enabledChanged = value.enabled != field.enabled
             field = value
@@ -239,7 +248,7 @@
             )
                 false
             else true
-        _data = Progress(enabled, seekAvailable, playing, scrubbing, position, duration)
+        _data = Progress(enabled, seekAvailable, playing, scrubbing, position, duration, listening)
         checkIfPollingNeeded()
     }
 
@@ -258,6 +267,7 @@
                 scrubbing = false,
                 elapsedTime = position,
                 duration = 100,
+                listening = false,
             )
     }
 
@@ -548,9 +558,12 @@
     data class Progress(
         val enabled: Boolean,
         val seekAvailable: Boolean,
+        /** whether playback state is not paused or connecting */
         val playing: Boolean,
         val scrubbing: Boolean,
         val elapsedTime: Int?,
-        val duration: Int
+        val duration: Int,
+        /** whether seekBar is listening to progress updates */
+        val listening: Boolean,
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
index 02f0d12..038582c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
@@ -26,14 +26,15 @@
 import androidx.core.view.GestureDetectorCompat
 import androidx.dynamicanimation.animation.FloatPropertyCompat
 import androidx.dynamicanimation.animation.SpringForce
+import com.android.app.tracing.TraceStateLogger
 import com.android.internal.annotations.VisibleForTesting
 import com.android.settingslib.Utils
 import com.android.systemui.Gefingerpoken
-import com.android.systemui.res.R
 import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
 import com.android.systemui.media.controls.util.MediaUiEventLogger
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.PageIndicator
+import com.android.systemui.res.R
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.wm.shell.animation.PhysicsAnimator
 
@@ -42,6 +43,7 @@
 private const val SCROLL_DELAY = 100L
 private const val RUBBERBAND_FACTOR = 0.2f
 private const val SETTINGS_BUTTON_TRANSLATION_FRACTION = 0.3f
+private const val TAG = "MediaCarouselScrollHandler"
 
 /**
  * Default spring configuration to use for animations where stiffness and/or damping ratio were not
@@ -63,6 +65,9 @@
     private val logSmartspaceImpression: (Boolean) -> Unit,
     private val logger: MediaUiEventLogger
 ) {
+    /** Trace state logger for media carousel visibility */
+    private val visibleStateLogger = TraceStateLogger("$TAG#visibleToUser")
+
     /** Is the view in RTL */
     val isRtl: Boolean
         get() = scrollView.isLayoutRtl
@@ -182,6 +187,7 @@
             if (field != value) {
                 field = value
                 seekBarUpdateListener.invoke(field)
+                visibleStateLogger.log("$visibleToUser")
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt
index 2ae3a63..e475647 100644
--- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt
@@ -16,13 +16,18 @@
 
 package com.android.systemui.media.muteawait
 
-import android.content.Context
+import android.annotation.SuppressLint
 import android.media.AudioAttributes.USAGE_MEDIA
 import android.media.AudioDeviceAttributes
 import android.media.AudioManager
+import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.commandline.Command
 import com.android.systemui.statusbar.commandline.CommandRegistry
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
 import java.io.PrintWriter
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
@@ -30,14 +35,15 @@
 /** A command line interface to manually test [MediaMuteAwaitConnectionManager]. */
 @SysUISingleton
 class MediaMuteAwaitConnectionCli @Inject constructor(
-    commandRegistry: CommandRegistry,
-    private val context: Context
-) {
-    init {
+    private val commandRegistry: CommandRegistry,
+    private val audioManager: AudioManager,
+) : CoreStartable {
+    override fun start() {
         commandRegistry.registerCommand(MEDIA_MUTE_AWAIT_COMMAND) { MuteAwaitCommand() }
     }
 
     inner class MuteAwaitCommand : Command {
+        @SuppressLint("MissingPermission")
         override fun execute(pw: PrintWriter, args: List<String>) {
             val device = AudioDeviceAttributes(
                 AudioDeviceAttributes.ROLE_OUTPUT,
@@ -49,8 +55,6 @@
             )
             val startOrCancel = args[2]
 
-            val audioManager: AudioManager =
-                context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
             when (startOrCancel) {
                 START ->
                     audioManager.muteAwaitConnection(
@@ -65,6 +69,14 @@
                     "[type] [name] [$START|$CANCEL]")
         }
     }
+
+    @Module
+    interface StartableModule {
+        @Binds
+        @IntoMap
+        @ClassKey(MediaMuteAwaitConnectionCli::class)
+        fun bindsMediaMuteAwaitConnectionCli(impl: MediaMuteAwaitConnectionCli): CoreStartable
+    }
 }
 
 private const val MEDIA_MUTE_AWAIT_COMMAND = "media-mute-await"
diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesManager.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesManager.kt
index 64b772b..0dc10f6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesManager.kt
@@ -18,9 +18,14 @@
 
 import android.media.INearbyMediaDevicesProvider
 import android.media.INearbyMediaDevicesUpdateCallback
-import com.android.systemui.dagger.SysUISingleton
 import android.os.IBinder
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.CommandQueue
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
 import javax.inject.Inject
 
 /**
@@ -30,9 +35,9 @@
  */
 @SysUISingleton
 class NearbyMediaDevicesManager @Inject constructor(
-    commandQueue: CommandQueue,
+    private val commandQueue: CommandQueue,
     private val logger: NearbyMediaDevicesLogger
-) {
+) : CoreStartable {
     private var providers: MutableList<INearbyMediaDevicesProvider> = mutableListOf()
     private var activeCallbacks: MutableList<INearbyMediaDevicesUpdateCallback> = mutableListOf()
 
@@ -69,7 +74,7 @@
         }
     }
 
-    init {
+    override fun start() {
         commandQueue.addCallback(commandQueueCallbacks)
     }
 
@@ -108,4 +113,12 @@
             }
         }
     }
+
+    @Module
+    interface StartableModule {
+        @Binds
+        @IntoMap
+        @ClassKey(NearbyMediaDevicesManager::class)
+        fun bindsNearbyMediaDevicesManager(impl: NearbyMediaDevicesManager): CoreStartable
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
index f1cade7..afe6285 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
@@ -16,6 +16,9 @@
 
 package com.android.systemui.mediaprojection
 
+import android.compat.annotation.ChangeId
+import android.compat.annotation.Disabled
+import android.compat.annotation.Overridable
 import android.content.Context
 import android.media.projection.IMediaProjection
 import android.media.projection.IMediaProjectionManager
@@ -31,6 +34,18 @@
  */
 class MediaProjectionServiceHelper {
     companion object {
+        /**
+         * This change id ensures that users are presented with a choice of capturing a single app
+         * or the entire screen when initiating a MediaProjection session, overriding the usage of
+         * MediaProjectionConfig#createConfigForDefaultDisplay.
+         *
+         * @hide
+         */
+        @ChangeId
+        @Overridable
+        @Disabled
+        const val OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION = 316897322L // buganizer id
+
         private const val TAG = "MediaProjectionServiceHelper"
         private val service =
             IMediaProjectionManager.Stub.asInterface(
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 8b034b2..0769731 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -16,21 +16,26 @@
 
 package com.android.systemui.mediaprojection.permission;
 
+import static android.Manifest.permission.LOG_COMPAT_CHANGE;
+import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG;
 import static android.media.projection.IMediaProjectionManager.EXTRA_PACKAGE_REUSING_GRANTED_CONSENT;
 import static android.media.projection.IMediaProjectionManager.EXTRA_USER_REVIEW_GRANTED_CONSENT;
 import static android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL;
 import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
+import static com.android.systemui.mediaprojection.MediaProjectionServiceHelper.OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION;
 import static com.android.systemui.mediaprojection.permission.ScreenShareOptionKt.ENTIRE_SCREEN;
 import static com.android.systemui.mediaprojection.permission.ScreenShareOptionKt.SINGLE_APP;
 
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityOptions.LaunchCookie;
 import android.app.AlertDialog;
 import android.app.StatusBarManager;
+import android.app.compat.CompatChanges;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -104,6 +109,7 @@
     }
 
     @Override
+    @RequiresPermission(allOf = {READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE})
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
@@ -231,6 +237,9 @@
         // the correct screen width when in split screen.
         Context dialogContext = getApplicationContext();
         if (isPartialScreenSharingEnabled()) {
+            final boolean overrideDisableSingleAppOption = CompatChanges.isChangeEnabled(
+                    OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
+                    mPackageName, getHostUserHandle());
             MediaProjectionPermissionDialogDelegate delegate =
                     new MediaProjectionPermissionDialogDelegate(
                             dialogContext,
@@ -242,6 +251,7 @@
                             },
                             () -> finish(RECORD_CANCEL, /* projection= */ null),
                             appName,
+                            overrideDisableSingleAppOption,
                             mUid,
                             mMediaProjectionMetricsLogger);
             mDialog =
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
index 0f54e93..9ce8070 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
@@ -30,11 +30,12 @@
     private val onStartRecordingClicked: Consumer<MediaProjectionPermissionDialogDelegate>,
     private val onCancelClicked: Runnable,
     private val appName: String?,
+    forceShowPartialScreenshare: Boolean,
     hostUid: Int,
     mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
 ) :
     BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
-        createOptionList(context, appName, mediaProjectionConfig),
+        createOptionList(context, appName, mediaProjectionConfig, forceShowPartialScreenshare),
         appName,
         hostUid,
         mediaProjectionMetricsLogger
@@ -65,7 +66,8 @@
         private fun createOptionList(
             context: Context,
             appName: String?,
-            mediaProjectionConfig: MediaProjectionConfig?
+            mediaProjectionConfig: MediaProjectionConfig?,
+            overrideDisableSingleAppOption: Boolean = false,
         ): List<ScreenShareOption> {
             val singleAppWarningText =
                 if (appName == null) {
@@ -80,8 +82,13 @@
                     R.string.media_projection_entry_app_permission_dialog_warning_entire_screen
                 }
 
+            // The single app option should only be disabled if there is an app name provided,
+            // the client has setup a MediaProjection with
+            // MediaProjectionConfig#createConfigForDefaultDisplay, AND it hasn't been overridden by
+            // the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override.
             val singleAppOptionDisabled =
                 appName != null &&
+                    !overrideDisableSingleAppOption &&
                     mediaProjectionConfig?.regionToCapture ==
                         MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 0167287..aa03e6e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -395,7 +395,7 @@
         }
         if (displayId == mDisplayId) {
             mLightBarController.onNavigationBarAppearanceChanged(appearance, nbModeChanged,
-                    BarTransitions.MODE_TRANSPARENT, navbarColorManagedByIme);
+                    mTransitionMode, navbarColorManagedByIme);
         }
         if (mBehavior != behavior) {
             mBehavior = behavior;
diff --git a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
index 2751072..3671dd4 100644
--- a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.process;
 
+import android.os.Process;
+import android.os.UserHandle;
+
 import javax.inject.Inject;
 
 /**
@@ -30,6 +33,15 @@
      * Returns {@code true} if System User is running the current process.
      */
     public boolean isSystemUser() {
-        return android.os.Process.myUserHandle().isSystem();
+        return myUserHandle().isSystem();
+    }
+
+    /**
+     * Returns {@link UserHandle} as returned statically by {@link Process#myUserHandle()}.
+     *
+     * Please strongly consider using {@link com.android.systemui.settings.UserTracker} instead.
+     */
+    public UserHandle myUserHandle() {
+        return Process.myUserHandle();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index a2dfc01..c0d9644 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -157,6 +157,12 @@
                 super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed,
                         parentHeightMeasureSpec, heightUsed);
             }
+        } else {
+            // Don't measure the customizer with all the children, it will be measured separately
+            if (child != mQSCustomizer) {
+                super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed,
+                        parentHeightMeasureSpec, heightUsed);
+            }
         }
     }
 
@@ -248,6 +254,25 @@
                 + mHeader.getHeight();
     }
 
+    // These next two methods are used with Scene container to determine the size of QQS and QS .
+
+    /**
+     * Returns the size of the QQS container, regardless of the measured size of this view.
+     * @return size in pixels of QQS
+     */
+    public int getQqsHeight() {
+        return mHeader.getHeight();
+    }
+
+    /**
+     * Returns the size of QS (or the QSCustomizer), regardless of the measured size of this view
+     * @return size in pixels of QS (or QSCustomizer)
+     */
+    public int getQsHeight() {
+        return mQSCustomizer.isCustomizing() ? mQSCustomizer.getMeasuredHeight()
+                : mQSPanel.getMeasuredHeight();
+    }
+
     public void setExpansion(float expansion) {
         mQsExpansion = expansion;
         if (mQSPanelContainer != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index 290821e..4d55714 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -984,6 +984,14 @@
         return mListeningAndVisibilityLifecycleOwner;
     }
 
+    public int getQQSHeight() {
+        return mContainer.getQqsHeight();
+    }
+
+    public int getQSHeight() {
+        return mContainer.getQsHeight();
+    }
+
     @NeverCompile
     @Override
     public void dump(PrintWriter pw, String[] args) {
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 1b3b584..58e7613 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
@@ -40,7 +40,7 @@
             val icon =
                 Icon.Loaded(
                     resources.getDrawable(
-                        if (data.isEnabled) {
+                        if (data is FlashlightTileModel.FlashlightAvailable && data.isEnabled) {
                             R.drawable.qs_flashlight_icon_on
                         } else {
                             R.drawable.qs_flashlight_icon_off
@@ -51,17 +51,22 @@
                 )
             this.icon = { icon }
 
-            if (data.isEnabled) {
+            contentDescription = label
+
+            if (data is FlashlightTileModel.FlashlightTemporarilyUnavailable) {
+                activationState = QSTileState.ActivationState.UNAVAILABLE
+                secondaryLabel =
+                    resources.getString(R.string.quick_settings_flashlight_camera_in_use)
+                stateDescription = secondaryLabel
+                supportedActions = setOf()
+                return@build
+            } else if (data is FlashlightTileModel.FlashlightAvailable && data.isEnabled) {
                 activationState = QSTileState.ActivationState.ACTIVE
                 secondaryLabel = resources.getStringArray(R.array.tile_states_flashlight)[2]
             } else {
                 activationState = QSTileState.ActivationState.INACTIVE
                 secondaryLabel = resources.getStringArray(R.array.tile_states_flashlight)[1]
             }
-            contentDescription = label
-            supportedActions =
-                setOf(
-                    QSTileState.UserAction.CLICK,
-                )
+            supportedActions = setOf(QSTileState.UserAction.CLICK)
         }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt
index 53d4cf9..1544804 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractor.kt
@@ -38,25 +38,30 @@
         user: UserHandle,
         triggers: Flow<DataUpdateTrigger>
     ): Flow<FlashlightTileModel> = conflatedCallbackFlow {
-        val initialValue = flashlightController.isEnabled
-        trySend(FlashlightTileModel(initialValue))
-
         val callback =
             object : FlashlightController.FlashlightListener {
                 override fun onFlashlightChanged(enabled: Boolean) {
-                    trySend(FlashlightTileModel(enabled))
+                    trySend(FlashlightTileModel.FlashlightAvailable(enabled))
                 }
                 override fun onFlashlightError() {
-                    trySend(FlashlightTileModel(false))
+                    trySend(FlashlightTileModel.FlashlightAvailable(false))
                 }
                 override fun onFlashlightAvailabilityChanged(available: Boolean) {
-                    trySend(FlashlightTileModel(flashlightController.isEnabled))
+                    trySend(
+                        if (available)
+                            FlashlightTileModel.FlashlightAvailable(flashlightController.isEnabled)
+                        else FlashlightTileModel.FlashlightTemporarilyUnavailable
+                    )
                 }
             }
         flashlightController.addCallback(callback)
         awaitClose { flashlightController.removeCallback(callback) }
     }
 
+    /**
+     * Used to determine if the tile should be displayed. Not to be confused with the availability
+     * in the data model above. This flow signals whether the tile should be shown or hidden.
+     */
     override fun availability(user: UserHandle): Flow<Boolean> =
         flowOf(flashlightController.hasFlashlight())
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt
index 9180e30..bedd65e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractor.kt
@@ -35,7 +35,10 @@
         with(input) {
             when (action) {
                 is QSTileUserAction.Click -> {
-                    if (!ActivityManager.isUserAMonkey()) {
+                    if (
+                        !ActivityManager.isUserAMonkey() &&
+                            input.data is FlashlightTileModel.FlashlightAvailable
+                    ) {
                         flashlightController.setFlashlight(!input.data.isEnabled)
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt
index ef6b2be..f54b371 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/model/FlashlightTileModel.kt
@@ -16,9 +16,14 @@
 
 package com.android.systemui.qs.tiles.impl.flashlight.domain.model
 
-/**
- * Flashlight tile model.
- *
- * @param isEnabled is true when the falshlight is enabled;
- */
-@JvmInline value class FlashlightTileModel(val isEnabled: Boolean)
+sealed interface FlashlightTileModel {
+    /**
+     * In this state, the tile can be turned on or off.
+     *
+     * @param isEnabled is true when the flashlight is on and false when off.
+     */
+    @JvmInline value class FlashlightAvailable(val isEnabled: Boolean) : FlashlightTileModel
+
+    /** In this state the tile is grayed out and flashlight cannot be turned on. */
+    data object FlashlightTemporarilyUnavailable : FlashlightTileModel
+}
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 0d43396..3d12eed 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
@@ -28,6 +28,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.qs.QSContainerController
+import com.android.systemui.qs.QSContainerImpl
 import com.android.systemui.qs.QSImpl
 import com.android.systemui.qs.dagger.QSSceneComponent
 import com.android.systemui.res.R
@@ -68,6 +69,15 @@
     /** Set the current state for QS. [state]. */
     fun setState(state: State)
 
+    /** The current height of QQS in the current [qsView], or 0 if there's no view. */
+    val qqsHeight: Int
+
+    /**
+     * The current height of QS in the current [qsView], or 0 if there's no view. If customizing, it
+     * will return the height allocated to the customizer.
+     */
+    val qsHeight: Int
+
     sealed class State(
         val isVisible: Boolean,
         val expansion: Float,
@@ -121,6 +131,11 @@
     val qsImpl = _qsImpl.asStateFlow()
     override val qsView: Flow<View> = _qsImpl.map { it?.view }.filterNotNull()
 
+    override val qqsHeight: Int
+        get() = qsImpl.value?.qqsHeight ?: 0
+    override val qsHeight: Int
+        get() = qsImpl.value?.qsHeight ?: 0
+
     // Same config changes as in FragmentHostManager
     private val interestingChanges =
         InterestingConfigChanges(
diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index fcbe9a6..356eb85 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -45,8 +45,8 @@
             sceneKeys =
                 listOf(
                     SceneKey.Gone,
-                    SceneKey.Shade,
                     SceneKey.QuickSettings,
+                    SceneKey.Shade,
                 ),
             initialSceneKey = SceneKey.Gone,
         )
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index 0f3acaf..c7d3a4a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -69,8 +69,8 @@
                         SceneKey.Communal,
                         SceneKey.Lockscreen,
                         SceneKey.Bouncer,
-                        SceneKey.Shade,
                         SceneKey.QuickSettings,
+                        SceneKey.Shade,
                     ),
                 initialSceneKey = SceneKey.Lockscreen,
             )
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 56c0ca9..dcd87c0 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
@@ -18,6 +18,7 @@
 
 package com.android.systemui.scene.domain.startable
 
+import android.app.StatusBarManager
 import com.android.systemui.CoreStartable
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -42,6 +43,7 @@
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
+import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
 import com.android.systemui.util.asIndenting
 import com.android.systemui.util.printSection
@@ -85,6 +87,7 @@
     private val authenticationInteractor: Lazy<AuthenticationInteractor>,
     private val windowController: NotificationShadeWindowController,
     private val deviceProvisioningInteractor: DeviceProvisioningInteractor,
+    private val centralSurfaces: CentralSurfaces,
 ) : CoreStartable {
 
     override fun start() {
@@ -95,6 +98,7 @@
             hydrateSystemUiState()
             collectFalsingSignals()
             hydrateWindowFocus()
+            hydrateInteractionState()
         } else {
             sceneLogger.logFrameworkEnabled(
                 isEnabled = false,
@@ -376,6 +380,46 @@
         }
     }
 
+    /** Keeps the interaction state of [CentralSurfaces] up-to-date. */
+    private fun hydrateInteractionState() {
+        applicationScope.launch {
+            deviceEntryInteractor.isUnlocked
+                .map { !it }
+                .flatMapLatest { isDeviceLocked ->
+                    if (isDeviceLocked) {
+                        sceneInteractor.transitionState
+                            .mapNotNull { it as? ObservableTransitionState.Idle }
+                            .map { it.scene }
+                            .distinctUntilChanged()
+                            .map { sceneKey ->
+                                when (sceneKey) {
+                                    // When locked, showing the lockscreen scene should be reported
+                                    // as "interacting" while showing other scenes should report as
+                                    // "not interacting".
+                                    //
+                                    // This is done here in order to match the legacy
+                                    // implementation. The real reason why is lost to lore and myth.
+                                    SceneKey.Lockscreen -> true
+                                    SceneKey.Bouncer -> false
+                                    SceneKey.Shade -> false
+                                    else -> null
+                                }
+                            }
+                    } else {
+                        flowOf(null)
+                    }
+                }
+                .collect { isInteractingOrNull ->
+                    isInteractingOrNull?.let { isInteracting ->
+                        centralSurfaces.setInteracting(
+                            StatusBarManager.WINDOW_STATUS_BAR,
+                            isInteracting,
+                        )
+                    }
+                }
+        }
+    }
+
     private fun switchToScene(targetSceneKey: SceneKey, loggingReason: String) {
         sceneInteractor.changeScene(
             scene = SceneModel(targetSceneKey),
diff --git a/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
index 76d1d3d..6199a83 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 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.
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index be1fa2b..861a2ed 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -30,11 +30,11 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.systemui.Gefingerpoken;
-import com.android.systemui.res.R;
 import com.android.systemui.classifier.Classifier;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.haptics.slider.SeekableSliderEventProducer;
+import com.android.systemui.haptics.slider.HapticSliderViewBinder;
+import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.util.ViewController;
@@ -42,8 +42,6 @@
 
 import javax.inject.Inject;
 
-import kotlinx.coroutines.CoroutineDispatcher;
-
 /**
  * {@code ViewController} for a {@code BrightnessSliderView}
  *
@@ -63,23 +61,16 @@
     private final FalsingManager mFalsingManager;
     private final UiEventLogger mUiEventLogger;
 
-    private final BrightnessSliderHapticPlugin mBrightnessSliderHapticPlugin;
+    private final SeekableSliderHapticPlugin mBrightnessSliderHapticPlugin;
 
     private final Gefingerpoken mOnInterceptListener = new Gefingerpoken() {
         @Override
         public boolean onInterceptTouchEvent(MotionEvent ev) {
+            mBrightnessSliderHapticPlugin.onTouchEvent(ev);
             int action = ev.getActionMasked();
             if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
                 mFalsingManager.isFalseTouch(Classifier.BRIGHTNESS_SLIDER);
-                if (mBrightnessSliderHapticPlugin.getVelocityTracker() != null) {
-                    mBrightnessSliderHapticPlugin.getVelocityTracker().clear();
-                }
-            } else if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE) {
-                if (mBrightnessSliderHapticPlugin.getVelocityTracker() != null) {
-                    mBrightnessSliderHapticPlugin.getVelocityTracker().addMovement(ev);
-                }
             }
-
             return false;
         }
 
@@ -93,7 +84,7 @@
             BrightnessSliderView brightnessSliderView,
             FalsingManager falsingManager,
             UiEventLogger uiEventLogger,
-            BrightnessSliderHapticPlugin brightnessSliderHapticPlugin) {
+            SeekableSliderHapticPlugin brightnessSliderHapticPlugin) {
         super(brightnessSliderView);
         mFalsingManager = falsingManager;
         mUiEventLogger = uiEventLogger;
@@ -112,7 +103,6 @@
     protected void onViewAttached() {
         mView.setOnSeekBarChangeListener(mSeekListener);
         mView.setOnInterceptListener(mOnInterceptListener);
-        mBrightnessSliderHapticPlugin.start();
     }
 
     @Override
@@ -120,7 +110,6 @@
         mView.setOnSeekBarChangeListener(null);
         mView.setOnDispatchTouchEventListener(null);
         mView.setOnInterceptListener(null);
-        mBrightnessSliderHapticPlugin.stop();
     }
 
     @Override
@@ -225,10 +214,8 @@
         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
             if (mListener != null) {
                 mListener.onChanged(mTracking, progress, false);
-                SeekableSliderEventProducer eventProducer =
-                        mBrightnessSliderHapticPlugin.getSeekableSliderEventProducer();
-                if (eventProducer != null && fromUser) {
-                    eventProducer.onProgressChanged(seekBar, progress, fromUser);
+                if (fromUser) {
+                    mBrightnessSliderHapticPlugin.onProgressChanged(seekBar, progress, fromUser);
                 }
             }
         }
@@ -236,14 +223,10 @@
         @Override
         public void onStartTrackingTouch(SeekBar seekBar) {
             mTracking = true;
-            mUiEventLogger.log(BrightnessSliderEvent.SLIDER_STARTED_TRACKING_TOUCH);
+            mUiEventLogger.log(BrightnessSliderEvent.BRIGHTNESS_SLIDER_STARTED_TRACKING_TOUCH);
             if (mListener != null) {
                 mListener.onChanged(mTracking, getValue(), false);
-                SeekableSliderEventProducer eventProducer =
-                        mBrightnessSliderHapticPlugin.getSeekableSliderEventProducer();
-                if (eventProducer != null) {
-                    eventProducer.onStartTrackingTouch(seekBar);
-                }
+                mBrightnessSliderHapticPlugin.onStartTrackingTouch(seekBar);
             }
 
             if (mMirrorController != null) {
@@ -255,14 +238,10 @@
         @Override
         public void onStopTrackingTouch(SeekBar seekBar) {
             mTracking = false;
-            mUiEventLogger.log(BrightnessSliderEvent.SLIDER_STOPPED_TRACKING_TOUCH);
+            mUiEventLogger.log(BrightnessSliderEvent.BRIGHTNESS_SLIDER_STOPPED_TRACKING_TOUCH);
             if (mListener != null) {
                 mListener.onChanged(mTracking, getValue(), true);
-                SeekableSliderEventProducer eventProducer =
-                        mBrightnessSliderHapticPlugin.getSeekableSliderEventProducer();
-                if (eventProducer != null) {
-                    eventProducer.onStopTrackingTouch(seekBar);
-                }
+                mBrightnessSliderHapticPlugin.onStopTrackingTouch(seekBar);
             }
 
             if (mMirrorController != null) {
@@ -280,21 +259,18 @@
         private final UiEventLogger mUiEventLogger;
         private final VibratorHelper mVibratorHelper;
         private final SystemClock mSystemClock;
-        private final CoroutineDispatcher mMainDispatcher;
 
         @Inject
         public Factory(
                 FalsingManager falsingManager,
                 UiEventLogger uiEventLogger,
                 VibratorHelper vibratorHelper,
-                SystemClock clock,
-                @Main CoroutineDispatcher mainDispatcher
+                SystemClock clock
         ) {
             mFalsingManager = falsingManager;
             mUiEventLogger = uiEventLogger;
             mVibratorHelper = vibratorHelper;
             mSystemClock = clock;
-            mMainDispatcher = mainDispatcher;
         }
 
         /**
@@ -310,15 +286,11 @@
             int layout = getLayout();
             BrightnessSliderView root = (BrightnessSliderView) LayoutInflater.from(context)
                     .inflate(layout, viewRoot, false);
-            BrightnessSliderHapticPlugin plugin;
-            if (hapticBrightnessSlider()) {
-                plugin = new BrightnessSliderHapticPluginImpl(
+            SeekableSliderHapticPlugin plugin = new SeekableSliderHapticPlugin(
                     mVibratorHelper,
-                    mSystemClock,
-                    mMainDispatcher
-                );
-            } else {
-                plugin = new BrightnessSliderHapticPlugin() {};
+                    mSystemClock);
+            if (hapticBrightnessSlider()) {
+                HapticSliderViewBinder.bind(viewRoot, plugin);
             }
             return new BrightnessSliderController(root, mFalsingManager, mUiEventLogger, plugin);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java
index 3a30880..a8641bf 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java
@@ -21,10 +21,10 @@
 
 public enum BrightnessSliderEvent implements UiEventLogger.UiEventEnum {
 
-    @UiEvent(doc = "slider started to track touch")
-    SLIDER_STARTED_TRACKING_TOUCH(1472),
-    @UiEvent(doc = "slider stopped tracking touch")
-    SLIDER_STOPPED_TRACKING_TOUCH(1473);
+    @UiEvent(doc = "brightness slider started to track touch")
+    BRIGHTNESS_SLIDER_STARTED_TRACKING_TOUCH(1472),
+    @UiEvent(doc = "brightness slider stopped tracking touch")
+    BRIGHTNESS_SLIDER_STOPPED_TRACKING_TOUCH(1473);
 
     private final int mId;
 
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderHapticPlugin.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderHapticPlugin.kt
deleted file mode 100644
index f775114..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderHapticPlugin.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.settings.brightness
-
-import android.view.VelocityTracker
-import com.android.systemui.haptics.slider.SeekableSliderEventProducer
-
-/** Plugin component for the System UI brightness slider to incorporate dynamic haptics */
-interface BrightnessSliderHapticPlugin {
-
-    /** Finger velocity tracker */
-    val velocityTracker: VelocityTracker?
-        get() = null
-
-    /** Producer of slider events from the underlying [android.widget.SeekBar] */
-    val seekableSliderEventProducer: SeekableSliderEventProducer?
-        get() = null
-
-    /**
-     * Start the plugin.
-     *
-     * This starts the tracking of slider states, events and triggering of haptic feedback.
-     */
-    fun start() {}
-
-    /**
-     * Stop the plugin
-     *
-     * This stops the tracking of slider states, events and triggers of haptic feedback.
-     */
-    fun stop() {}
-}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderHapticPluginImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderHapticPluginImpl.kt
deleted file mode 100644
index 32561f0..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderHapticPluginImpl.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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.settings.brightness
-
-import android.view.VelocityTracker
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.haptics.slider.SeekableSliderEventProducer
-import com.android.systemui.haptics.slider.SeekableSliderTracker
-import com.android.systemui.haptics.slider.SliderHapticFeedbackProvider
-import com.android.systemui.haptics.slider.SliderTracker
-import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.CoroutineDispatcher
-
-/**
- * Implementation of the [BrightnessSliderHapticPlugin].
- *
- * For the specifics of the brightness slider in System UI, a [SeekableSliderEventProducer] is used
- * as the producer of slider events, a [SliderHapticFeedbackProvider] is used as the listener of
- * slider states to play haptic feedback depending on the state, and a [SeekableSliderTracker] is
- * used as the state machine handler that tracks and manipulates the slider state.
- */
-class BrightnessSliderHapticPluginImpl
-@JvmOverloads
-constructor(
-    vibratorHelper: VibratorHelper,
-    systemClock: SystemClock,
-    @Main mainDispatcher: CoroutineDispatcher,
-    override val velocityTracker: VelocityTracker = VelocityTracker.obtain(),
-    override val seekableSliderEventProducer: SeekableSliderEventProducer =
-        SeekableSliderEventProducer(),
-) : BrightnessSliderHapticPlugin {
-
-    private val sliderHapticFeedbackProvider: SliderHapticFeedbackProvider =
-        SliderHapticFeedbackProvider(vibratorHelper, velocityTracker, clock = systemClock)
-    private val sliderTracker: SliderTracker =
-        SeekableSliderTracker(
-            sliderHapticFeedbackProvider,
-            seekableSliderEventProducer,
-            mainDispatcher,
-        )
-
-    val isTracking: Boolean
-        get() = sliderTracker.isTracking
-
-    override fun start() {
-        if (!sliderTracker.isTracking) {
-            sliderTracker.startTracking()
-        }
-    }
-
-    override fun stop() {
-        sliderTracker.stopTracking()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index e219bcc..73d229e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -113,7 +113,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.animation.LaunchAnimator;
+import com.android.systemui.animation.TransitionAnimator;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
@@ -1021,22 +1021,24 @@
                 instantCollapse();
             } else {
                 mView.animate().cancel();
-                mView.animate()
-                        .alpha(0f)
-                        .setStartDelay(0)
-                        // Translate up by 4%.
-                        .translationY(mView.getHeight() * -0.04f)
-                        // This start delay is to give us time to animate out before
-                        // the launcher icons animation starts, so use that as our
-                        // duration.
-                        .setDuration(unlockAnimationStartDelay)
-                        .setInterpolator(EMPHASIZED_ACCELERATE)
-                        .withEndAction(() -> {
-                            instantCollapse();
-                            mView.setAlpha(1f);
-                            mView.setTranslationY(0f);
-                        })
-                        .start();
+                if (!KeyguardShadeMigrationNssl.isEnabled()) {
+                    mView.animate()
+                            .alpha(0f)
+                            .setStartDelay(0)
+                            // Translate up by 4%.
+                            .translationY(mView.getHeight() * -0.04f)
+                            // This start delay is to give us time to animate out before
+                            // the launcher icons animation starts, so use that as our
+                            // duration.
+                            .setDuration(unlockAnimationStartDelay)
+                            .setInterpolator(EMPHASIZED_ACCELERATE)
+                            .withEndAction(() -> {
+                                instantCollapse();
+                                mView.setAlpha(1f);
+                                mView.setTranslationY(0f);
+                            })
+                            .start();
+                }
             }
         }
     }
@@ -1346,7 +1348,7 @@
         }
         updateClockAppearance();
         mQsController.updateQsState();
-        if (!KeyguardShadeMigrationNssl.isEnabled()) {
+        if (!KeyguardShadeMigrationNssl.isEnabled() && !FooterViewRefactor.isEnabled()) {
             mNotificationStackScrollLayoutController.updateFooter();
         }
     }
@@ -3229,7 +3231,7 @@
 
     @Override
     public void applyLaunchAnimationProgress(float linearProgress) {
-        boolean hideIcons = LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS,
+        boolean hideIcons = TransitionAnimator.getProgress(ActivityLaunchAnimator.TIMINGS,
                 linearProgress, ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
         if (hideIcons != mHideIconsDuringLaunchAnimation) {
             mHideIconsDuringLaunchAnimation = hideIcons;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
index 67bb814..f89a9c70 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.shade.data.repository.ShadeRepositoryImpl
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorEmptyImpl
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.domain.interactor.ShadeInteractorEmptyImpl
 import dagger.Binds
@@ -35,6 +36,10 @@
 
     @Binds
     @SysUISingleton
+    abstract fun bindsShadeBack(sbai: ShadeViewControllerEmptyImpl): ShadeBackActionInteractor
+
+    @Binds
+    @SysUISingleton
     abstract fun bindsShadeController(sc: ShadeControllerEmptyImpl): ShadeController
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index fc2c3ee..e4d5d22 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -24,6 +24,8 @@
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorSceneContainerImpl
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractorImpl
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl
 import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl
@@ -78,6 +80,20 @@
                 sceneContainerOff.get()
             }
         }
+
+        @Provides
+        @SysUISingleton
+        fun provideShadeBackActionInteractor(
+            sceneContainerFlags: SceneContainerFlags,
+            sceneContainerOn: Provider<ShadeBackActionInteractorImpl>,
+            sceneContainerOff: Provider<NotificationPanelViewController>
+        ): ShadeBackActionInteractor {
+            return if (sceneContainerFlags.isEnabled()) {
+                sceneContainerOn.get()
+            } else {
+                sceneContainerOff.get()
+            }
+        }
     }
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
index 4f970b3..0befb61 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.shade
 
 import android.view.ViewPropertyAnimator
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
 import com.android.systemui.statusbar.GestureRecorder
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -25,7 +26,7 @@
  * this class. If any method in this class is needed outside of CentralSurfacesImpl, it must be
  * pulled up into ShadeViewController.
  */
-interface ShadeSurface : ShadeViewController {
+interface ShadeSurface : ShadeViewController, ShadeBackActionInteractor {
     /** Initialize objects instead of injecting to avoid circular dependencies. */
     fun initDependencies(
         centralSurfaces: CentralSurfaces,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 3430eed..74035bd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -79,15 +79,8 @@
     /** Collapses the shade instantly without animation. */
     fun instantCollapse()
 
-    /**
-     * Animate QS collapse by flinging it. If QS is expanded, it will collapse into QQS and stop. If
-     * in split shade, it will collapse the whole shade.
-     *
-     * @param fullyCollapse Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
-     */
-    fun animateCollapseQs(fullyCollapse: Boolean)
-
     /** Returns whether the shade can be collapsed. */
+    @Deprecated("Do not use outside of the shade package. Not supported by scenes.")
     fun canBeCollapsed(): Boolean
 
     /** Returns whether the shade is in the process of collapsing. */
@@ -142,22 +135,6 @@
     /** Sets the amount of progress in the status bar launch animation. */
     fun applyLaunchAnimationProgress(linearProgress: Float)
 
-    /**
-     * Close the keyguard user switcher if it is open and capable of closing.
-     *
-     * Has no effect if user switcher isn't supported, if the user switcher is already closed, or if
-     * the user switcher uses "simple" mode. The simple user switcher cannot be closed.
-     *
-     * @return true if the keyguard user switcher was open, and is now closed
-     */
-    fun closeUserSwitcherIfOpen(): Boolean
-
-    /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */
-    fun onBackPressed()
-
-    /** Sets progress of the predictive back animation. */
-    fun onBackProgressed(progressFraction: Float)
-
     /** Sets the alpha value of the shade to a value between 0 and 255. */
     fun setAlpha(alpha: Int, animate: Boolean)
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index 1240c6e..5d966ac 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -19,13 +19,15 @@
 import android.view.MotionEvent
 import android.view.ViewGroup
 import android.view.ViewTreeObserver
+import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController
 import java.util.function.Consumer
 import javax.inject.Inject
 
 /** Empty implementation of ShadeViewController for variants with no shade. */
-class ShadeViewControllerEmptyImpl @Inject constructor() : ShadeViewController {
+class ShadeViewControllerEmptyImpl @Inject constructor() :
+    ShadeViewController, ShadeBackActionInteractor {
     override fun expand(animate: Boolean) {}
     override fun expandToQs() {}
     override fun expandToNotifications() {}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractor.kt
new file mode 100644
index 0000000..15ea219
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractor.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.shade.domain.interactor
+
+/** Allows the back action to interact with the shade. */
+interface ShadeBackActionInteractor {
+    /**
+     * Animate QS collapse by flinging it. If QS is expanded, it will collapse into QQS and stop. If
+     * in split shade, it will collapse the whole shade.
+     *
+     * @param fullyCollapse Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
+     */
+    fun animateCollapseQs(fullyCollapse: Boolean)
+
+    /** Returns whether the shade can be collapsed. */
+    fun canBeCollapsed(): Boolean
+
+    /**
+     * Close the keyguard user switcher if it is open and capable of closing.
+     *
+     * Has no effect if user switcher isn't supported, if the user switcher is already closed, or if
+     * the user switcher uses "simple" mode. The simple user switcher cannot be closed.
+     *
+     * @return true if the keyguard user switcher was open, and is now closed
+     */
+    @Deprecated("Only supported by very old devices that will not adopt scenes.")
+    fun closeUserSwitcherIfOpen(): Boolean
+
+    /** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */
+    fun onBackPressed()
+
+    /** Sets progress of the predictive back animation. */
+    @Deprecated("According to b/318376223, shade predictive back is not be supported.")
+    fun onBackProgressed(progressFraction: Float)
+}
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
new file mode 100644
index 0000000..9bbe1bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.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.systemui.shade.domain.interactor
+
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Implementation of ShadeBackActionInteractor backed by scenes. */
+@OptIn(ExperimentalCoroutinesApi::class)
+class ShadeBackActionInteractorImpl
+@Inject
+constructor(
+    val shadeInteractor: ShadeInteractor,
+    val sceneInteractor: SceneInteractor,
+    val deviceEntryInteractor: DeviceEntryInteractor,
+) : ShadeBackActionInteractor {
+    override fun animateCollapseQs(fullyCollapse: Boolean) {
+        if (shadeInteractor.isQsExpanded.value) {
+            val key =
+                if (fullyCollapse) {
+                    if (deviceEntryInteractor.isDeviceEntered.value) {
+                        SceneKey.Gone
+                    } else {
+                        SceneKey.Lockscreen
+                    }
+                } else {
+                    SceneKey.Shade
+                }
+            sceneInteractor.changeScene(SceneModel(key), "animateCollapseQs")
+        }
+    }
+
+    override fun canBeCollapsed(): Boolean {
+        return shadeInteractor.isAnyExpanded.value && !shadeInteractor.isUserInteracting.value
+    }
+
+    @Deprecated("Only supported by very old devices that will not adopt scenes.")
+    override fun closeUserSwitcherIfOpen(): Boolean {
+        return false
+    }
+
+    override fun onBackPressed() {
+        animateCollapseQs(false)
+    }
+
+    @Deprecated("According to b/318376223, shade predictive back is not be supported.")
+    override fun onBackProgressed(progressFraction: Float) {
+        // Not supported. Do nothing.
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt
index 52a1c15..095d30e 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt
@@ -21,9 +21,7 @@
 import android.widget.RemoteViews
 import com.android.systemui.communal.smartspace.CommunalSmartspaceController
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
-import java.util.concurrent.Executor
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -43,7 +41,6 @@
 @Inject
 constructor(
     private val communalSmartspaceController: CommunalSmartspaceController,
-    @Main private val uiExecutor: Executor,
 ) : SmartspaceRepository, BcSmartspaceDataPlugin.SmartspaceTargetListener {
 
     override val isSmartspaceRemoteViewsEnabled: Boolean
@@ -54,18 +51,12 @@
     override val communalSmartspaceTargets: Flow<List<SmartspaceTarget>> =
         _communalSmartspaceTargets
             .onStart {
-                uiExecutor.execute {
-                    communalSmartspaceController.addListener(
-                        listener = this@SmartspaceRepositoryImpl
-                    )
-                }
+                communalSmartspaceController.addListener(listener = this@SmartspaceRepositoryImpl)
             }
             .onCompletion {
-                uiExecutor.execute {
-                    communalSmartspaceController.removeListener(
-                        listener = this@SmartspaceRepositoryImpl
-                    )
-                }
+                communalSmartspaceController.removeListener(
+                    listener = this@SmartspaceRepositoryImpl
+                )
             }
 
     override fun onSmartspaceTargetsUpdated(targetsNullable: MutableList<out Parcelable>?) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 6e85074..ac510fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -156,6 +156,54 @@
     }
 }
 
+data class LinearSideLightRevealEffect(private val isVertical: Boolean) : LightRevealEffect {
+
+    override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
+        scrim.interpolatedRevealAmount = amount
+        scrim.startColorAlpha =
+            getPercentPastThreshold(1 - amount, threshold = 1 - START_COLOR_REVEAL_PERCENTAGE)
+        scrim.revealGradientEndColorAlpha =
+            1f -
+                getPercentPastThreshold(
+                    amount,
+                    threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE
+                )
+
+        val gradientBoundsAmount = lerp(GRADIENT_START_BOUNDS_PERCENTAGE, 1f, amount)
+        if (isVertical) {
+            scrim.setRevealGradientBounds(
+                left = -(scrim.viewWidth) * gradientBoundsAmount,
+                top = -(scrim.viewHeight) * gradientBoundsAmount,
+                right = (scrim.viewWidth) * gradientBoundsAmount,
+                bottom = (scrim.viewHeight) + (scrim.viewHeight) * gradientBoundsAmount
+            )
+        } else {
+            scrim.setRevealGradientBounds(
+                left = -(scrim.viewWidth) * gradientBoundsAmount,
+                top = -(scrim.viewHeight) * gradientBoundsAmount,
+                right = (scrim.viewWidth) + (scrim.viewWidth) * gradientBoundsAmount,
+                bottom = (scrim.viewHeight) * gradientBoundsAmount
+            )
+        }
+    }
+
+    private companion object {
+        // From which percentage we should start the gradient reveal width
+        // E.g. if 0 - starts with 0px width, 0.6f - starts with 60% width
+        private const val GRADIENT_START_BOUNDS_PERCENTAGE: Float = 1f
+
+        // When to start changing alpha color of the gradient scrim
+        // E.g. if 0.6f - starts fading the gradient away at 60% and becomes completely
+        // transparent at 100%
+        private const val REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE: Float = 1f
+
+        // When to finish displaying start color fill that reveals the content
+        // E.g. if 0.6f - the content won't be visible at 0% and it will gradually
+        // reduce the alpha until 60% (at this point the color fill is invisible)
+        private const val START_COLOR_REVEAL_PERCENTAGE: Float = 1f
+    }
+}
+
 data class CircleReveal(
     /** X-value of the circle center of the reveal. */
     val centerX: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
index 785e65d..6f4a7cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
@@ -4,7 +4,7 @@
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.app.animation.Interpolators
-import com.android.systemui.animation.LaunchAnimator
+import com.android.systemui.animation.TransitionAnimator
 import kotlin.math.min
 
 /** Parameters for the notifications launch expanding animations. */
@@ -16,7 +16,7 @@
 
     topCornerRadius: Float = 0f,
     bottomCornerRadius: Float = 0f
-) : LaunchAnimator.State(top, bottom, left, right, topCornerRadius, bottomCornerRadius) {
+) : TransitionAnimator.State(top, bottom, left, right, topCornerRadius, bottomCornerRadius) {
     @VisibleForTesting
     constructor() : this(
         top = 0, bottom = 0, left = 0, right = 0, topCornerRadius = 0f, bottomCornerRadius = 0f
@@ -58,7 +58,7 @@
         }
 
     fun getProgress(delay: Long, duration: Long): Float {
-        return LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS, linearProgress, delay,
+        return TransitionAnimator.getProgress(ActivityLaunchAnimator.TIMINGS, linearProgress, delay,
             duration)
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
index 5983fc1..8fc9619 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
@@ -20,7 +20,7 @@
 import android.view.ViewGroup
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.LaunchAnimator
+import com.android.systemui.animation.TransitionAnimator
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
@@ -75,13 +75,13 @@
     private val notificationEntry = notification.entry
     private val notificationKey = notificationEntry.sbn.key
 
-    override var launchContainer: ViewGroup
+    override var transitionContainer: ViewGroup
         get() = notification.rootView as ViewGroup
         set(ignored) {
             // Do nothing. Notifications are always animated inside their rootView.
         }
 
-    override fun createAnimatorState(): LaunchAnimator.State {
+    override fun createAnimatorState(): TransitionAnimator.State {
         // If the notification panel is collapsed, the clip may be larger than the height.
         val height = max(0, notification.actualHeight - notification.clipBottomAmount)
         val location = notification.locationOnScreen
@@ -186,14 +186,14 @@
         onFinishAnimationCallback?.run()
     }
 
-    override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+    override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
         notification.isExpandAnimationRunning = true
         notificationListContainer.setExpandingNotification(notification)
 
         jankMonitor.begin(notification, InteractionJankMonitor.CUJ_NOTIFICATION_APP_START)
     }
 
-    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+    override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
         if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
             Log.d(TAG, "onLaunchAnimationEnd()")
         }
@@ -213,8 +213,8 @@
         notificationListContainer.applyLaunchAnimationParams(params)
     }
 
-    override fun onLaunchAnimationProgress(
-        state: LaunchAnimator.State,
+    override fun onTransitionAnimationProgress(
+        state: TransitionAnimator.State,
         progress: Float,
         linearProgress: Float
     ) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 3f2c818..7c71864 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -386,6 +386,7 @@
         }
     }
 
+    @Deprecated("As part of b/301915812")
     private fun scheduleDelayedDozeAmountAnimation() {
         val alreadyRunning = delayedDozeAmountAnimator != null
         logger.logStartDelayedDozeAmountAnimation(alreadyRunning)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index 6e2beb4..8b0b973 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -56,8 +56,8 @@
             if (FooterViewRefactor.isEnabled) {
                 activeNotificationsInteractor.setNotifStats(notifStats)
             }
-            // TODO(b/293167744): This shouldn't be done if the footer flag is on, once the footer
-            //  visibility is handled in the new stack.
+            // TODO(b/293167744): This shouldn't be done if the footer flag is on, once the silent
+            //  section clear action is handled in the new stack.
             controller.setNotifStats(notifStats)
             if (NotificationIconContainerRefactor.isEnabled || FooterViewRefactor.isEnabled) {
                 renderListInteractor.setRenderedList(entries)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt
index 0a9e12a..ccf6f40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 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.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
index 5111c11..b23ef35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -52,6 +52,9 @@
             isVisible =
                 activeNotificationsInteractor.hasClearableNotifications
                     .sample(
+                        // TODO(b/322167853): This check is currently duplicated in
+                        //  NotificationListViewModel, but instead it should be a field in
+                        //  ShadeAnimationInteractor.
                         combine(
                                 shadeInteractor.isShadeFullyExpanded,
                                 shadeInteractor.isShadeTouchable,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
index d903f06..8768ea2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
@@ -29,6 +29,8 @@
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
@@ -56,6 +58,8 @@
                 panelTouchesEnabled && isKeyguardVisible
             }
             .flowOn(bgContext)
+            .conflate()
+            .distinctUntilChanged()
 
     /** Amount of a "white" tint to be applied to the icons. */
     val tintAlpha: Flow<Float> =
@@ -70,6 +74,8 @@
                 aodAmt + dozeAmt // If transitioning between them, they should sum to 1f
             }
             .flowOn(bgContext)
+            .conflate()
+            .distinctUntilChanged()
 
     /** Are notification icons animated (ex: animated gif)? */
     val areIconAnimationsEnabled: Flow<Boolean> =
@@ -78,8 +84,10 @@
                 // Don't animate icons when we're on AOD / dozing
                 it != KeyguardState.AOD && it != KeyguardState.DOZING
             }
-            .flowOn(bgContext)
             .onStart { emit(true) }
+            .flowOn(bgContext)
+            .conflate()
+            .distinctUntilChanged()
 
     /** [NotificationIconsViewData] indicating which icons to display in the view. */
     val icons: Flow<NotificationIconsViewData> =
@@ -91,4 +99,6 @@
                 )
             }
             .flowOn(bgContext)
+            .conflate()
+            .distinctUntilChanged()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
index 3574828..260cccd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
@@ -21,6 +21,8 @@
 import javax.inject.Inject
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 
@@ -56,4 +58,6 @@
                 )
             }
             .flowOn(bgContext)
+            .conflate()
+            .distinctUntilChanged()
 }
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 38921c2..a64f888 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
@@ -35,6 +35,7 @@
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.conflate
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flowOn
@@ -64,10 +65,13 @@
                 panelTouchesEnabled && !isKeyguardShowing
             }
             .flowOn(bgContext)
+            .conflate()
+            .distinctUntilChanged()
 
     /** The colors with which to display the notification icons. */
     val iconColors: Flow<NotificationIconColorLookup> =
-        combine(darkIconInteractor.tintAreas, darkIconInteractor.tintColor) { areas, tint ->
+        darkIconInteractor.darkState
+            .map { (areas: Collection<Rect>, tint: Int) ->
                 NotificationIconColorLookup { viewBounds: Rect ->
                     if (DarkIconDispatcher.isInAreas(areas, viewBounds)) {
                         IconColorsImpl(tint, areas)
@@ -77,6 +81,8 @@
                 }
             }
             .flowOn(bgContext)
+            .conflate()
+            .distinctUntilChanged()
 
     /** [NotificationIconsViewData] indicating which icons to display in the view. */
     val icons: Flow<NotificationIconsViewData> =
@@ -88,6 +94,8 @@
                 )
             }
             .flowOn(bgContext)
+            .conflate()
+            .distinctUntilChanged()
 
     /** An Icon to show "isolated" in the IconContainer. */
     val isolatedIcon: Flow<AnimatedValue<NotificationIconInfo?>> =
@@ -99,6 +107,8 @@
             }
             .distinctUntilChanged()
             .flowOn(bgContext)
+            .conflate()
+            .distinctUntilChanged()
             .pairwise(initialValue = null)
             .sample(shadeInteractor.shadeExpansion) { (prev, iconInfo), shadeExpansion ->
                 val animate =
@@ -113,7 +123,7 @@
 
     /** Location to show an isolated icon, if there is one. */
     val isolatedIconLocation: Flow<Rect> =
-        headsUpIconInteractor.isolatedIconLocation.filterNotNull()
+        headsUpIconInteractor.isolatedIconLocation.filterNotNull().conflate().distinctUntilChanged()
 
     private class IconColorsImpl(
         override val tint: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 20c8add..6836816 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -238,6 +238,9 @@
     ) {
     val TAG = "AvalancheSuppressor"
 
+    override var reason: String = "avalanche"
+        protected set
+
     enum class State {
         ALLOW_CONVERSATION_AFTER_AVALANCHE,
         ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME,
@@ -252,13 +255,13 @@
     override fun shouldSuppress(entry: NotificationEntry): Boolean {
         val timeSinceAvalanche = systemClock.currentTimeMillis() - avalancheProvider.startTime
         val isActive = timeSinceAvalanche < avalancheProvider.timeoutMs
-        val state = allow(entry)
+        val state = calculateState(entry)
         val suppress = isActive && state == State.SUPPRESS
         reason = "avalanche suppress=$suppress isActive=$isActive state=$state"
         return suppress
     }
 
-    fun allow(entry: NotificationEntry): State  {
+    private fun calculateState(entry: NotificationEntry): State  {
         if (
             entry.ranking.isConversation &&
                 entry.sbn.notification.`when` > avalancheProvider.startTime
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
index 2f80c5d..ee79727 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
@@ -85,7 +85,7 @@
 /** A reason why visual interruptions might be suppressed based on the notification. */
 abstract class VisualInterruptionFilter(
     override val types: Set<VisualInterruptionType>,
-    override var reason: String,
+    override val reason: String,
     override val uiEventId: UiEventEnum? = null,
     override val eventLogData: EventLogData? = null
 ) : VisualInterruptionSuppressor {
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 0a11eb2..42b56e7 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
@@ -752,6 +752,7 @@
     }
 
     public void setIsRemoteInputActive(boolean isActive) {
+        FooterViewRefactor.assertInLegacyMode();
         mIsRemoteInputActive = isActive;
         updateFooter();
     }
@@ -764,6 +765,7 @@
 
     @VisibleForTesting
     public void updateFooter() {
+        FooterViewRefactor.assertInLegacyMode();
         if (mFooterView == null || mController == null) {
             return;
         }
@@ -776,10 +778,12 @@
     }
 
     private boolean shouldShowDismissView() {
+        FooterViewRefactor.assertInLegacyMode();
         return mController.hasActiveClearableNotifications(ROWS_ALL);
     }
 
     private boolean shouldShowFooterView(boolean showDismissView) {
+        FooterViewRefactor.assertInLegacyMode();
         return (showDismissView || mController.getVisibleNotificationCount() > 0)
                 && mIsCurrentUserSetup // see: b/193149550
                 && !onKeyguard()
@@ -4359,6 +4363,12 @@
                     layoutEnd -= mShelf.getIntrinsicHeight() + mPaddingBetweenElements;
                 }
                 if (endPosition > layoutEnd) {
+                    // if Scene Container is active, send bottom notification expansion delta
+                    // to it so that it can scroll the stack and scrim accordingly.
+                    if (SceneContainerFlag.isEnabled()) {
+                        float diff = endPosition - layoutEnd;
+                        mController.sendSyntheticScrollToSceneFramework(diff);
+                    }
                     setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd));
                     mDisallowScrollingInThisMotion = true;
                 }
@@ -4723,9 +4733,6 @@
                 footerView.setClearAllButtonVisible(false /* visible */, true /* animate */);
             });
         }
-        if (FooterViewRefactor.isEnabled()) {
-            updateFooter();
-        }
     }
 
     public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
@@ -4790,16 +4797,15 @@
     }
 
     public void updateFooterView(boolean visible, boolean showDismissView, boolean showHistory) {
+        FooterViewRefactor.assertInLegacyMode();
         if (mFooterView == null || mNotificationStackSizeCalculator == null) {
             return;
         }
         boolean animate = mIsExpanded && mAnimationsEnabled;
         mFooterView.setVisible(visible, animate);
-        if (!FooterViewRefactor.isEnabled()) {
-            mFooterView.showHistory(showHistory);
-            mFooterView.setClearAllButtonVisible(showDismissView, animate);
-            mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
-        }
+        mFooterView.showHistory(showHistory);
+        mFooterView.setClearAllButtonVisible(showDismissView, animate);
+        mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
     }
 
     @VisibleForTesting
@@ -5070,7 +5076,7 @@
         if (mOwnScrollY > 0) {
             setOwnScrollY((int) MathUtils.lerp(mOwnScrollY, 0, mQsExpansionFraction));
         }
-        if (footerAffected) {
+        if (!FooterViewRefactor.isEnabled() && footerAffected) {
             updateFooter();
         }
     }
@@ -5081,6 +5087,10 @@
     }
 
     private void setOwnScrollY(int ownScrollY, boolean animateStackYChangeListener) {
+        // If scene container is active, NSSL should not control its own scrolling.
+        if (SceneContainerFlag.isEnabled()) {
+            return;
+        }
         // Avoid Flicking during clear all
         // when the shade finishes closing, onExpansionStopped will call
         // resetScrollPosition to setOwnScrollY to 0
@@ -5176,6 +5186,7 @@
     }
 
     void setUpcomingStatusBarState(int upcomingStatusBarState) {
+        FooterViewRefactor.assertInLegacyMode();
         mUpcomingStatusBarState = upcomingStatusBarState;
         if (mUpcomingStatusBarState != mStatusBarState) {
             updateFooter();
@@ -5193,7 +5204,9 @@
 
         setDimmed(onKeyguard, fromShadeLocked);
         setExpandingEnabled(!onKeyguard);
-        updateFooter();
+        if (!FooterViewRefactor.isEnabled()) {
+            updateFooter();
+        }
         requestChildrenUpdate();
         onUpdateRowStates();
         updateVisibility();
@@ -5270,8 +5283,11 @@
                     for (int i = 0; i < childCount; i++) {
                         ExpandableView child = getChildAtIndex(i);
                         child.dump(pw, args);
-                        if (child instanceof FooterView) {
-                            DumpUtilsKt.withIncreasedIndent(pw, () -> dumpFooterViewVisibility(pw));
+                        if (!FooterViewRefactor.isEnabled()) {
+                            if (child instanceof FooterView) {
+                                DumpUtilsKt.withIncreasedIndent(pw,
+                                        () -> dumpFooterViewVisibility(pw));
+                            }
                         }
                         pw.println();
                     }
@@ -5290,6 +5306,7 @@
     }
 
     private void dumpFooterViewVisibility(IndentingPrintWriter pw) {
+        FooterViewRefactor.assertInLegacyMode();
         final boolean showDismissView = shouldShowDismissView();
 
         pw.println("showFooterView: " + shouldShowFooterView(showDismissView));
@@ -5988,6 +6005,7 @@
      * Sets whether the current user is set up, which is required to show the footer (b/193149550)
      */
     public void setCurrentUserSetup(boolean isCurrentUserSetup) {
+        FooterViewRefactor.assertInLegacyMode();
         if (mIsCurrentUserSetup != isCurrentUserSetup) {
             mIsCurrentUserSetup = isCurrentUserSetup;
             updateFooter();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index a2ff406..5fa0624 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -314,8 +314,10 @@
             // The bottom might change because we're using the final actual height of the view
             mView.setAnimateBottomOnLayout(true);
         }
-        // Let's update the footer once the notifications have been updated (in the next frame)
-        mView.post(this::updateFooter);
+        if (!FooterViewRefactor.isEnabled()) {
+            // Let's update the footer once the notifications have been updated (in the next frame)
+            mView.post(this::updateFooter);
+        }
     };
 
     @VisibleForTesting
@@ -342,8 +344,8 @@
             mView.reinflateViews();
             if (!FooterViewRefactor.isEnabled()) {
                 updateShowEmptyShadeView();
+                updateFooter();
             }
-            updateFooter();
         }
 
         @Override
@@ -389,7 +391,9 @@
 
                 @Override
                 public void onUpcomingStateChanged(int newState) {
-                    mView.setUpcomingStatusBarState(newState);
+                    if (!FooterViewRefactor.isEnabled()) {
+                        mView.setUpcomingStatusBarState(newState);
+                    }
                 }
 
                 @Override
@@ -407,7 +411,9 @@
         public void onUserChanged(int userId) {
             updateSensitivenessWithAnimation(false);
             mHistoryEnabled = null;
-            updateFooter();
+            if (!FooterViewRefactor.isEnabled()) {
+                updateFooter();
+            }
         }
     };
 
@@ -810,14 +816,14 @@
         if (!FooterViewRefactor.isEnabled()) {
             mView.setFooterClearAllListener(() ->
                     mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES));
+            mView.setIsRemoteInputActive(mRemoteInputManager.isRemoteInputActive());
+            mRemoteInputManager.addControllerCallback(new RemoteInputController.Callback() {
+                @Override
+                public void onRemoteInputActive(boolean active) {
+                    mView.setIsRemoteInputActive(active);
+                }
+            });
         }
-        mView.setIsRemoteInputActive(mRemoteInputManager.isRemoteInputActive());
-        mRemoteInputManager.addControllerCallback(new RemoteInputController.Callback() {
-            @Override
-            public void onRemoteInputActive(boolean active) {
-                mView.setIsRemoteInputActive(active);
-            }
-        });
         mView.setClearAllFinishedWhilePanelExpandedRunnable(()-> {
             final Runnable doCollapseRunnable = () ->
                     mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
@@ -871,7 +877,9 @@
                     switch (key) {
                         case Settings.Secure.NOTIFICATION_HISTORY_ENABLED:
                             mHistoryEnabled = null;  // invalidate
-                            updateFooter();
+                            if (!FooterViewRefactor.isEnabled()) {
+                                updateFooter();
+                            }
                             break;
                         case HIGH_PRIORITY:
                             mView.setHighPriorityBeforeSpeedBump("1".equals(newValue));
@@ -893,9 +901,11 @@
             return kotlin.Unit.INSTANCE;
         });
 
-        // attach callback, and then call it to update mView immediately
-        mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
-        mDeviceProvisionedListener.onDeviceProvisionedChanged();
+        if (!FooterViewRefactor.isEnabled()) {
+            // attach callback, and then call it to update mView immediately
+            mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
+            mDeviceProvisionedListener.onDeviceProvisionedChanged();
+        }
 
         if (screenshareNotificationHiding()) {
             mSensitiveNotificationProtectionController
@@ -1106,8 +1116,7 @@
     }
 
     public int getVisibleNotificationCount() {
-        // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle footer
-        //  visibility in the refactored code
+        FooterViewRefactor.assertInLegacyMode();
         return mNotifStats.getNumActiveNotifs();
     }
 
@@ -1142,6 +1151,11 @@
         }
     }
 
+    /** Send internal notification expansion to the scene container framework. */
+    public void sendSyntheticScrollToSceneFramework(Float delta) {
+        mStackAppearanceInteractor.setSyntheticScroll(delta);
+    }
+
     /** Get the y-coordinate of the top bound of the stack. */
     public float getPlaceholderTop() {
         return mStackAppearanceInteractor.getStackBounds().getValue().getTop();
@@ -1509,14 +1523,14 @@
      * Return whether there are any clearable notifications
      */
     public boolean hasActiveClearableNotifications(@SelectedRows int selection) {
-        // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the footer
-        //  visibility in the refactored code
+        // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the silent
+        //  section clear action in the new stack.
         return hasNotifications(selection, true /* clearable */);
     }
 
     public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) {
-        // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the footer
-        //  visibility in the refactored code
+        // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the silent
+        //  section clear action in the new stack.
         boolean hasAlertingMatchingClearable = isClearable
                 ? mNotifStats.getHasClearableAlertingNotifs()
                 : mNotifStats.getHasNonClearableAlertingNotifs();
@@ -1558,7 +1572,9 @@
                     boolean remoteInputActive) {
                 mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
                 entry.notifyHeightChanged(true /* needsAnimation */);
-                updateFooter();
+                if (!FooterViewRefactor.isEnabled()) {
+                    updateFooter();
+                }
             }
 
             public void lockScrollTo(NotificationEntry entry) {
@@ -1573,6 +1589,7 @@
     }
 
     public void updateFooter() {
+        FooterViewRefactor.assertInLegacyMode();
         Trace.beginSection("NSSLC.updateFooter");
         mView.updateFooter();
         Trace.endSection();
@@ -2134,19 +2151,16 @@
     private class NotifStackControllerImpl implements NotifStackController {
         @Override
         public void setNotifStats(@NonNull NotifStats notifStats) {
-            // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once footer visibility
-            // is handled in the refactored stack.
+            // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the silent
+            //  section clear action in the new stack.
             mNotifStats = notifStats;
 
             if (!FooterViewRefactor.isEnabled()) {
                 mView.setHasFilteredOutSeenNotifications(
                         mSeenNotificationsInteractor
                                 .getHasFilteredOutSeenNotifications().getValue());
-            }
 
-            updateFooter();
-
-            if (!FooterViewRefactor.isEnabled()) {
+                updateFooter();
                 updateShowEmptyShadeView();
                 updateImportantForAccessibility();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 664a6b6..15fde0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -33,6 +33,7 @@
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.SourceType;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -137,7 +138,7 @@
     }
 
     private void updateAlphaState(StackScrollAlgorithmState algorithmState,
-                                  AmbientState ambientState) {
+            AmbientState ambientState) {
         for (ExpandableView view : algorithmState.visibleChildren) {
             final ViewState viewState = view.getViewState();
             final boolean isHunGoingToShade = ambientState.isShadeExpanded()
@@ -295,7 +296,7 @@
     }
 
     private void updateSpeedBumpState(StackScrollAlgorithmState algorithmState,
-                                      int speedBumpIndex) {
+            int speedBumpIndex) {
         int childCount = algorithmState.visibleChildren.size();
         int belowSpeedBump = speedBumpIndex;
         for (int i = 0; i < childCount; i++) {
@@ -322,7 +323,7 @@
     }
 
     private void updateClipping(StackScrollAlgorithmState algorithmState,
-                                AmbientState ambientState) {
+            AmbientState ambientState) {
         float drawStart = ambientState.isOnKeyguard() ? 0
                 : ambientState.getStackY() - ambientState.getScrollY();
         float clipStart = 0;
@@ -454,7 +455,7 @@
     }
 
     private int updateNotGoneIndex(StackScrollAlgorithmState state, int notGoneIndex,
-                                   ExpandableView v) {
+            ExpandableView v) {
         ExpandableViewState viewState = v.getViewState();
         viewState.notGoneIndex = notGoneIndex;
         state.visibleChildren.add(v);
@@ -480,7 +481,7 @@
      * @param ambientState   The current ambient state
      */
     protected void updatePositionsForState(StackScrollAlgorithmState algorithmState,
-                                           AmbientState ambientState) {
+            AmbientState ambientState) {
         if (!ambientState.isOnKeyguard()
                 || (ambientState.isBypassEnabled() && ambientState.isPulseExpanding())) {
             algorithmState.mCurrentYPosition += mNotificationScrimPadding;
@@ -494,7 +495,7 @@
     }
 
     private void setLocation(ExpandableViewState expandableViewState, float currentYPosition,
-                             int i) {
+            int i) {
         expandableViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
         if (currentYPosition <= 0) {
             expandableViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
@@ -598,18 +599,31 @@
                 viewEnd, /* hunMax */ ambientState.getMaxHeadsUpTranslation()
         );
         if (view instanceof FooterView) {
-            final boolean shadeClosed = !ambientState.isShadeExpanded();
-            final boolean isShelfShowing = algorithmState.firstViewInShelf != null;
-            if (shadeClosed) {
-                viewState.hidden = true;
-            } else {
+            if (FooterViewRefactor.isEnabled()) {
                 final float footerEnd = algorithmState.mCurrentExpandedYPosition
                         + view.getIntrinsicHeight();
                 final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight();
+                // TODO(b/293167744): May be able to keep only noSpaceForFooter here if we add an
+                //  emission when clearAllNotifications is called, and then use that in the footer
+                //  visibility flow.
                 ((FooterView.FooterViewState) viewState).hideContent =
-                        isShelfShowing || noSpaceForFooter
-                                || (ambientState.isClearAllInProgress()
+                        noSpaceForFooter || (ambientState.isClearAllInProgress()
                                 && !hasNonClearableNotifs(algorithmState));
+
+            } else {
+                final boolean shadeClosed = !ambientState.isShadeExpanded();
+                final boolean isShelfShowing = algorithmState.firstViewInShelf != null;
+                if (shadeClosed) {
+                    viewState.hidden = true;
+                } else {
+                    final float footerEnd = algorithmState.mCurrentExpandedYPosition
+                            + view.getIntrinsicHeight();
+                    final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight();
+                    ((FooterView.FooterViewState) viewState).hideContent =
+                            isShelfShowing || noSpaceForFooter
+                                    || (ambientState.isClearAllInProgress()
+                                    && !hasNonClearableNotifs(algorithmState));
+                }
             }
         } else {
             if (view instanceof EmptyShadeView) {
@@ -731,7 +745,7 @@
 
     @VisibleForTesting
     void updatePulsingStates(StackScrollAlgorithmState algorithmState,
-                                     AmbientState ambientState) {
+            AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
         ExpandableNotificationRow pulsingRow = null;
         for (int i = 0; i < childCount; i++) {
@@ -761,7 +775,7 @@
     }
 
     private void updateHeadsUpStates(StackScrollAlgorithmState algorithmState,
-                                     AmbientState ambientState) {
+            AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
 
         // Move the tracked heads up into position during the appear animation, by interpolating
@@ -870,18 +884,18 @@
     boolean shouldHunBeVisibleWhenScrolled(boolean mustStayOnScreen, boolean headsUpIsVisible,
             boolean showingPulsing, boolean isOnKeyguard, boolean headsUpOnKeyguard) {
         return mustStayOnScreen && !headsUpIsVisible
-                        && !showingPulsing
-                        && (!isOnKeyguard || headsUpOnKeyguard);
+                && !showingPulsing
+                && (!isOnKeyguard || headsUpOnKeyguard);
     }
 
-     /**
+    /**
      * When shade is open and we are scrolled to the bottom of notifications,
      * clamp incoming HUN in its collapsed form, right below qs offset.
      * Transition pinned collapsed HUN to full height when scrolling back up.
      */
     @VisibleForTesting
     void clampHunToTop(float clampInset, float stackTranslation, float collapsedHeight,
-                       ExpandableViewState viewState) {
+            ExpandableViewState viewState) {
 
         final float newTranslation = Math.max(clampInset + stackTranslation,
                 viewState.getYTranslation());
@@ -896,7 +910,7 @@
     // Pin HUN to bottom of expanded QS
     // while the rest of notifications are scrolled offscreen.
     private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row,
-                                          ExpandableViewState childState) {
+            ExpandableViewState childState) {
         float maxHeadsUpTranslation = ambientState.getMaxHeadsUpTranslation();
         final float maxShelfPosition = ambientState.getInnerHeight() + ambientState.getTopPadding()
                 + ambientState.getStackTranslation();
@@ -919,7 +933,7 @@
 
     @VisibleForTesting
     float computeCornerRoundnessForPinnedHun(float hostViewHeight, float stackY,
-                                             float viewMaxHeight, float originalCornerRadius) {
+            float viewMaxHeight, float originalCornerRadius) {
 
         // Compute y where corner roundness should be in its original unpinned state.
         // We use view max height because the pinned collapsed HUN expands to max height
@@ -948,7 +962,7 @@
      * @param ambientState   The ambient state of the algorithm
      */
     private void updateZValuesForState(StackScrollAlgorithmState algorithmState,
-                                       AmbientState ambientState) {
+            AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
         float childrenOnTop = 0.0f;
 
@@ -976,13 +990,13 @@
      *                      vertically top of screen. Top HUNs should have drop shadows
      * @param childrenOnTop It is greater than 0 when there's an existing HUN that is elevated
      * @return childrenOnTop The decimal part represents the fraction of the elevated HUN's height
-     *                      that overlaps with QQS Panel. The integer part represents the count of
-     *                      previous HUNs whose Z positions are greater than 0.
+     * that overlaps with QQS Panel. The integer part represents the count of
+     * previous HUNs whose Z positions are greater than 0.
      */
     protected float updateChildZValue(int i, float childrenOnTop,
-                                      StackScrollAlgorithmState algorithmState,
-                                      AmbientState ambientState,
-                                      boolean isTopHun) {
+            StackScrollAlgorithmState algorithmState,
+            AmbientState ambientState,
+            boolean isTopHun) {
         ExpandableView child = algorithmState.visibleChildren.get(i);
         ExpandableViewState childViewState = child.getViewState();
         float baseZ = ambientState.getBaseZHeight();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
index 311ba83..9efe632 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
@@ -47,4 +47,11 @@
      * further.
      */
     val scrolledToTop = MutableStateFlow(true)
+
+    /**
+     * The amount in px that the notification stack should scroll due to internal expansion. This
+     * should only happen when a notification expansion hits the bottom of the screen, so it is
+     * necessary to scroll up to keep expanding the notification.
+     */
+    val syntheticScroll = MutableStateFlow(0f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index 9984ba9..08df473 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.stack.data.repository.NotificationStackAppearanceRepository
 import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
@@ -50,6 +51,13 @@
      */
     val scrolledToTop: StateFlow<Boolean> = repository.scrolledToTop.asStateFlow()
 
+    /**
+     * The amount in px that the notification stack should scroll due to internal expansion. This
+     * should only happen when a notification expansion hits the bottom of the screen, so it is
+     * necessary to scroll up to keep expanding the notification.
+     */
+    val syntheticScroll: Flow<Float> = repository.syntheticScroll.asStateFlow()
+
     /** Sets the position of the notification stack in the current scene. */
     fun setStackBounds(bounds: NotificationContainerBounds) {
         check(bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" }
@@ -70,4 +78,9 @@
     fun setScrolledToTop(scrolledToTop: Boolean) {
         repository.scrolledToTop.value = scrolledToTop
     }
+
+    /** Sets the amount (px) that the notification stack should scroll due to internal expansion. */
+    fun setSyntheticScroll(delta: Float) {
+        repository.syntheticScroll.value = delta
+    }
 }
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 4d65b9d..883aa9b 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
@@ -18,11 +18,10 @@
 
 import android.view.LayoutInflater
 import androidx.lifecycle.lifecycleScope
-import com.android.app.tracing.traceSection
+import com.android.app.tracing.TraceUtils.traceAsync
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.logging.nano.MetricsProto
 import com.android.systemui.common.ui.ConfigurationState
-import com.android.systemui.common.ui.reinflateAndBindLatest
 import com.android.systemui.common.ui.view.setImportantForAccessibilityYesNo
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -33,6 +32,7 @@
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
 import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
+import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerShelfViewBinder
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder
@@ -43,12 +43,18 @@
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.HideNotificationsBinder.bindHideList
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
+import com.android.systemui.util.kotlin.awaitCancellationThenDispose
 import com.android.systemui.util.kotlin.getOrNull
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.value
 import java.util.Optional
 import javax.inject.Inject
 import javax.inject.Provider
 import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.launch
 
 /** Binds a [NotificationStackScrollLayout] to its [view model][NotificationListViewModel]. */
@@ -83,7 +89,7 @@
                 bindHideList(viewController, viewModel, hiderTracker)
 
                 if (FooterViewRefactor.isEnabled) {
-                    launch { bindFooter(view) }
+                    launch { reinflateAndBindFooter(view) }
                     launch { bindEmptyShade(view) }
                     launch {
                         viewModel.isImportantForAccessibility.collect { isImportantForAccessibility
@@ -108,42 +114,61 @@
         )
     }
 
-    private suspend fun bindFooter(parentView: NotificationStackScrollLayout) {
+    private suspend fun reinflateAndBindFooter(parentView: NotificationStackScrollLayout) {
         viewModel.footer.getOrNull()?.let { footerViewModel ->
             // The footer needs to be re-inflated every time the theme or the font size changes.
-            configuration.reinflateAndBindLatest(
-                R.layout.status_bar_notification_footer,
-                parentView,
-                attachToRoot = false,
-                backgroundDispatcher,
-            ) { footerView: FooterView ->
-                traceSection("bind FooterView") {
-                    val disposableHandle =
-                        FooterViewBinder.bindWhileAttached(
-                            footerView,
-                            footerViewModel,
-                            clearAllNotifications = {
-                                metricsLogger.action(
-                                    MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES
-                                )
-                                parentView.clearAllNotifications()
-                            },
-                            launchNotificationSettings = { view ->
-                                notificationActivityStarter
-                                    .get()
-                                    .startHistoryIntent(view, /* showHistory = */ false)
-                            },
-                            launchNotificationHistory = { view ->
-                                notificationActivityStarter
-                                    .get()
-                                    .startHistoryIntent(view, /* showHistory = */ true)
-                            },
-                        )
-                    parentView.setFooterView(footerView)
-                    return@reinflateAndBindLatest disposableHandle
+            configuration
+                .inflateLayout<FooterView>(
+                    R.layout.status_bar_notification_footer,
+                    parentView,
+                    attachToRoot = false,
+                )
+                .flowOn(backgroundDispatcher)
+                .collectLatest { footerView: FooterView ->
+                    traceAsync("bind FooterView") {
+                        parentView.setFooterView(footerView)
+                        bindFooter(footerView, footerViewModel, parentView)
+                    }
                 }
+        }
+    }
+
+    /**
+     * Binds the footer (including its visibility) and dispose of the [DisposableHandle] when done.
+     */
+    private suspend fun bindFooter(
+        footerView: FooterView,
+        footerViewModel: FooterViewModel,
+        parentView: NotificationStackScrollLayout
+    ): Unit = coroutineScope {
+        val disposableHandle =
+            FooterViewBinder.bindWhileAttached(
+                footerView,
+                footerViewModel,
+                clearAllNotifications = {
+                    metricsLogger.action(MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES)
+                    parentView.clearAllNotifications()
+                },
+                launchNotificationSettings = { view ->
+                    notificationActivityStarter
+                        .get()
+                        .startHistoryIntent(view, /* showHistory = */ false)
+                },
+                launchNotificationHistory = { view ->
+                    notificationActivityStarter
+                        .get()
+                        .startHistoryIntent(view, /* showHistory = */ true)
+                },
+            )
+        launch {
+            viewModel.shouldShowFooterView.collect { animatedVisibility ->
+                footerView.setVisible(
+                    /* visible = */ animatedVisibility.value,
+                    /* animate = */ animatedVisibility.isAnimating,
+                )
             }
         }
+        disposableHandle.awaitCancellationThenDispose()
     }
 
     private suspend fun bindEmptyShade(parentView: NotificationStackScrollLayout) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
index 814146c..a157785 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
@@ -69,6 +69,9 @@
                         controller.setMaxAlphaForExpansion(
                             ((expandFraction - 0.5f) / 0.5f).coerceAtLeast(0f)
                         )
+                        if (expandFraction == 0f || expandFraction == 1f) {
+                            controller.onExpansionStopped()
+                        }
                     }
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 86c0a678..a6c6586 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -16,21 +16,32 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
 import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
+import com.android.systemui.statusbar.policy.domain.interactor.UserSetupInteractor
 import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
+import com.android.systemui.util.kotlin.combine
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.ui.AnimatableEvent
+import com.android.systemui.util.ui.AnimatedValue
+import com.android.systemui.util.ui.toAnimatedValueFlow
 import java.util.Optional
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 
 /** ViewModel for the list of notifications. */
@@ -42,9 +53,13 @@
     val footer: Optional<FooterViewModel>,
     val logger: Optional<NotificationLoggerViewModel>,
     activeNotificationsInteractor: ActiveNotificationsInteractor,
+    keyguardInteractor: KeyguardInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    powerInteractor: PowerInteractor,
+    remoteInputInteractor: RemoteInputInteractor,
     seenNotificationsInteractor: SeenNotificationsInteractor,
     shadeInteractor: ShadeInteractor,
+    userSetupInteractor: UserSetupInteractor,
     zenModeInteractor: ZenModeInteractor,
 ) {
     /**
@@ -76,6 +91,10 @@
             combine(
                     activeNotificationsInteractor.areAnyNotificationsPresent,
                     shadeInteractor.isQsFullscreen,
+                    // TODO(b/293167744): It looks like we're essentially trying to check the same
+                    //  things for the empty shade visibility as we do for the footer, just in a
+                    //  slightly different way. We should change this so we also check
+                    //  statusBarState and isAwake instead of specific keyguard transitions.
                     keyguardTransitionInteractor.isInTransitionToState(KeyguardState.AOD).onStart {
                         emit(false)
                     },
@@ -97,6 +116,80 @@
         }
     }
 
+    val shouldShowFooterView: Flow<AnimatedValue<Boolean>> by lazy {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+            flowOf(AnimatedValue.NotAnimating(false))
+        } else {
+            combine(
+                    activeNotificationsInteractor.areAnyNotificationsPresent,
+                    userSetupInteractor.isUserSetUp,
+                    keyguardInteractor.statusBarState.map { it == StatusBarState.KEYGUARD },
+                    shadeInteractor.qsExpansion,
+                    shadeInteractor.isQsFullscreen,
+                    powerInteractor.isAsleep,
+                    remoteInputInteractor.isRemoteInputActive,
+                    shadeInteractor.shadeExpansion.map { it == 0f }
+                ) {
+                    hasNotifications,
+                    isUserSetUp,
+                    isOnKeyguard,
+                    qsExpansion,
+                    qsFullScreen,
+                    isAsleep,
+                    isRemoteInputActive,
+                    isShadeClosed ->
+                    Pair(
+                        // Should the footer be visible?
+                        when {
+                            !hasNotifications -> false
+                            // Hide the footer until the user setup is complete, to prevent access
+                            // to settings (b/193149550).
+                            !isUserSetUp -> false
+                            // Do not show the footer if the lockscreen is visible (incl. AOD),
+                            // except if the shade is opened on top. See also b/219680200.
+                            isOnKeyguard -> false
+                            // Make sure we're not showing the footer in the transition to AOD while
+                            // going to sleep (b/190227875). The StatusBarState is unfortunately not
+                            // updated quickly enough when the power button is pressed, so this is
+                            // necessary in addition to the isOnKeyguard check.
+                            isAsleep -> false
+                            // Do not show the footer if quick settings are fully expanded (except
+                            // for the foldable split shade view). See b/201427195 && b/222699879.
+                            qsExpansion == 1f && qsFullScreen -> false
+                            // Hide the footer if remote input is active (i.e. user is replying to a
+                            // notification). See b/75984847.
+                            isRemoteInputActive -> false
+                            // Never show the footer if the shade is collapsed (e.g. when HUNing).
+                            isShadeClosed -> false
+                            else -> true
+                        },
+                        // This could in theory be in the .sample below, but it tends to be
+                        // inconsistent, so we're passing it on to make sure we have the same state.
+                        isOnKeyguard
+                    )
+                }
+                .distinctUntilChanged()
+                // Should we animate the visibility change?
+                .sample(
+                    // TODO(b/322167853): This check is currently duplicated in FooterViewModel,
+                    //  but instead it should be a field in ShadeAnimationInteractor.
+                    combine(
+                            shadeInteractor.isShadeFullyExpanded,
+                            shadeInteractor.isShadeTouchable,
+                            ::Pair
+                        )
+                        .onStart { emit(Pair(false, false)) }
+                ) { (visible, isOnKeyguard), (isShadeFullyExpanded, animationsEnabled) ->
+                    // Animate if the shade is interactive, but NOT on the lockscreen. Having
+                    // animations enabled while on the lockscreen makes the footer appear briefly
+                    // when transitioning between the shade and keyguard.
+                    val shouldAnimate = isShadeFullyExpanded && animationsEnabled && !isOnKeyguard
+                    AnimatableEvent(visible, shouldAnimate)
+                }
+                .toAnimatedValueFlow()
+        }
+    }
+
     // TODO(b/308591475): This should be tracked separately by the empty shade.
     val areNotificationsHiddenInShade: Flow<Boolean> by lazy {
         if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
index bdf1a64..3a0f03f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
@@ -28,6 +28,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 
 /** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */
 @SysUISingleton
@@ -45,31 +46,32 @@
      */
     val expandFraction: Flow<Float> =
         combine(
-            shadeInteractor.shadeExpansion,
-            sceneInteractor.transitionState,
-        ) { shadeExpansion, transitionState ->
-            when (transitionState) {
-                is ObservableTransitionState.Idle -> {
-                    if (transitionState.scene == SceneKey.Lockscreen) {
-                        1f
-                    } else {
-                        shadeExpansion
+                shadeInteractor.shadeExpansion,
+                sceneInteractor.transitionState,
+            ) { shadeExpansion, transitionState ->
+                when (transitionState) {
+                    is ObservableTransitionState.Idle -> {
+                        if (transitionState.scene == SceneKey.Lockscreen) {
+                            1f
+                        } else {
+                            shadeExpansion
+                        }
                     }
-                }
-                is ObservableTransitionState.Transition -> {
-                    if (
-                        (transitionState.fromScene == SceneKey.Shade &&
-                            transitionState.toScene == SceneKey.QuickSettings) ||
-                            (transitionState.fromScene == SceneKey.QuickSettings &&
-                                transitionState.toScene == SceneKey.Shade)
-                    ) {
-                        1f
-                    } else {
-                        shadeExpansion
+                    is ObservableTransitionState.Transition -> {
+                        if (
+                            (transitionState.fromScene == SceneKey.Shade &&
+                                transitionState.toScene == SceneKey.QuickSettings) ||
+                                (transitionState.fromScene == SceneKey.QuickSettings &&
+                                    transitionState.toScene == SceneKey.Shade)
+                        ) {
+                            1f
+                        } else {
+                            shadeExpansion
+                        }
                     }
                 }
             }
-        }
+            .distinctUntilChanged()
 
     /** The bounds of the notification stack in the current scene. */
     val stackBounds: Flow<NotificationContainerBounds> = stackAppearanceInteractor.stackBounds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 65d9c9f..7ac5cd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -86,6 +86,13 @@
      */
     val expandFraction: Flow<Float> = shadeInteractor.shadeExpansion
 
+    /**
+     * The amount in px that the notification stack should scroll due to internal expansion. This
+     * should only happen when a notification expansion hits the bottom of the screen, so it is
+     * necessary to scroll up to keep expanding the notification.
+     */
+    val syntheticScroll: Flow<Float> = interactor.syntheticScroll
+
     /** Sets the y-coord in px of the top of the contents of the notification stack. */
     fun onContentTopChanged(padding: Float) {
         interactor.setContentTop(padding)
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 3915c376..811da51 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
@@ -21,6 +21,7 @@
 
 import com.android.systemui.common.shared.model.NotificationContainerBounds
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -31,20 +32,24 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
 import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
@@ -70,6 +75,7 @@
 import kotlinx.coroutines.isActive
 
 /** View-model for the shared notification container, used by both the shade and keyguard spaces */
+@SysUISingleton
 class SharedNotificationContainerViewModel
 @Inject
 constructor(
@@ -80,6 +86,9 @@
     private val shadeInteractor: ShadeInteractor,
     communalInteractor: CommunalInteractor,
     private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+    lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel,
+    alternateBouncerToGoneTransitionViewModel: AlternateBouncerToGoneTransitionViewModel,
+    primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
     lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel,
     dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
     lockscreenToDreamingTransitionViewModel: LockscreenToDreamingTransitionViewModel,
@@ -94,6 +103,12 @@
         mapOf<Edge?, Flow<Float>>(
             Edge(from = LOCKSCREEN, to = DREAMING) to
                 lockscreenToDreamingTransitionViewModel.lockscreenAlpha,
+            Edge(from = LOCKSCREEN, to = GONE) to
+                lockscreenToGoneTransitionViewModel.lockscreenAlpha,
+            Edge(from = ALTERNATE_BOUNCER, to = GONE) to
+                alternateBouncerToGoneTransitionViewModel.lockscreenAlpha,
+            Edge(from = PRIMARY_BOUNCER, to = GONE) to
+                primaryBouncerToGoneTransitionViewModel.lockscreenAlpha,
             Edge(from = DREAMING, to = LOCKSCREEN) to
                 dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
             Edge(from = LOCKSCREEN, to = OCCLUDED) to
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 8a56da3..b49af0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -837,7 +837,7 @@
             if (animationController == null) {
                 return null
             }
-            val rootView = animationController.launchContainer.rootView
+            val rootView = animationController.transitionContainer.rootView
             val controllerFromStatusBar: Optional<ActivityLaunchAnimator.Controller> =
                 statusBarWindowController.wrapAnimationControllerIfInStatusBar(
                     rootView,
@@ -881,8 +881,8 @@
                         }
                     }
 
-                    override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
-                        super.onLaunchAnimationStart(isExpandingFullyAbove)
+                    override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
+                        super.onTransitionAnimationStart(isExpandingFullyAbove)
 
                         // Double check that the keyguard is still showing and not going
                         // away, but if so set the keyguard occluded. Typically, WM will let
@@ -902,14 +902,14 @@
                         }
                     }
 
-                    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+                    override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
                         // Set mIsLaunchingActivityOverLockscreen to false before actually
                         // finishing the animation so that we can assume that
                         // mIsLaunchingActivityOverLockscreen being true means that we will
                         // collapse the shade (or at least run the post collapse runnables)
                         // later on.
                         centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
-                        delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+                        delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
                     }
 
                     override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) {
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 2099361..3e7089c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -28,6 +28,7 @@
 
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 import static com.android.systemui.Flags.lightRevealMigration;
+import static com.android.systemui.Flags.newAodTransition;
 import static com.android.systemui.Flags.predictiveBackSysui;
 import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
 import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
@@ -2497,7 +2498,8 @@
             mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
                 mDeviceInteractive = true;
 
-                if (shouldAnimateDozeWakeup()) {
+                boolean isFlaggedOff = newAodTransition() && KeyguardShadeMigrationNssl.isEnabled();
+                if (!isFlaggedOff && shouldAnimateDozeWakeup()) {
                     // If this is false, the power button must be physically pressed in order to
                     // trigger fingerprint authentication.
                     final boolean touchToUnlockAnytime = Settings.Secure.getIntForUser(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index be5c6b3..8e3d678 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -349,8 +349,12 @@
             }
         }
         if (child instanceof StatusBarIconView) {
-            ((StatusBarIconView) child).updateIconDimens();
-            if (!NotificationIconContainerRefactor.isEnabled()) {
+            if (NotificationIconContainerRefactor.isEnabled()) {
+                if (!mChangingViewPositions) {
+                    ((StatusBarIconView) child).updateIconDimens();
+                }
+            } else {
+                ((StatusBarIconView) child).updateIconDimens();
                 ((StatusBarIconView) child).setDozing(mDozing, false, 0);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 69282ae..3ad60d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -1442,10 +1442,6 @@
             hideAlternateBouncer(false);
             executeAfterKeyguardGoneAction();
         }
-
-        if (KeyguardWmStateRefactor.isEnabled()) {
-            mKeyguardTransitionInteractor.startDismissKeyguardTransition();
-        }
     }
 
     /** Display security message to relevant KeyguardMessageArea. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
index 8ca5bfc..d43f470 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
@@ -2,7 +2,7 @@
 
 import android.view.View
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.LaunchAnimator
+import com.android.systemui.animation.TransitionAnimator
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
@@ -34,8 +34,8 @@
         }
     }
 
-    override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
-        delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+    override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
+        delegate.onTransitionAnimationStart(isExpandingFullyAbove)
         shadeAnimationInteractor.setIsLaunchingActivity(true)
         if (!isExpandingFullyAbove) {
             shadeViewController.collapseWithDuration(
@@ -43,18 +43,18 @@
         }
     }
 
-    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
-        delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+    override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+        delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
         shadeAnimationInteractor.setIsLaunchingActivity(false)
         shadeController.onLaunchAnimationEnd(isExpandingFullyAbove)
     }
 
-    override fun onLaunchAnimationProgress(
-        state: LaunchAnimator.State,
+    override fun onTransitionAnimationProgress(
+        state: TransitionAnimator.State,
         progress: Float,
         linearProgress: Float
     ) {
-        delegate.onLaunchAnimationProgress(state, progress, linearProgress)
+        delegate.onTransitionAnimationProgress(state, progress, linearProgress)
         shadeViewController.applyLaunchAnimationProgress(linearProgress)
     }
 
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 246645e..72f4540 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
@@ -15,20 +15,14 @@
  */
 package com.android.systemui.statusbar.phone.domain.interactor
 
-import android.graphics.Rect
 import com.android.systemui.statusbar.phone.data.repository.DarkIconRepository
+import com.android.systemui.statusbar.phone.domain.model.DarkState
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.map
 
 /** States pertaining to calculating colors for icons in dark mode. */
 class DarkIconInteractor @Inject constructor(repository: DarkIconRepository) {
-    /** @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.areas */
-    val tintAreas: Flow<Collection<Rect>> = repository.darkState.map { it.areas }
-    /**
-     * @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.darkIntensity
-     */
-    val darkIntensity: Flow<Float> = repository.darkState.map { it.darkIntensity }
-    /** @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.tint */
-    val tintColor: Flow<Int> = repository.darkState.map { it.tint }
+    /** Dark-mode state for tinting icons. */
+    val darkState: Flow<DarkState> = repository.darkState.map { DarkState(it.areas, it.tint) }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/model/DarkState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/model/DarkState.kt
new file mode 100644
index 0000000..3cab7cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/model/DarkState.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.domain.model
+
+import android.graphics.Rect
+
+/** Dark mode visual states. */
+data class DarkState(
+    /** Areas on screen that require a dark-mode adjustment. */
+    val areas: Collection<Rect>,
+    /** Tint color to apply to UI elements that fall within [areas]. */
+    val tint: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index 3741f14..c0e36b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -91,7 +91,9 @@
     @StatusBarFragmentScope
     @Named(OPERATOR_NAME_VIEW)
     static View provideOperatorNameView(@RootView PhoneStatusBarView view) {
-        return ((ViewStub) view.findViewById(R.id.operator_name_stub)).inflate();
+        View operatorName = ((ViewStub) view.findViewById(R.id.operator_name_stub)).inflate();
+        operatorName.setVisibility(View.GONE);
+        return operatorName;
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt
index 63566ee..e1798d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.satellite.ui.model
 
+import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
@@ -36,7 +37,10 @@
             SatelliteConnectionState.On ->
                 Icon.Resource(
                     res = R.drawable.ic_satellite_not_connected,
-                    contentDescription = null,
+                    contentDescription =
+                        ContentDescription.Resource(
+                            R.string.accessibility_status_bar_satellite_available
+                        ),
                 )
             SatelliteConnectionState.Connected -> fromSignalStrength(signalStrength)
         }
@@ -51,15 +55,36 @@
         // TODO(b/316634365): these need content descriptions
         when (signalStrength) {
             // No signal
-            0 -> Icon.Resource(res = R.drawable.ic_satellite_connected_0, contentDescription = null)
+            0 ->
+                Icon.Resource(
+                    res = R.drawable.ic_satellite_connected_0,
+                    contentDescription =
+                        ContentDescription.Resource(
+                            R.string.accessibility_status_bar_satellite_no_connection
+                        )
+                )
 
             // Poor -> Moderate
             1,
-            2 -> Icon.Resource(res = R.drawable.ic_satellite_connected_1, contentDescription = null)
+            2 ->
+                Icon.Resource(
+                    res = R.drawable.ic_satellite_connected_1,
+                    contentDescription =
+                        ContentDescription.Resource(
+                            R.string.accessibility_status_bar_satellite_poor_connection
+                        )
+                )
 
             // Good -> Great
             3,
-            4 -> Icon.Resource(res = R.drawable.ic_satellite_connected_2, contentDescription = null)
+            4 ->
+                Icon.Resource(
+                    res = R.drawable.ic_satellite_connected_2,
+                    contentDescription =
+                        ContentDescription.Resource(
+                            R.string.accessibility_status_bar_satellite_good_connection
+                        )
+                )
             else -> null
         }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index b598782..21d3fa4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -47,13 +47,13 @@
 import android.view.WindowManager;
 
 import com.android.internal.policy.SystemBarUtils;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.DelegateLaunchAnimatorController;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.fragments.FragmentService;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
 import com.android.systemui.unfold.util.JankMonitorTransitionProgressListener;
@@ -194,17 +194,17 @@
             return Optional.empty();
         }
 
-        animationController.setLaunchContainer(mLaunchAnimationContainer);
+        animationController.setTransitionContainer(mLaunchAnimationContainer);
         return Optional.of(new DelegateLaunchAnimatorController(animationController) {
             @Override
-            public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {
-                getDelegate().onLaunchAnimationStart(isExpandingFullyAbove);
+            public void onTransitionAnimationStart(boolean isExpandingFullyAbove) {
+                getDelegate().onTransitionAnimationStart(isExpandingFullyAbove);
                 setLaunchAnimationRunning(true);
             }
 
             @Override
-            public void onLaunchAnimationEnd(boolean isExpandingFullyAbove) {
-                getDelegate().onLaunchAnimationEnd(isExpandingFullyAbove);
+            public void onTransitionAnimationEnd(boolean isExpandingFullyAbove) {
+                getDelegate().onTransitionAnimationEnd(isExpandingFullyAbove);
                 setLaunchAnimationRunning(false);
             }
         });
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
new file mode 100644
index 0000000..5c53ff9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
@@ -0,0 +1,153 @@
+/*
+ * 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.unfold
+
+import android.animation.ValueAnimator
+import android.annotation.BinderThread
+import android.content.Context
+import android.os.Handler
+import android.os.SystemProperties
+import android.util.Log
+import android.view.animation.DecelerateInterpolator
+import androidx.core.animation.addListener
+import com.android.internal.foldables.FoldLockSettingAvailabilityProvider
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DeviceStateRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.ScreenPowerState
+import com.android.systemui.statusbar.LinearSideLightRevealEffect
+import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.ALPHA_OPAQUE
+import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.ALPHA_TRANSPARENT
+import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.isVerticalRotation
+import com.android.systemui.unfold.dagger.UnfoldBg
+import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.android.asCoroutineDispatcher
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withTimeout
+
+class FoldLightRevealOverlayAnimation
+@Inject
+constructor(
+    private val context: Context,
+    @UnfoldBg private val bgHandler: Handler,
+    private val deviceStateRepository: DeviceStateRepository,
+    private val powerInteractor: PowerInteractor,
+    @Background private val applicationScope: CoroutineScope,
+    private val animationStatusRepository: AnimationStatusRepository,
+    private val controllerFactory: FullscreenLightRevealAnimationController.Factory
+) : FullscreenLightRevealAnimation {
+
+    private val revealProgressValueAnimator: ValueAnimator =
+        ValueAnimator.ofFloat(ALPHA_OPAQUE, ALPHA_TRANSPARENT)
+    private lateinit var controller: FullscreenLightRevealAnimationController
+    @Volatile private var readyCallback: CompletableDeferred<Runnable>? = null
+
+    override fun init() {
+        // This method will be called only on devices where this animation is enabled,
+        // so normally this thread won't be created
+        if (!FoldLockSettingAvailabilityProvider(context.resources).isFoldLockBehaviorAvailable) {
+            return
+        }
+
+        controller =
+            controllerFactory.create(
+                displaySelector = { minByOrNull { it.naturalWidth } },
+                effectFactory = { LinearSideLightRevealEffect(it.isVerticalRotation()) },
+                overlayContainerName = SURFACE_CONTAINER_NAME
+            )
+        controller.init()
+
+        applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
+            powerInteractor.screenPowerState.collect {
+                if (it == ScreenPowerState.SCREEN_ON) {
+                    readyCallback = null
+                }
+            }
+        }
+
+        applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
+            deviceStateRepository.state
+                .map { it != DeviceStateRepository.DeviceState.FOLDED }
+                .distinctUntilChanged()
+                .filter { isUnfolded -> isUnfolded }
+                .collect { controller.ensureOverlayRemoved() }
+        }
+
+        applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
+            deviceStateRepository.state
+                .filter {
+                    animationStatusRepository.areAnimationsEnabled().first() &&
+                        it == DeviceStateRepository.DeviceState.FOLDED
+                }
+                .collect {
+                    try {
+                        withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) {
+                            readyCallback = CompletableDeferred()
+                            val onReady = readyCallback?.await()
+                            readyCallback = null
+                            controller.addOverlay(ALPHA_OPAQUE, onReady)
+                            waitForScreenTurnedOn()
+                            playFoldLightRevealOverlayAnimation()
+                        }
+                    } catch (e: TimeoutCancellationException) {
+                        Log.e(TAG, "Fold light reveal animation timed out")
+                        ensureOverlayRemovedInternal()
+                    }
+                }
+        }
+    }
+
+    @BinderThread
+    override fun onScreenTurningOn(onOverlayReady: Runnable) {
+        readyCallback?.complete(onOverlayReady) ?: onOverlayReady.run()
+    }
+
+    private suspend fun waitForScreenTurnedOn() {
+        powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
+    }
+
+    private fun ensureOverlayRemovedInternal() {
+        revealProgressValueAnimator.cancel()
+        controller.ensureOverlayRemoved()
+    }
+
+    private fun playFoldLightRevealOverlayAnimation() {
+        revealProgressValueAnimator.duration = ANIMATION_DURATION
+        revealProgressValueAnimator.interpolator = DecelerateInterpolator()
+        revealProgressValueAnimator.addUpdateListener { animation ->
+            controller.updateRevealAmount(animation.animatedFraction)
+        }
+        revealProgressValueAnimator.addListener(onEnd = { controller.ensureOverlayRemoved() })
+        revealProgressValueAnimator.start()
+    }
+
+    private companion object {
+        const val TAG = "FoldLightRevealOverlayAnimation"
+        const val WAIT_FOR_ANIMATION_TIMEOUT_MS = 2000L
+        const val SURFACE_CONTAINER_NAME = "fold-overlay-container"
+        val ANIMATION_DURATION: Long
+            get() = SystemProperties.getLong("persist.fold_animation_duration", 200L)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt
new file mode 100644
index 0000000..668b143
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt
@@ -0,0 +1,271 @@
+/*
+ * 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.unfold
+
+import android.content.Context
+import android.graphics.PixelFormat
+import android.hardware.display.DisplayManager
+import android.os.Handler
+import android.os.Looper
+import android.os.Trace
+import android.view.Choreographer
+import android.view.Display
+import android.view.DisplayInfo
+import android.view.Surface
+import android.view.Surface.Rotation
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.SurfaceSession
+import android.view.WindowManager
+import android.view.WindowlessWindowManager
+import com.android.app.tracing.traceSection
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.statusbar.LightRevealEffect
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.unfold.dagger.UnfoldBg
+import com.android.systemui.unfold.updates.RotationChangeProvider
+import com.android.systemui.util.concurrency.ThreadFactory
+import com.android.wm.shell.displayareahelper.DisplayAreaHelper
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.lang.IllegalArgumentException
+import java.util.Optional
+import java.util.concurrent.Executor
+import java.util.function.Consumer
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.launch
+
+interface FullscreenLightRevealAnimation {
+    fun init()
+
+    fun onScreenTurningOn(onOverlayReady: Runnable)
+}
+
+class FullscreenLightRevealAnimationController
+@AssistedInject
+constructor(
+    private val context: Context,
+    private val displayManager: DisplayManager,
+    private val threadFactory: ThreadFactory,
+    @UnfoldBg private val bgHandler: Handler,
+    @UnfoldBg private val rotationChangeProvider: RotationChangeProvider,
+    private val displayAreaHelper: Optional<DisplayAreaHelper>,
+    private val displayTracker: DisplayTracker,
+    @Background private val applicationScope: CoroutineScope,
+    @Main private val executor: Executor,
+    @Assisted private val displaySelector: Sequence<DisplayInfo>.() -> DisplayInfo?,
+    @Assisted private val lightRevealEffectFactory: (rotation: Int) -> LightRevealEffect,
+    @Assisted private val overlayContainerName: String
+) {
+
+    private lateinit var bgExecutor: Executor
+    private lateinit var wwm: WindowlessWindowManager
+
+    private var currentRotation: Int = context.display.rotation
+    private var root: SurfaceControlViewHost? = null
+    private var scrimView: LightRevealScrim? = null
+
+    private val rotationWatcher = RotationWatcher()
+    private val internalDisplayInfos: Sequence<DisplayInfo>
+        get() =
+            displayManager
+                .getDisplays(DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
+                .asSequence()
+                .map { DisplayInfo().apply { it.getDisplayInfo(this) } }
+                .filter { it.type == Display.TYPE_INTERNAL }
+
+    var isTouchBlocked: Boolean = false
+        set(value) {
+            if (value != field) {
+                traceSection("$TAG#relayoutToUpdateTouch") { root?.relayout(getLayoutParams()) }
+                field = value
+            }
+        }
+
+    fun init() {
+        bgExecutor = threadFactory.buildDelayableExecutorOnHandler(bgHandler)
+        rotationChangeProvider.addCallback(rotationWatcher)
+
+        buildSurface { builder ->
+            applicationScope.launch(executor.asCoroutineDispatcher()) {
+                val overlayContainer = builder.build()
+
+                SurfaceControl.Transaction()
+                    .setLayer(overlayContainer, OVERLAY_LAYER_Z_INDEX)
+                    .show(overlayContainer)
+                    .apply()
+
+                wwm =
+                    WindowlessWindowManager(context.resources.configuration, overlayContainer, null)
+            }
+        }
+    }
+
+    fun addOverlay(
+        initialAlpha: Float,
+        onOverlayReady: Runnable? = null,
+    ) {
+        if (!::wwm.isInitialized) {
+            // Surface overlay is not created yet on the first SysUI launch
+            onOverlayReady?.run()
+            return
+        }
+        ensureInBackground()
+        ensureOverlayRemoved()
+        prepareOverlay(onOverlayReady, wwm, bgExecutor, initialAlpha)
+    }
+
+    fun ensureOverlayRemoved() {
+        ensureInBackground()
+
+        traceSection("ensureOverlayRemoved") {
+            root?.release()
+            root = null
+            scrimView = null
+        }
+    }
+
+    fun isOverlayVisible(): Boolean {
+        return scrimView == null
+    }
+
+    fun updateRevealAmount(revealAmount: Float) {
+        scrimView?.revealAmount = revealAmount
+    }
+
+    private fun buildSurface(onUpdated: Consumer<SurfaceControl.Builder>) {
+        val containerBuilder =
+            SurfaceControl.Builder(SurfaceSession())
+                .setContainerLayer()
+                .setName(overlayContainerName)
+
+        displayAreaHelper
+            .get()
+            .attachToRootDisplayArea(displayTracker.defaultDisplayId, containerBuilder, onUpdated)
+    }
+
+    private fun prepareOverlay(
+        onOverlayReady: Runnable? = null,
+        wwm: WindowlessWindowManager,
+        bgExecutor: Executor,
+        initialAlpha: Float,
+    ) {
+        val newRoot = SurfaceControlViewHost(context, context.display, wwm, javaClass.simpleName)
+
+        val params = getLayoutParams()
+        val newView =
+            LightRevealScrim(
+                    context,
+                    attrs = null,
+                    initialWidth = params.width,
+                    initialHeight = params.height
+                )
+                .apply {
+                    revealEffect = lightRevealEffectFactory(currentRotation)
+                    revealAmount = initialAlpha
+                }
+
+        newRoot.setView(newView, params)
+
+        if (onOverlayReady != null) {
+            Trace.beginAsyncSection("$TAG#relayout", 0)
+
+            newRoot.relayout(params) { transaction ->
+                val vsyncId = Choreographer.getSfInstance().vsyncId
+                transaction.setFrameTimelineVsync(vsyncId).apply()
+
+                transaction
+                    .setFrameTimelineVsync(vsyncId + 1)
+                    .addTransactionCommittedListener(bgExecutor) {
+                        Trace.endAsyncSection("$TAG#relayout", 0)
+                        onOverlayReady.run()
+                    }
+                    .apply()
+            }
+        }
+        root = newRoot
+        scrimView = newView
+    }
+
+    private fun ensureInBackground() {
+        check(Looper.myLooper() == bgHandler.looper) { "Not being executed in the background!" }
+    }
+
+    private fun getLayoutParams(): WindowManager.LayoutParams {
+        val displayInfo =
+            internalDisplayInfos.displaySelector()
+                ?: throw IllegalArgumentException("No internal displays found!")
+        return WindowManager.LayoutParams().apply {
+            if (currentRotation.isVerticalRotation()) {
+                height = displayInfo.naturalHeight
+                width = displayInfo.naturalWidth
+            } else {
+                height = displayInfo.naturalWidth
+                width = displayInfo.naturalHeight
+            }
+            format = PixelFormat.TRANSLUCENT
+            type = WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY
+            title = javaClass.simpleName
+            layoutInDisplayCutoutMode =
+                WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+            fitInsetsTypes = 0
+
+            flags =
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+                    WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+            setTrustedOverlay()
+
+            packageName = context.opPackageName
+        }
+    }
+
+    private inner class RotationWatcher : RotationChangeProvider.RotationListener {
+        override fun onRotationChanged(newRotation: Int) {
+            traceSection("$TAG#onRotationChanged") {
+                if (currentRotation != newRotation) {
+                    currentRotation = newRotation
+                    scrimView?.revealEffect = lightRevealEffectFactory(currentRotation)
+                    root?.relayout(getLayoutParams())
+                }
+            }
+        }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            displaySelector: Sequence<DisplayInfo>.() -> DisplayInfo?,
+            effectFactory: (rotation: Int) -> LightRevealEffect,
+            overlayContainerName: String
+        ): FullscreenLightRevealAnimationController
+    }
+
+    companion object {
+        private const val TAG = "FullscreenLightRevealAnimation"
+        private const val ROTATION_ANIMATION_OVERLAY_Z_INDEX = Integer.MAX_VALUE
+        private const val OVERLAY_LAYER_Z_INDEX = ROTATION_ANIMATION_OVERLAY_Z_INDEX - 1
+        const val ALPHA_TRANSPARENT = 1f
+        const val ALPHA_OPAQUE = 0f
+
+        fun @receiver:Rotation Int.isVerticalRotation(): Boolean =
+            this == Surface.ROTATION_0 || this == Surface.ROTATION_180
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
index 0016d95..139ac7e 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.unfold
 
 import com.android.keyguard.KeyguardUnfoldTransition
+import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.shade.NotificationPanelUnfoldAnimationController
 import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController
@@ -25,10 +26,14 @@
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
 import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityManager
 import com.android.systemui.util.kotlin.getOrNull
+import dagger.Binds
 import dagger.BindsInstance
 import dagger.Module
 import dagger.Provides
 import dagger.Subcomponent
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
 import java.util.Optional
 import javax.inject.Named
 import javax.inject.Scope
@@ -70,8 +75,33 @@
     }
 }
 
+@Module
+interface SysUIUnfoldStartableModule {
+    @Binds
+    @IntoMap
+    @ClassKey(UnfoldInitializationStartable::class)
+    fun bindsUnfoldInitializationStartable(impl: UnfoldInitializationStartable): CoreStartable
+}
+
+@Module
+abstract class SysUIUnfoldInternalModule {
+    @Binds
+    @IntoSet
+    @SysUIUnfoldScope
+    abstract fun bindsUnfoldLightRevealOverlayAnimation(
+        anim: UnfoldLightRevealOverlayAnimation
+    ): FullscreenLightRevealAnimation
+
+    @Binds
+    @IntoSet
+    @SysUIUnfoldScope
+    abstract fun bindsFoldLightRevealOverlayAnimation(
+        anim: FoldLightRevealOverlayAnimation
+    ): FullscreenLightRevealAnimation
+}
+
 @SysUIUnfoldScope
-@Subcomponent
+@Subcomponent(modules = [SysUIUnfoldInternalModule::class])
 interface SysUIUnfoldComponent {
 
     @Subcomponent.Factory
@@ -92,12 +122,12 @@
 
     fun getFoldAodAnimationController(): FoldAodAnimationController
 
+    fun getFullScreenLightRevealAnimations(): Set<FullscreenLightRevealAnimation>
+
     fun getUnfoldTransitionWallpaperController(): UnfoldTransitionWallpaperController
 
     fun getUnfoldHapticsPlayer(): UnfoldHapticsPlayer
 
-    fun getUnfoldLightRevealOverlayAnimation(): UnfoldLightRevealOverlayAnimation
-
     fun getUnfoldKeyguardVisibilityManager(): UnfoldKeyguardVisibilityManager
 
     fun getUnfoldLatencyTracker(): UnfoldLatencyTracker
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldInitializationStartable.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldInitializationStartable.kt
new file mode 100644
index 0000000..75d8a58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldInitializationStartable.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.unfold
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.Flags
+import com.android.systemui.unfold.dagger.UnfoldBg
+import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder
+import java.util.Optional
+import javax.inject.Inject
+
+class UnfoldInitializationStartable
+@Inject
+constructor(
+    private val unfoldComponentOptional: Optional<SysUIUnfoldComponent>,
+    private val foldStateLoggingProviderOptional: Optional<FoldStateLoggingProvider>,
+    private val foldStateLoggerOptional: Optional<FoldStateLogger>,
+    @UnfoldBg
+    private val unfoldBgTransitionProgressProviderOptional:
+        Optional<UnfoldTransitionProgressProvider>,
+    private val unfoldTransitionProgressProviderOptional:
+        Optional<UnfoldTransitionProgressProvider>,
+    private val unfoldTransitionProgressForwarder: Optional<UnfoldTransitionProgressForwarder>
+) : CoreStartable {
+    override fun start() {
+        unfoldComponentOptional.ifPresent { c: SysUIUnfoldComponent ->
+            c.getFullScreenLightRevealAnimations().forEach { it: FullscreenLightRevealAnimation ->
+                it.init()
+            }
+            c.getUnfoldTransitionWallpaperController().init()
+            c.getUnfoldHapticsPlayer()
+            c.getNaturalRotationUnfoldProgressProvider().init()
+            c.getUnfoldLatencyTracker().init()
+        }
+
+        foldStateLoggingProviderOptional.ifPresent { obj: FoldStateLoggingProvider -> obj.init() }
+        foldStateLoggerOptional.ifPresent { obj: FoldStateLogger -> obj.init() }
+
+        val unfoldTransitionProgressProvider: Optional<UnfoldTransitionProgressProvider> =
+            if (Flags.unfoldAnimationBackgroundProgress()) {
+                unfoldBgTransitionProgressProviderOptional
+            } else {
+                unfoldTransitionProgressProviderOptional
+            }
+        unfoldTransitionProgressProvider.ifPresent {
+            progressProvider: UnfoldTransitionProgressProvider ->
+            unfoldTransitionProgressForwarder.ifPresent {
+                listener: UnfoldTransitionProgressForwarder ->
+                progressProvider.addCallback(listener)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index b72c6f1..f355dd8 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -18,42 +18,22 @@
 import android.annotation.BinderThread
 import android.content.ContentResolver
 import android.content.Context
-import android.graphics.PixelFormat
 import android.hardware.devicestate.DeviceStateManager
-import android.hardware.devicestate.DeviceStateManager.FoldStateListener
-import android.hardware.display.DisplayManager
 import android.hardware.input.InputManagerGlobal
 import android.os.Handler
-import android.os.Looper
 import android.os.Trace
-import android.view.Choreographer
-import android.view.Display
-import android.view.DisplayInfo
-import android.view.Surface
-import android.view.SurfaceControl
-import android.view.SurfaceControlViewHost
-import android.view.SurfaceSession
-import android.view.WindowManager
-import android.view.WindowlessWindowManager
-import com.android.app.tracing.traceSection
-import com.android.keyguard.logging.ScrimLogger
 import com.android.systemui.Flags.unfoldAnimationBackgroundProgress
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
-import com.android.systemui.settings.DisplayTracker
-import com.android.systemui.statusbar.LightRevealEffect
-import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.LinearLightRevealEffect
+import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.ALPHA_OPAQUE
+import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.ALPHA_TRANSPARENT
+import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.isVerticalRotation
 import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.FOLD
 import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.UNFOLD
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import com.android.systemui.unfold.dagger.UnfoldBg
-import com.android.systemui.unfold.updates.RotationChangeProvider
 import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled
 import com.android.systemui.util.concurrency.ThreadFactory
-import com.android.wm.shell.displayareahelper.DisplayAreaHelper
-import java.util.Optional
 import java.util.concurrent.Executor
 import java.util.function.Consumer
 import javax.inject.Inject
@@ -64,80 +44,43 @@
 @Inject
 constructor(
     private val context: Context,
-    private val featureFlags: FeatureFlags,
-    private val deviceStateManager: DeviceStateManager,
+    private val featureFlags: FeatureFlagsClassic,
     private val contentResolver: ContentResolver,
-    private val displayManager: DisplayManager,
+    @UnfoldBg private val unfoldProgressHandler: Handler,
     @UnfoldBg
     private val unfoldTransitionBgProgressProvider: Provider<UnfoldTransitionProgressProvider>,
     private val unfoldTransitionProgressProvider: Provider<UnfoldTransitionProgressProvider>,
-    private val displayAreaHelper: Optional<DisplayAreaHelper>,
-    @Main private val executor: Executor,
+    private val deviceStateManager: DeviceStateManager,
     private val threadFactory: ThreadFactory,
-    @UnfoldBg private val rotationChangeProvider: RotationChangeProvider,
-    @UnfoldBg private val unfoldProgressHandler: Handler,
-    private val displayTracker: DisplayTracker,
-    private val scrimLogger: ScrimLogger,
-) {
+    private val fullscreenLightRevealAnimationControllerFactory:
+        FullscreenLightRevealAnimationController.Factory
+) : FullscreenLightRevealAnimation {
 
     private val transitionListener = TransitionListener()
-    private val rotationWatcher = RotationWatcher()
-
-    private lateinit var bgHandler: Handler
-    private lateinit var bgExecutor: Executor
-
-    private lateinit var wwm: WindowlessWindowManager
-    private lateinit var unfoldedDisplayInfo: DisplayInfo
-    private lateinit var overlayContainer: SurfaceControl
-
-    private var root: SurfaceControlViewHost? = null
-    private var scrimView: LightRevealScrim? = null
     private var isFolded: Boolean = false
     private var isUnfoldHandled: Boolean = true
-    private var overlayAddReason: AddOverlayReason? = null
-    private var isTouchBlocked: Boolean = true
+    private var overlayAddReason: AddOverlayReason = UNFOLD
+    private lateinit var controller: FullscreenLightRevealAnimationController
+    private lateinit var bgExecutor: Executor
 
-    private var currentRotation: Int = context.display!!.rotation
-
-    fun init() {
+    override fun init() {
         // This method will be called only on devices where this animation is enabled,
         // so normally this thread won't be created
-        bgHandler = unfoldProgressHandler
-        bgExecutor = threadFactory.buildDelayableExecutorOnHandler(bgHandler)
 
+        controller =
+            fullscreenLightRevealAnimationControllerFactory.create(
+                displaySelector = { maxByOrNull { it.naturalWidth } },
+                effectFactory = { LinearLightRevealEffect(it.isVerticalRotation()) },
+                overlayContainerName = SURFACE_CONTAINER_NAME,
+            )
+        controller.init()
+        bgExecutor = threadFactory.buildDelayableExecutorOnHandler(unfoldProgressHandler)
         deviceStateManager.registerCallback(bgExecutor, FoldListener())
         if (unfoldAnimationBackgroundProgress()) {
             unfoldTransitionBgProgressProvider.get().addCallback(transitionListener)
         } else {
             unfoldTransitionProgressProvider.get().addCallback(transitionListener)
         }
-        rotationChangeProvider.addCallback(rotationWatcher)
-
-        val containerBuilder =
-            SurfaceControl.Builder(SurfaceSession())
-                .setContainerLayer()
-                .setName("unfold-overlay-container")
-
-        displayAreaHelper.get().attachToRootDisplayArea(
-            displayTracker.defaultDisplayId,
-            containerBuilder
-        ) { builder ->
-            executor.execute {
-                overlayContainer = builder.build()
-
-                SurfaceControl.Transaction()
-                    .setLayer(overlayContainer, UNFOLD_OVERLAY_LAYER_Z_INDEX)
-                    .show(overlayContainer)
-                    .apply()
-
-                wwm =
-                    WindowlessWindowManager(context.resources.configuration, overlayContainer, null)
-            }
-        }
-
-        // Get unfolded display size immediately as 'current display info' might be
-        // not up-to-date during unfolding
-        unfoldedDisplayInfo = getUnfoldedDisplayInfo()
     }
 
     /**
@@ -148,17 +91,18 @@
      * @see [com.android.systemui.keyguard.KeyguardViewMediator]
      */
     @BinderThread
-    fun onScreenTurningOn(onOverlayReady: Runnable) {
+    override fun onScreenTurningOn(onOverlayReady: Runnable) {
         executeInBackground {
             Trace.beginSection("$TAG#onScreenTurningOn")
             try {
                 // Add the view only if we are unfolding and this is the first screen on
                 if (!isFolded && !isUnfoldHandled && contentResolver.areAnimationsEnabled()) {
-                    addOverlay(onOverlayReady, reason = UNFOLD)
+                    overlayAddReason = UNFOLD
+                    controller.addOverlay(calculateRevealAmount(), onOverlayReady)
                     isUnfoldHandled = true
                 } else {
                     // No unfold transition, immediately report that overlay is ready
-                    ensureOverlayRemoved()
+                    controller.ensureOverlayRemoved()
                     onOverlayReady.run()
                 }
             } finally {
@@ -167,78 +111,15 @@
         }
     }
 
-    private fun addOverlay(onOverlayReady: Runnable? = null, reason: AddOverlayReason) {
-        if (!::wwm.isInitialized) {
-            // Surface overlay is not created yet on the first SysUI launch
-            onOverlayReady?.run()
-            return
-        }
-
-        ensureInBackground()
-        ensureOverlayRemoved()
-
-        overlayAddReason = reason
-
-        val newRoot =
-            SurfaceControlViewHost(
-                context,
-                context.display,
-                wwm,
-                "UnfoldLightRevealOverlayAnimation"
-            )
-        val params = getLayoutParams()
-        val newView =
-            LightRevealScrim(
-                    context,
-                    attrs = null,
-                    initialWidth = params.width,
-                    initialHeight = params.height
-                )
-                .apply {
-                    revealEffect = createLightRevealEffect()
-                    revealAmount = calculateRevealAmount()
-                    scrimLogger = [email protected]
-                }
-
-        newRoot.setView(newView, params)
-
-        if (onOverlayReady != null) {
-            Trace.beginAsyncSection("$TAG#relayout", 0)
-
-            newRoot.relayout(params) { transaction ->
-                val vsyncId = Choreographer.getSfInstance().vsyncId
-
-                // Apply the transaction that contains the first frame of the overlay and apply
-                // another empty transaction with 'vsyncId + 1' to make sure that it is actually
-                // displayed on the screen. The second transaction is necessary to remove the screen
-                // blocker (turn on the brightness) only when the content is actually visible as it
-                // might be presented only in the next frame.
-                // See b/197538198
-                transaction.setFrameTimelineVsync(vsyncId).apply()
-
-                transaction
-                    .setFrameTimelineVsync(vsyncId + 1)
-                    .addTransactionCommittedListener(bgExecutor) {
-                        Trace.endAsyncSection("$TAG#relayout", 0)
-                        onOverlayReady.run()
-                    }
-                    .apply()
-            }
-        }
-
-        scrimView = newView
-        root = newRoot
-    }
-
     private fun calculateRevealAmount(animationProgress: Float? = null): Float {
-        val overlayAddReason = overlayAddReason ?: UNFOLD
+        val overlayAddReason = overlayAddReason
 
         if (animationProgress == null) {
-            // Animation progress is unknown, calculate the initial value based on the overlay
+            // Animation progress unknown, calculate the initial value based on the overlay
             // add reason
             return when (overlayAddReason) {
-                FOLD -> TRANSPARENT
-                UNFOLD -> BLACK
+                FOLD -> ALPHA_TRANSPARENT
+                UNFOLD -> ALPHA_OPAQUE
             }
         }
 
@@ -249,144 +130,57 @@
             // Do not darken the content when SHOW_VIGNETTE_WHEN_FOLDING flag is off
             // and we are folding the device. We still add the overlay to block touches
             // while the animation is running but the overlay is transparent.
-            TRANSPARENT
+            ALPHA_TRANSPARENT
         } else {
             animationProgress
         }
     }
 
-    private fun getLayoutParams(): WindowManager.LayoutParams {
-        val params: WindowManager.LayoutParams = WindowManager.LayoutParams()
+    private inner class TransitionListener :
+        UnfoldTransitionProgressProvider.TransitionProgressListener {
 
-        val rotation = currentRotation
-        val isNatural = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
-
-        params.height =
-            if (isNatural) unfoldedDisplayInfo.naturalHeight else unfoldedDisplayInfo.naturalWidth
-        params.width =
-            if (isNatural) unfoldedDisplayInfo.naturalWidth else unfoldedDisplayInfo.naturalHeight
-
-        params.format = PixelFormat.TRANSLUCENT
-        params.type = WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY
-        params.title = "Unfold Light Reveal Animation"
-        params.layoutInDisplayCutoutMode =
-            WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
-        params.fitInsetsTypes = 0
-
-        val touchFlags =
-            if (isTouchBlocked) {
-                // Touchable by default, so it will block the touches
-                0
-            } else {
-                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
-            }
-        params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or touchFlags
-        params.setTrustedOverlay()
-
-        val packageName: String = context.opPackageName
-        params.packageName = packageName
-
-        return params
-    }
-
-    private fun updateTouchBlockIfNeeded(progress: Float) {
-        // When unfolding unblock touches a bit earlier than the animation end as the
-        // interpolation has a long tail of very slight movement at the end which should not
-        // affect much the usage of the device
-        val shouldBlockTouches =
-            if (overlayAddReason == UNFOLD) {
-                progress < UNFOLD_BLOCK_TOUCHES_UNTIL_PROGRESS
-            } else {
-                true
-            }
-
-        if (isTouchBlocked != shouldBlockTouches) {
-            isTouchBlocked = shouldBlockTouches
-
-            traceSection("$TAG#relayoutToUpdateTouch") { root?.relayout(getLayoutParams()) }
-        }
-    }
-
-    private fun createLightRevealEffect(): LightRevealEffect {
-        val isVerticalFold =
-            currentRotation == Surface.ROTATION_0 || currentRotation == Surface.ROTATION_180
-        return LinearLightRevealEffect(isVertical = isVerticalFold)
-    }
-
-    private fun ensureOverlayRemoved() {
-        ensureInBackground()
-        traceSection("ensureOverlayRemoved") {
-            root?.release()
-            root = null
-            scrimView = null
-        }
-    }
-
-    private fun getUnfoldedDisplayInfo(): DisplayInfo =
-        displayManager
-            .getDisplays(DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
-            .asSequence()
-            .map { DisplayInfo().apply { it.getDisplayInfo(this) } }
-            .filter { it.type == Display.TYPE_INTERNAL }
-            .maxByOrNull { it.naturalWidth }!!
-
-    private inner class TransitionListener : TransitionProgressListener {
-
-        override fun onTransitionProgress(progress: Float) {
-            executeInBackground {
-                scrimView?.revealAmount = calculateRevealAmount(progress)
-                updateTouchBlockIfNeeded(progress)
-            }
+        override fun onTransitionProgress(progress: Float) = executeInBackground {
+            controller.updateRevealAmount(calculateRevealAmount(progress))
+            // When unfolding unblock touches a bit earlier than the animation end as the
+            // interpolation has a long tail of very slight movement at the end which should not
+            // affect much the usage of the device
+            controller.isTouchBlocked =
+                overlayAddReason == FOLD || progress < UNFOLD_BLOCK_TOUCHES_UNTIL_PROGRESS
         }
 
-        override fun onTransitionFinished() {
-            executeInBackground { ensureOverlayRemoved() }
+        override fun onTransitionFinished() = executeInBackground {
+            controller.ensureOverlayRemoved()
         }
 
         override fun onTransitionStarted() {
             // Add view for folding case (when unfolding the view is added earlier)
-            if (scrimView == null) {
-                executeInBackground { addOverlay(reason = FOLD) }
+            if (controller.isOverlayVisible()) {
+                executeInBackground {
+                    overlayAddReason = FOLD
+                    controller.addOverlay(calculateRevealAmount())
+                }
             }
             // Disable input dispatching during transition.
             InputManagerGlobal.getInstance().cancelCurrentTouch()
         }
     }
 
-    private inner class RotationWatcher : RotationChangeProvider.RotationListener {
-        override fun onRotationChanged(newRotation: Int) {
-            executeInBackground {
-                traceSection("$TAG#onRotationChanged") {
-                    if (currentRotation != newRotation) {
-                        currentRotation = newRotation
-                        scrimView?.revealEffect = createLightRevealEffect()
-                        root?.relayout(getLayoutParams())
-                    }
-                }
-            }
-        }
-    }
-
     private fun executeInBackground(f: () -> Unit) {
         // This is needed to allow progresses to be received both from the main thread (that will
         // schedule a runnable on the bg thread), and from the bg thread directly (no reposting).
-        if (bgHandler.looper.isCurrentThread) {
+        if (unfoldProgressHandler.looper.isCurrentThread) {
             f()
         } else {
-            bgHandler.post(f)
+            unfoldProgressHandler.post(f)
         }
     }
 
-    private fun ensureInBackground() {
-        check(Looper.myLooper() == bgHandler.looper) { "Not being executed in the background!" }
-    }
-
     private inner class FoldListener :
-        FoldStateListener(
+        DeviceStateManager.FoldStateListener(
             context,
             Consumer { isFolded ->
                 if (isFolded) {
-                    ensureOverlayRemoved()
+                    controller.ensureOverlayRemoved()
                     isUnfoldHandled = false
                 }
                 this.isFolded = isFolded
@@ -400,16 +194,7 @@
 
     private companion object {
         const val TAG = "UnfoldLightRevealOverlayAnimation"
-        const val ROTATION_ANIMATION_OVERLAY_Z_INDEX = Integer.MAX_VALUE
-
-        // Put the unfold overlay below the rotation animation screenshot to hide the moment
-        // when it is rotated but the rotation of the other windows hasn't happen yet
-        const val UNFOLD_OVERLAY_LAYER_Z_INDEX = ROTATION_ANIMATION_OVERLAY_Z_INDEX - 1
-
-        // constants for revealAmount.
-        const val TRANSPARENT = 1f
-        const val BLACK = 0f
-
-        private const val UNFOLD_BLOCK_TOUCHES_UNTIL_PROGRESS = 0.8f
+        const val SURFACE_CONTAINER_NAME = "unfold-overlay-container"
+        const val UNFOLD_BLOCK_TOUCHES_UNTIL_PROGRESS = 0.8f
     }
 }
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 c170eb5..a122311 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
@@ -27,7 +27,6 @@
 import android.graphics.drawable.BitmapDrawable
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.Icon
-import android.os.Process
 import android.os.RemoteException
 import android.os.UserHandle
 import android.os.UserManager
@@ -50,6 +49,7 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.process.ProcessWrapper
 import com.android.systemui.qs.user.UserSwitchDialogController
 import com.android.systemui.res.R
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
@@ -108,6 +108,7 @@
     private val guestUserInteractor: GuestUserInteractor,
     private val uiEventLogger: UiEventLogger,
     private val userRestrictionChecker: UserRestrictionChecker,
+    private val processWrapper: ProcessWrapper
 ) {
     /**
      * Defines interface for classes that can be notified when the state of users on the device is
@@ -669,7 +670,7 @@
 
         // Connect to the new secondary user's service (purely to ensure that a persistent
         // SystemUI application is created for that user)
-        if (userId != Process.myUserHandle().identifier) {
+        if (userId != processWrapper.myUserHandle().identifier && !processWrapper.isSystemUser) {
             applicationContext.startServiceAsUser(
                 intent,
                 UserHandle.of(userId),
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 f36c335e..d509b2d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
@@ -16,6 +16,9 @@
 
 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;
 
@@ -36,4 +39,9 @@
     /** 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
new file mode 100644
index 0000000..d3e5080
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.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.util.settings.repository
+
+import android.provider.Settings
+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.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
+
+/**
+ * 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
diff --git a/packages/SystemUI/src/com/android/systemui/util/view/DisposableHandleExt.kt b/packages/SystemUI/src/com/android/systemui/util/view/DisposableHandleExt.kt
deleted file mode 100644
index d3653b4..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/view/DisposableHandleExt.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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.util.view
-
-import android.view.View
-import com.android.systemui.util.kotlin.awaitCancellationThenDispose
-import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collectLatest
-
-/**
- * Use the [bind] method to bind the view every time this flow emits, and suspend to await for more
- * updates. New emissions lead to the previous binding call being cancelled if not completed.
- * Dispose of the [DisposableHandle] returned by [bind] when done.
- */
-suspend fun <T : View> Flow<T>.bindLatest(bind: (T) -> DisposableHandle?) {
-    this.collectLatest { view ->
-        val disposableHandle = bind(view)
-        disposableHandle?.awaitCancellationThenDispose()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index ce6d740..90c5c62 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -116,9 +116,8 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.Prefs;
-import com.android.systemui.dagger.qualifiers.Application;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.haptics.slider.HapticSliderViewBinder;
 import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin;
 import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
@@ -145,9 +144,6 @@
 import java.util.List;
 import java.util.function.Consumer;
 
-import kotlinx.coroutines.CoroutineDispatcher;
-import kotlinx.coroutines.CoroutineScope;
-
 /**
  * Visual presentation of the volume dialog.
  *
@@ -311,8 +307,6 @@
     private int mOrientation;
     private final Lazy<SecureSettings> mSecureSettings;
     private int mDialogTimeoutMillis;
-    private final CoroutineDispatcher mMainDispatcher;
-    private final CoroutineScope mApplicationScope;
     private final VibratorHelper mVibratorHelper;
     private final com.android.systemui.util.time.SystemClock mSystemClock;
 
@@ -333,14 +327,10 @@
             DumpManager dumpManager,
             Lazy<SecureSettings> secureSettings,
             VibratorHelper vibratorHelper,
-            @Main CoroutineDispatcher mainDispatcher,
-            @Application CoroutineScope applicationScope,
             com.android.systemui.util.time.SystemClock systemClock) {
         mContext =
                 new ContextThemeWrapper(context, R.style.volume_dialog_theme);
         mHandler = new H(looper);
-        mMainDispatcher = mainDispatcher;
-        mApplicationScope = applicationScope;
         mVibratorHelper = vibratorHelper;
         mSystemClock = systemClock;
         mShouldListenForJank = shouldListenForJank;
@@ -858,7 +848,10 @@
             row.header.setFilters(new InputFilter[] {new InputFilter.LengthFilter(13)});
         }
         row.slider = row.view.findViewById(R.id.volume_row_slider);
-        row.createPlugin(mVibratorHelper, mSystemClock, mMainDispatcher, mApplicationScope);
+        if (hapticVolumeSlider()) {
+            row.createPlugin(mVibratorHelper, mSystemClock);
+            HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin);
+        }
         row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
         row.number = row.view.findViewById(R.id.volume_number);
 
@@ -1498,7 +1491,7 @@
         for (int i = 0; i < mRows.size(); i++) {
             VolumeRow row = mRows.get(i);
             if (row.slider.getVisibility() == VISIBLE) {
-                row.addHaptics();
+                row.addTouchListener();
             }
         }
         Trace.endSection();
@@ -2620,17 +2613,13 @@
 
         void createPlugin(
                 VibratorHelper vibratorHelper,
-                com.android.systemui.util.time.SystemClock systemClock,
-                CoroutineDispatcher mainDispatcher,
-                CoroutineScope applicationScope) {
-            if (!hapticVolumeSlider() || mHapticPlugin != null) return;
+                com.android.systemui.util.time.SystemClock systemClock) {
+            if (mHapticPlugin != null) return;
 
             mHapticPlugin = new SeekableSliderHapticPlugin(
-                    vibratorHelper,
-                    systemClock,
-                    mainDispatcher,
-                    applicationScope,
-                    sSliderHapticFeedbackConfig);
+                vibratorHelper,
+                systemClock,
+                sSliderHapticFeedbackConfig);
         }
 
 
@@ -2647,19 +2636,9 @@
             });
         }
 
-        void addHaptics() {
-            if (mHapticPlugin != null) {
-                addTouchListener();
-                mHapticPlugin.start();
-            }
-        }
-
         @SuppressLint("ClickableViewAccessibility")
         void removeHaptics() {
             slider.setOnTouchListener(null);
-            if (mHapticPlugin != null) {
-                mHapticPlugin.stop();
-            }
         }
     }
 
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 ff1daea..278ffc6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -21,6 +21,8 @@
 import com.android.settingslib.volume.data.repository.AudioRepository
 import com.android.settingslib.volume.data.repository.AudioRepositoryImpl
 import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
+import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
+import com.android.settingslib.volume.shared.AudioManagerIntentsReceiverImpl
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import dagger.Module
@@ -35,13 +37,19 @@
     companion object {
 
         @Provides
-        fun provideAudioRepository(
+        fun provideAudioManagerIntentsReceiver(
             @Application context: Context,
+            @Application coroutineScope: CoroutineScope,
+        ): AudioManagerIntentsReceiver = AudioManagerIntentsReceiverImpl(context, coroutineScope)
+
+        @Provides
+        fun provideAudioRepository(
+            intentsReceiver: AudioManagerIntentsReceiver,
             audioManager: AudioManager,
             @Background coroutineContext: CoroutineContext,
             @Application coroutineScope: CoroutineScope,
         ): AudioRepository =
-            AudioRepositoryImpl(context, audioManager, coroutineContext, coroutineScope)
+            AudioRepositoryImpl(intentsReceiver, audioManager, coroutineContext, coroutineScope)
 
         @Provides
         fun provideAudioModeInteractor(repository: AudioRepository): AudioModeInteractor =
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
index 2ff9af9..ab76d45 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
@@ -16,11 +16,11 @@
 
 package com.android.systemui.volume.dagger
 
-import android.content.Context
 import android.media.session.MediaSessionManager
 import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.settingslib.volume.data.repository.MediaControllerRepository
 import com.android.settingslib.volume.data.repository.MediaControllerRepositoryImpl
+import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -37,14 +37,14 @@
         @Provides
         @SysUISingleton
         fun provideMediaDeviceSessionRepository(
-            @Application context: Context,
+            intentsReceiver: AudioManagerIntentsReceiver,
             mediaSessionManager: MediaSessionManager,
             localBluetoothManager: LocalBluetoothManager?,
             @Application coroutineScope: CoroutineScope,
             @Background backgroundContext: CoroutineContext,
         ): MediaControllerRepository =
             MediaControllerRepositoryImpl(
-                context,
+                intentsReceiver,
                 mediaSessionManager,
                 localBluetoothManager,
                 coroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 2718839..3285637 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -23,8 +23,6 @@
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.systemui.CoreStartable;
-import com.android.systemui.dagger.qualifiers.Application;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.VolumeDialog;
@@ -55,9 +53,6 @@
 import dagger.multibindings.IntoMap;
 import dagger.multibindings.IntoSet;
 
-import kotlinx.coroutines.CoroutineDispatcher;
-import kotlinx.coroutines.CoroutineScope;
-
 /** Dagger Module for code in the volume package. */
 @Module(
         includes = {
@@ -112,8 +107,6 @@
             DumpManager dumpManager,
             Lazy<SecureSettings> secureSettings,
             VibratorHelper vibratorHelper,
-            @Main CoroutineDispatcher mainDispatcher,
-            @Application CoroutineScope applicationScope,
             SystemClock systemClock) {
         VolumeDialogImpl impl = new VolumeDialogImpl(
                 context,
@@ -132,8 +125,6 @@
                 dumpManager,
                 secureSettings,
                 vibratorHelper,
-                mainDispatcher,
-                applicationScope,
                 systemClock);
         impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
         impl.setAutomute(true);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
index 57ac435..0a1ee24 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
@@ -15,8 +15,10 @@
  */
 package com.android.systemui.volume.panel.component.mediaoutput.data.repository
 
+import android.media.MediaRouter2Manager
 import com.android.settingslib.volume.data.repository.LocalMediaRepository
 import com.android.settingslib.volume.data.repository.LocalMediaRepositoryImpl
+import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.media.controls.pipeline.LocalMediaManagerFactory
@@ -27,6 +29,8 @@
 class LocalMediaRepositoryFactory
 @Inject
 constructor(
+    private val intentsReceiver: AudioManagerIntentsReceiver,
+    private val mediaRouter2Manager: MediaRouter2Manager,
     private val localMediaManagerFactory: LocalMediaManagerFactory,
     @Application private val coroutineScope: CoroutineScope,
     @Background private val backgroundCoroutineContext: CoroutineContext,
@@ -34,7 +38,9 @@
 
     fun create(packageName: String?): LocalMediaRepository =
         LocalMediaRepositoryImpl(
+            intentsReceiver,
             localMediaManagerFactory.create(packageName),
+            mediaRouter2Manager,
             coroutineScope,
             backgroundCoroutineContext,
         )
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index c2efc05..d048cbe 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -28,6 +28,7 @@
 import com.android.internal.widget.LockscreenCredential
 import com.android.keyguard.KeyguardPinViewController.PinBouncerUiEvent
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.classifier.FalsingCollectorFake
@@ -88,6 +89,7 @@
 
     @Mock private val mEmergencyButtonController: EmergencyButtonController? = null
     private val falsingCollector: FalsingCollector = FalsingCollectorFake()
+    private val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
     @Mock lateinit var postureController: DevicePostureController
     @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
 
@@ -143,7 +145,7 @@
             featureFlags,
             mSelectedUserInteractor,
             uiEventLogger,
-            FakeKeyboardRepository()
+            keyguardKeyboardInteractor
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index 0959f1b..4a2554eb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -23,6 +23,7 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
@@ -80,6 +81,7 @@
             LayoutInflater.from(context).inflate(R.layout.keyguard_sim_pin_view, null)
                 as KeyguardSimPinView
         val fakeFeatureFlags = FakeFeatureFlags()
+        val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
 
         mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)
         underTest =
@@ -97,7 +99,7 @@
                 emergencyButtonController,
                 fakeFeatureFlags,
                 mSelectedUserInteractor,
-                FakeKeyboardRepository()
+                keyguardKeyboardInteractor
             )
         underTest.init()
         underTest.onViewAttached()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index 1281e44..4f46184 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -24,6 +24,7 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
@@ -75,6 +76,7 @@
         simPukView =
             LayoutInflater.from(context).inflate(R.layout.keyguard_sim_puk_view, null)
                 as KeyguardSimPukView
+        val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
         val fakeFeatureFlags = FakeFeatureFlags()
         mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)
         underTest =
@@ -92,7 +94,7 @@
                 emergencyButtonController,
                 fakeFeatureFlags,
                 mSelectedUserInteractor,
-                FakeKeyboardRepository()
+                keyguardKeyboardInteractor
             )
         underTest.init()
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
index b45c894..fb649c8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
@@ -24,8 +24,8 @@
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.unfold.FoldAodAnimationController
+import com.android.systemui.unfold.FullscreenLightRevealAnimation
 import com.android.systemui.unfold.SysUIUnfoldComponent
-import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.utils.os.FakeHandler
 import com.android.systemui.utils.os.FakeHandler.Mode.QUEUEING
@@ -53,7 +53,9 @@
     @Mock
     private lateinit var foldAodAnimationController: FoldAodAnimationController
     @Mock
-    private lateinit var unfoldAnimation: UnfoldLightRevealOverlayAnimation
+    private lateinit var fullscreenLightRevealAnimation: FullscreenLightRevealAnimation
+    @Mock
+    private lateinit var fullScreenLightRevealAnimations: Set<FullscreenLightRevealAnimation>
     @Captor
     private lateinit var readyCaptor: ArgumentCaptor<Runnable>
 
@@ -67,9 +69,9 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-
-        `when`(unfoldComponent.getUnfoldLightRevealOverlayAnimation())
-                .thenReturn(unfoldAnimation)
+        fullScreenLightRevealAnimations = setOf(fullscreenLightRevealAnimation)
+        `when`(unfoldComponent.getFullScreenLightRevealAnimations())
+                .thenReturn(fullScreenLightRevealAnimations)
         `when`(unfoldComponent.getFoldAodAnimationController())
                 .thenReturn(foldAodAnimationController)
 
@@ -164,7 +166,7 @@
     }
 
     private fun onUnfoldOverlayReady() {
-        verify(unfoldAnimation).onScreenTurningOn(capture(readyCaptor))
+        verify(fullscreenLightRevealAnimation).onScreenTurningOn(capture(readyCaptor))
         readyCaptor.value.run()
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt
index 202d9ce..e157fc5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/SystemUIApplicationTest.kt
@@ -91,7 +91,7 @@
         whenever(sysuiComponent.startables)
             .thenReturn(mutableMapOf(StartableA::class.java to Provider { startableA }))
         app.onCreate()
-        app.startServicesIfNeeded()
+        app.startSystemUserServicesIfNeeded()
         assertThat(startableA.started).isTrue()
     }
 
@@ -105,7 +105,7 @@
                 )
             )
         app.onCreate()
-        app.startServicesIfNeeded()
+        app.startSystemUserServicesIfNeeded()
         assertThat(startableA.started).isTrue()
         assertThat(startableB.started).isTrue()
     }
@@ -121,7 +121,7 @@
                 )
             )
         app.onCreate()
-        app.startServicesIfNeeded()
+        app.startSystemUserServicesIfNeeded()
         assertThat(startableA.started).isTrue()
         assertThat(startableB.started).isTrue()
         assertThat(startableC.started).isTrue()
@@ -141,7 +141,7 @@
                 )
             )
         app.onCreate()
-        app.startServicesIfNeeded()
+        app.startSystemUserServicesIfNeeded()
         assertThat(startableA.started).isTrue()
         assertThat(startableB.started).isTrue()
         assertThat(startableC.started).isTrue()
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 375ebe8..8299acb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -51,7 +51,6 @@
 import android.view.accessibility.IRemoteMagnificationAnimationCallback;
 import android.view.animation.AccelerateInterpolator;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
@@ -80,7 +79,6 @@
 
 @LargeTest
 @RunWith(AndroidTestingRunner.class)
-@FlakyTest(bugId = 308501761)
 public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
 
     @Rule
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
index 8faf715..722107c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
@@ -45,11 +45,11 @@
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper
 class ActivityLaunchAnimatorTest : SysuiTestCase() {
-    private val launchContainer = LinearLayout(mContext)
-    private val testLaunchAnimator = fakeLaunchAnimator()
+    private val transitionContainer = LinearLayout(mContext)
+    private val testTransitionAnimator = fakeTransitionAnimator()
     @Mock lateinit var callback: ActivityLaunchAnimator.Callback
     @Mock lateinit var listener: ActivityLaunchAnimator.Listener
-    @Spy private val controller = TestLaunchAnimatorController(launchContainer)
+    @Spy private val controller = TestLaunchAnimatorController(transitionContainer)
     @Mock lateinit var iCallback: IRemoteAnimationFinishedCallback
 
     private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
@@ -58,7 +58,11 @@
     @Before
     fun setup() {
         activityLaunchAnimator =
-            ActivityLaunchAnimator(testLaunchAnimator, testLaunchAnimator, disableWmTimeout = true)
+            ActivityLaunchAnimator(
+                testTransitionAnimator,
+                testTransitionAnimator,
+                disableWmTimeout = true
+            )
         activityLaunchAnimator.callback = callback
         activityLaunchAnimator.addListener(listener)
     }
@@ -165,7 +169,7 @@
 
         waitForIdleSync()
         verify(controller).onLaunchAnimationCancelled()
-        verify(controller, never()).onLaunchAnimationStart(anyBoolean())
+        verify(controller, never()).onTransitionAnimationStart(anyBoolean())
         verify(listener).onLaunchAnimationCancelled()
         verify(listener, never()).onLaunchAnimationStart()
         assertNull(runner.delegate)
@@ -178,7 +182,7 @@
 
         waitForIdleSync()
         verify(controller).onLaunchAnimationCancelled()
-        verify(controller, never()).onLaunchAnimationStart(anyBoolean())
+        verify(controller, never()).onTransitionAnimationStart(anyBoolean())
         verify(listener).onLaunchAnimationCancelled()
         verify(listener, never()).onLaunchAnimationStart()
         assertNull(runner.delegate)
@@ -190,7 +194,7 @@
         runner.onAnimationStart(0, arrayOf(fakeWindow()), emptyArray(), emptyArray(), iCallback)
         waitForIdleSync()
         verify(listener).onLaunchAnimationStart()
-        verify(controller).onLaunchAnimationStart(anyBoolean())
+        verify(controller).onTransitionAnimationStart(anyBoolean())
     }
 
     @Test
@@ -240,10 +244,10 @@
  * A simple implementation of [ActivityLaunchAnimator.Controller] which throws if it is called
  * outside of the main thread.
  */
-private class TestLaunchAnimatorController(override var launchContainer: ViewGroup) :
+private class TestLaunchAnimatorController(override var transitionContainer: ViewGroup) :
     ActivityLaunchAnimator.Controller {
     override fun createAnimatorState() =
-        LaunchAnimator.State(
+        TransitionAnimator.State(
             top = 100,
             bottom = 200,
             left = 300,
@@ -262,19 +266,19 @@
         assertOnMainThread()
     }
 
-    override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+    override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
         assertOnMainThread()
     }
 
-    override fun onLaunchAnimationProgress(
-        state: LaunchAnimator.State,
+    override fun onTransitionAnimationProgress(
+        state: TransitionAnimator.State,
         progress: Float,
         linearProgress: Float
     ) {
         assertOnMainThread()
     }
 
-    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+    override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
         assertOnMainThread()
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index 2233e322..a586421 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -129,14 +129,14 @@
 
         // The dialog shouldn't be dismissable during the animation.
         runOnMainThreadAndWaitForIdleSync {
-            controller.onLaunchAnimationStart(isExpandingFullyAbove = true)
+            controller.onTransitionAnimationStart(isExpandingFullyAbove = true)
             secondDialog.dismiss()
         }
         assertTrue(secondDialog.isShowing)
 
         // Both dialogs should be dismissed at the end of the animation.
         runOnMainThreadAndWaitForIdleSync {
-            controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
+            controller.onTransitionAnimationEnd(isExpandingFullyAbove = true)
         }
         assertFalse(firstDialog.isShowing)
         assertFalse(secondDialog.isShowing)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
index d1ac0e8..8442a62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
@@ -32,13 +32,13 @@
 class GhostedViewLaunchAnimatorControllerTest : SysuiTestCase() {
     @Test
     fun animatingOrphanViewDoesNotCrash() {
-        val state = LaunchAnimator.State(top = 0, bottom = 0, left = 0, right = 0)
+        val state = TransitionAnimator.State(top = 0, bottom = 0, left = 0, right = 0)
 
         val controller = GhostedViewLaunchAnimatorController(LaunchableFrameLayout(mContext))
         controller.onIntentStarted(willAnimate = true)
-        controller.onLaunchAnimationStart(isExpandingFullyAbove = true)
-        controller.onLaunchAnimationProgress(state, progress = 0f, linearProgress = 0f)
-        controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
+        controller.onTransitionAnimationStart(isExpandingFullyAbove = true)
+        controller.onTransitionAnimationProgress(state, progress = 0f, linearProgress = 0f)
+        controller.onTransitionAnimationEnd(isExpandingFullyAbove = true)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index 0ba9abe..f490f3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -45,7 +45,7 @@
 import com.android.systemui.scene.ui.view.WindowRootView
 import com.android.systemui.shade.QuickSettingsController
 import com.android.systemui.shade.ShadeController
-import com.android.systemui.shade.ShadeViewController
+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
@@ -89,7 +89,7 @@
     @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
     @Mock private lateinit var shadeController: ShadeController
     @Mock private lateinit var qsController: QuickSettingsController
-    @Mock private lateinit var shadeViewController: ShadeViewController
+    @Mock private lateinit var shadeBackActionInteractor: ShadeBackActionInteractor
     @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
     @Mock private lateinit var windowRootView: WindowRootView
     @Mock private lateinit var viewRootImpl: ViewRootImpl
@@ -123,7 +123,7 @@
                 notificationShadeWindowController,
                 windowRootViewVisibilityInteractor
             )
-            .apply { this.setup(qsController, shadeViewController) }
+            .apply { this.setup(qsController, shadeBackActionInteractor) }
     }
 
     private val powerInteractor = PowerInteractorFactory.create().powerInteractor
@@ -165,19 +165,19 @@
         val result = backActionInteractor.onBackRequested()
 
         assertTrue(result)
-        verify(shadeViewController, atLeastOnce()).animateCollapseQs(anyBoolean())
+        verify(shadeBackActionInteractor, atLeastOnce()).animateCollapseQs(anyBoolean())
         verify(statusBarKeyguardViewManager, never()).onBackPressed()
     }
 
     @Test
     fun testOnBackRequested_closeUserSwitcherIfOpen() {
-        whenever(shadeViewController.closeUserSwitcherIfOpen()).thenReturn(true)
+        whenever(shadeBackActionInteractor.closeUserSwitcherIfOpen()).thenReturn(true)
 
         val result = backActionInteractor.onBackRequested()
 
         assertTrue(result)
         verify(statusBarKeyguardViewManager, never()).onBackPressed()
-        verify(shadeViewController, never()).animateCollapseQs(anyBoolean())
+        verify(shadeBackActionInteractor, never()).animateCollapseQs(anyBoolean())
     }
 
     @Test
@@ -189,7 +189,7 @@
 
         assertFalse(result)
         verify(statusBarKeyguardViewManager, never()).onBackPressed()
-        verify(shadeViewController, never()).animateCollapseQs(anyBoolean())
+        verify(shadeBackActionInteractor, never()).animateCollapseQs(anyBoolean())
     }
 
     @Test
@@ -290,11 +290,11 @@
         powerInteractor.setAwakeForTest()
         val callback = getBackInvokedCallback() as OnBackAnimationCallback
 
-        whenever(shadeViewController.canBeCollapsed()).thenReturn(false)
+        whenever(shadeBackActionInteractor.canBeCollapsed()).thenReturn(false)
 
         callback.onBackProgressed(createBackEvent(0.3f))
 
-        verify(shadeViewController, never()).onBackProgressed(0.3f)
+        verify(shadeBackActionInteractor, never()).onBackProgressed(0.3f)
     }
 
     @Test
@@ -305,11 +305,11 @@
         powerInteractor.setAwakeForTest()
         val callback = getBackInvokedCallback() as OnBackAnimationCallback
 
-        whenever(shadeViewController.canBeCollapsed()).thenReturn(true)
+        whenever(shadeBackActionInteractor.canBeCollapsed()).thenReturn(true)
 
         callback.onBackProgressed(createBackEvent(0.4f))
 
-        verify(shadeViewController).onBackProgressed(0.4f)
+        verify(shadeBackActionInteractor).onBackProgressed(0.4f)
     }
 
     private fun getBackInvokedCallback(): OnBackInvokedCallback {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
index a84778a..eae953e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
@@ -16,11 +16,9 @@
 
 package com.android.systemui.keyguard.data.repository
 
-import android.hardware.devicestate.DeviceStateManager
-import android.hardware.display.DisplayManager
-import android.os.Handler
 import android.util.Size
 import android.view.Display
+import android.view.Display.DEFAULT_DISPLAY
 import android.view.DisplayInfo
 import android.view.Surface
 import androidx.test.filters.SmallTest
@@ -29,61 +27,37 @@
 import com.android.systemui.biometrics.data.repository.DisplayStateRepositoryImpl
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
+import com.android.systemui.display.data.repository.FakeDeviceStateRepository
+import com.android.systemui.display.data.repository.FakeDisplayRepository
 import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.mockito.withArgCaptor
-import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.same
-import org.mockito.Captor
-import org.mockito.Mock
 import org.mockito.Mockito.spy
-import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoRule
-
-private const val NORMAL_DISPLAY_MODE_DEVICE_STATE = 2
-private const val REAR_DISPLAY_MODE_DEVICE_STATE = 3
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
 class DisplayStateRepositoryTest : SysuiTestCase() {
-    @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
-    @Mock private lateinit var deviceStateManager: DeviceStateManager
-    @Mock private lateinit var displayManager: DisplayManager
-    @Mock private lateinit var handler: Handler
-    @Mock private lateinit var display: Display
-    private lateinit var underTest: DisplayStateRepository
-
+    private val display = mock<Display>()
     private val testScope = TestScope(StandardTestDispatcher())
-    private val fakeExecutor = FakeExecutor(FakeSystemClock())
+    private val fakeDeviceStateRepository = FakeDeviceStateRepository()
+    private val fakeDisplayRepository = FakeDisplayRepository()
 
-    @Captor
-    private lateinit var displayListenerCaptor: ArgumentCaptor<DisplayManager.DisplayListener>
+    private lateinit var underTest: DisplayStateRepository
 
     @Before
     fun setUp() {
-        val rearDisplayDeviceStates = intArrayOf(REAR_DISPLAY_MODE_DEVICE_STATE)
-        mContext.orCreateTestableResources.addOverride(
-            com.android.internal.R.array.config_rearDisplayDeviceStates,
-            rearDisplayDeviceStates
-        )
-
         mContext.orCreateTestableResources.addOverride(
             com.android.internal.R.bool.config_reverseDefaultRotation,
             false
@@ -96,11 +70,8 @@
             DisplayStateRepositoryImpl(
                 testScope.backgroundScope,
                 mContext,
-                deviceStateManager,
-                displayManager,
-                handler,
-                fakeExecutor,
-                UnconfinedTestDispatcher(),
+                fakeDeviceStateRepository,
+                fakeDisplayRepository,
             )
     }
 
@@ -110,12 +81,10 @@
             val isInRearDisplayMode by collectLastValue(underTest.isInRearDisplayMode)
             runCurrent()
 
-            val callback = deviceStateManager.captureCallback()
-
-            callback.onStateChanged(NORMAL_DISPLAY_MODE_DEVICE_STATE)
+            fakeDeviceStateRepository.emit(DeviceState.FOLDED)
             assertThat(isInRearDisplayMode).isFalse()
 
-            callback.onStateChanged(REAR_DISPLAY_MODE_DEVICE_STATE)
+            fakeDeviceStateRepository.emit(DeviceState.REAR_DISPLAY)
             assertThat(isInRearDisplayMode).isTrue()
         }
 
@@ -125,19 +94,13 @@
             val currentRotation by collectLastValue(underTest.currentRotation)
             runCurrent()
 
-            verify(displayManager)
-                .registerDisplayListener(
-                    displayListenerCaptor.capture(),
-                    same(handler),
-                    eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED)
-                )
-
             whenever(display.getDisplayInfo(any())).then {
                 val info = it.getArgument<DisplayInfo>(0)
                 info.rotation = Surface.ROTATION_90
                 return@then true
             }
-            displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_90)
+
+            fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
             assertThat(currentRotation).isEqualTo(DisplayRotation.ROTATION_90)
 
             whenever(display.getDisplayInfo(any())).then {
@@ -145,7 +108,8 @@
                 info.rotation = Surface.ROTATION_180
                 return@then true
             }
-            displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_180)
+
+            fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
             assertThat(currentRotation).isEqualTo(DisplayRotation.ROTATION_180)
         }
 
@@ -155,13 +119,6 @@
             val currentSize by collectLastValue(underTest.currentDisplaySize)
             runCurrent()
 
-            verify(displayManager)
-                .registerDisplayListener(
-                    displayListenerCaptor.capture(),
-                    same(handler),
-                    eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED)
-                )
-
             whenever(display.getDisplayInfo(any())).then {
                 val info = it.getArgument<DisplayInfo>(0)
                 info.rotation = Surface.ROTATION_0
@@ -169,7 +126,7 @@
                 info.logicalHeight = 200
                 return@then true
             }
-            displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_0)
+            fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
             assertThat(currentSize).isEqualTo(Size(100, 200))
 
             whenever(display.getDisplayInfo(any())).then {
@@ -179,12 +136,7 @@
                 info.logicalHeight = 200
                 return@then true
             }
-            displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_180)
+            fakeDisplayRepository.emitDisplayChangeEvent(DEFAULT_DISPLAY)
             assertThat(currentSize).isEqualTo(Size(200, 100))
         }
 }
-
-private fun DeviceStateManager.captureCallback() =
-    withArgCaptor<DeviceStateManager.DeviceStateCallback> {
-        verify(this@captureCallback).registerCallback(any(), capture())
-    }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/ConfigurationStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/ui/ConfigurationStateTest.kt
deleted file mode 100644
index 112cec2..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/ConfigurationStateTest.kt
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * 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.common.ui
-
-import android.content.Context
-import android.testing.AndroidTestingRunner
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.mockito.captureMany
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.cancelAndJoin
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.atLeastOnce
-import org.mockito.Mockito.verify
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class ConfigurationStateTest : SysuiTestCase() {
-
-    private val configurationController: ConfigurationController = mock()
-    private val layoutInflater = TestLayoutInflater()
-    private val backgroundDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(backgroundDispatcher)
-
-    val underTest = ConfigurationState(configurationController, context, layoutInflater)
-
-    @Test
-    fun reinflateAndBindLatest_inflatesWithoutEmission() =
-        testScope.runTest {
-            var callbackCount = 0
-            backgroundScope.launch {
-                underTest.reinflateAndBindLatest<View>(
-                    resource = 0,
-                    root = null,
-                    attachToRoot = false,
-                    backgroundDispatcher,
-                ) {
-                    callbackCount++
-                    null
-                }
-            }
-
-            // Inflates without an emission
-            runCurrent()
-            assertThat(layoutInflater.inflationCount).isEqualTo(1)
-            assertThat(callbackCount).isEqualTo(1)
-        }
-
-    @Test
-    fun reinflateAndBindLatest_reinflatesOnThemeChanged() =
-        testScope.runTest {
-            var callbackCount = 0
-            backgroundScope.launch {
-                underTest.reinflateAndBindLatest<View>(
-                    resource = 0,
-                    root = null,
-                    attachToRoot = false,
-                    backgroundDispatcher,
-                ) {
-                    callbackCount++
-                    null
-                }
-            }
-            runCurrent()
-
-            val configListeners: List<ConfigurationController.ConfigurationListener> = captureMany {
-                verify(configurationController, atLeastOnce()).addCallback(capture())
-            }
-
-            listOf(1, 2, 3).forEach { count ->
-                assertThat(layoutInflater.inflationCount).isEqualTo(count)
-                assertThat(callbackCount).isEqualTo(count)
-                configListeners.forEach { it.onThemeChanged() }
-                runCurrent()
-            }
-        }
-
-    @Test
-    fun reinflateAndBindLatest_reinflatesOnDensityOrFontScaleChanged() =
-        testScope.runTest {
-            var callbackCount = 0
-            backgroundScope.launch {
-                underTest.reinflateAndBindLatest<View>(
-                    resource = 0,
-                    root = null,
-                    attachToRoot = false,
-                    backgroundDispatcher,
-                ) {
-                    callbackCount++
-                    null
-                }
-            }
-            runCurrent()
-
-            val configListeners: List<ConfigurationController.ConfigurationListener> = captureMany {
-                verify(configurationController, atLeastOnce()).addCallback(capture())
-            }
-
-            listOf(1, 2, 3).forEach { count ->
-                assertThat(layoutInflater.inflationCount).isEqualTo(count)
-                assertThat(callbackCount).isEqualTo(count)
-                configListeners.forEach { it.onDensityOrFontScaleChanged() }
-                runCurrent()
-            }
-        }
-
-    @Test
-    fun testReinflateAndBindLatest_disposesOnCancel() =
-        testScope.runTest {
-            var callbackCount = 0
-            var disposed = false
-            val job = launch {
-                underTest.reinflateAndBindLatest<View>(
-                    resource = 0,
-                    root = null,
-                    attachToRoot = false,
-                    backgroundDispatcher,
-                ) {
-                    callbackCount++
-                    DisposableHandle { disposed = true }
-                }
-            }
-
-            runCurrent()
-            job.cancelAndJoin()
-            assertThat(disposed).isTrue()
-        }
-
-    inner class TestLayoutInflater : LayoutInflater(context) {
-
-        var inflationCount = 0
-
-        override fun inflate(resource: Int, root: ViewGroup?, attachToRoot: Boolean): View {
-            inflationCount++
-            return View(context)
-        }
-
-        override fun cloneInContext(p0: Context?): LayoutInflater {
-            // not needed for this test
-            return this
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index b28d0c8..e30dd35d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -33,8 +33,7 @@
 import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig
-import com.android.systemui.deviceentry.data.repository.faceWakeUpTriggersConfig
+import com.android.systemui.deviceentry.data.repository.fakeFaceWakeUpTriggersConfig
 import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
 import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
 import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
@@ -56,10 +55,9 @@
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -68,37 +66,33 @@
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() {
-    val kosmos =
-        testKosmos().apply { this.faceWakeUpTriggersConfig = mock<FaceWakeUpTriggersConfig>() }
+    private val kosmos = testKosmos()
+    private val testScope: TestScope = kosmos.testScope
 
     private lateinit var underTest: SystemUIDeviceEntryFaceAuthInteractor
-    private val testScope = kosmos.testScope
     private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
     private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
     private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
     private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
     private val fakeUserRepository = kosmos.fakeUserRepository
     private val facePropertyRepository = kosmos.facePropertyRepository
-    private val fakeDeviceEntryFingerprintAuthRepository =
-        kosmos.fakeDeviceEntryFingerprintAuthRepository
+    private val fakeDeviceEntryFingerprintAuthInteractor =
+        kosmos.deviceEntryFingerprintAuthInteractor
     private val powerInteractor = kosmos.powerInteractor
     private val fakeBiometricSettingsRepository = kosmos.fakeBiometricSettingsRepository
 
     private val keyguardUpdateMonitor = kosmos.keyguardUpdateMonitor
-    private val faceWakeUpTriggersConfig = kosmos.faceWakeUpTriggersConfig
+    private val faceWakeUpTriggersConfig = kosmos.fakeFaceWakeUpTriggersConfig
     private val trustManager = kosmos.trustManager
 
     @Before
     fun setup() {
-        MockitoAnnotations.initMocks(this)
         fakeUserRepository.setUserInfos(listOf(primaryUser, secondaryUser))
-
         underTest =
             SystemUIDeviceEntryFaceAuthInteractor(
                 mContext,
@@ -110,7 +104,7 @@
                 keyguardTransitionInteractor,
                 FaceAuthenticationLogger(logcatLogBuffer("faceAuthBuffer")),
                 keyguardUpdateMonitor,
-                fakeDeviceEntryFingerprintAuthRepository,
+                fakeDeviceEntryFingerprintAuthInteractor,
                 fakeUserRepository,
                 facePropertyRepository,
                 faceWakeUpTriggersConfig,
@@ -126,10 +120,9 @@
             underTest.start()
 
             powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_LID)
-            whenever(
-                    faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(WakeSleepReason.LID)
-                )
-                .thenReturn(true)
+            faceWakeUpTriggersConfig.setTriggerFaceAuthOnWakeUpFrom(
+                setOf(WakeSleepReason.LID.powerManagerWakeReason)
+            )
 
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
@@ -168,10 +161,9 @@
             underTest.start()
 
             powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_LID)
-            whenever(
-                    faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(WakeSleepReason.LID)
-                )
-                .thenReturn(true)
+            faceWakeUpTriggersConfig.setTriggerFaceAuthOnWakeUpFrom(
+                setOf(WakeSleepReason.LID.powerManagerWakeReason)
+            )
 
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
@@ -194,10 +186,9 @@
             underTest.start()
 
             powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_LIFT)
-            whenever(
-                    faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(WakeSleepReason.LIFT)
-                )
-                .thenReturn(false)
+            faceWakeUpTriggersConfig.setTriggerFaceAuthOnWakeUpFrom(
+                setOf(WakeSleepReason.LID.powerManagerWakeReason)
+            )
 
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
@@ -217,10 +208,9 @@
             underTest.start()
 
             powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_LID)
-            whenever(
-                    faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(WakeSleepReason.LID)
-                )
-                .thenReturn(true)
+            faceWakeUpTriggersConfig.setTriggerFaceAuthOnWakeUpFrom(
+                setOf(WakeSleepReason.LID.powerManagerWakeReason)
+            )
 
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
@@ -440,7 +430,45 @@
             underTest.start()
             fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
 
-            fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)
+            runCurrent()
+
+            assertThat(faceAuthRepository.isLockedOut.value).isTrue()
+        }
+
+    @Test
+    fun faceLockoutStateIsResetWheneverFingerprintIsNotLockedOut() =
+        testScope.runTest {
+            underTest.start()
+            fakeUserRepository.setSelectedUserInfo(primaryUser, SelectionStatus.SELECTION_COMPLETE)
+            fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)
+            runCurrent()
+
+            assertThat(faceAuthRepository.isLockedOut.value).isTrue()
+            facePropertyRepository.setLockoutMode(primaryUserId, LockoutMode.NONE)
+
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(false)
+            runCurrent()
+
+            assertThat(faceAuthRepository.isLockedOut.value).isFalse()
+        }
+
+    @Test
+    fun faceLockoutStateIsSetToUsersLockoutStateWheneverFingerprintIsNotLockedOut() =
+        testScope.runTest {
+            underTest.start()
+            fakeUserRepository.setSelectedUserInfo(primaryUser, SelectionStatus.SELECTION_COMPLETE)
+            fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)
+            runCurrent()
+
+            assertThat(faceAuthRepository.isLockedOut.value).isTrue()
+            facePropertyRepository.setLockoutMode(primaryUserId, LockoutMode.TIMED)
+
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(false)
             runCurrent()
 
             assertThat(faceAuthRepository.isLockedOut.value).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
index db04962..796d6d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
@@ -20,8 +20,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestCoroutineScheduler
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runTest
@@ -36,6 +36,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class SeekableSliderTrackerTest : SysuiTestCase() {
 
@@ -51,7 +52,7 @@
     @Test
     fun initializeSliderTracker_startsTracking() = runTest {
         // GIVEN Initialized tracker
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // THEN the tracker job is active
         assertThat(mSeekableSliderTracker.isTracking).isTrue()
@@ -61,7 +62,7 @@
     fun stopTracking_onAnyState_resetsToIdle() = runTest {
         enumValues<SliderState>().forEach {
             // GIVEN Initialized tracker
-            initTracker(testScheduler)
+            initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
             // GIVEN a state in the state machine
             mSeekableSliderTracker.setState(it)
@@ -79,7 +80,7 @@
     @Test
     fun initializeSliderTracker_isIdle() = runTest {
         // GIVEN Initialized tracker
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // THEN The state is idle and the listener is not called to play haptics
         assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
@@ -88,7 +89,7 @@
 
     @Test
     fun startsTrackingTouch_onIdle_entersWaitState() = runTest {
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // GIVEN a start of tracking touch event
         val progress = 0f
@@ -106,7 +107,7 @@
     @Test
     fun waitCompletes_onWait_movesToHandleAcquired() = runTest {
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
         // GIVEN a start of tracking touch event that moves the tracker to WAIT
         val progress = 0f
@@ -126,7 +127,7 @@
     @Test
     fun impreciseTouch_onWait_movesToHandleAcquired() = runTest {
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
         // GIVEN a start of tracking touch event that moves the tracker to WAIT at the middle of the
         // slider
@@ -151,7 +152,7 @@
     @Test
     fun trackJump_onWait_movesToJumpTrackLocationSelected() = runTest {
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
         // GIVEN a start of tracking touch event that moves the tracker to WAIT at the middle of the
         // slider
@@ -175,7 +176,7 @@
     @Test
     fun upperBookendSelection_onWait_movesToBookendSelected() = runTest {
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
         // GIVEN a start of tracking touch event that moves the tracker to WAIT at the middle of the
         // slider
@@ -197,7 +198,7 @@
     @Test
     fun lowerBookendSelection_onWait_movesToBookendSelected() = runTest {
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
         // GIVEN a start of tracking touch event that moves the tracker to WAIT at the middle of the
         // slider
@@ -219,7 +220,7 @@
     @Test
     fun stopTracking_onWait_whenWaitingJobIsActive_resetsToIdle() = runTest {
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
         // GIVEN a start of tracking touch event that moves the tracker to WAIT at the middle of the
         // slider
@@ -240,7 +241,7 @@
 
     @Test
     fun progressChangeByUser_onJumpTrackLocationSelected_movesToDragHandleDragging() = runTest {
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // GIVEN a JUMP_TRACK_LOCATION_SELECTED state
         mSeekableSliderTracker.setState(SliderState.JUMP_TRACK_LOCATION_SELECTED)
@@ -256,7 +257,7 @@
 
     @Test
     fun touchRelease_onJumpTrackLocationSelected_movesToIdle() = runTest {
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // GIVEN a JUMP_TRACK_LOCATION_SELECTED state
         mSeekableSliderTracker.setState(SliderState.JUMP_TRACK_LOCATION_SELECTED)
@@ -272,7 +273,7 @@
 
     @Test
     fun progressChangeByUser_onJumpBookendSelected_movesToDragHandleDragging() = runTest {
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // GIVEN a JUMP_BOOKEND_SELECTED state
         mSeekableSliderTracker.setState(SliderState.JUMP_BOOKEND_SELECTED)
@@ -288,7 +289,7 @@
 
     @Test
     fun touchRelease_onJumpBookendSelected_movesToIdle() = runTest {
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // GIVEN a JUMP_BOOKEND_SELECTED state
         mSeekableSliderTracker.setState(SliderState.JUMP_BOOKEND_SELECTED)
@@ -306,7 +307,7 @@
 
     @Test
     fun progressChangeByUser_onHandleAcquired_movesToDragHandleDragging() = runTest {
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // GIVEN a DRAG_HANDLE_ACQUIRED_BY_TOUCH state
         mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_ACQUIRED_BY_TOUCH)
@@ -325,7 +326,7 @@
 
     @Test
     fun touchRelease_onHandleAcquired_movesToIdle() = runTest {
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // GIVEN a DRAG_HANDLE_ACQUIRED_BY_TOUCH state
         mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_ACQUIRED_BY_TOUCH)
@@ -344,7 +345,7 @@
     @Test
     fun progressChangeByUser_onHandleDragging_progressOutsideOfBookends_doesNotChangeState() =
         runTest {
-            initTracker(testScheduler)
+            initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
             // GIVEN a DRAG_HANDLE_DRAGGING state
             mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_DRAGGING)
@@ -366,7 +367,7 @@
     fun progressChangeByUser_onHandleDragging_reachesLowerBookend_movesToHandleReachedBookend() =
         runTest {
             val config = SeekableSliderTrackerConfig()
-            initTracker(testScheduler, config)
+            initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
             // GIVEN a DRAG_HANDLE_DRAGGING state
             mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_DRAGGING)
@@ -389,7 +390,7 @@
     fun progressChangeByUser_onHandleDragging_reachesUpperBookend_movesToHandleReachedBookend() =
         runTest {
             val config = SeekableSliderTrackerConfig()
-            initTracker(testScheduler, config)
+            initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
             // GIVEN a DRAG_HANDLE_DRAGGING state
             mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_DRAGGING)
@@ -410,7 +411,7 @@
 
     @Test
     fun touchRelease_onHandleDragging_movesToIdle() = runTest {
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // GIVEN a DRAG_HANDLE_DRAGGING state
         mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_DRAGGING)
@@ -430,7 +431,7 @@
     fun progressChangeByUser_outsideOfBookendRange_onLowerBookend_movesToDragHandleDragging() =
         runTest {
             val config = SeekableSliderTrackerConfig()
-            initTracker(testScheduler, config)
+            initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
             // GIVEN a DRAG_HANDLE_REACHED_BOOKEND state
             mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_REACHED_BOOKEND)
@@ -451,7 +452,7 @@
     @Test
     fun progressChangeByUser_insideOfBookendRange_onLowerBookend_doesNotChangeState() = runTest {
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
         // GIVEN a DRAG_HANDLE_REACHED_BOOKEND state
         mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_REACHED_BOOKEND)
@@ -473,7 +474,7 @@
     fun progressChangeByUser_outsideOfBookendRange_onUpperBookend_movesToDragHandleDragging() =
         runTest {
             val config = SeekableSliderTrackerConfig()
-            initTracker(testScheduler, config)
+            initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
             // GIVEN a DRAG_HANDLE_REACHED_BOOKEND state
             mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_REACHED_BOOKEND)
@@ -494,7 +495,7 @@
     @Test
     fun progressChangeByUser_insideOfBookendRange_onUpperBookend_doesNotChangeState() = runTest {
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
         // GIVEN a DRAG_HANDLE_REACHED_BOOKEND state
         mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_REACHED_BOOKEND)
@@ -514,7 +515,7 @@
 
     @Test
     fun touchRelease_onHandleReachedBookend_movesToIdle() = runTest {
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // GIVEN a DRAG_HANDLE_REACHED_BOOKEND state
         mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_REACHED_BOOKEND)
@@ -531,7 +532,7 @@
     @Test
     fun onProgressChangeByProgram_atTheMiddle_onIdle_movesToArrowHandleMovedOnce() = runTest {
         // GIVEN an initialized tracker in the IDLE state
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // GIVEN a progress due to an external source that lands at the middle of the slider
         val progress = 0.5f
@@ -550,7 +551,7 @@
     fun onProgressChangeByProgram_atUpperBookend_onIdle_movesToIdle() = runTest {
         // GIVEN an initialized tracker in the IDLE state
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
         // GIVEN a progress due to an external source that lands at the upper bookend
         val progress = config.upperBookendThreshold + 0.01f
@@ -567,7 +568,7 @@
     fun onProgressChangeByProgram_atLowerBookend_onIdle_movesToIdle() = runTest {
         // GIVEN an initialized tracker in the IDLE state
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
         // WHEN a progress is recorded due to an external source that lands at the lower bookend
         val progress = config.lowerBookendThreshold - 0.01f
@@ -583,7 +584,7 @@
     @Test
     fun onArrowUp_onArrowMovedOnce_movesToIdle() = runTest {
         // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
         mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
 
         // WHEN the external stimulus is released
@@ -598,7 +599,7 @@
     @Test
     fun onStartTrackingTouch_onArrowMovedOnce_movesToWait() = runTest {
         // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
         mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
 
         // WHEN the slider starts tracking touch
@@ -615,7 +616,7 @@
     @Test
     fun onProgressChangeByProgram_onArrowMovedOnce_movesToArrowMovesContinuously() = runTest {
         // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
         mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
 
         // WHEN the slider gets an external progress change
@@ -634,7 +635,7 @@
     @Test
     fun onArrowUp_onArrowMovesContinuously_movesToIdle() = runTest {
         // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
         mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
 
         // WHEN the external stimulus is released
@@ -649,7 +650,7 @@
     @Test
     fun onStartTrackingTouch_onArrowMovesContinuously_movesToWait() = runTest {
         // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
         mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
 
         // WHEN the slider starts tracking touch
@@ -665,7 +666,7 @@
     @Test
     fun onProgressChangeByProgram_onArrowMovesContinuously_preservesState() = runTest {
         // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
         mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
 
         // WHEN the slider changes progress programmatically at the middle
@@ -684,7 +685,7 @@
     fun onProgramProgress_atLowerBookend_onArrowMovesContinuously_movesToIdle() = runTest {
         // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
         mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
 
         // WHEN the slider reaches the lower bookend programmatically
@@ -702,7 +703,7 @@
     fun onProgramProgress_atUpperBookend_onArrowMovesContinuously_movesToIdle() = runTest {
         // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
         mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
 
         // WHEN the slider reaches the lower bookend programmatically
@@ -718,16 +719,11 @@
 
     @OptIn(ExperimentalCoroutinesApi::class)
     private fun initTracker(
-        scheduler: TestCoroutineScheduler,
+        scope: CoroutineScope,
         config: SeekableSliderTrackerConfig = SeekableSliderTrackerConfig(),
     ) {
         mSeekableSliderTracker =
-            SeekableSliderTracker(
-                sliderStateListener,
-                sliderEventProducer,
-                UnconfinedTestDispatcher(scheduler),
-                config
-            )
+            SeekableSliderTracker(sliderStateListener, sliderEventProducer, scope, config)
         mSeekableSliderTracker.startTracking()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt
deleted file mode 100644
index ed80a86..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-package com.android.systemui.keyboard.stickykeys.data.repository
-
-import android.content.pm.UserInfo
-import android.hardware.input.InputManager
-import android.provider.Settings.Secure.ACCESSIBILITY_STICKY_KEYS
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.coroutines.collectValues
-import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.settings.FakeSettings
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-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.junit.runners.JUnit4
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class StickyKeysRepositoryImplTest : SysuiTestCase() {
-
-    private val dispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(dispatcher)
-    private val secureSettings = FakeSettings()
-    private val userRepository = Kosmos().fakeUserRepository
-    private lateinit var stickyKeysRepository: StickyKeysRepositoryImpl
-
-    @Before
-    fun setup() {
-        stickyKeysRepository = StickyKeysRepositoryImpl(
-            mock<InputManager>(),
-            dispatcher,
-            secureSettings,
-            userRepository,
-            mock<StickyKeysLogger>()
-        )
-        userRepository.setUserInfos(USER_INFOS)
-        setStickyKeySettingForUser(enabled = true, userInfo = SETTING_ENABLED_USER)
-        setStickyKeySettingForUser(enabled = false, userInfo = SETTING_DISABLED_USER)
-    }
-
-    @Test
-    fun settingEnabledEmitsValueForCurrentUser() {
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
-
-            val enabled by collectLastValue(stickyKeysRepository.settingEnabled)
-
-            assertThat(enabled).isTrue()
-        }
-    }
-
-    @Test
-    fun settingEnabledEmitsNewValueWhenSettingChanges() {
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
-            val enabled by collectValues(stickyKeysRepository.settingEnabled)
-            runCurrent()
-
-            setStickyKeySettingForUser(enabled = false, userInfo = SETTING_ENABLED_USER)
-
-            assertThat(enabled).containsExactly(true, false).inOrder()
-        }
-    }
-
-    @Test
-    fun settingEnabledEmitsValueForNewUserWhenUserChanges() {
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
-            val enabled by collectLastValue(stickyKeysRepository.settingEnabled)
-            runCurrent()
-
-            userRepository.setSelectedUserInfo(SETTING_DISABLED_USER)
-
-            assertThat(enabled).isFalse()
-        }
-    }
-
-    private fun setStickyKeySettingForUser(enabled: Boolean, userInfo: UserInfo) {
-        val newValue = if (enabled) "1" else "0"
-        secureSettings.putStringForUser(ACCESSIBILITY_STICKY_KEYS, newValue, userInfo.id)
-    }
-
-    private companion object {
-        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)
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
index 6eebb6d..d14d72d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
@@ -37,6 +37,7 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 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.StandardTestDispatcher
@@ -68,11 +69,15 @@
 
     @Before
     fun setup() {
+        val settingsRepository = UserAwareSecureSettingsRepositoryImpl(
+            secureSettings,
+            userRepository,
+            dispatcher
+        )
         val stickyKeysRepository = StickyKeysRepositoryImpl(
             inputManager,
             dispatcher,
-            secureSettings,
-            userRepository,
+            settingsRepository,
             mock<StickyKeysLogger>()
         )
         setStickyKeySetting(enabled = false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 14cae0b..24cf164 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -94,7 +94,6 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.flags.SystemPropertiesHelper;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
@@ -754,7 +753,7 @@
 
     @Test
     public void testUpdateIsKeyguardAfterOccludeAnimationEnds() {
-        mViewMediator.mOccludeAnimationController.onLaunchAnimationEnd(
+        mViewMediator.mOccludeAnimationController.onTransitionAnimationEnd(
                 false /* isExpandingFullyAbove */);
 
         // Since the updateIsKeyguard call is delayed during the animation, ensure it's called once
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.kt
new file mode 100644
index 0000000..c174cb8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorTest.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.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+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.Mockito.reset
+import org.mockito.Mockito.spy
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromGoneTransitionInteractorTest : SysuiTestCase() {
+    private val kosmos =
+        testKosmos().apply {
+            fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+        }
+    private val testScope = kosmos.testScope
+    private val underTest = kosmos.fromGoneTransitionInteractor
+    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+    @Before
+    fun setUp() {
+        underTest.start()
+    }
+
+    @Test
+    fun testDoesNotTransitionToLockscreen_ifStartedButNotFinishedInGone() =
+        testScope.runTest {
+            keyguardTransitionRepository.sendTransitionSteps(
+                listOf(
+                    TransitionStep(
+                        from = KeyguardState.LOCKSCREEN,
+                        to = KeyguardState.GONE,
+                        transitionState = TransitionState.STARTED,
+                    ),
+                    TransitionStep(
+                        from = KeyguardState.LOCKSCREEN,
+                        to = KeyguardState.GONE,
+                        transitionState = TransitionState.RUNNING,
+                    ),
+                ),
+                testScope,
+            )
+            reset(keyguardTransitionRepository)
+            kosmos.fakeKeyguardRepository.setKeyguardShowing(true)
+            runCurrent()
+
+            // We're in the middle of a LOCKSCREEN -> GONE transition.
+            assertThat(keyguardTransitionRepository).noTransitionsStarted()
+        }
+
+    @Test
+    fun testTransitionsToLockscreen_ifFinishedInGone() =
+        testScope.runTest {
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                testScope,
+            )
+            reset(keyguardTransitionRepository)
+            kosmos.fakeKeyguardRepository.setKeyguardShowing(true)
+            runCurrent()
+
+            // We're in the middle of a LOCKSCREEN -> GONE transition.
+            assertThat(keyguardTransitionRepository)
+                .startedTransition(
+                    to = KeyguardState.LOCKSCREEN,
+                )
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
index 668fb64..6d8e7aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
@@ -27,17 +27,15 @@
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat as assertThatRepository
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.shade.data.repository.FlingInfo
 import com.android.systemui.shade.data.repository.fakeShadeRepository
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
-import junit.framework.Assert.assertEquals
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -132,13 +130,11 @@
             )
             runCurrent()
 
-            withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-                .also {
-                    assertEquals(KeyguardState.LOCKSCREEN, it.from)
-                    assertEquals(KeyguardState.GONE, it.to)
-                }
+            assertThatRepository(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
         }
 
     @Test
@@ -155,6 +151,6 @@
             )
             runCurrent()
 
-            verify(transitionRepository, never()).startTransition(any())
+            assertThatRepository(transitionRepository).noTransitionsStarted()
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
index f23dd55..3f05bfa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.keyguard.util.mockTopActivityClassName
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.shared.system.activityManagerWrapper
+import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor
 import com.android.systemui.testKosmos
 import com.android.systemui.util.assertValuesMatch
 import com.google.common.truth.Truth.assertThat
@@ -192,4 +193,38 @@
                 )
                 .inOrder()
         }
+
+    @Test
+    fun testSurfaceBehindModel_fromNotificationLaunch() =
+        testScope.runTest {
+            val values by collectValues(underTest.viewParams)
+            runCurrent()
+
+            kosmos.notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(true)
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    transitionState = TransitionState.STARTED,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    transitionState = TransitionState.RUNNING,
+                    value = 0.5f,
+                )
+            )
+            runCurrent()
+
+            values.assertValuesMatch(
+                // We should be at alpha = 0f during the animation.
+                { it == KeyguardSurfaceBehindModel(alpha = 0f) },
+            )
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index bb61d18..5b93df5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -37,9 +37,9 @@
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -51,13 +51,12 @@
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.mockito.withArgCaptor
-import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.cancelChildren
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -254,15 +253,13 @@
             bouncerRepository.setPrimaryShow(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to PRIMARY_BOUNCER should occur
-            assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    from = KeyguardState.LOCKSCREEN,
+                    ownerName = "FromLockscreenTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -281,15 +278,13 @@
             powerInteractor.setAsleepForTest()
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.to).isEqualTo(KeyguardState.DOZING)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.DOZING,
+                    from = KeyguardState.OCCLUDED,
+                    ownerName = "FromOccludedTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -308,15 +303,13 @@
             powerInteractor.setAsleepForTest()
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.to).isEqualTo(KeyguardState.AOD)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.AOD,
+                    from = KeyguardState.OCCLUDED,
+                    ownerName = "FromOccludedTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -336,17 +329,15 @@
 
             // WHEN the device begins to dream
             keyguardRepository.setDreamingWithOverlay(true)
-            advanceUntilIdle()
+            advanceTimeBy(100L)
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DREAMING should occur
-            assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.to).isEqualTo(KeyguardState.DREAMING)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.DREAMING,
+                    from = KeyguardState.LOCKSCREEN,
+                    ownerName = "FromLockscreenTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -367,17 +358,15 @@
             // WHEN the device begins to dream and the dream is lockscreen hosted
             keyguardRepository.setDreamingWithOverlay(true)
             keyguardRepository.setIsActiveDreamLockscreenHosted(true)
-            advanceUntilIdle()
+            advanceTimeBy(100L)
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
-            assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    ownerName = "FromLockscreenTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -396,15 +385,13 @@
             powerInteractor.setAsleepForTest()
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.to).isEqualTo(KeyguardState.DOZING)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.DOZING,
+                    from = KeyguardState.LOCKSCREEN,
+                    ownerName = "FromLockscreenTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -423,15 +410,13 @@
             powerInteractor.setAsleepForTest()
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.to).isEqualTo(KeyguardState.AOD)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.AOD,
+                    from = KeyguardState.LOCKSCREEN,
+                    ownerName = "FromLockscreenTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -454,17 +439,15 @@
 
             // WHEN the lockscreen hosted dream stops
             keyguardRepository.setIsActiveDreamLockscreenHosted(false)
-            advanceUntilIdle()
+            advanceTimeBy(100L)
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to Lockscreen should occur
-            assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
-            assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.LOCKSCREEN,
+                    from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+                    ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -484,15 +467,13 @@
             )
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to Gone should occur
-            assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
-            assertThat(info.to).isEqualTo(KeyguardState.GONE)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.GONE,
+                    from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+                    ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -514,15 +495,13 @@
             bouncerRepository.setPrimaryShow(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to PRIMARY_BOUNCER should occur
-            assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
-            assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+                    ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -547,15 +526,13 @@
             )
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
-            assertThat(info.to).isEqualTo(KeyguardState.DOZING)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.DOZING,
+                    from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+                    ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -579,15 +556,13 @@
             keyguardRepository.setKeyguardOccluded(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to OCCLUDED should occur
-            assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
-            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.OCCLUDED,
+                    from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+                    ownerName = "FromDreamingLockscreenHostedTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -603,15 +578,13 @@
             powerInteractor.setAwakeForTest()
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.DOZING)
-            assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.LOCKSCREEN,
+                    from = KeyguardState.DOZING,
+                    ownerName = "FromDozingTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -649,7 +622,7 @@
 
             // WHEN a signal comes that dreaming is enabled
             keyguardRepository.setDreamingWithOverlay(true)
-            advanceUntilIdle()
+            advanceTimeBy(100L)
 
             // THEN the transition is ignored
             verify(transitionRepository, never()).startTransition(any())
@@ -667,15 +640,13 @@
             keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.DOZING)
-            assertThat(info.to).isEqualTo(KeyguardState.GONE)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.GONE,
+                    from = KeyguardState.DOZING,
+                    ownerName = "FromDozingTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -699,15 +670,13 @@
             powerInteractor.setAwakeForTest()
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo(FromDozingTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.DOZING)
-            assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    from = KeyguardState.DOZING,
+                    ownerName = FromDozingTransitionInteractor::class.simpleName,
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -726,15 +695,13 @@
             powerInteractor.setAsleepForTest()
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.GONE)
-            assertThat(info.to).isEqualTo(KeyguardState.DOZING)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.DOZING,
+                    from = KeyguardState.GONE,
+                    ownerName = "FromGoneTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -753,15 +720,13 @@
             powerInteractor.setAsleepForTest()
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to AOD should occur
-            assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.GONE)
-            assertThat(info.to).isEqualTo(KeyguardState.AOD)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.AOD,
+                    from = KeyguardState.GONE,
+                    ownerName = "FromGoneTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -776,15 +741,13 @@
             keyguardRepository.setKeyguardShowing(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to AOD should occur
-            assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.GONE)
-            assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.LOCKSCREEN,
+                    from = KeyguardState.GONE,
+                    ownerName = "FromGoneTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -804,17 +767,15 @@
 
             // WHEN the device begins to dream
             keyguardRepository.setDreamingWithOverlay(true)
-            advanceUntilIdle()
+            advanceTimeBy(100L)
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DREAMING should occur
-            assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.GONE)
-            assertThat(info.to).isEqualTo(KeyguardState.DREAMING)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.DREAMING,
+                    from = KeyguardState.GONE,
+                    ownerName = "FromGoneTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -835,17 +796,15 @@
             // WHEN the device begins to dream with the lockscreen hosted dream
             keyguardRepository.setDreamingWithOverlay(true)
             keyguardRepository.setIsActiveDreamLockscreenHosted(true)
-            advanceUntilIdle()
+            advanceTimeBy(100L)
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
-            assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.GONE)
-            assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+                    from = KeyguardState.GONE,
+                    ownerName = "FromGoneTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -868,15 +827,13 @@
             keyguardRepository.setKeyguardShowing(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo(FromGoneTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.GONE)
-            assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    from = KeyguardState.GONE,
+                    ownerName = FromGoneTransitionInteractor::class.simpleName,
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -894,21 +851,19 @@
             bouncerRepository.setPrimaryShow(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to PRIMARY_BOUNCER should occur
-            assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
-            assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    ownerName = "FromAlternateBouncerTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
 
     @Test
-    fun alternateBoucnerToAod() =
+    fun alternateBouncerToAod() =
         testScope.runTest {
             // GIVEN a prior transition has run to ALTERNATE_BOUNCER
             bouncerRepository.setAlternateVisible(true)
@@ -924,17 +879,15 @@
 
             // WHEN the alternateBouncer stops showing
             bouncerRepository.setAlternateVisible(false)
-            advanceUntilIdle()
+            advanceTimeBy(200L)
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to AOD should occur
-            assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
-            assertThat(info.to).isEqualTo(KeyguardState.AOD)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.AOD,
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    ownerName = "FromAlternateBouncerTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -957,17 +910,15 @@
 
             // WHEN the alternateBouncer stops showing
             bouncerRepository.setAlternateVisible(false)
-            advanceUntilIdle()
+            advanceTimeBy(200L)
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
-            assertThat(info.to).isEqualTo(KeyguardState.DOZING)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.DOZING,
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    ownerName = "FromAlternateBouncerTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -987,17 +938,15 @@
 
             // WHEN the alternateBouncer stops showing
             bouncerRepository.setAlternateVisible(false)
-            advanceUntilIdle()
+            advanceTimeBy(200L)
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to LOCKSCREEN should occur
-            assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
-            assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromAlternateBouncerTransitionInteractor",
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.LOCKSCREEN,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1025,18 +974,16 @@
 
             // WHEN the alternateBouncer stops showing
             bouncerRepository.setAlternateVisible(false)
-            advanceUntilIdle()
+            advanceTimeBy(200L)
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to LOCKSCREEN should occur
-            assertThat(info.ownerName)
-                .isEqualTo(FromAlternateBouncerTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
-            assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromAlternateBouncerTransitionInteractor::class.simpleName,
+                    from = KeyguardState.ALTERNATE_BOUNCER,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1056,15 +1003,14 @@
             bouncerRepository.setPrimaryShow(false)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to AOD should occur
-            assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.to).isEqualTo(KeyguardState.AOD)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromPrimaryBouncerTransitionInteractor",
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.AOD,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1084,15 +1030,14 @@
             bouncerRepository.setPrimaryShow(false)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to DOZING should occur
-            assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.to).isEqualTo(KeyguardState.DOZING)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromPrimaryBouncerTransitionInteractor",
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.DOZING,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1108,15 +1053,14 @@
             bouncerRepository.setPrimaryShow(false)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to LOCKSCREEN should occur
-            assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromPrimaryBouncerTransitionInteractor",
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.LOCKSCREEN,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1140,16 +1084,14 @@
             bouncerRepository.setPrimaryShow(false)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to LOCKSCREEN should occur
-            assertThat(info.ownerName)
-                .isEqualTo(FromPrimaryBouncerTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromPrimaryBouncerTransitionInteractor::class.simpleName,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1171,15 +1113,14 @@
             bouncerRepository.setPrimaryShow(false)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition back to DREAMING_LOCKSCREEN_HOSTED should occur
-            assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromPrimaryBouncerTransitionInteractor",
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1202,15 +1143,14 @@
             keyguardRepository.setKeyguardOccluded(false)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to GONE should occur
-            assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.to).isEqualTo(KeyguardState.GONE)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromOccludedTransitionInteractor",
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.GONE,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1231,15 +1171,14 @@
             keyguardRepository.setKeyguardOccluded(false)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to LOCKSCREEN should occur
-            assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromOccludedTransitionInteractor",
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.LOCKSCREEN,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1268,15 +1207,14 @@
             keyguardRepository.setKeyguardOccluded(false)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to GLANCEABLE_HUB should occur
-            assertThat(info.ownerName).isEqualTo(FromOccludedTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromOccludedTransitionInteractor::class.simpleName,
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1293,15 +1231,14 @@
             bouncerRepository.setAlternateVisible(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to AlternateBouncer should occur
-            assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.to).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromOccludedTransitionInteractor",
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.ALTERNATE_BOUNCER,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1318,15 +1255,14 @@
             bouncerRepository.setPrimaryShow(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to AlternateBouncer should occur
-            assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromOccludedTransitionInteractor",
+                    from = KeyguardState.OCCLUDED,
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1344,15 +1280,14 @@
             bouncerRepository.setPrimaryShow(false)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to OCCLUDED should occur
-            assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromPrimaryBouncerTransitionInteractor",
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.OCCLUDED,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1369,15 +1304,14 @@
             powerInteractor.setAwakeForTest()
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to OCCLUDED should occur
-            assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.DOZING)
-            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromDozingTransitionInteractor",
+                    from = KeyguardState.DOZING,
+                    to = KeyguardState.OCCLUDED,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1396,15 +1330,14 @@
             powerInteractor.setAwakeForTest()
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to OCCLUDED should occur
-            assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
-            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromDreamingTransitionInteractor",
+                    from = KeyguardState.DREAMING,
+                    to = KeyguardState.OCCLUDED,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1426,15 +1359,14 @@
             )
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to AOD should occur
-            assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
-            assertThat(info.to).isEqualTo(KeyguardState.AOD)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromDreamingTransitionInteractor",
+                    from = KeyguardState.DREAMING,
+                    to = KeyguardState.AOD,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1450,15 +1382,14 @@
             keyguardRepository.setKeyguardOccluded(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to OCCLUDED should occur
-            assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromLockscreenTransitionInteractor",
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.OCCLUDED,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1474,15 +1405,14 @@
             keyguardRepository.setKeyguardOccluded(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to OCCLUDED should occur
-            assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.AOD)
-            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromAodTransitionInteractor",
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.OCCLUDED,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1498,15 +1428,14 @@
             bouncerRepository.setPrimaryShow(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
             // THEN a transition to OCCLUDED should occur
-            assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.AOD)
-            assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromAodTransitionInteractor",
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1532,14 +1461,13 @@
             runCurrent()
 
             // THEN a transition from LOCKSCREEN => OCCLUDED should occur
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromLockscreenTransitionInteractor",
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.OCCLUDED,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1559,14 +1487,13 @@
             runCurrent()
 
             // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
-            assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.animator).isNull() // dragging should be manually animated
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = "FromLockscreenTransitionInteractor",
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    animatorAssertion = { it.isNull() }, // dragging should be manually animated
+                )
 
             // WHEN the user stops dragging and shade is back to expanded
             clearInvocations(transitionRepository)
@@ -1575,14 +1502,13 @@
             shadeRepository.setLegacyShadeExpansion(1f)
             runCurrent()
 
-            // THEN a transition from PRIMARY_BOUNCER => LOCKSCREEN should occur
-            val info2 =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            assertThat(info2.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info2.to).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info2.animator).isNotNull()
+            // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
+            assertThat(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.LOCKSCREEN,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1614,15 +1540,13 @@
             runCurrent()
 
             // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            assertThat(info.ownerName)
-                .isEqualTo(FromLockscreenTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.animator).isNull() // transition should be manually animated
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromLockscreenTransitionInteractor::class.simpleName,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                    animatorAssertion = { it.isNull() }, // transition should be manually animated
+                )
 
             // WHEN the user stops dragging and the glanceable hub opening is cancelled
             clearInvocations(transitionRepository)
@@ -1634,14 +1558,13 @@
             communalInteractor.setTransitionState(idleTransitionState)
             runCurrent()
 
-            // THEN a transition from GLANCEABLE_HUB => LOCKSCREEN should occur
-            val info2 =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            assertThat(info2.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info2.to).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.animator).isNull() // transition should be manually animated
+            // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromLockscreenTransitionInteractor::class.simpleName,
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.LOCKSCREEN,
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1672,16 +1595,13 @@
             progress.value = .1f
             runCurrent()
 
-            // THEN a transition from GLANCEABLE_HUB => LOCKSCREEN should occur
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            assertThat(info.ownerName)
-                .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info.animator).isNull() // transition should be manually animated
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.LOCKSCREEN,
+                    animatorAssertion = { it.isNull() }, // transition should be manually animated
+                )
 
             // WHEN the user stops dragging and the glanceable hub closing is cancelled
             clearInvocations(transitionRepository)
@@ -1693,14 +1613,11 @@
             communalInteractor.setTransitionState(idleTransitionState)
             runCurrent()
 
-            // THEN a transition from LOCKSCREEN => GLANCEABLE_HUB should occur
-            val info2 =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            assertThat(info2.from).isEqualTo(KeyguardState.LOCKSCREEN)
-            assertThat(info2.to).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.animator).isNull() // transition should be manually animated
+            assertThat(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GLANCEABLE_HUB,
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1715,16 +1632,13 @@
             powerInteractor.setAsleepForTest()
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName)
-                .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.to).isEqualTo(KeyguardState.DOZING)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.DOZING,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1739,16 +1653,13 @@
             bouncerRepository.setPrimaryShow(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to PRIMARY_BOUNCER should occur
-            assertThat(info.ownerName)
-                .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1763,16 +1674,13 @@
             bouncerRepository.setAlternateVisible(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to PRIMARY_BOUNCER should occur
-            assertThat(info.ownerName)
-                .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.to).isEqualTo(KeyguardState.ALTERNATE_BOUNCER)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.ALTERNATE_BOUNCER,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1796,16 +1704,13 @@
             keyguardRepository.setKeyguardOccluded(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to OCCLUDED should occur
-            assertThat(info.ownerName)
-                .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.OCCLUDED,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1820,16 +1725,13 @@
             keyguardRepository.setKeyguardGoingAway(true)
             runCurrent()
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DOZING should occur
-            assertThat(info.ownerName)
-                .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.to).isEqualTo(KeyguardState.GONE)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.GONE,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
@@ -1849,33 +1751,19 @@
 
             // WHEN the device begins to dream
             keyguardRepository.setDreamingWithOverlay(true)
-            advanceUntilIdle()
+            advanceTimeBy(100L)
 
-            val info =
-                withArgCaptor<TransitionInfo> {
-                    verify(transitionRepository).startTransition(capture())
-                }
-            // THEN a transition to DREAMING should occur
-            assertThat(info.ownerName)
-                .isEqualTo(FromGlanceableHubTransitionInteractor::class.simpleName)
-            assertThat(info.from).isEqualTo(KeyguardState.GLANCEABLE_HUB)
-            assertThat(info.to).isEqualTo(KeyguardState.DREAMING)
-            assertThat(info.animator).isNotNull()
+            assertThat(transitionRepository)
+                .startedTransition(
+                    ownerName = FromGlanceableHubTransitionInteractor::class.simpleName,
+                    from = KeyguardState.GLANCEABLE_HUB,
+                    to = KeyguardState.DREAMING,
+                    animatorAssertion = { it.isNotNull() },
+                )
 
             coroutineContext.cancelChildren()
         }
 
-    private fun createKeyguardInteractor(): KeyguardInteractor {
-        return KeyguardInteractorFactory.create(
-                featureFlags = featureFlags,
-                repository = keyguardRepository,
-                commandQueue = commandQueue,
-                bouncerRepository = bouncerRepository,
-                powerInteractor = powerInteractor,
-            )
-            .keyguardInteractor
-    }
-
     private suspend fun TestScope.runTransitionAndSetWakefulness(
         from: KeyguardState,
         to: KeyguardState
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
index 2d9d5ed..0e9197e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
@@ -181,6 +181,31 @@
         }
 
     @Test
+    fun usesOnStepToDoubleValueWithState() =
+        testScope.runTest {
+            val flow =
+                underTest.sharedFlowWithState(
+                    duration = 1000.milliseconds,
+                    onStep = { it * 2 },
+                )
+            val animationValues by collectLastValue(flow)
+            runCurrent()
+
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            assertThat(animationValues).isEqualTo(StateToValue(TransitionState.STARTED, 0f))
+            repository.sendTransitionStep(step(0.3f, TransitionState.RUNNING))
+            assertThat(animationValues).isEqualTo(StateToValue(TransitionState.RUNNING, 0.6f))
+            repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING))
+            assertThat(animationValues).isEqualTo(StateToValue(TransitionState.RUNNING, 1.2f))
+            repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING))
+            assertThat(animationValues).isEqualTo(StateToValue(TransitionState.RUNNING, 1.6f))
+            repository.sendTransitionStep(step(1f, TransitionState.RUNNING))
+            assertThat(animationValues).isEqualTo(StateToValue(TransitionState.RUNNING, 2f))
+            repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
+            assertThat(animationValues).isEqualTo(StateToValue(TransitionState.FINISHED, null))
+        }
+
+    @Test
     fun sameFloatValueWithTheSameTransitionStateDoesNotEmitTwice() =
         testScope.runTest {
             val flow =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
index 1c9c942..bfa8433 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.StateToValue
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
 import com.google.common.collect.Range
@@ -57,17 +58,19 @@
 
             // The animation should only start > .4f way through
             repository.sendTransitionStep(step(0f, TransitionState.STARTED))
-            assertThat(enterFromTopTranslationY).isEqualTo(pixels)
+            assertThat(enterFromTopTranslationY)
+                .isEqualTo(StateToValue(TransitionState.STARTED, pixels))
 
-            repository.sendTransitionStep(step(0.4f))
-            assertThat(enterFromTopTranslationY).isEqualTo(pixels)
+            repository.sendTransitionStep(step(.55f))
+            assertThat(enterFromTopTranslationY!!.value ?: -1f).isIn(Range.closed(pixels, 0f))
 
             repository.sendTransitionStep(step(.85f))
-            assertThat(enterFromTopTranslationY).isIn(Range.closed(pixels, 0f))
+            assertThat(enterFromTopTranslationY!!.value ?: -1f).isIn(Range.closed(pixels, 0f))
 
             // At the end, the translation should be complete and set to zero
             repository.sendTransitionStep(step(1f))
-            assertThat(enterFromTopTranslationY).isEqualTo(0f)
+            assertThat(enterFromTopTranslationY)
+                .isEqualTo(StateToValue(TransitionState.RUNNING, 0f))
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.kt
new file mode 100644
index 0000000..655a551
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRepositorySpySubject.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.keyguard.util
+
+import androidx.core.animation.ValueAnimator
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.FailureMetadata
+import com.google.common.truth.Subject
+import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertAbout
+import junit.framework.Assert.assertEquals
+import kotlin.test.fail
+import org.mockito.Mockito
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+/** [Subject] used to make assertions about a [Mockito.spy] KeyguardTransitionRepository. */
+class KeyguardTransitionRepositorySpySubject
+private constructor(
+    failureMetadata: FailureMetadata,
+    private val repository: KeyguardTransitionRepository,
+) : Subject(failureMetadata, repository) {
+
+    /**
+     * Asserts that we started a transition to the given state, optionally checking additional
+     * parameters. If an animator param or assertion is not provided, we will not assert anything
+     * about the animator.
+     */
+    fun startedTransition(
+        ownerName: String? = null,
+        from: KeyguardState? = null,
+        to: KeyguardState,
+        modeOnCanceled: TransitionModeOnCanceled? = null,
+    ) {
+        startedTransition(ownerName, from, to, {}, modeOnCanceled)
+    }
+
+    /**
+     * Asserts that we started a transition to the given state, optionally verifying additional
+     * params.
+     */
+    fun startedTransition(
+        ownerName: String? = null,
+        from: KeyguardState? = null,
+        to: KeyguardState,
+        animator: ValueAnimator?,
+        modeOnCanceled: TransitionModeOnCanceled? = null,
+    ) {
+        startedTransition(ownerName, from, to, { assertEquals(animator, it) }, modeOnCanceled)
+    }
+
+    /**
+     * Asserts that we started a transition to the given state, optionally verifying additional
+     * params.
+     */
+    fun startedTransition(
+        ownerName: String? = null,
+        from: KeyguardState? = null,
+        to: KeyguardState,
+        animatorAssertion: (Subject) -> Unit,
+        modeOnCanceled: TransitionModeOnCanceled? = null,
+    ) {
+        withArgCaptor<TransitionInfo> { verify(repository).startTransition(capture()) }
+            .also { transitionInfo ->
+                assertEquals(to, transitionInfo.to)
+                animatorAssertion.invoke(Truth.assertThat(transitionInfo.animator))
+                from?.let { assertEquals(it, transitionInfo.from) }
+                ownerName?.let { assertEquals(it, transitionInfo.ownerName) }
+                modeOnCanceled?.let { assertEquals(it, transitionInfo.modeOnCanceled) }
+            }
+    }
+
+    /** Verifies that [KeyguardTransitionRepository.startTransition] was never called. */
+    fun noTransitionsStarted() {
+        verify(repository, never()).startTransition(any())
+    }
+
+    companion object {
+        fun assertThat(
+            repository: KeyguardTransitionRepository
+        ): KeyguardTransitionRepositorySpySubject =
+            assertAbout { failureMetadata, repository: KeyguardTransitionRepository ->
+                    if (!Mockito.mockingDetails(repository).isSpy) {
+                        fail(
+                            "Cannot assert on a non-spy KeyguardTransitionRepository. " +
+                                "Use Mockito.spy(keyguardTransitionRepository)."
+                        )
+                    }
+                    KeyguardTransitionRepositorySpySubject(failureMetadata, repository)
+                }
+                .that(repository)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
index 043dae6..100e579 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
@@ -24,9 +24,9 @@
 import android.widget.SeekBar
 import android.widget.TextView
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.media.controls.ui.SquigglyProgress
+import com.android.systemui.res.R
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Rule
@@ -86,7 +86,7 @@
     fun seekBarGone() {
         // WHEN seek bar is disabled
         val isEnabled = false
-        val data = SeekBarViewModel.Progress(isEnabled, false, false, false, null, 0)
+        val data = SeekBarViewModel.Progress(isEnabled, false, false, false, null, 0, false)
         observer.onChanged(data)
         // THEN seek bar shows just a thin line with no text
         assertThat(seekBarView.isEnabled()).isFalse()
@@ -99,7 +99,7 @@
     fun seekBarVisible() {
         // WHEN seek bar is enabled
         val isEnabled = true
-        val data = SeekBarViewModel.Progress(isEnabled, true, false, false, 3000, 12000)
+        val data = SeekBarViewModel.Progress(isEnabled, true, false, false, 3000, 12000, true)
         observer.onChanged(data)
         // THEN seek bar is visible and thick
         assertThat(seekBarView.getVisibility()).isEqualTo(View.VISIBLE)
@@ -109,7 +109,7 @@
     @Test
     fun seekBarProgress() {
         // WHEN part of the track has been played
-        val data = SeekBarViewModel.Progress(true, true, true, false, 3000, 120000)
+        val data = SeekBarViewModel.Progress(true, true, true, false, 3000, 120000, true)
         observer.onChanged(data)
         // THEN seek bar shows the progress
         assertThat(seekBarView.progress).isEqualTo(3000)
@@ -123,7 +123,8 @@
     fun seekBarDisabledWhenSeekNotAvailable() {
         // WHEN seek is not available
         val isSeekAvailable = false
-        val data = SeekBarViewModel.Progress(true, isSeekAvailable, false, false, 3000, 120000)
+        val data =
+            SeekBarViewModel.Progress(true, isSeekAvailable, false, false, 3000, 120000, false)
         observer.onChanged(data)
         // THEN seek bar is not enabled
         assertThat(seekBarView.isEnabled()).isFalse()
@@ -133,7 +134,8 @@
     fun seekBarEnabledWhenSeekNotAvailable() {
         // WHEN seek is available
         val isSeekAvailable = true
-        val data = SeekBarViewModel.Progress(true, isSeekAvailable, false, false, 3000, 120000)
+        val data =
+            SeekBarViewModel.Progress(true, isSeekAvailable, false, false, 3000, 120000, false)
         observer.onChanged(data)
         // THEN seek bar is not enabled
         assertThat(seekBarView.isEnabled()).isTrue()
@@ -144,7 +146,7 @@
         // WHEN playing
         val isPlaying = true
         val isScrubbing = false
-        val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+        val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
         observer.onChanged(data)
         // THEN progress drawable is animating
         verify(mockSquigglyProgress).animate = true
@@ -155,7 +157,7 @@
         // WHEN not playing & not scrubbing
         val isPlaying = false
         val isScrubbing = false
-        val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+        val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
         observer.onChanged(data)
         // THEN progress drawable is not animating
         verify(mockSquigglyProgress).animate = false
@@ -166,7 +168,7 @@
         // WHEN playing & scrubbing
         val isPlaying = true
         val isScrubbing = true
-        val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+        val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
         observer.onChanged(data)
         // THEN progress drawable is not animating
         verify(mockSquigglyProgress).animate = false
@@ -177,7 +179,7 @@
         // WHEN playing & scrubbing
         val isPlaying = false
         val isScrubbing = true
-        val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+        val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
         observer.onChanged(data)
         // THEN progress drawable is not animating
         verify(mockSquigglyProgress).animate = false
@@ -187,7 +189,7 @@
     fun seekBarProgress_enabledAndScrubbing_timeViewsHaveTime() {
         val isEnabled = true
         val isScrubbing = true
-        val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000)
+        val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000, true)
 
         observer.onChanged(data)
 
@@ -199,7 +201,7 @@
     fun seekBarProgress_disabledAndScrubbing_timeViewsEmpty() {
         val isEnabled = false
         val isScrubbing = true
-        val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000)
+        val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000, true)
 
         observer.onChanged(data)
 
@@ -211,7 +213,7 @@
     fun seekBarProgress_enabledAndNotScrubbing_timeViewsEmpty() {
         val isEnabled = true
         val isScrubbing = false
-        val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000)
+        val data = SeekBarViewModel.Progress(isEnabled, true, true, isScrubbing, 3000, 120000, true)
 
         observer.onChanged(data)
 
@@ -221,8 +223,8 @@
 
     @Test
     fun seekBarJumpAnimation() {
-        val data0 = SeekBarViewModel.Progress(true, true, true, false, 4000, 120000)
-        val data1 = SeekBarViewModel.Progress(true, true, true, false, 10, 120000)
+        val data0 = SeekBarViewModel.Progress(true, true, true, false, 4000, 120000, true)
+        val data1 = SeekBarViewModel.Progress(true, true, true, false, 10, 120000, true)
 
         // Set initial position of progress bar
         observer.onChanged(data0)
@@ -241,7 +243,7 @@
         observer.animationEnabled = false
         val isPlaying = true
         val isScrubbing = false
-        val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000)
+        val data = SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, true)
         observer.onChanged(data)
 
         // THEN progress drawable does not animate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt
index 301d887..d9453d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt
@@ -33,6 +33,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         manager = NearbyMediaDevicesManager(commandQueue, logger)
+        manager.start()
 
         val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
         verify(commandQueue).addCallback(callbackCaptor.capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
new file mode 100644
index 0000000..e044eec
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.mediaprojection.permission
+
+import android.app.AlertDialog
+import android.media.projection.MediaProjectionConfig
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.WindowManager
+import android.widget.Spinner
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.AlertDialogWithDelegate
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.mockito.mock
+import junit.framework.Assert.assertEquals
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
[email protected](setAsMainLooper = true)
+class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
+
+    private lateinit var dialog: AlertDialog
+
+    private val flags = mock<FeatureFlagsClassic>()
+    private val onStartRecordingClicked = mock<Runnable>()
+    private val mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>()
+
+    private val mediaProjectionConfig: MediaProjectionConfig =
+        MediaProjectionConfig.createConfigForDefaultDisplay()
+    private val appName: String = "testApp"
+    private val hostUid: Int = 12345
+
+    private val resIdSingleApp = R.string.screen_share_permission_dialog_option_single_app
+    private val resIdFullScreen = R.string.screen_share_permission_dialog_option_entire_screen
+    private val resIdSingleAppDisabled =
+        R.string.media_projection_entry_app_permission_dialog_single_app_disabled
+
+    @Before
+    fun setUp() {
+        whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
+    }
+
+    @After
+    fun teardown() {
+        if (::dialog.isInitialized) {
+            dialog.dismiss()
+        }
+    }
+
+    @Test
+    fun showDialog_forceShowPartialScreenShareFalse() {
+        // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
+        // overrideDisableSingleAppOption = false
+        val overrideDisableSingleAppOption = false
+        setUpAndShowDialog(overrideDisableSingleAppOption)
+
+        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+        val secondOptionText =
+            spinner.adapter
+                .getDropDownView(1, null, spinner)
+                .findViewById<TextView>(android.R.id.text2)
+                ?.text
+
+        // check that the first option is full screen and enabled
+        assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)
+
+        // check that the second option is single app and disabled
+        assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionText)
+    }
+
+    @Test
+    fun showDialog_forceShowPartialScreenShareTrue() {
+        // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
+        // overrideDisableSingleAppOption = true
+        val overrideDisableSingleAppOption = true
+        setUpAndShowDialog(overrideDisableSingleAppOption)
+
+        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+        val secondOptionText =
+            spinner.adapter
+                .getDropDownView(1, null, spinner)
+                .findViewById<TextView>(android.R.id.text1)
+                ?.text
+
+        // check that the first option is single app and enabled
+        assertEquals(context.getString(resIdSingleApp), spinner.selectedItem)
+
+        // check that the second option is full screen and enabled
+        assertEquals(context.getString(resIdFullScreen), secondOptionText)
+    }
+
+    private fun setUpAndShowDialog(overrideDisableSingleAppOption: Boolean) {
+        val delegate =
+            MediaProjectionPermissionDialogDelegate(
+                context,
+                mediaProjectionConfig,
+                {},
+                onStartRecordingClicked,
+                appName,
+                overrideDisableSingleAppOption,
+                hostUid,
+                mediaProjectionMetricsLogger
+            )
+
+        dialog = AlertDialogWithDelegate(context, R.style.Theme_SystemUI_Dialog, delegate)
+        SystemUIDialog.applyFlags(dialog)
+        SystemUIDialog.setDialogSize(dialog)
+
+        dialog.window?.addSystemFlags(
+            WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
+        )
+
+        delegate.onCreate(dialog, savedInstanceState = null)
+        dialog.show()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
index 707a297..ab90b9b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
@@ -24,10 +24,13 @@
 import com.android.settingslib.RestrictedLockUtils
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin
+import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.BrightnessMirrorController
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
@@ -35,6 +38,7 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.isNull
@@ -61,7 +65,7 @@
     @Mock
     private lateinit var listener: ToggleSlider.Listener
     @Mock
-    private lateinit var mBrightnessSliderHapticPlugin: BrightnessSliderHapticPlugin
+    private lateinit var vibratorHelper: VibratorHelper
 
     @Captor
     private lateinit var seekBarChangeCaptor: ArgumentCaptor<SeekBar.OnSeekBarChangeListener>
@@ -69,6 +73,7 @@
     private lateinit var seekBar: SeekBar
     private val uiEventLogger = UiEventLoggerFake()
     private var mFalsingManager: FalsingManagerFake = FalsingManagerFake()
+    private val systemClock = FakeSystemClock()
 
     private lateinit var mController: BrightnessSliderController
 
@@ -78,13 +83,14 @@
 
         whenever(mirrorController.toggleSlider).thenReturn(mirror)
         whenever(motionEvent.copy()).thenReturn(motionEvent)
+        whenever(vibratorHelper.getPrimitiveDurations(anyInt())).thenReturn(intArrayOf(0))
 
         mController =
             BrightnessSliderController(
                 brightnessSliderView,
                 mFalsingManager,
                 uiEventLogger,
-                mBrightnessSliderHapticPlugin,
+                SeekableSliderHapticPlugin(vibratorHelper, systemClock),
             )
         mController.init()
         mController.setOnChangedListener(listener)
@@ -100,7 +106,6 @@
         mController.onViewAttached()
 
         verify(brightnessSliderView).setOnSeekBarChangeListener(notNull())
-        verify(mBrightnessSliderHapticPlugin).start()
     }
 
     @Test
@@ -110,7 +115,6 @@
 
         verify(brightnessSliderView).setOnSeekBarChangeListener(isNull())
         verify(brightnessSliderView).setOnDispatchTouchEventListener(isNull())
-        verify(mBrightnessSliderHapticPlugin).stop()
     }
 
     @Test
@@ -202,7 +206,7 @@
     @Test
     fun testSeekBarTrackingStarted() {
         whenever(brightnessSliderView.value).thenReturn(42)
-        val event = BrightnessSliderEvent.SLIDER_STARTED_TRACKING_TOUCH
+        val event = BrightnessSliderEvent.BRIGHTNESS_SLIDER_STARTED_TRACKING_TOUCH
 
         mController.onViewAttached()
         mController.setMirrorControllerAndMirror(mirrorController)
@@ -220,7 +224,7 @@
     @Test
     fun testSeekBarTrackingStopped() {
         whenever(brightnessSliderView.value).thenReturn(23)
-        val event = BrightnessSliderEvent.SLIDER_STOPPED_TRACKING_TOUCH
+        val event = BrightnessSliderEvent.BRIGHTNESS_SLIDER_STOPPED_TRACKING_TOUCH
 
         mController.onViewAttached()
         mController.setMirrorControllerAndMirror(mirrorController)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderHapticPluginImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderHapticPluginImplTest.kt
deleted file mode 100644
index 51629b5..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderHapticPluginImplTest.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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.settings.brightness
-
-import android.view.VelocityTracker
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.haptics.slider.SeekableSliderEventProducer
-import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-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.Mock
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class BrightnessSliderHapticPluginImplTest : SysuiTestCase() {
-
-    @Mock private lateinit var vibratorHelper: VibratorHelper
-    @Mock private lateinit var velocityTracker: VelocityTracker
-    @Mock private lateinit var mainDispatcher: CoroutineDispatcher
-
-    private val systemClock = FakeSystemClock()
-    private val sliderEventProducer = SeekableSliderEventProducer()
-
-    private lateinit var plugin: BrightnessSliderHapticPluginImpl
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        whenever(vibratorHelper.getPrimitiveDurations(anyInt())).thenReturn(intArrayOf(0))
-    }
-
-    @Test
-    fun start_beginsTrackingSlider() = runTest {
-        createPlugin(UnconfinedTestDispatcher(testScheduler))
-        plugin.start()
-
-        assertThat(plugin.isTracking).isTrue()
-    }
-
-    @Test
-    fun stop_stopsTrackingSlider() = runTest {
-        createPlugin(UnconfinedTestDispatcher(testScheduler))
-        // GIVEN that the plugin started the tracking component
-        plugin.start()
-
-        // WHEN called to stop
-        plugin.stop()
-
-        // THEN the tracking component stops
-        assertThat(plugin.isTracking).isFalse()
-    }
-
-    @Test
-    fun start_afterStop_startsTheTrackingAgain() = runTest {
-        createPlugin(UnconfinedTestDispatcher(testScheduler))
-        // GIVEN that the plugin started the tracking component
-        plugin.start()
-
-        // WHEN the plugin is restarted
-        plugin.stop()
-        plugin.start()
-
-        // THEN the tracking begins again
-        assertThat(plugin.isTracking).isTrue()
-    }
-
-    private fun createPlugin(dispatcher: CoroutineDispatcher) {
-        plugin =
-            BrightnessSliderHapticPluginImpl(
-                vibratorHelper,
-                systemClock,
-                dispatcher,
-                velocityTracker,
-                sliderEventProducer,
-            )
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index c772ee2..8a22f4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -63,7 +63,6 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository;
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
@@ -197,16 +196,8 @@
                 () -> sceneInteractor);
         CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
 
-        FakeKeyguardTransitionRepository keyguardTransitionRepository =
-                new FakeKeyguardTransitionRepository();
-
         KeyguardTransitionInteractor keyguardTransitionInteractor =
-                new KeyguardTransitionInteractor(
-                        mTestScope.getBackgroundScope(),
-                        keyguardTransitionRepository,
-                        () -> keyguardInteractor,
-                        () -> mFromLockscreenTransitionInteractor,
-                        () -> mFromPrimaryBouncerTransitionInteractor);
+                mKosmos.getKeyguardTransitionInteractor();
 
         mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
         mFromPrimaryBouncerTransitionInteractor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 72c52ec..f582402 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -41,7 +41,6 @@
 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.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
 import com.android.systemui.dump.DumpManager;
@@ -50,7 +49,6 @@
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository;
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
@@ -223,18 +221,9 @@
                 new ConfigurationInteractor(configurationRepository),
                 mShadeRepository,
                 () -> sceneInteractor);
-        CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
-
-        FakeKeyguardTransitionRepository keyguardTransitionRepository =
-                new FakeKeyguardTransitionRepository();
 
         KeyguardTransitionInteractor keyguardTransitionInteractor =
-                new KeyguardTransitionInteractor(
-                        mTestScope.getBackgroundScope(),
-                        keyguardTransitionRepository,
-                        () -> keyguardInteractor,
-                        () -> mFromLockscreenTransitionInteractor,
-                        () -> mFromPrimaryBouncerTransitionInteractor);
+                mKosmos.getKeyguardTransitionInteractor();
 
         mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
         mFromPrimaryBouncerTransitionInteractor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 8fd9c80..fb105e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -35,9 +35,9 @@
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -133,14 +133,7 @@
                 shadeRepository,
                 { kosmos.sceneInteractor },
             )
-        val keyguardTransitionInteractor =
-            KeyguardTransitionInteractor(
-                testScope.backgroundScope,
-                keyguardTransitionRepository,
-                { keyguardInteractor },
-                { fromLockscreenTransitionInteractor },
-                { fromPrimaryBouncerTransitionInteractor }
-            )
+        val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
         fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor
         fromPrimaryBouncerTransitionInteractor = kosmos.fromPrimaryBouncerTransitionInteractor
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
index cd74410..f58ff0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
@@ -115,7 +115,7 @@
     @Test
     fun testHunIsRemovedAndCallbackIsInvokedWhenAnimationEnds() {
         flagNotificationAsHun()
-        controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
+        controller.onTransitionAnimationEnd(isExpandingFullyAbove = true)
 
         assertFalse(HeadsUpUtil.isClickedHeadsUpNotification(notification))
         assertFalse(notification.entry.isExpandAnimationRunning)
@@ -157,7 +157,7 @@
         assertNotSame(GROUP_ALERT_SUMMARY, summary.sbn.notification.groupAlertBehavior)
         assertNotSame(GROUP_ALERT_SUMMARY, notification.entry.sbn.notification.groupAlertBehavior)
 
-        controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
+        controller.onTransitionAnimationEnd(isExpandingFullyAbove = true)
 
         verify(headsUpManager)
             .removeNotification(summary.key, true /* releaseImmediately */, false /* animate */)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 7589a49..354f3f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -797,6 +797,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateFooter_remoteInput() {
         ArgumentCaptor<RemoteInputController.Callback> callbackCaptor =
                 ArgumentCaptor.forClass(RemoteInputController.Callback.class);
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 4afcc8c..04f3216 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
@@ -440,6 +440,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateFooter_noNotifications() {
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(true);
@@ -451,6 +452,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateFooter_remoteInput() {
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(true);
@@ -467,6 +469,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateFooter_withoutNotifications() {
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(true);
@@ -482,6 +485,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateFooter_oneClearableNotification() {
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(true);
@@ -497,6 +501,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateFooter_withoutHistory() {
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(true);
@@ -513,6 +518,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateFooter_oneClearableNotification_beforeUserSetup() {
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(false);
@@ -528,6 +534,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateFooter_oneNonClearableNotification() {
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(true);
@@ -544,9 +551,7 @@
     }
 
     @Test
-    public void testUpdateFooter_atEnd() {
-        mStackScroller.setCurrentUserSetup(true);
-
+    public void testFooterPosition_atEnd() {
         // add footer
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
@@ -559,8 +564,6 @@
 
         // Expecting the footer to be the last child
         int expected = mStackScroller.getChildCount() - 1;
-
-        // move footer to end
         verify(mStackScroller).changeViewPosition(any(FooterView.class), eq(expected));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index 88e4f5a..3a7659d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -27,19 +27,27 @@
 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.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState
 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.power.data.repository.fakePowerRepository
+import com.android.systemui.power.shared.model.WakefulnessState
 import com.android.systemui.res.R
 import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.statusbar.data.repository.fakeRemoteInputRepository
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
+import com.android.systemui.statusbar.policy.data.repository.fakeUserSetupRepository
 import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
 import com.android.systemui.statusbar.policy.fakeConfigurationController
 import com.android.systemui.testKosmos
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.value
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
@@ -58,11 +66,16 @@
             fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
         }
     private val testScope = kosmos.testScope
+
     private val activeNotificationListRepository = kosmos.activeNotificationListRepository
-    private val fakeKeyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
-    private val fakeShadeRepository = kosmos.fakeShadeRepository
-    private val zenModeRepository = kosmos.zenModeRepository
     private val fakeConfigurationController = kosmos.fakeConfigurationController
+    private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
+    private val fakeKeyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val fakePowerRepository = kosmos.fakePowerRepository
+    private val fakeRemoteInputRepository = kosmos.fakeRemoteInputRepository
+    private val fakeShadeRepository = kosmos.fakeShadeRepository
+    private val fakeUserSetupRepository = kosmos.fakeUserSetupRepository
+    private val zenModeRepository = kosmos.zenModeRepository
 
     val underTest = kosmos.notificationListViewModel
 
@@ -273,4 +286,193 @@
 
             assertThat(hasFilteredNotifs).isFalse()
         }
+
+    @Test
+    fun testShouldShowFooterView_trueWhenShade() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+            // WHEN has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND shade is open
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            runCurrent()
+
+            // THEN footer is visible
+            assertThat(shouldShow?.value).isTrue()
+        }
+
+    @Test
+    fun testShouldShowFooterView_trueWhenLockedShade() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+            // WHEN has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND shade is open on lockscreen
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
+            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            runCurrent()
+
+            // THEN footer is visible
+            assertThat(shouldShow?.value).isTrue()
+        }
+
+    @Test
+    fun testShouldShowFooterView_falseWhenKeyguard() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+            // WHEN has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND is on keyguard
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+            runCurrent()
+
+            // THEN footer is not visible
+            assertThat(shouldShow?.value).isFalse()
+        }
+
+    @Test
+    fun testShouldShowFooterView_falseWhenUserNotSetUp() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+            // WHEN has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND shade is open
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            // AND user is not set up
+            fakeUserSetupRepository.setUserSetUp(false)
+            runCurrent()
+
+            // THEN footer is not visible
+            assertThat(shouldShow?.value).isFalse()
+        }
+
+    @Test
+    fun testShouldShowFooterView_falseWhenStartingToSleep() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+            // WHEN has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND shade is open
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            // AND device is starting to go to sleep
+            fakePowerRepository.updateWakefulness(WakefulnessState.STARTING_TO_SLEEP)
+            runCurrent()
+
+            // THEN footer is not visible
+            assertThat(shouldShow?.value).isFalse()
+        }
+
+    @Test
+    fun testShouldShowFooterView_falseWhenQsExpandedDefault() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+            // WHEN has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND shade is open
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            // AND quick settings are expanded
+            fakeShadeRepository.setQsExpansion(1f)
+            fakeShadeRepository.legacyQsFullscreen.value = true
+            runCurrent()
+
+            // THEN footer is not visible
+            assertThat(shouldShow?.value).isFalse()
+        }
+
+    @Test
+    fun testShouldShowFooterView_trueWhenQsExpandedSplitShade() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+            // WHEN has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND shade is open
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            // AND quick settings are expanded
+            fakeShadeRepository.setQsExpansion(1f)
+            // AND split shade is enabled
+            overrideResource(R.bool.config_use_split_notification_shade, true)
+            fakeConfigurationController.notifyConfigurationChanged()
+            runCurrent()
+
+            // THEN footer is visible
+            assertThat(shouldShow?.value).isTrue()
+        }
+
+    @Test
+    fun testShouldShowFooterView_falseWhenRemoteInputActive() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+            // WHEN has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND shade is open
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            // AND remote input is active
+            fakeRemoteInputRepository.isRemoteInputActive.value = true
+            runCurrent()
+
+            // THEN footer is not visible
+            assertThat(shouldShow?.value).isFalse()
+        }
+
+    @Test
+    fun testShouldShowFooterView_falseWhenShadeIsClosed() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+            // WHEN has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND shade is closed
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            fakeShadeRepository.setLegacyShadeExpansion(0f)
+            runCurrent()
+
+            // THEN footer is not visible
+            assertThat(shouldShow?.value).isFalse()
+        }
+
+    @Test
+    fun testShouldShowFooterView_animatesWhenShade() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+            // WHEN has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND shade is open and fully expanded
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            runCurrent()
+
+            // THEN footer visibility animates
+            assertThat(shouldShow?.isAnimating).isTrue()
+        }
+
+    @Test
+    fun testShouldShowFooterView_notAnimatingOnKeyguard() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+            // WHEN has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND we are on the keyguard
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            runCurrent()
+
+            // THEN footer visibility does not animate
+            assertThat(shouldShow?.isAnimating).isFalse()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 32c727c..ff882b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -69,7 +69,10 @@
 
     val kosmos =
         testKosmos().apply {
-            fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
+            fakeFeatureFlagsClassic.apply {
+                set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+                set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
+            }
         }
 
     init {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
index b6a033a..1b43851 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
@@ -46,6 +46,7 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.process.processWrapper
 import com.android.systemui.qs.user.UserSwitchDialogController
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -1147,6 +1148,7 @@
                 uiEventLogger = uiEventLogger,
                 featureFlags = kosmos.fakeFeatureFlagsClassic,
                 userRestrictionChecker = mock(),
+                processWrapper = kosmos.processWrapper,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
index 21d4549..661837b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.process.ProcessWrapperFake
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
@@ -264,6 +265,7 @@
                     guestUserInteractor = guestUserInteractor,
                     uiEventLogger = uiEventLogger,
                     userRestrictionChecker = mock(),
+                    processWrapper = ProcessWrapperFake()
                 )
         )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index d0804be..5661e20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.process.ProcessWrapperFake
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
@@ -176,6 +177,7 @@
                         guestUserInteractor = guestUserInteractor,
                         uiEventLogger = uiEventLogger,
                         userRestrictionChecker = mock(),
+                        processWrapper = ProcessWrapperFake()
                     ),
                 guestUserInteractor = guestUserInteractor,
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
new file mode 100644
index 0000000..913759f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.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.util.settings.repository
+
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+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.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+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.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class UserAwareSecureSettingsRepositoryTest : SysuiTestCase() {
+
+    private val dispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(dispatcher)
+    private val secureSettings = FakeSettings()
+    private val userRepository = Kosmos().fakeUserRepository
+    private lateinit var repository: UserAwareSecureSettingsRepository
+
+    @Before
+    fun setup() {
+        repository = UserAwareSecureSettingsRepositoryImpl(
+            secureSettings,
+            userRepository,
+            dispatcher,
+        )
+        userRepository.setUserInfos(USER_INFOS)
+        setSettingValueForUser(enabled = true, userInfo = SETTING_ENABLED_USER)
+        setSettingValueForUser(enabled = false, userInfo = SETTING_DISABLED_USER)
+    }
+
+    @Test
+    fun settingEnabledEmitsValueForCurrentUser() {
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+
+            val enabled by collectLastValue(repository.boolSettingForActiveUser(SETTING_NAME))
+
+            assertThat(enabled).isTrue()
+        }
+    }
+
+    @Test
+    fun settingEnabledEmitsNewValueWhenSettingChanges() {
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+            val enabled by collectValues(repository.boolSettingForActiveUser(SETTING_NAME))
+            runCurrent()
+
+            setSettingValueForUser(enabled = false, userInfo = SETTING_ENABLED_USER)
+
+            assertThat(enabled).containsExactly(true, false).inOrder()
+        }
+    }
+
+    @Test
+    fun settingEnabledEmitsValueForNewUserWhenUserChanges() {
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+            val enabled by collectLastValue(repository.boolSettingForActiveUser(SETTING_NAME))
+            runCurrent()
+
+            userRepository.setSelectedUserInfo(SETTING_DISABLED_USER)
+
+            assertThat(enabled).isFalse()
+        }
+    }
+
+    private fun setSettingValueForUser(enabled: Boolean, userInfo: UserInfo) {
+        secureSettings.putBoolForUser(SETTING_NAME, enabled, userInfo.id)
+    }
+
+    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)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 7a8dce8..8a33778 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -64,7 +64,6 @@
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.testing.UiEventLoggerFake;
-import com.android.keyguard.TestScopeProvider;
 import com.android.systemui.Prefs;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.AnimatorTestRule;
@@ -102,8 +101,6 @@
 import java.util.Arrays;
 import java.util.function.Predicate;
 
-import kotlinx.coroutines.Dispatchers;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -208,8 +205,6 @@
                 mDumpManager,
                 mLazySecureSettings,
                 mVibratorHelper,
-                Dispatchers.getUnconfined(),
-                TestScopeProvider.getTestScope(),
                 new FakeSystemClock());
         mDialog.init(0, null);
         State state = createShellState();
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 1d428c8..d45a9a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -95,11 +95,9 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
-import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
@@ -107,7 +105,6 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository;
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
@@ -428,17 +425,8 @@
                 shadeRepository,
                 () -> sceneInteractor);
 
-        FakeKeyguardTransitionRepository keyguardTransitionRepository =
-                new FakeKeyguardTransitionRepository();
-
         KeyguardTransitionInteractor keyguardTransitionInteractor =
-                new KeyguardTransitionInteractor(
-                        mTestScope.getBackgroundScope(),
-                        keyguardTransitionRepository,
-                        () -> keyguardInteractor,
-                        () -> mFromLockscreenTransitionInteractor,
-                        () -> mFromPrimaryBouncerTransitionInteractor);
-        CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor();
+                mKosmos.getKeyguardTransitionInteractor();
 
         mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor();
         mFromPrimaryBouncerTransitionInteractor =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt
index f723a9e5..5b84a41 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt
@@ -36,7 +36,7 @@
             object : AnimationFeatureFlags {
                 override val isPredictiveBackQsDialogAnim = isPredictiveBackQsDialogAnim
             },
-        launchAnimator = fakeLaunchAnimator(),
+        transitionAnimator = fakeTransitionAnimator(),
         isForTesting = true,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt
similarity index 79%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt
index 0983041..bc7ec3f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt
@@ -16,19 +16,19 @@
 
 import com.android.app.animation.Interpolators
 
-/** A [LaunchAnimator] to be used in tests. */
-fun fakeLaunchAnimator(): LaunchAnimator {
-    return LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
+/** A [TransitionAnimator] to be used in tests. */
+fun fakeTransitionAnimator(): TransitionAnimator {
+    return TransitionAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
 }
 
 /**
- * A [LaunchAnimator.Timings] to be used in tests.
+ * A [TransitionAnimator.Timings] to be used in tests.
  *
  * Note that all timings except the total duration are non-zero to avoid divide-by-zero exceptions
  * when computing the progress of a sub-animation (the contents fade in/out).
  */
 private val TEST_TIMINGS =
-    LaunchAnimator.Timings(
+    TransitionAnimator.Timings(
         totalDuration = 0L,
         contentBeforeFadeOutDelay = 1L,
         contentBeforeFadeOutDuration = 1L,
@@ -36,9 +36,9 @@
         contentAfterFadeInDuration = 1L
     )
 
-/** A [LaunchAnimator.Interpolators] to be used in tests. */
+/** A [TransitionAnimator.Interpolators] to be used in tests. */
 private val TEST_INTERPOLATORS =
-    LaunchAnimator.Interpolators(
+    TransitionAnimator.Interpolators(
         positionInterpolator = Interpolators.STANDARD,
         positionXInterpolator = Interpolators.STANDARD,
         contentBeforeFadeOutInterpolator = Interpolators.STANDARD,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigKosmos.kt
index 21cff0d..3b3e23e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigKosmos.kt
@@ -18,5 +18,7 @@
 
 import com.android.systemui.kosmos.Kosmos
 
+var Kosmos.fakeFaceWakeUpTriggersConfig by Kosmos.Fixture { FakeFaceWakeUpTriggersConfig() }
+
 var Kosmos.faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig by
-    Kosmos.Fixture { FakeFaceWakeUpTriggersConfig() }
+    Kosmos.Fixture { fakeFaceWakeUpTriggersConfig }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
index 5575b05..a8fc27a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
@@ -27,7 +27,6 @@
 import com.android.systemui.deviceentry.data.repository.faceWakeUpTriggersConfig
 import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -51,7 +50,7 @@
             keyguardTransitionInteractor = keyguardTransitionInteractor,
             faceAuthenticationLogger = faceAuthLogger,
             keyguardUpdateMonitor = keyguardUpdateMonitor,
-            deviceEntryFingerprintAuthRepository = deviceEntryFingerprintAuthRepository,
+            deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
             userRepository = userRepository,
             facePropertyRepository = facePropertyRepository,
             faceWakeUpTriggersConfig = faceWakeUpTriggersConfig,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
index 85a233fd..534f773 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
@@ -20,6 +20,7 @@
 import com.android.keyguard.KeyguardClockSwitch.ClockSize
 import com.android.keyguard.KeyguardClockSwitch.LARGE
 import com.android.systemui.keyguard.shared.model.SettingsClockSize
+import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockId
 import com.android.systemui.shared.clocks.DEFAULT_CLOCK_ID
 import com.android.systemui.util.mockito.mock
@@ -29,6 +30,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import org.mockito.Mockito
 
 class FakeKeyguardClockRepository @Inject constructor() : KeyguardClockRepository {
     private val _clockSize = MutableStateFlow(LARGE)
@@ -42,6 +44,9 @@
 
     private val _currentClock = MutableStateFlow(null)
     override val currentClock = _currentClock
+
+    private val _previewClock = MutableStateFlow(Mockito.mock(ClockController::class.java))
+    override val previewClock: StateFlow<ClockController> = _previewClock
     override val clockEventController: ClockEventController
         get() = mock()
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..da5cd67
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.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.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+
+val Kosmos.fromAodTransitionInteractor by
+    Kosmos.Fixture {
+        FromAodTransitionInteractor(
+            transitionRepository = fakeKeyguardTransitionRepository,
+            transitionInteractor = keyguardTransitionInteractor,
+            scope = testScope,
+            bgDispatcher = testDispatcher,
+            mainDispatcher = testDispatcher,
+            keyguardInteractor = keyguardInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..25fc67a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.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.keyguard.domain.interactor
+
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+
+val Kosmos.fromGoneTransitionInteractor by
+    Kosmos.Fixture {
+        FromGoneTransitionInteractor(
+            transitionRepository = fakeKeyguardTransitionRepository,
+            transitionInteractor = keyguardTransitionInteractor,
+            scope = applicationCoroutineScope,
+            bgDispatcher = testDispatcher,
+            mainDispatcher = testDispatcher,
+            keyguardInteractor = keyguardInteractor,
+            powerInteractor = powerInteractor,
+            communalInteractor = communalInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt
index a646bc6..c9c17d9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorKosmos.kt
@@ -19,6 +19,7 @@
 import android.content.applicationContext
 import com.android.systemui.keyguard.data.repository.keyguardSurfaceBehindRepository
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor
 
 var Kosmos.keyguardSurfaceBehindInteractor by
     Kosmos.Fixture {
@@ -30,5 +31,6 @@
                 inWindowLauncherUnlockAnimationInteractor
             },
             swipeToDismissInteractor = swipeToDismissInteractor,
+            notificationLaunchInteractor = notificationLaunchAnimationInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
index e4d115e..0c38fd9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -30,5 +30,6 @@
             fromLockscreenTransitionInteractor = Lazy { fromLockscreenTransitionInteractor },
             fromPrimaryBouncerTransitionInteractor =
                 Lazy { fromPrimaryBouncerTransitionInteractor },
+            fromAodTransitionInteractor = Lazy { fromAodTransitionInteractor },
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
index 0207280..d84988d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor
 
 val Kosmos.windowManagerLockscreenVisibilityInteractor by
     Kosmos.Fixture {
@@ -26,5 +27,6 @@
             surfaceBehindInteractor = keyguardSurfaceBehindInteractor,
             fromLockscreenInteractor = fromLockscreenTransitionInteractor,
             fromBouncerInteractor = fromPrimaryBouncerTransitionInteractor,
+            notificationLaunchAnimationInteractor = notificationLaunchAnimationInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt
index a8f45b0..6f168d4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt
@@ -33,6 +33,7 @@
         keyguardInteractor = keyguardInteractor,
         keyguardTransitionInteractor = keyguardTransitionInteractor,
         goneToAodTransitionViewModel = goneToAodTransitionViewModel,
+        aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
         occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
         keyguardClockViewModel = keyguardClockViewModel,
     )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index d376f12..24bb9c5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -38,6 +38,9 @@
         keyguardTransitionInteractor = keyguardTransitionInteractor,
         notificationsKeyguardInteractor = notificationsKeyguardInteractor,
         aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
+        lockscreenToGoneTransitionViewModel = lockscreenToGoneTransitionViewModel,
+        alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
+        primaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel,
         screenOffAnimationController = screenOffAnimationController,
         aodBurnInViewModel = aodBurnInViewModel,
         aodAlphaViewModel = aodAlphaViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessWrapperFake.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessWrapperFake.kt
index 9841778..dee3644 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessWrapperFake.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/process/ProcessWrapperFake.kt
@@ -16,11 +16,17 @@
 
 package com.android.systemui.process
 
+import android.os.UserHandle
+
 class ProcessWrapperFake : ProcessWrapper() {
 
     var systemUser: Boolean = false
 
+    var userHandle: UserHandle = UserHandle.getUserHandleForUid(0)
+
     override fun isSystemUser(): Boolean {
         return systemUser
     }
+
+    override fun myUserHandle() = userHandle
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
index e67df9d..8e430db 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
@@ -26,6 +26,8 @@
 
 class FakeQSSceneAdapter(
     private val inflateDelegate: suspend (Context) -> View,
+    override val qqsHeight: Int = 0,
+    override val qsHeight: Int = 0,
 ) : QSSceneAdapter {
     private val _customizing = MutableStateFlow(false)
     override val isCustomizing: StateFlow<Boolean> = _customizing.asStateFlow()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorKosmos.kt
new file mode 100644
index 0000000..5dc0333
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorKosmos.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.shade.domain.interactor
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+
+val Kosmos.shadeBackActionInteractor by
+    Kosmos.Fixture {
+        ShadeBackActionInteractorImpl(
+            shadeInteractor = shadeInteractor,
+            sceneInteractor = sceneInteractor,
+            deviceEntryInteractor = deviceEntryInteractor,
+        )
+    }
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepositoryKosmos.kt
similarity index 62%
copy from packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepositoryKosmos.kt
index 48fd4fe..5638cfc 100644
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepositoryKosmos.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 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.
@@ -14,15 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.soundpicker;
+package com.android.systemui.statusbar.notification.data.repository
 
-import android.app.Application;
+import com.android.systemui.kosmos.Kosmos
 
-import dagger.hilt.android.HiltAndroidApp;
-
-/**
- * The main application class for the project.
- */
-@HiltAndroidApp(Application.class)
-public class RingtonePickerApplication extends Hilt_RingtonePickerApplication {
-}
+val Kosmos.notificationLaunchAnimationRepository by
+    Kosmos.Fixture { NotificationLaunchAnimationRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorKosmos.kt
new file mode 100644
index 0000000..0d84bab
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.notification.data.repository.notificationLaunchAnimationRepository
+
+val Kosmos.notificationLaunchAnimationInteractor by
+    Kosmos.Fixture {
+        NotificationLaunchAnimationInteractor(
+            repository = notificationLaunchAnimationRepository,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
index 998e579..37b2b76 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
@@ -16,10 +16,14 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.shade.domain.interactor.shadeAnimationInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
 import com.android.systemui.statusbar.notification.footer.ui.viewmodel.footerViewModel
@@ -30,14 +34,18 @@
 
 val Kosmos.notificationListViewModel by Fixture {
     NotificationListViewModel(
-        shelf = notificationShelfViewModel,
-        hideListViewModel = hideListViewModel,
-        footer = Optional.of(footerViewModel),
-        logger = Optional.of(notificationListLoggerViewModel),
-        activeNotificationsInteractor = activeNotificationsInteractor,
-        keyguardTransitionInteractor = keyguardTransitionInteractor,
-        seenNotificationsInteractor = seenNotificationsInteractor,
-        shadeInteractor = shadeInteractor,
-        zenModeInteractor = zenModeInteractor,
+        notificationShelfViewModel,
+        hideListViewModel,
+        Optional.of(footerViewModel),
+        Optional.of(notificationListLoggerViewModel),
+        activeNotificationsInteractor,
+        keyguardInteractor,
+        keyguardTransitionInteractor,
+        powerInteractor,
+        remoteInputInteractor,
+        seenNotificationsInteractor,
+        shadeInteractor,
+        userSetupInteractor,
+        zenModeInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index 549a775..30d4105 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -19,13 +19,16 @@
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
 import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.lockscreenToDreamingTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.lockscreenToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.occludedToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.primaryBouncerToGoneTransitionViewModel
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -41,6 +44,9 @@
         shadeInteractor = shadeInteractor,
         communalInteractor = communalInteractor,
         occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
+        lockscreenToGoneTransitionViewModel = lockscreenToGoneTransitionViewModel,
+        alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
+        primaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel,
         lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
         dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
         lockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt
index 4e2dc7a..1504df4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.plugins.activityStarter
+import com.android.systemui.process.processWrapper
 import com.android.systemui.telephony.domain.interactor.telephonyInteractor
 import com.android.systemui.user.data.repository.userRepository
 import com.android.systemui.utils.userRestrictionChecker
@@ -53,5 +54,6 @@
             guestUserInteractor = guestUserInteractor,
             uiEventLogger = uiEventLogger,
             userRestrictionChecker = userRestrictionChecker,
+            processWrapper = processWrapper,
         )
     }
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
new file mode 100644
index 0000000..5054e29
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.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.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/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
similarity index 62%
copy from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
index 52bbfaa8..94b2bdf 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2023 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.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0N
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.ui.model
+package com.android.systemui.util.settings
 
-data class PasswordUiModel(val email: String)
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.userAwareSecureSettingsRepository by
+    Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
index f68baf5..592c6f4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
@@ -30,11 +30,11 @@
     private final List<FlashlightListener> callbacks = new ArrayList<>();
 
     @VisibleForTesting
-    public boolean isAvailable;
+    public boolean isAvailable = true;
     @VisibleForTesting
-    public boolean isEnabled;
+    public boolean isEnabled = false;
     @VisibleForTesting
-    public boolean hasFlashlight;
+    public boolean hasFlashlight = true;
 
     public FakeFlashlightController(LeakCheck test) {
         super(test, "flashlight");
@@ -52,16 +52,26 @@
         callbacks.forEach(FlashlightListener::onFlashlightError);
     }
 
+    /**
+     * Used to decide if tile should be shown or gone
+     * @return available/unavailable
+     */
     @Override
     public boolean hasFlashlight() {
         return hasFlashlight;
     }
 
+    /**
+     * @param newState active/inactive
+     */
     @Override
     public void setFlashlight(boolean newState) {
         callbacks.forEach(flashlightListener -> flashlightListener.onFlashlightChanged(newState));
     }
 
+    /**
+     * @return temporary availability
+     */
     @Override
     public boolean isAvailable() {
         return isAvailable;
@@ -76,6 +86,9 @@
     public void addCallback(FlashlightListener listener) {
         super.addCallback(listener);
         callbacks.add(listener);
+
+        listener.onFlashlightAvailabilityChanged(isAvailable());
+        listener.onFlashlightChanged(isEnabled());
     }
 
     @Override
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 78f07e4..a11cf8c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -818,7 +818,7 @@
             }
 
             // Don't need to add the embedded hierarchy windows into the accessibility windows list.
-            if (mHostEmbeddedMap.size() > 0 && isEmbeddedHierarchyWindowsLocked(windowId)) {
+            if (isEmbeddedHierarchyWindowsLocked(windowId)) {
                 return null;
             }
             final AccessibilityWindowInfo reportedWindow = AccessibilityWindowInfo.obtain();
@@ -866,21 +866,6 @@
             return reportedWindow;
         }
 
-        private boolean isEmbeddedHierarchyWindowsLocked(int windowId) {
-            final IBinder leashToken = mWindowIdMap.get(windowId);
-            if (leashToken == null) {
-                return false;
-            }
-
-            for (int i = 0; i < mHostEmbeddedMap.size(); i++) {
-                if (mHostEmbeddedMap.keyAt(i).equals(leashToken)) {
-                    return true;
-                }
-            }
-
-            return false;
-        }
-
         private int getTypeForWindowManagerWindowType(int windowType) {
             switch (windowType) {
                 case WindowManager.LayoutParams.TYPE_APPLICATION:
@@ -1490,7 +1475,7 @@
      * @return The windowId of the parent window, or self if no parent exists
      */
     public int resolveParentWindowIdLocked(int windowId) {
-        final IBinder token = getTokenLocked(windowId);
+        final IBinder token = getLeashTokenLocked(windowId);
         if (token == null) {
             return windowId;
         }
@@ -2095,7 +2080,7 @@
      * @param windowId The windowID.
      * @return The token, or {@code NULL} if this windowID doesn't exist
      */
-    IBinder getTokenLocked(int windowId) {
+    IBinder getLeashTokenLocked(int windowId) {
         return mWindowIdMap.get(windowId);
     }
 
@@ -2124,6 +2109,23 @@
     }
 
     /**
+     * Checks if the window is embedded into another window so that the window should be excluded
+     * from the exposed accessibility windows, and the node tree should be embedded in the host.
+     */
+    boolean isEmbeddedHierarchyWindowsLocked(int windowId) {
+        if (mHostEmbeddedMap.size() == 0) {
+            return false;
+        }
+
+        final IBinder leashToken = getLeashTokenLocked(windowId);
+        if (leashToken == null) {
+            return false;
+        }
+
+        return mHostEmbeddedMap.containsKey(leashToken);
+    }
+
+    /**
      * Checks if the window belongs to a proxy display and if so clears the focused window id.
      * @param focusClearedWindowId the cleared window id.
      * @return true if an observer is proxy-ed and has cleared its focused window id.
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index cf414d1..3645c40 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -254,6 +254,14 @@
         mEventInternal.ifPresent(event -> event.mIsCredentialRequest = isCredentialRequest);
     }
 
+    /**
+     * Set webview_requested_credential
+     */
+    public void maybeSetWebviewRequestedCredential(boolean webviewRequestedCredential) {
+        mEventInternal.ifPresent(event ->
+                event.mWebviewRequestedCredential = webviewRequestedCredential);
+    }
+
     public void maybeSetNoPresentationEventReason(@NotShownReason int reason) {
         mEventInternal.ifPresent(event -> {
             if (event.mCountShown == 0) {
@@ -578,7 +586,8 @@
                     + " mDetectionPreference=" + event.mDetectionPreference
                     + " mFieldClassificationRequestId=" + event.mFieldClassificationRequestId
                     + " mAppPackageUid=" + mCallingAppUid
-                    + " mIsCredentialRequest=" + event.mIsCredentialRequest);
+                    + " mIsCredentialRequest=" + event.mIsCredentialRequest
+                    + " mWebviewRequestedCredential=" + event.mWebviewRequestedCredential);
         }
 
         // TODO(b/234185326): Distinguish empty responses from other no presentation reasons.
@@ -618,7 +627,8 @@
                 event.mDetectionPreference,
                 event.mFieldClassificationRequestId,
                 mCallingAppUid,
-                event.mIsCredentialRequest);
+                event.mIsCredentialRequest,
+                event.mWebviewRequestedCredential);
         mEventInternal = Optional.empty();
     }
 
@@ -653,6 +663,7 @@
         @DetectionPreference int mDetectionPreference = DETECTION_PREFER_UNKNOWN;
         int mFieldClassificationRequestId = -1;
         boolean mIsCredentialRequest = false;
+        boolean mWebviewRequestedCredential = false;
 
         PresentationStatsEventInternal() {}
     }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 049feee..2ff23ee 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -16,8 +16,11 @@
 
 package com.android.server.autofill;
 
+import static android.credentials.Constants.FAILURE_CREDMAN_SELECTOR;
+import static android.credentials.Constants.SUCCESS_CREDMAN_SELECTOR;
 import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
 import static android.service.autofill.AutofillService.EXTRA_FILL_RESPONSE;
+import static android.service.autofill.AutofillService.WEBVIEW_REQUESTED_CREDENTIAL_KEY;
 import static android.service.autofill.Dataset.PICK_REASON_NO_PCC;
 import static android.service.autofill.Dataset.PICK_REASON_PCC_DETECTION_ONLY;
 import static android.service.autofill.Dataset.PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER;
@@ -112,6 +115,8 @@
 import android.content.IntentFilter;
 import android.content.IntentSender;
 import android.content.pm.ServiceInfo;
+import android.credentials.GetCredentialException;
+import android.credentials.GetCredentialResponse;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -122,10 +127,12 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
+import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Process;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.SystemClock;
 import android.service.assist.classification.FieldClassificationRequest;
 import android.service.assist.classification.FieldClassificationResponse;
@@ -152,6 +159,7 @@
 import android.service.autofill.SaveRequest;
 import android.service.autofill.UserData;
 import android.service.autofill.ValueFinder;
+import android.service.credentials.CredentialProviderService;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -2506,7 +2514,7 @@
                         + id + " destroyed");
                 return;
             }
-            fillInIntent = createAuthFillInIntentLocked(requestId, extras, /* authExtras= */ null);
+            fillInIntent = createAuthFillInIntentLocked(requestId, extras);
             if (fillInIntent == null) {
                 forceRemoveFromServiceLocked();
                 return;
@@ -2807,6 +2815,7 @@
         mSessionFlags.mExpiredResponse = false;
 
         final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT);
+
         final Bundle newClientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE);
         if (sDebug) {
             Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result
@@ -2817,6 +2826,12 @@
             mPresentationStatsEventLogger.maybeSetAuthenticationResult(
                 AUTHENTICATION_RESULT_SUCCESS);
             replaceResponseLocked(authenticatedResponse, (FillResponse) result, newClientState);
+        } else if (result instanceof GetCredentialResponse) {
+            Slog.d(TAG, "Received GetCredentialResponse from authentication flow");
+            Dataset dataset = getDatasetFromCredentialResponse((GetCredentialResponse) result);
+            if (dataset != null) {
+                autoFill(requestId, datasetIdx, dataset, false, UI_TYPE_UNKNOWN);
+            }
         } else if (result instanceof Dataset) {
             if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) {
                 logAuthenticationStatusLocked(requestId,
@@ -2853,6 +2868,17 @@
         }
     }
 
+    private Dataset getDatasetFromCredentialResponse(GetCredentialResponse result) {
+        if (result == null) {
+            return null;
+        }
+        Bundle bundle = result.getCredential().getData();
+        if (bundle == null) {
+            return null;
+        }
+        return bundle.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT, Dataset.class);
+    }
+
     Dataset getEffectiveDatasetForAuthentication(Dataset authenticatedDataset) {
         FillResponse response = new FillResponse.Builder().addDataset(authenticatedDataset).build();
         response = getEffectiveFillResponse(response);
@@ -4689,6 +4715,11 @@
 
         }
 
+        if (isCredmanIntegrationActive(response)) {
+            Slog.d(TAG, "Attempting to add Credential Manager callback to pinned entries");
+            addCredentialManagerCallback(response);
+        }
+
         if (response.supportsInlineSuggestions()) {
             synchronized (mLock) {
                 if (requestShowInlineSuggestionsLocked(response, filterText)) {
@@ -4748,6 +4779,11 @@
         }
     }
 
+    private boolean isCredmanIntegrationActive(FillResponse response) {
+        return Flags.autofillCredmanIntegration()
+                && (response.getFlags() & FillResponse.FLAG_CREDENTIAL_MANAGER_RESPONSE) != 0;
+    }
+
     @GuardedBy("mLock")
     private void updateFillDialogTriggerIdsLocked() {
         final FillResponse response = getLastResponseLocked(null);
@@ -4963,6 +4999,69 @@
         return mInlineSessionController.setInlineFillUiLocked(inlineFillUi);
     }
 
+    private void addCredentialManagerCallback(FillResponse response) {
+        if (response.getDatasets() == null) {
+            return;
+        }
+        for (Dataset dataset: response.getDatasets()) {
+            if (isPinnedDataset(dataset)) {
+                Slog.d(TAG, "Adding Credential Manager callback to a pinned entry");
+                addCredentialManagerCallbackForDataset(dataset, response.getRequestId());
+            }
+        }
+    }
+
+    private void addCredentialManagerCallbackForDataset(Dataset dataset, int requestId) {
+        final ResultReceiver resultReceiver = new ResultReceiver(mHandler) {
+            @Override
+            protected void onReceiveResult(int resultCode, Bundle resultData) {
+                if (resultCode == SUCCESS_CREDMAN_SELECTOR) {
+                    Slog.d(TAG, "onReceiveResult from Credential Manager bottom sheet");
+                    GetCredentialResponse getCredentialResponse =
+                            resultData.getParcelable(
+                                    CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
+                                    GetCredentialResponse.class);
+                    Dataset datasetFromCredential = getDatasetFromCredentialResponse(
+                            getCredentialResponse);
+                    if (datasetFromCredential != null) {
+                        autoFill(requestId, /*datasetIndex=*/-1,
+                                datasetFromCredential, false,
+                                UI_TYPE_CREDMAN_BOTTOM_SHEET);
+                    }
+                } else if (resultCode == FAILURE_CREDMAN_SELECTOR) {
+                    GetCredentialException exception =  resultData.getParcelable(
+                            CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION,
+                            GetCredentialException.class);
+                    Slog.d(TAG, "Credman bottom sheet from pinned "
+                            + "entry failed with: + " + exception.getType() + " , "
+                            + exception.getMessage());
+                } else {
+                    Slog.d(TAG, "Unknown resultCode from credential "
+                            + "manager bottom sheet: " + resultCode);
+                }
+            }
+        };
+        ResultReceiver ipcFriendlyResultReceiver =
+                toIpcFriendlyResultReceiver(resultReceiver);
+
+        Intent metadataIntent = dataset.getCredentialFillInIntent();
+        metadataIntent.putExtra(
+                android.credentials.selection.Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
+                ipcFriendlyResultReceiver);
+        dataset.setCredentialFillInIntent(metadataIntent);
+    }
+
+    private ResultReceiver toIpcFriendlyResultReceiver(ResultReceiver resultReceiver) {
+        final Parcel parcel = Parcel.obtain();
+        resultReceiver.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        return ipcFriendly;
+    }
+
     boolean isDestroyed() {
         synchronized (mLock) {
             return mDestroyed;
@@ -5516,8 +5615,11 @@
         mResponses.put(requestId, newResponse);
         mClientState = newClientState != null ? newClientState : newResponse.getClientState();
 
+        boolean webviewRequestedCredman = newClientState != null && newClientState.getBoolean(
+                WEBVIEW_REQUESTED_CREDENTIAL_KEY, false);
         List<Dataset> datasetList = newResponse.getDatasets();
 
+        mPresentationStatsEventLogger.maybeSetWebviewRequestedCredential(webviewRequestedCredman);
         mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(sIdCounterForPcc.get());
         mPresentationStatsEventLogger.maybeSetAvailableCount(datasetList, mCurrentViewId);
         mFillResponseEventLogger.maybeSetDatasetsCountAfterPotentialPccFiltering(datasetList);
@@ -5665,8 +5767,14 @@
             // does not matter the value of isPrimary because null response won't be overridden.
             setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH,
                     /* clearResponse= */ false, /* isPrimary= */ true);
-            final Intent fillInIntent = createAuthFillInIntentLocked(requestId, mClientState,
-                    dataset.getAuthenticationExtras());
+            final Intent fillInIntent;
+            if (dataset.getCredentialFillInIntent() != null && Flags.autofillCredmanIntegration()) {
+                Slog.d(TAG, "Setting credential fill intent");
+                fillInIntent = dataset.getCredentialFillInIntent();
+            } else {
+                fillInIntent = createAuthFillInIntentLocked(requestId, mClientState);
+            }
+
             if (fillInIntent == null) {
                 forceRemoveFromServiceLocked();
                 return;
@@ -5682,8 +5790,7 @@
     // TODO: this should never be null, but we got at least one occurrence, probably due to a race.
     @GuardedBy("mLock")
     @Nullable
-    private Intent createAuthFillInIntentLocked(int requestId, Bundle extras,
-            @Nullable Bundle authExtras) {
+    private Intent createAuthFillInIntentLocked(int requestId, Bundle extras) {
         final Intent fillInIntent = new Intent();
 
         final FillContext context = getFillContextByRequestIdLocked(requestId);
@@ -5700,9 +5807,6 @@
         }
         fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, context.getStructure());
         fillInIntent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, extras);
-        if (authExtras != null) {
-            fillInIntent.putExtra(AutofillManager.EXTRA_AUTH_STATE, authExtras);
-        }
         return fillInIntent;
     }
 
diff --git a/services/autofill/java/com/android/server/autofill/ui/BottomSheetButtonBarLayout.java b/services/autofill/java/com/android/server/autofill/ui/BottomSheetButtonBarLayout.java
new file mode 100644
index 0000000..69c7b29
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/ui/BottomSheetButtonBarLayout.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.internal.R;
+import com.android.internal.widget.ButtonBarLayout;
+
+/** An extension of {@link ButtonBarLayout} for use in Autofill bottom sheets. */
+public class BottomSheetButtonBarLayout extends ButtonBarLayout {
+
+    public BottomSheetButtonBarLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        final View spacer = findViewById(R.id.autofill_button_bar_spacer);
+        if (spacer == null) {
+            return;
+        }
+        if (isStacked()) {
+            spacer.getLayoutParams().width = 0;
+            spacer.getLayoutParams().height =
+                    getResources().getDimensionPixelSize(R.dimen.autofill_button_bar_spacer_height);
+            setGravity(Gravity.CENTER_VERTICAL | Gravity.END);
+        } else {
+            spacer.getLayoutParams().width =
+                    getResources().getDimensionPixelSize(R.dimen.autofill_button_bar_spacer_width);
+            spacer.getLayoutParams().height = 0;
+            setGravity(Gravity.CENTER_VERTICAL);
+        }
+    }
+
+    private boolean isStacked() {
+        return getOrientation() == LinearLayout.VERTICAL;
+    }
+}
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index b2716ec..d580f3a 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.autofill.ui;
 
+import static android.service.autofill.FillRequest.FLAG_VIEW_REQUESTS_CREDMAN_SERVICE;
 import static com.android.server.autofill.Helper.paramsToString;
 import static com.android.server.autofill.Helper.sDebug;
 import static com.android.server.autofill.Helper.sFullScreenMode;
@@ -31,6 +32,7 @@
 import android.service.autofill.Dataset;
 import android.service.autofill.Dataset.DatasetFieldFilter;
 import android.service.autofill.FillResponse;
+import android.service.autofill.Flags;
 import android.text.TextUtils;
 import android.util.PluralsMessageFormatter;
 import android.util.Slog;
@@ -79,6 +81,7 @@
             com.android.internal.R.style.Theme_DeviceDefault_Light_Autofill;
     private static final int THEME_ID_DARK =
             com.android.internal.R.style.Theme_DeviceDefault_Autofill;
+    private static final int AUTOFILL_CREDMAN_MAX_VISIBLE_DATASETS = 5;
 
     private static final TypedValue sTempTypedValue = new TypedValue();
 
@@ -211,7 +214,11 @@
             if (sVerbose) {
                 Slog.v(TAG, "overriding maximum visible datasets to " + mVisibleDatasetsMaxCount);
             }
-        } else {
+        } else if (Flags.autofillCredmanIntegration() && (
+                (response.getFlags() & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0)) {
+            mVisibleDatasetsMaxCount = AUTOFILL_CREDMAN_MAX_VISIBLE_DATASETS;
+        }
+        else {
             mVisibleDatasetsMaxCount = mContext.getResources()
                     .getInteger(com.android.internal.R.integer.autofill_max_visible_datasets);
         }
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index 6a63b3a..71f2b9e 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -32,4 +32,13 @@
     description: "Enables clearing the pipe buffer after restoring a single file to a BackupAgent."
     bug: "320633449"
     is_fixed_read_only: true
+}
+
+flag {
+    name: "enable_increase_datatypes_for_agent_logging"
+    namespace: "onboarding"
+    description: "Increase the number of a supported datatypes that an agent can define for its "
+            "logger."
+    bug: "296844513"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 8962bf0..1b49f18e 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -692,32 +692,37 @@
 
         private int mInputDeviceId = IInputConstants.INVALID_INPUT_DEVICE_ID;
 
-        WaitForDevice(String deviceName, int vendorId, int productId) {
+        WaitForDevice(String deviceName, int vendorId, int productId, int associatedDisplayId) {
             mListener = new InputManager.InputDeviceListener() {
                 @Override
                 public void onInputDeviceAdded(int deviceId) {
-                    final InputDevice device = InputManagerGlobal.getInstance().getInputDevice(
-                            deviceId);
-                    Objects.requireNonNull(device, "Newly added input device was null.");
-                    if (!device.getName().equals(deviceName)) {
-                        return;
-                    }
-                    final InputDeviceIdentifier id = device.getIdentifier();
-                    if (id.getVendorId() != vendorId || id.getProductId() != productId) {
-                        return;
-                    }
-                    mInputDeviceId = deviceId;
-                    mDeviceAddedLatch.countDown();
+                    onInputDeviceChanged(deviceId);
                 }
 
                 @Override
                 public void onInputDeviceRemoved(int deviceId) {
-
                 }
 
                 @Override
                 public void onInputDeviceChanged(int deviceId) {
+                    if (isMatchingDevice(deviceId)) {
+                        mInputDeviceId = deviceId;
+                        mDeviceAddedLatch.countDown();
+                    }
+                }
 
+                private boolean isMatchingDevice(int deviceId) {
+                    final InputDevice device = InputManagerGlobal.getInstance().getInputDevice(
+                            deviceId);
+                    Objects.requireNonNull(device, "Newly added input device was null.");
+                    if (!device.getName().equals(deviceName)) {
+                        return false;
+                    }
+                    final InputDeviceIdentifier id = device.getIdentifier();
+                    if (id.getVendorId() != vendorId || id.getProductId() != productId) {
+                        return false;
+                    }
+                    return device.getAssociatedDisplayId() == associatedDisplayId;
                 }
             };
             InputManagerGlobal.getInstance().registerInputDeviceListener(mListener, mHandler);
@@ -799,7 +804,7 @@
         final int inputDeviceId;
 
         setUniqueIdAssociation(displayId, phys);
-        try (WaitForDevice waiter = new WaitForDevice(deviceName, vendorId, productId)) {
+        try (WaitForDevice waiter = new WaitForDevice(deviceName, vendorId, productId, displayId)) {
             ptr = deviceOpener.get();
             // See INVALID_PTR in libs/input/VirtualInputDevice.cpp.
             if (ptr == 0) {
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 9d9e7c9..7979936 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -16,8 +16,8 @@
 
 package com.android.server;
 
-import static android.os.Flags.stateOfHealthPublic;
 import static android.os.Flags.batteryServiceSupportCurrentAdbCommand;
+import static android.os.Flags.stateOfHealthPublic;
 
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import static com.android.server.health.Utils.copyV1Battery;
@@ -81,6 +81,7 @@
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.NoSuchElementException;
+import java.util.concurrent.CopyOnWriteArraySet;
 
 /**
  * <p>BatteryService monitors the charging status, and charge level of the device
@@ -157,6 +158,12 @@
     private int mLastChargeCounter;
     private int mLastBatteryCycleCount;
     private int mLastCharingState;
+    /**
+     * The last seen charging policy. This requires the
+     * {@link android.Manifest.permission#BATTERY_STATS} permission and should therefore not be
+     * included in the ACTION_BATTERY_CHANGED intent extras.
+     */
+    private int mLastChargingPolicy;
 
     private int mSequence = 1;
 
@@ -197,6 +204,9 @@
     private ArrayDeque<Bundle> mBatteryLevelsEventQueue;
     private long mLastBatteryLevelChangedSentMs;
 
+    private final CopyOnWriteArraySet<BatteryManagerInternal.ChargingPolicyChangeListener>
+            mChargingPolicyChangeListeners = new CopyOnWriteArraySet<>();
+
     private Bundle mBatteryChangedOptions = BroadcastOptions.makeBasic()
             .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
             .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
@@ -527,6 +537,11 @@
         shutdownIfNoPowerLocked();
         shutdownIfOverTempLocked();
 
+        if (force || mHealthInfo.chargingPolicy != mLastChargingPolicy) {
+            mLastChargingPolicy = mHealthInfo.chargingPolicy;
+            mHandler.post(this::notifyChargingPolicyChanged);
+        }
+
         if (force
                 || (mHealthInfo.batteryStatus != mLastBatteryStatus
                         || mHealthInfo.batteryHealth != mLastBatteryHealth
@@ -827,6 +842,17 @@
         mLastBatteryLevelChangedSentMs = SystemClock.elapsedRealtime();
     }
 
+    private void notifyChargingPolicyChanged() {
+        final int newPolicy;
+        synchronized (mLock) {
+            newPolicy = mLastChargingPolicy;
+        }
+        for (BatteryManagerInternal.ChargingPolicyChangeListener listener
+                : mChargingPolicyChangeListeners) {
+            listener.onChargingPolicyChanged(newPolicy);
+        }
+    }
+
     // TODO: Current code doesn't work since "--unplugged" flag in BSS was purposefully removed.
     private void logBatteryStatsLocked() {
         IBinder batteryInfoService = ServiceManager.getService(BatteryStats.SERVICE_NAME);
@@ -1220,6 +1246,8 @@
                 pw.println("  voltage: " + mHealthInfo.batteryVoltageMillivolts);
                 pw.println("  temperature: " + mHealthInfo.batteryTemperatureTenthsCelsius);
                 pw.println("  technology: " + mHealthInfo.batteryTechnology);
+                pw.println("  Charging state: " + mHealthInfo.chargingState);
+                pw.println("  Charging policy: " + mHealthInfo.chargingPolicy);
             } else {
                 Shell shell = new Shell();
                 shell.exec(mBinderService, null, fd, null, args, null, new ResultReceiver(null));
@@ -1452,6 +1480,19 @@
         }
 
         @Override
+        public void registerChargingPolicyChangeListener(
+                BatteryManagerInternal.ChargingPolicyChangeListener listener) {
+            mChargingPolicyChangeListeners.add(listener);
+        }
+
+        @Override
+        public int getChargingPolicy() {
+            synchronized (mLock) {
+                return mLastChargingPolicy;
+            }
+        }
+
+        @Override
         public int getInvalidCharger() {
             synchronized (mLock) {
                 return mInvalidCharger;
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index 9554e63..fb527c1 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -20,8 +20,9 @@
 import android.content.Context;
 import android.content.Intent;
 import android.database.ContentObserver;
-import android.media.AudioAttributes;
+import android.media.AudioManager;
 import android.media.Ringtone;
+import android.media.RingtoneManager;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Handler;
@@ -305,16 +306,11 @@
                     if (soundPath != null) {
                         final Uri soundUri = Uri.parse("file://" + soundPath);
                         if (soundUri != null) {
-                            AudioAttributes audioAttributes = new AudioAttributes.Builder()
-                                    .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
-                                    .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-                                    .build();
-                            final Ringtone sfx = new Ringtone.Builder(getContext(),
-                                    Ringtone.MEDIA_SOUND, audioAttributes)
-                                    .setUri(soundUri)
-                                    .setPreferBuiltinDevice()
-                                    .build();
+                            final Ringtone sfx = RingtoneManager.getRingtone(
+                                    getContext(), soundUri);
                             if (sfx != null) {
+                                sfx.setStreamType(AudioManager.STREAM_SYSTEM);
+                                sfx.preferBuiltinDevice(true);
                                 sfx.play();
                             }
                         }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index ea1b0f5..e7fae24 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -48,6 +48,7 @@
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -3597,6 +3598,13 @@
         return mInternalStorageSize;
     }
 
+    @EnforcePermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @Override
+    public int getInternalStorageRemainingLifetime() throws RemoteException {
+        super.getInternalStorageRemainingLifetime_enforcePermission();
+        return mVold.getStorageRemainingLifetime();
+    }
+
     /**
      * Enforces that the caller is the {@link ExternalStorageService}
      *
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index adc0255..cd45b03 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -581,7 +581,8 @@
             if (DEBUG_FOREGROUND_SERVICE) {
                 Slog.i(TAG, "  Stopping fg for service " + r);
             }
-            setServiceForegroundInnerLocked(r, 0, null, 0, 0);
+            setServiceForegroundInnerLocked(r, 0, null, 0, 0,
+                    0);
         }
     }
 
@@ -989,7 +990,7 @@
 
         if (fgRequired) {
             logFgsBackgroundStart(r);
-            if (!r.isFgsAllowedStart() && isBgFgsRestrictionEnabled(r)) {
+            if (!r.isFgsAllowedStart() && isBgFgsRestrictionEnabled(r, callingUid)) {
                 String msg = "startForegroundService() not allowed due to "
                         + "mAllowStartForeground false: service "
                         + r.shortInstanceName;
@@ -1787,11 +1788,13 @@
     public void setServiceForegroundLocked(ComponentName className, IBinder token,
             int id, Notification notification, int flags, int foregroundServiceType) {
         final int userId = UserHandle.getCallingUserId();
+        final int callingUid = mAm.mInjector.getCallingUid();
         final long origId = mAm.mInjector.clearCallingIdentity();
         try {
             ServiceRecord r = findServiceLocked(className, token, userId);
             if (r != null) {
-                setServiceForegroundInnerLocked(r, id, notification, flags, foregroundServiceType);
+                setServiceForegroundInnerLocked(r, id, notification, flags, foregroundServiceType,
+                        callingUid);
             }
         } finally {
             mAm.mInjector.restoreCallingIdentity(origId);
@@ -2106,7 +2109,8 @@
      */
     @GuardedBy("mAm")
     private void setServiceForegroundInnerLocked(final ServiceRecord r, int id,
-            Notification notification, int flags, int foregroundServiceType) {
+            Notification notification, int flags, int foregroundServiceType,
+            int callingUidIfStart) {
         if (id != 0) {
             if (notification == null) {
                 throw new IllegalArgumentException("null notification");
@@ -2234,7 +2238,8 @@
                 }
 
                 // Whether FGS-BG-start restriction is enabled for this service.
-                final boolean isBgFgsRestrictionEnabledForService = isBgFgsRestrictionEnabled(r);
+                final boolean isBgFgsRestrictionEnabledForService = isBgFgsRestrictionEnabled(r,
+                        callingUidIfStart);
 
                 // Whether to extend the SHORT_SERVICE time out.
                 boolean extendShortServiceTimeout = false;
@@ -8486,14 +8491,43 @@
                 NOTE_FOREGROUND_SERVICE_BG_LAUNCH, n.build(), UserHandle.ALL);
     }
 
-    private boolean isBgFgsRestrictionEnabled(ServiceRecord r) {
-        return mAm.mConstants.mFlagFgsStartRestrictionEnabled
-                // Checking service's targetSdkVersion.
-                && CompatChanges.isChangeEnabled(FGS_BG_START_RESTRICTION_CHANGE_ID, r.appInfo.uid)
-                && (!mAm.mConstants.mFgsStartRestrictionCheckCallerTargetSdk
-                    // Checking callingUid's targetSdkVersion.
-                    || CompatChanges.isChangeEnabled(
-                            FGS_BG_START_RESTRICTION_CHANGE_ID, r.mRecentCallingUid));
+    private boolean isBgFgsRestrictionEnabled(ServiceRecord r, int actualCallingUid) {
+        // mFlagFgsStartRestrictionEnabled controls whether to enable the BG FGS restrictions:
+        // - If true (default), BG-FGS restrictions are enabled if the service targets >= S.
+        // - If false, BG-FGS restrictions are disabled for all apps.
+        if (!mAm.mConstants.mFlagFgsStartRestrictionEnabled) {
+            return false;
+        }
+
+        // If the service target below S, then don't enable the restrictions.
+        if (!CompatChanges.isChangeEnabled(FGS_BG_START_RESTRICTION_CHANGE_ID, r.appInfo.uid)) {
+            return false;
+        }
+
+        // mFgsStartRestrictionCheckCallerTargetSdk controls whether we take the caller's target
+        // SDK level into account or not:
+        // - If true (default), BG-FGS restrictions only happens if the caller _also_ targets >= S.
+        // - If false, BG-FGS restrictions do _not_ use the caller SDK levels.
+        if (!mAm.mConstants.mFgsStartRestrictionCheckCallerTargetSdk) {
+            return true; // In this case, we only check the service's target SDK level.
+        }
+        final int callingUid;
+        if (Flags.newFgsRestrictionLogic()) {
+            // We always consider SYSTEM_UID to target S+, so just enable the restrictions.
+            if (actualCallingUid == Process.SYSTEM_UID) {
+                return true;
+            }
+            callingUid = actualCallingUid;
+        } else {
+            // Legacy logic used mRecentCallingUid.
+            callingUid = r.mRecentCallingUid;
+        }
+        if (!CompatChanges.isChangeEnabled(FGS_BG_START_RESTRICTION_CHANGE_ID, callingUid)) {
+            return false; // If the caller targets < S, then we still disable the restrictions.
+        }
+
+        // Both the service and the caller target S+, so enable the check.
+        return true;
     }
 
     private void logFgsBackgroundStart(ServiceRecord r) {
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index c37e619..d1c8c30 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -152,6 +152,11 @@
     private int mActiveIndex;
 
     /**
+     * True if the broadcast actively being dispatched to this process was re-enqueued previously.
+     */
+    private boolean mActiveReEnqueued;
+
+    /**
      * Count of {@link #mActive} broadcasts that have been dispatched since this
      * queue was last idle.
      */
@@ -312,6 +317,7 @@
         final SomeArgs broadcastArgs = SomeArgs.obtain();
         broadcastArgs.arg1 = record;
         broadcastArgs.argi1 = recordIndex;
+        broadcastArgs.argi2 = 1;
         getQueueForBroadcast(record).addFirst(broadcastArgs);
         onBroadcastEnqueued(record, recordIndex);
     }
@@ -609,6 +615,7 @@
         final SomeArgs next = removeNextBroadcast();
         mActive = (BroadcastRecord) next.arg1;
         mActiveIndex = next.argi1;
+        mActiveReEnqueued = (next.argi2 == 1);
         mActiveCountSinceIdle++;
         mActiveAssumedDeliveryCountSinceIdle +=
                 (mActive.isAssumedDelivered(mActiveIndex) ? 1 : 0);
@@ -624,12 +631,21 @@
     public void makeActiveIdle() {
         mActive = null;
         mActiveIndex = 0;
+        mActiveReEnqueued = false;
         mActiveCountSinceIdle = 0;
         mActiveAssumedDeliveryCountSinceIdle = 0;
         mActiveViaColdStart = false;
         invalidateRunnableAt();
     }
 
+    public boolean wasActiveBroadcastReEnqueued() {
+        // If the flag is not enabled, treat as if the broadcast was never re-enqueued.
+        if (!Flags.avoidRepeatedBcastReEnqueues()) {
+            return false;
+        }
+        return mActiveReEnqueued;
+    }
+
     /**
      * Update summary statistics when the given record has been enqueued.
      */
@@ -1476,6 +1492,9 @@
         if (runningOomAdjusted) {
             pw.print("runningOomAdjusted:"); pw.println(runningOomAdjusted);
         }
+        if (mActiveReEnqueued) {
+            pw.print("activeReEnqueued:"); pw.println(mActiveReEnqueued);
+        }
     }
 
     @NeverCompile
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index db0f03f..98263df 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -542,8 +542,8 @@
                 updateOomAdj |= queue.runningOomAdjusted;
                 try {
                     completed = scheduleReceiverWarmLocked(queue);
-                } catch (BroadcastDeliveryFailedException e) {
-                    reEnqueueActiveBroadcast(queue);
+                } catch (BroadcastRetryException e) {
+                    finishOrReEnqueueActiveBroadcast(queue);
                     completed = true;
                 }
             } else {
@@ -586,7 +586,12 @@
 
     private void clearInvalidPendingColdStart() {
         logw("Clearing invalid pending cold start: " + mRunningColdStart);
-        mRunningColdStart.reEnqueueActiveBroadcast();
+        if (mRunningColdStart.wasActiveBroadcastReEnqueued()) {
+            finishReceiverActiveLocked(mRunningColdStart, BroadcastRecord.DELIVERY_FAILURE,
+                    "invalid start with re-enqueued broadcast");
+        } else {
+            mRunningColdStart.reEnqueueActiveBroadcast();
+        }
         demoteFromRunningLocked(mRunningColdStart);
         clearRunningColdStart();
         enqueueUpdateRunningList();
@@ -613,19 +618,26 @@
         }
     }
 
-    private void reEnqueueActiveBroadcast(@NonNull BroadcastProcessQueue queue) {
+    private void finishOrReEnqueueActiveBroadcast(@NonNull BroadcastProcessQueue queue) {
         checkState(queue.isActive(), "isActive");
 
-        final BroadcastRecord record = queue.getActive();
-        final int index = queue.getActiveIndex();
-        setDeliveryState(queue, queue.app, record, index, record.receivers.get(index),
-                BroadcastRecord.DELIVERY_PENDING, "reEnqueueActiveBroadcast");
-        queue.reEnqueueActiveBroadcast();
+        if (queue.wasActiveBroadcastReEnqueued()) {
+            // If the broadcast was already re-enqueued previously, finish it to avoid repeated
+            // delivery attempts
+            finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
+                    "re-enqueued broadcast delivery failed");
+        } else {
+            final BroadcastRecord record = queue.getActive();
+            final int index = queue.getActiveIndex();
+            setDeliveryState(queue, queue.app, record, index, record.receivers.get(index),
+                    BroadcastRecord.DELIVERY_PENDING, "reEnqueueActiveBroadcast");
+            queue.reEnqueueActiveBroadcast();
+        }
     }
 
     @Override
     public boolean onApplicationAttachedLocked(@NonNull ProcessRecord app)
-            throws BroadcastDeliveryFailedException {
+            throws BroadcastRetryException {
         if (DEBUG_BROADCAST) {
             logv("Process " + app + " is attached");
         }
@@ -653,8 +665,8 @@
                 if (scheduleReceiverWarmLocked(queue)) {
                     demoteFromRunningLocked(queue);
                 }
-            } catch (BroadcastDeliveryFailedException e) {
-                reEnqueueActiveBroadcast(queue);
+            } catch (BroadcastRetryException e) {
+                finishOrReEnqueueActiveBroadcast(queue);
                 demoteFromRunningLocked(queue);
                 throw e;
             }
@@ -983,7 +995,7 @@
     @CheckResult
     @GuardedBy("mService")
     private boolean scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue)
-            throws BroadcastDeliveryFailedException {
+            throws BroadcastRetryException {
         checkState(queue.isActive(), "isActive");
 
         final int cookie = traceBegin("scheduleReceiverWarmLocked");
@@ -1065,7 +1077,7 @@
      */
     @CheckResult
     private boolean dispatchReceivers(@NonNull BroadcastProcessQueue queue,
-            @NonNull BroadcastRecord r, int index) throws BroadcastDeliveryFailedException {
+            @NonNull BroadcastRecord r, int index) throws BroadcastRetryException {
         final ProcessRecord app = queue.app;
         final Object receiver = r.receivers.get(index);
 
@@ -1157,7 +1169,7 @@
                 // to try redelivering the broadcast to this receiver.
                 if (receiver instanceof ResolveInfo) {
                     cancelDeliveryTimeoutLocked(queue);
-                    throw new BroadcastDeliveryFailedException(e);
+                    throw new BroadcastRetryException(e);
                 }
                 finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
                         "remote app");
@@ -1316,8 +1328,8 @@
                 demoteFromRunningLocked(queue);
                 return true;
             }
-        } catch (BroadcastDeliveryFailedException e) {
-            reEnqueueActiveBroadcast(queue);
+        } catch (BroadcastRetryException e) {
+            finishOrReEnqueueActiveBroadcast(queue);
             demoteFromRunningLocked(queue);
             return true;
         }
diff --git a/services/core/java/com/android/server/am/BroadcastRetryException.java b/services/core/java/com/android/server/am/BroadcastRetryException.java
new file mode 100644
index 0000000..8bd6664
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastRetryException.java
@@ -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.server.am;
+
+/**
+ * Exception to represent that broadcast delivery failed and we should try redelivering it.
+ */
+public class BroadcastRetryException extends BroadcastDeliveryFailedException {
+    public BroadcastRetryException(String name) {
+        super(name);
+    }
+
+    public BroadcastRetryException(Exception cause) {
+        super(cause);
+    }
+}
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 2aed847..0f75ad48 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -31,7 +31,7 @@
 30017 am_low_memory (Num Processes|1|1)
 
 # Kill a process to reclaim memory.
-30023 am_kill (User|1|5),(PID|1|5),(Process Name|3),(OomAdj|1|5),(Reason|3)
+30023 am_kill (User|1|5),(PID|1|5),(Process Name|3),(OomAdj|1|5),(Reason|3),(Rss|2|2)
 # Discard an undelivered serialized broadcast (timeout/ANR/crash)
 30024 am_broadcast_discard_filter (User|1|5),(Broadcast|1|5),(Action|3),(Receiver Number|1|1),(BroadcastFilter|1|5)
 30025 am_broadcast_discard_app (User|1|5),(Broadcast|1|5),(Action|3),(Receiver Number|1|1),(App|3)
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 862542e..7d82f0c 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1499,8 +1499,12 @@
                             && !uidRec.isCurAllowListed()) {
                         // UID is now in the background (and not on the temp allowlist).  Was it
                         // previously in the foreground (or on the temp allowlist)?
+                        // Or, it wasn't in the foreground / allowlist, but its last background
+                        // timestamp is also 0, this means it's never been in the
+                        // foreground / allowlist since it's born at all.
                         if (!ActivityManager.isProcStateBackground(uidRec.getSetProcState())
-                                || uidRec.isSetAllowListed()) {
+                                || uidRec.isSetAllowListed()
+                                || uidRec.getLastBackgroundTime() == 0) {
                             uidRec.setLastBackgroundTime(nowElapsed);
                             if (mService.mDeterministicUidIdle
                                     || !mService.mHandler.hasMessages(IDLE_UIDS_MSG)) {
@@ -1526,6 +1530,7 @@
                             uidRec.setIdle(false);
                         }
                         uidRec.setLastBackgroundTime(0);
+                        uidRec.setLastIdleTime(0);
                     }
                     final boolean wasCached = uidRec.getSetProcState()
                             > ActivityManager.PROCESS_STATE_RECEIVER;
@@ -3700,12 +3705,14 @@
         for (int i = N - 1; i >= 0; i--) {
             final UidRecord uidRec = mActiveUids.valueAt(i);
             final long bgTime = uidRec.getLastBackgroundTime();
-            if (bgTime > 0 && !uidRec.isIdle()) {
+            final long idleTime = uidRec.getLastIdleTime();
+            if (bgTime > 0 && (!uidRec.isIdle() || idleTime == 0)) {
                 if (bgTime <= maxBgTime) {
                     EventLogTags.writeAmUidIdle(uidRec.getUid());
                     synchronized (mProcLock) {
                         uidRec.setIdle(true);
                         uidRec.setSetIdle(true);
+                        uidRec.setLastIdleTime(nowElapsed);
                     }
                     mService.doStopUidLocked(uidRec.getUid(), uidRec);
                 } else {
diff --git a/services/core/java/com/android/server/am/PhantomProcessRecord.java b/services/core/java/com/android/server/am/PhantomProcessRecord.java
index 1a692df..ac96bdc 100644
--- a/services/core/java/com/android/server/am/PhantomProcessRecord.java
+++ b/services/core/java/com/android/server/am/PhantomProcessRecord.java
@@ -105,6 +105,11 @@
         }
     }
 
+    public long getRss(int pid) {
+        long[] rss = Process.getRss(pid);
+        return (rss != null && rss.length > 0) ? rss[0] : 0;
+    }
+
     @GuardedBy("mLock")
     void killLocked(String reason, boolean noisy) {
         if (!mKilled) {
@@ -115,7 +120,7 @@
             }
             if (mPid > 0) {
                 EventLog.writeEvent(EventLogTags.AM_KILL, UserHandle.getUserId(mUid),
-                        mPid, mProcessName, mAdj, reason);
+                        mPid, mProcessName, mAdj, reason, getRss(mPid));
                 if (!Process.supportsPidFd()) {
                     onProcDied(false);
                 } else {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index cd2e441..3adea7a 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -4986,19 +4986,7 @@
     }
 
     void dispatchProcessStarted(ProcessRecord app, int pid) {
-        int i = mProcessObservers.beginBroadcast();
-        while (i > 0) {
-            i--;
-            final IProcessObserver observer = mProcessObservers.getBroadcastItem(i);
-            if (observer != null) {
-                try {
-                    observer.onProcessStarted(pid, app.uid, app.info.uid,
-                            app.info.packageName, app.processName);
-                } catch (RemoteException e) {
-                }
-            }
-        }
-        mProcessObservers.finishBroadcast();
+        // TODO(b/323959187) Add the implementation.
     }
 
     void dispatchProcessDied(int pid, int uid) {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index de6f034..d23d9fb 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1217,6 +1217,11 @@
         }
     }
 
+    public long getRss(int pid) {
+        long[] rss = Process.getRss(pid);
+        return (rss != null && rss.length > 0) ? rss[0] : 0;
+    }
+
     @GuardedBy("mService")
     void killLocked(String reason, @Reason int reasonCode, boolean noisy) {
         killLocked(reason, reasonCode, ApplicationExitInfo.SUBREASON_UNKNOWN, noisy, true);
@@ -1260,7 +1265,7 @@
             if (mPid > 0) {
                 mService.mProcessList.noteAppKill(this, reasonCode, subReason, description);
                 EventLog.writeEvent(EventLogTags.AM_KILL,
-                        userId, mPid, processName, mState.getSetAdj(), reason);
+                        userId, mPid, processName, mState.getSetAdj(), reason, getRss(mPid));
                 Process.killProcessQuiet(mPid);
                 killProcessGroupIfNecessaryLocked(asyncKPG);
             } else {
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index 4329afc..45fd470 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -66,6 +66,9 @@
     private long mLastBackgroundTime;
 
     @CompositeRWLock({"mService", "mProcLock"})
+    private long mLastIdleTime;
+
+    @CompositeRWLock({"mService", "mProcLock"})
     private boolean mEphemeral;
 
     @CompositeRWLock({"mService", "mProcLock"})
@@ -255,6 +258,16 @@
     }
 
     @GuardedBy(anyOf = {"mService", "mProcLock"})
+    long getLastIdleTime() {
+        return mLastIdleTime;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setLastIdleTime(long lastActiveTime) {
+        mLastIdleTime = lastActiveTime;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
     boolean isEphemeral() {
         return mEphemeral;
     }
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 2b81dbc..96c6be8 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -993,17 +993,20 @@
     /**
      * Stops a single User. This can also trigger locking user data out depending on device's
      * config ({@code mDelayUserDataLocking}) and arguments.
-     * User will be unlocked when
-     * - {@code mDelayUserDataLocking} is not set.
-     * - {@code mDelayUserDataLocking} is set and {@code keyEvictedCallback} is non-null.
+     *
+     * In the default configuration for most device and users, users will be locked when stopping.
+     * User will remain unlocked only if all the following are true
+     * <li> {@link #canDelayDataLockingForUser(int)} (based on mDelayUserDataLocking) is true
+     * <li> the parameter {@code allowDelayedLocking} is true
+     * <li> {@code keyEvictedCallback} is null
      * -
      *
      * @param userId User Id to stop and lock the data.
      * @param allowDelayedLocking When set, do not lock user after stopping. Locking can happen
      *                            later when number of unlocked users reaches
      *                            {@code mMaxRunnngUsers}. Note that this is respected only when
-     *                            {@code mDelayUserDataLocking} is set and {@keyEvictedCallback} is
-     *                            null. Otherwise the user will be locked.
+     *                            delayed locking is enabled for this user and {@keyEvictedCallback}
+     *                            is null. Otherwise the user nonetheless will be locked.
      * @param stopUserCallback Callback to notify that user has stopped.
      * @param keyEvictedCallback Callback to notify that user has been unlocked.
      */
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 31d9cc9..16dbe18 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -42,3 +42,14 @@
     description: "Optimize the service bindings by different policies like skipping oom adjuster"
     bug: "318717054"
 }
+
+flag {
+    namespace: "backstage_power"
+    name: "avoid_repeated_bcast_re_enqueues"
+    description: "Avoid re-enqueueing a broadcast repeatedly"
+    bug: "319225224"
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
new file mode 100644
index 0000000..a923daa
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+
+/**
+ * This class provides the handler to process biometric operations.
+ */
+public class BiometricHandlerProvider {
+    private static final BiometricHandlerProvider sBiometricHandlerProvider =
+            new BiometricHandlerProvider();
+
+    private final Handler mBiometricsCallbackHandler;
+    private final Handler mFingerprintHandler;
+    private final Handler mFaceHandler;
+
+    /**
+     * @return an instance of {@link BiometricHandlerProvider} which contains the three
+     *         threads needed for running biometric operations
+     */
+    public static BiometricHandlerProvider getInstance() {
+        return sBiometricHandlerProvider;
+    }
+
+    private BiometricHandlerProvider() {
+        mBiometricsCallbackHandler = getNewHandler("BiometricsCallbackHandler");
+        mFingerprintHandler = getNewHandler("FingerprintHandler");
+        mFaceHandler = getNewHandler("FaceHandler");
+    }
+
+    /**
+    * @return the handler to process all biometric callback operations
+    */
+    public synchronized Handler getBiometricCallbackHandler() {
+        return mBiometricsCallbackHandler;
+    }
+
+    /**
+     * @return the handler to process all face related biometric operations
+     */
+    public synchronized Handler getFaceHandler() {
+        return mFaceHandler;
+    }
+
+    /**
+     * @return the handler to process all fingerprint related biometric operations
+     */
+    public synchronized Handler getFingerprintHandler() {
+        return mFingerprintHandler;
+    }
+
+    private Handler getNewHandler(String tag) {
+        if (Flags.deHidl()) {
+            HandlerThread handlerThread = new HandlerThread(tag);
+            handlerThread.start();
+            return new Handler(handlerThread.getLooper());
+        }
+        return new Handler(Looper.getMainLooper());
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 91a68ea..fc948da 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -62,7 +62,6 @@
 import android.os.DeadObjectException;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
@@ -140,7 +139,7 @@
     // The current authentication session, null if idle/done.
     @VisibleForTesting
     AuthSession mAuthSession;
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final Handler mHandler;
 
     private final BiometricCameraManager mBiometricCameraManager;
 
@@ -1113,14 +1112,16 @@
      * @param context The system server context.
      */
     public BiometricService(Context context) {
-        this(context, new Injector());
+        this(context, new Injector(), BiometricHandlerProvider.getInstance());
     }
 
     @VisibleForTesting
-    BiometricService(Context context, Injector injector) {
+    BiometricService(Context context, Injector injector,
+            BiometricHandlerProvider biometricHandlerProvider) {
         super(context);
 
         mInjector = injector;
+        mHandler = biometricHandlerProvider.getBiometricCallbackHandler();
         mDevicePolicyManager = mInjector.getDevicePolicyManager(context);
         mImpl = new BiometricServiceWrapper();
         mEnabledOnKeyguardCallbacks = new ArrayList<>();
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index fbd32a6..d061e2d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -31,6 +31,7 @@
 
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
 
 import java.util.function.Supplier;
 
@@ -202,6 +203,16 @@
         }
     }
 
+    // TODO(b/317414324): Deprecate setIgnoreDisplayTouches
+    protected final void resetIgnoreDisplayTouches() {
+        final AidlSession session = (AidlSession) getFreshDaemon();
+        try {
+            session.getSession().setIgnoreDisplayTouches(false);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception when resetting setIgnoreDisplayTouches");
+        }
+    }
+
     @Override
     public boolean isInterruptable() {
         return true;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index d01c268..f469f62 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -39,7 +39,6 @@
 import android.hardware.face.IFaceServiceReceiver;
 import android.os.Binder;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
@@ -53,6 +52,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
 import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.BiometricHandlerProvider;
 import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
@@ -124,6 +124,8 @@
     private final BiometricContext mBiometricContext;
     @NonNull
     private final AuthSessionCoordinator mAuthSessionCoordinator;
+    @NonNull
+    private final BiometricHandlerProvider mBiometricHandlerProvider;
     @Nullable
     private AuthenticationStatsCollector mAuthenticationStatsCollector;
     @Nullable
@@ -166,8 +168,9 @@
             @NonNull BiometricContext biometricContext,
             boolean resetLockoutRequiresChallenge) {
         this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName,
-                lockoutResetDispatcher, biometricContext, null /* daemon */, getHandler(),
-                resetLockoutRequiresChallenge, false /* testHalEnabled */);
+                lockoutResetDispatcher, biometricContext, null /* daemon */,
+                BiometricHandlerProvider.getInstance(), resetLockoutRequiresChallenge,
+                false /* testHalEnabled */);
     }
 
     @VisibleForTesting FaceProvider(@NonNull Context context,
@@ -178,7 +181,7 @@
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull BiometricContext biometricContext,
             @Nullable IFace daemon,
-            @NonNull Handler handler,
+            @NonNull BiometricHandlerProvider biometricHandlerProvider,
             boolean resetLockoutRequiresChallenge,
             boolean testHalEnabled) {
         mContext = context;
@@ -187,7 +190,7 @@
         mHalInstanceName = halInstanceName;
         mFaceSensors = new SensorList<>(ActivityManager.getService());
         if (Flags.deHidl()) {
-            mHandler = handler;
+            mHandler = biometricHandlerProvider.getFaceHandler();
         } else {
             mHandler = new Handler(Looper.getMainLooper());
         }
@@ -199,18 +202,12 @@
         mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
         mDaemon = daemon;
         mTestHalEnabled = testHalEnabled;
+        mBiometricHandlerProvider = biometricHandlerProvider;
 
         initAuthenticationBroadcastReceiver();
         initSensors(resetLockoutRequiresChallenge, props);
     }
 
-    @NonNull
-    private static Handler getHandler() {
-        HandlerThread handlerThread = new HandlerThread(TAG);
-        handlerThread.start();
-        return new Handler(handlerThread.getLooper());
-    }
-
     private void initAuthenticationBroadcastReceiver() {
         new AuthenticationStatsBroadcastReceiver(
                 mContext,
@@ -622,15 +619,29 @@
                 @Override
                 public void onClientStarted(
                          BaseClientMonitor clientMonitor) {
-                    mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
+                    if (Flags.deHidl()) {
+                        mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+                                mAuthSessionCoordinator.authStartedFor(userId, sensorId,
+                                        requestId));
+                    } else {
+                        mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
+                    }
                 }
 
                 @Override
                 public void onClientFinished(
                         BaseClientMonitor clientMonitor,
                         boolean success) {
-                    mAuthSessionCoordinator.authEndedFor(userId, Utils.getCurrentStrength(sensorId),
-                            sensorId, requestId, client.wasAuthSuccessful());
+                    if (Flags.deHidl()) {
+                        mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+                                mAuthSessionCoordinator.authEndedFor(userId,
+                                        Utils.getCurrentStrength(sensorId), sensorId, requestId,
+                                        client.wasAuthSuccessful()));
+                    } else {
+                        mAuthSessionCoordinator.authEndedFor(userId,
+                                Utils.getCurrentStrength(sensorId),
+                                sensorId, requestId, client.wasAuthSuccessful());
+                    }
                 }
             });
         });
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 8121a63..93d1b6e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -232,6 +232,7 @@
         handleLockout(authenticated);
         if (authenticated) {
             mState = STATE_STOPPED;
+            resetIgnoreDisplayTouches();
             mSensorOverlays.hide(getSensorId());
             if (sidefpsControllerRefactor()) {
                 mAuthenticationStateListeners.onAuthenticationStopped();
@@ -268,6 +269,7 @@
                 // Send the error, but do not invoke the FinishCallback yet. Since lockout is not
                 // controlled by the HAL, the framework must stop the sensor before finishing the
                 // client.
+                resetIgnoreDisplayTouches();
                 mSensorOverlays.hide(getSensorId());
                 if (sidefpsControllerRefactor()) {
                     mAuthenticationStateListeners.onAuthenticationStopped();
@@ -298,6 +300,7 @@
             BiometricNotificationUtils.showBadCalibrationNotification(getContext());
         }
 
+        resetIgnoreDisplayTouches();
         mSensorOverlays.hide(getSensorId());
         if (sidefpsControllerRefactor()) {
             mAuthenticationStateListeners.onAuthenticationStopped();
@@ -306,6 +309,7 @@
 
     @Override
     protected void startHalOperation() {
+        resetIgnoreDisplayTouches();
         mSensorOverlays.show(getSensorId(), getRequestReason(), this);
         if (sidefpsControllerRefactor()) {
             mAuthenticationStateListeners.onAuthenticationStarted(getRequestReason());
@@ -419,6 +423,7 @@
 
     @Override
     protected void stopHalOperation() {
+        resetIgnoreDisplayTouches();
         mSensorOverlays.hide(getSensorId());
         if (sidefpsControllerRefactor()) {
             mAuthenticationStateListeners.onAuthenticationStopped();
@@ -518,6 +523,7 @@
             Slog.e(TAG, "Remote exception", e);
         }
 
+        resetIgnoreDisplayTouches();
         mSensorOverlays.hide(getSensorId());
         if (sidefpsControllerRefactor()) {
             mAuthenticationStateListeners.onAuthenticationStopped();
@@ -548,6 +554,7 @@
             Slog.e(TAG, "Remote exception", e);
         }
 
+        resetIgnoreDisplayTouches();
         mSensorOverlays.hide(getSensorId());
         if (sidefpsControllerRefactor()) {
             mAuthenticationStateListeners.onAuthenticationStopped();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index cb220b9e..8d2b46f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -87,6 +87,7 @@
 
     @Override
     protected void stopHalOperation() {
+        resetIgnoreDisplayTouches();
         mSensorOverlays.hide(getSensorId());
         unsubscribeBiometricContext();
 
@@ -102,6 +103,7 @@
 
     @Override
     protected void startHalOperation() {
+        resetIgnoreDisplayTouches();
         mSensorOverlays.show(getSensorId(), BiometricRequestConstants.REASON_AUTH_KEYGUARD,
                 this);
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 225bd59..79975e5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -144,6 +144,7 @@
                 controller -> controller.onEnrollmentProgress(getSensorId(), remaining));
 
         if (remaining == 0) {
+            resetIgnoreDisplayTouches();
             mSensorOverlays.hide(getSensorId());
             if (sidefpsControllerRefactor()) {
                 mAuthenticationStateListeners.onAuthenticationStopped();
@@ -178,6 +179,7 @@
     @Override
     public void onError(int errorCode, int vendorCode) {
         super.onError(errorCode, vendorCode);
+        resetIgnoreDisplayTouches();
         mSensorOverlays.hide(getSensorId());
         if (sidefpsControllerRefactor()) {
             mAuthenticationStateListeners.onAuthenticationStopped();
@@ -192,6 +194,7 @@
 
     @Override
     protected void startHalOperation() {
+        resetIgnoreDisplayTouches();
         mSensorOverlays.show(getSensorId(), getRequestReasonFromEnrollReason(mEnrollReason),
                 this);
         if (sidefpsControllerRefactor()) {
@@ -273,6 +276,7 @@
 
     @Override
     protected void stopHalOperation() {
+        resetIgnoreDisplayTouches();
         mSensorOverlays.hide(getSensorId());
         if (sidefpsControllerRefactor()) {
             mAuthenticationStateListeners.onAuthenticationStopped();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index c0388d1..fd938ed 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -46,7 +46,6 @@
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Binder;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
@@ -60,6 +59,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
 import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.BiometricHandlerProvider;
 import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
@@ -129,11 +129,12 @@
     // for requests that do not use biometric prompt
     @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
     @NonNull private final BiometricContext mBiometricContext;
+    @NonNull private final BiometricHandlerProvider mBiometricHandlerProvider;
     @Nullable private IFingerprint mDaemon;
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     @Nullable private ISidefpsController mSidefpsController;
-    private AuthSessionCoordinator mAuthSessionCoordinator;
+    private final AuthSessionCoordinator mAuthSessionCoordinator;
     @Nullable private AuthenticationStatsCollector mAuthenticationStatsCollector;
 
     private final class BiometricTaskStackListener extends TaskStackListener {
@@ -175,8 +176,8 @@
             boolean resetLockoutRequiresHardwareAuthToken) {
         this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName,
                 lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext,
-                null /* daemon */, getHandler(), resetLockoutRequiresHardwareAuthToken,
-                false /* testHalEnabled */);
+                null /* daemon */, BiometricHandlerProvider.getInstance(),
+                resetLockoutRequiresHardwareAuthToken, false /* testHalEnabled */);
     }
 
     @VisibleForTesting FingerprintProvider(@NonNull Context context,
@@ -187,7 +188,7 @@
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull BiometricContext biometricContext,
             @Nullable IFingerprint daemon,
-            @NonNull Handler handler,
+            @NonNull BiometricHandlerProvider biometricHandlerProvider,
             boolean resetLockoutRequiresHardwareAuthToken,
             boolean testHalEnabled) {
         mContext = context;
@@ -196,7 +197,7 @@
         mHalInstanceName = halInstanceName;
         mFingerprintSensors = new SensorList<>(ActivityManager.getService());
         if (Flags.deHidl()) {
-            mHandler = handler;
+            mHandler = biometricHandlerProvider.getFingerprintHandler();
         } else {
             mHandler = new Handler(Looper.getMainLooper());
         }
@@ -207,18 +208,12 @@
         mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
         mDaemon = daemon;
         mTestHalEnabled = testHalEnabled;
+        mBiometricHandlerProvider = biometricHandlerProvider;
 
         initAuthenticationBroadcastReceiver();
         initSensors(resetLockoutRequiresHardwareAuthToken, props, gestureAvailabilityDispatcher);
     }
 
-    @NonNull
-    private static Handler getHandler() {
-        HandlerThread handlerThread = new HandlerThread(TAG);
-        handlerThread.start();
-        return new Handler(handlerThread.getLooper());
-    }
-
     private void initAuthenticationBroadcastReceiver() {
         new AuthenticationStatsBroadcastReceiver(
                 mContext,
@@ -620,7 +615,13 @@
                 @Override
                 public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
                     mBiometricStateCallback.onClientStarted(clientMonitor);
-                    mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
+                    if (Flags.deHidl()) {
+                        mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+                                mAuthSessionCoordinator.authStartedFor(userId, sensorId,
+                                        requestId));
+                    } else {
+                        mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
+                    }
                 }
 
                 @Override
@@ -632,8 +633,15 @@
                 public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
                         boolean success) {
                     mBiometricStateCallback.onClientFinished(clientMonitor, success);
-                    mAuthSessionCoordinator.authEndedFor(userId, Utils.getCurrentStrength(sensorId),
-                            sensorId, requestId, success);
+                    if (Flags.deHidl()) {
+                        mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+                                mAuthSessionCoordinator.authEndedFor(userId,
+                                        Utils.getCurrentStrength(sensorId), sensorId, requestId,
+                                        success));
+                    } else {
+                        mAuthSessionCoordinator.authEndedFor(userId,
+                                Utils.getCurrentStrength(sensorId), sensorId, requestId, success);
+                    }
                 }
             });
 
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 458fd82..05e681e 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -1020,6 +1020,10 @@
         }
     }
 
+    private boolean isAutomotive() {
+        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+    }
+
     private Set<Integer> getEnabledUserHandles(int currentUserHandle) {
         int[] userProfiles = mUserManager.getEnabledProfileIds(currentUserHandle);
         Set<Integer> handles = new ArraySet<>(userProfiles.length);
@@ -1030,8 +1034,8 @@
 
         if (Flags.cameraHsumPermission()) {
             // If the device is running in headless system user mode then allow
-            // User 0 to access camera.
-            if (UserManager.isHeadlessSystemUserMode()) {
+            // User 0 to access camera only for automotive form factor.
+            if (UserManager.isHeadlessSystemUserMode() && isAutomotive()) {
                 handles.add(UserHandle.USER_SYSTEM);
             }
         }
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 7726609..574be34 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -621,10 +621,6 @@
         mBatteryController.systemRunning();
         mKeyboardBacklightController.systemRunning();
         mKeyRemapper.systemRunning();
-
-        mNative.setStylusPointerIconEnabled(
-                Objects.requireNonNull(mContext.getSystemService(InputManager.class))
-                        .isStylusPointerIconEnabled());
     }
 
     private void reloadDeviceAliases() {
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index 5ffc380..c02d5249 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -94,7 +94,9 @@
                 Map.entry(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_SLOW_KEYS),
                         (reason) -> updateAccessibilitySlowKeys()),
                 Map.entry(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_STICKY_KEYS),
-                        (reason) -> updateAccessibilityStickyKeys()));
+                        (reason) -> updateAccessibilityStickyKeys()),
+                Map.entry(Settings.Secure.getUriFor(Settings.Secure.STYLUS_POINTER_ICON_ENABLED),
+                        (reason) -> updateStylusPointerIconEnabled()));
     }
 
     /**
@@ -254,4 +256,8 @@
             mNative.setMinTimeBetweenUserActivityPokes(intervalMillis);
         }
     }
+
+    private void updateStylusPointerIconEnabled() {
+        mNative.setStylusPointerIconEnabled(InputSettings.isStylusPointerIconEnabled(mContext));
+    }
 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 8aa038f..3bd1e1a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2515,6 +2515,7 @@
             if (DEBUG) {
                 Slog.v(TAG, "Restoring default device input method: " + defaultDeviceMethodId);
             }
+            mSettings.putSelectedDefaultDeviceInputMethod(null);
             return defaultDeviceMethodId;
         }
 
@@ -3179,6 +3180,26 @@
                 }
             }
         }
+
+        if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+            String ime = SecureSettingsWrapper.getString(
+                    Settings.Secure.DEFAULT_INPUT_METHOD, null, mSettings.getUserId());
+            String defaultDeviceIme = SecureSettingsWrapper.getString(
+                    Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+            if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
+                if (DEBUG) {
+                    Slog.v(TAG, "Current input method " + ime + " differs from the stored default"
+                            + " device input method for user " + mSettings.getUserId()
+                            + " - restoring " + defaultDeviceIme);
+                }
+                SecureSettingsWrapper.putString(
+                        Settings.Secure.DEFAULT_INPUT_METHOD, defaultDeviceIme,
+                        mSettings.getUserId());
+                SecureSettingsWrapper.putString(
+                        Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+            }
+        }
+
         // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
         // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
         // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
@@ -5339,7 +5360,7 @@
                             InputMethodInfoUtils.getMostApplicableDefaultIME(
                                         mSettings.getEnabledInputMethodList());
                     mSettings.putSelectedDefaultDeviceInputMethod(
-                            newDefaultIme == null ? "" : newDefaultIme.getId());
+                            newDefaultIme == null ? null : newDefaultIme.getId());
                 }
                 // Previous state was enabled.
                 return true;
@@ -5382,6 +5403,10 @@
 
     @GuardedBy("ImfLock.class")
     private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
+        mDeviceIdToShowIme = DEVICE_ID_DEFAULT;
+        mDisplayIdToShowIme = INVALID_DISPLAY;
+        mSettings.putSelectedDefaultDeviceInputMethod(null);
+
         InputMethodInfo imi = mSettings.getMethodMap().get(newDefaultIme);
         int lastSubtypeId = NOT_A_SUBTYPE_ID;
         // newDefaultIme is empty when there is no candidate for the selected IME.
@@ -6565,6 +6590,7 @@
 
                         // Reset selected IME.
                         settings.putSelectedInputMethod(nextIme);
+                        settings.putSelectedDefaultDeviceInputMethod(null);
                         settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
                     }
                     out.println("Reset current and enabled IMEs for user #" + userId);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
index a51002b..e444db1 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -36,7 +36,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Objects;
 import java.util.function.Predicate;
 
 /**
@@ -88,12 +87,6 @@
         mMethodMap = methodMap;
         mMethodList = methodMap.values();
         mUserId = userId;
-        String ime = getSelectedInputMethod();
-        String defaultDeviceIme = getSelectedDefaultDeviceInputMethod();
-        if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
-            putSelectedInputMethod(defaultDeviceIme);
-            putSelectedDefaultDeviceInputMethod(null);
-        }
     }
 
     @AnyThread
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index dff02bf..e349fa3 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.notification;
 
+import static android.app.Notification.COLOR_DEFAULT;
 import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
 import static android.app.Notification.FLAG_AUTO_CANCEL;
 import static android.app.Notification.FLAG_GROUP_SUMMARY;
@@ -23,15 +24,24 @@
 import static android.app.Notification.FLAG_ONGOING_EVENT;
 
 import android.annotation.NonNull;
+import android.app.Notification;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
 import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * NotificationManagerService helper for auto-grouping notifications.
@@ -41,6 +51,8 @@
 
     protected static final String AUTOGROUP_KEY = "ranker_group";
 
+    protected static final int FLAG_INVALID = -1;
+
     // Flags that all autogroup summaries have
     protected static final int BASE_FLAGS =
             FLAG_AUTOGROUP_SUMMARY | FLAG_GROUP_SUMMARY | FLAG_LOCAL_ONLY;
@@ -51,17 +63,22 @@
 
     private final Callback mCallback;
     private final int mAutoGroupAtCount;
+    private final Context mContext;
+    private final PackageManager mPackageManager;
 
     // Only contains notifications that are not explicitly grouped by the app (aka no group or
     // sort key).
     // userId|packageName -> (keys of notifications that aren't in an explicit app group -> flags)
     @GuardedBy("mUngroupedNotifications")
-    private final ArrayMap<String, ArrayMap<String, Integer>> mUngroupedNotifications
+    private final ArrayMap<String, ArrayMap<String, NotificationAttributes>> mUngroupedNotifications
             = new ArrayMap<>();
 
-    public GroupHelper(int autoGroupAtCount, Callback callback) {
+    public GroupHelper(Context context, PackageManager packageManager, int autoGroupAtCount,
+            Callback callback) {
         mAutoGroupAtCount = autoGroupAtCount;
         mCallback =  callback;
+        mContext = context;
+        mPackageManager = packageManager;
     }
 
     private String generatePackageKey(int userId, String pkg) {
@@ -70,15 +87,16 @@
 
     @VisibleForTesting
     @GuardedBy("mUngroupedNotifications")
-    protected int getAutogroupSummaryFlags(@NonNull final ArrayMap<String, Integer> children) {
+    protected int getAutogroupSummaryFlags(
+            @NonNull final ArrayMap<String, NotificationAttributes> children) {
         boolean allChildrenHasFlag = children.size() > 0;
         int anyChildFlagSet = 0;
         for (int i = 0; i < children.size(); i++) {
-            if (!hasAnyFlag(children.valueAt(i), ALL_CHILDREN_FLAG)) {
+            if (!hasAnyFlag(children.valueAt(i).flags, ALL_CHILDREN_FLAG)) {
                 allChildrenHasFlag = false;
             }
-            if (hasAnyFlag(children.valueAt(i), ANY_CHILDREN_FLAGS)) {
-                anyChildFlagSet |= (children.valueAt(i) & ANY_CHILDREN_FLAGS);
+            if (hasAnyFlag(children.valueAt(i).flags, ANY_CHILDREN_FLAGS)) {
+                anyChildFlagSet |= (children.valueAt(i).flags & ANY_CHILDREN_FLAGS);
             }
         }
         return BASE_FLAGS | (allChildrenHasFlag ? ALL_CHILDREN_FLAG : 0) | anyChildFlagSet;
@@ -95,7 +113,6 @@
             } else {
                 maybeUngroup(sbn, false, sbn.getUserId());
             }
-
         } catch (Exception e) {
             Slog.e(TAG, "Failure processing new notification", e);
         }
@@ -121,25 +138,47 @@
     private void maybeGroup(StatusBarNotification sbn, boolean autogroupSummaryExists) {
         int flags = 0;
         List<String> notificationsToGroup = new ArrayList<>();
+        List<NotificationAttributes> childrenAttr = new ArrayList<>();
         synchronized (mUngroupedNotifications) {
             String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
-            final ArrayMap<String, Integer> children =
+            final ArrayMap<String, NotificationAttributes> children =
                     mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
 
-            children.put(sbn.getKey(), sbn.getNotification().flags);
+            NotificationAttributes attr = new NotificationAttributes(sbn.getNotification().flags,
+                    sbn.getNotification().getSmallIcon(), sbn.getNotification().color);
+            children.put(sbn.getKey(), attr);
             mUngroupedNotifications.put(key, children);
 
             if (children.size() >= mAutoGroupAtCount || autogroupSummaryExists) {
                 flags = getAutogroupSummaryFlags(children);
                 notificationsToGroup.addAll(children.keySet());
+                childrenAttr.addAll(children.values());
             }
         }
         if (notificationsToGroup.size() > 0) {
             if (autogroupSummaryExists) {
-                mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), flags);
+                NotificationAttributes attr = new NotificationAttributes(flags,
+                        sbn.getNotification().getSmallIcon(), sbn.getNotification().color);
+                if (Flags.autogroupSummaryIconUpdate()) {
+                    attr = updateAutobundledSummaryIcon(sbn.getPackageName(), childrenAttr, attr);
+                }
+
+                mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), attr);
             } else {
-                mCallback.addAutoGroupSummary(
-                        sbn.getUserId(), sbn.getPackageName(), sbn.getKey(), flags);
+                Icon summaryIcon = sbn.getNotification().getSmallIcon();
+                int summaryIconColor = sbn.getNotification().color;
+                if (Flags.autogroupSummaryIconUpdate()) {
+                    // Calculate the initial summary icon and icon color
+                    NotificationAttributes iconAttr = getAutobundledSummaryIconAndColor(
+                            sbn.getPackageName(), childrenAttr);
+                    summaryIcon = iconAttr.icon;
+                    summaryIconColor = iconAttr.iconColor;
+                }
+
+                NotificationAttributes attr = new NotificationAttributes(flags, summaryIcon,
+                        summaryIconColor);
+                mCallback.addAutoGroupSummary(sbn.getUserId(), sbn.getPackageName(), sbn.getKey(),
+                        attr);
             }
             for (String key : notificationsToGroup) {
                 mCallback.addAutoGroup(key);
@@ -154,16 +193,17 @@
      * (b) if we need to remove our autogroup overlay for this notification
      * (c) we need to remove the autogroup summary
      *
-     * And updates the internal state of un-app-grouped notifications and their flags
+     * And updates the internal state of un-app-grouped notifications and their flags.
      */
     private void maybeUngroup(StatusBarNotification sbn, boolean notificationGone, int userId) {
         boolean removeSummary = false;
-        int summaryFlags = 0;
+        int summaryFlags = FLAG_INVALID;
         boolean updateSummaryFlags = false;
         boolean removeAutogroupOverlay = false;
+        List<NotificationAttributes> childrenAttrs = new ArrayList<>();
         synchronized (mUngroupedNotifications) {
             String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
-            final ArrayMap<String, Integer> children =
+            final ArrayMap<String, NotificationAttributes> children =
                     mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
             if (children.size() == 0) {
                 return;
@@ -173,7 +213,7 @@
             if (children.containsKey(sbn.getKey())) {
                 // if this notification was contributing flags that aren't covered by other
                 // children to the summary, reevaluate flags for the summary
-                int flags = children.remove(sbn.getKey());
+                int flags = children.remove(sbn.getKey()).flags;
                 // this
                 if (hasAnyFlag(flags, ANY_CHILDREN_FLAGS)) {
                     updateSummaryFlags = true;
@@ -188,14 +228,29 @@
                 // If there are no more children left to autogroup, remove the summary
                 if (children.size() == 0) {
                     removeSummary = true;
+                } else {
+                    childrenAttrs.addAll(children.values());
                 }
             }
         }
+
         if (removeSummary) {
             mCallback.removeAutoGroupSummary(userId, sbn.getPackageName());
         } else {
-            if (updateSummaryFlags) {
-                mCallback.updateAutogroupSummary(userId, sbn.getPackageName(), summaryFlags);
+            NotificationAttributes attr = new NotificationAttributes(summaryFlags,
+                    sbn.getNotification().getSmallIcon(), sbn.getNotification().color);
+            boolean iconUpdated = false;
+            if (Flags.autogroupSummaryIconUpdate()) {
+                NotificationAttributes newAttr = updateAutobundledSummaryIcon(sbn.getPackageName(),
+                        childrenAttrs, attr);
+                if (!newAttr.equals(attr)) {
+                    iconUpdated = true;
+                    attr = newAttr;
+                }
+            }
+
+            if (updateSummaryFlags || iconUpdated) {
+                mCallback.updateAutogroupSummary(userId, sbn.getPackageName(), attr);
             }
         }
         if (removeAutogroupOverlay) {
@@ -207,17 +262,139 @@
     int getNotGroupedByAppCount(int userId, String pkg) {
         synchronized (mUngroupedNotifications) {
             String key = generatePackageKey(userId, pkg);
-            final ArrayMap<String, Integer> children =
+            final ArrayMap<String, NotificationAttributes> children =
                     mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
             return children.size();
         }
     }
 
+    NotificationAttributes getAutobundledSummaryIconAndColor(@NonNull String packageName,
+            @NonNull List<NotificationAttributes> childrenAttr) {
+        Icon newIcon = null;
+        boolean childrenHaveSameIcon = true;
+        int newColor = Notification.COLOR_INVALID;
+        boolean childrenHaveSameColor = true;
+
+        // Both the icon drawable and the icon background color are updated according to this rule:
+        // - if all child icons are identical => use the common icon
+        // - if child icons are different: use the monochromatic app icon, if exists.
+        // Otherwise fall back to a generic icon representing a stack.
+        for (NotificationAttributes state: childrenAttr) {
+            // Check for icon
+            if (newIcon == null) {
+                newIcon = state.icon;
+            } else {
+                if (!newIcon.sameAs(state.icon)) {
+                    childrenHaveSameIcon = false;
+                }
+            }
+            // Check for color
+            if (newColor == Notification.COLOR_INVALID) {
+                newColor = state.iconColor;
+            } else {
+                if (newColor != state.iconColor) {
+                    childrenHaveSameColor = false;
+                }
+            }
+        }
+        if (!childrenHaveSameIcon) {
+            newIcon = getMonochromeAppIcon(packageName);
+        }
+        if (!childrenHaveSameColor) {
+            newColor = COLOR_DEFAULT;
+        }
+
+        return new NotificationAttributes(0, newIcon, newColor);
+    }
+
+    NotificationAttributes updateAutobundledSummaryIcon(@NonNull String packageName,
+            @NonNull List<NotificationAttributes> childrenAttr,
+            @NonNull NotificationAttributes oldAttr) {
+        NotificationAttributes newAttr = getAutobundledSummaryIconAndColor(packageName,
+                childrenAttr);
+        Icon newIcon = newAttr.icon;
+        int newColor = newAttr.iconColor;
+        if (newAttr.icon == null) {
+            newIcon = oldAttr.icon;
+        }
+        if (newAttr.iconColor == Notification.COLOR_INVALID) {
+            newColor = oldAttr.iconColor;
+        }
+
+        return new NotificationAttributes(oldAttr.flags, newIcon, newColor);
+    }
+
+    /**
+     * Get the monochrome app icon for an app from the adaptive launcher icon
+     *  or a fallback generic icon for autogroup summaries.
+     *
+     * @param pkg packageName of the app
+     * @return a monochrome app icon or a fallback generic icon
+     */
+    @NonNull
+    Icon getMonochromeAppIcon(@NonNull final String pkg) {
+        Icon monochromeIcon = null;
+        final int fallbackIconResId = R.drawable.ic_notification_summary_auto;
+        try {
+            final Drawable appIcon = mPackageManager.getApplicationIcon(pkg);
+            if (appIcon instanceof AdaptiveIconDrawable) {
+                if (((AdaptiveIconDrawable) appIcon).getMonochrome() != null) {
+                    monochromeIcon = Icon.createWithResourceAdaptiveDrawable(pkg,
+                            ((AdaptiveIconDrawable) appIcon).getSourceDrawableResId(), true,
+                            -2.0f * AdaptiveIconDrawable.getExtraInsetFraction());
+                }
+            }
+        } catch (NameNotFoundException e) {
+            Slog.e(TAG, "Failed to getApplicationIcon() in getMonochromeAppIcon()", e);
+        }
+        if (monochromeIcon != null) {
+            return monochromeIcon;
+        } else {
+            return Icon.createWithResource(mContext, fallbackIconResId);
+        }
+    }
+
+    protected static class NotificationAttributes {
+        public final int flags;
+        public final int iconColor;
+        public final Icon icon;
+
+        public NotificationAttributes(int flags, Icon icon, int iconColor) {
+            this.flags = flags;
+            this.icon = icon;
+            this.iconColor = iconColor;
+        }
+
+        public NotificationAttributes(@NonNull NotificationAttributes attr) {
+            this.flags = attr.flags;
+            this.icon = attr.icon;
+            this.iconColor = attr.iconColor;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof NotificationAttributes that)) {
+                return false;
+            }
+            return flags == that.flags && iconColor == that.iconColor && icon.sameAs(that.icon);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(flags, iconColor, icon);
+        }
+    }
+
     protected interface Callback {
         void addAutoGroup(String key);
         void removeAutoGroup(String key);
-        void addAutoGroupSummary(int userId, String pkg, String triggeringKey, int flags);
+
+        void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
+                NotificationAttributes summaryAttr);
         void removeAutoGroupSummary(int user, String pkg);
-        void updateAutogroupSummary(int userId, String pkg, int flags);
+        void updateAutogroupSummary(int userId, String pkg, NotificationAttributes summaryAttr);
     }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 638382e..7dbe880 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -227,6 +227,7 @@
 import android.content.pm.VersionedPackage;
 import android.content.res.Resources;
 import android.database.ContentObserver;
+import android.graphics.drawable.Icon;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
@@ -341,6 +342,7 @@
 import com.android.server.job.JobSchedulerInternal;
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
+import com.android.server.notification.GroupHelper.NotificationAttributes;
 import com.android.server.notification.ManagedServices.ManagedServiceInfo;
 import com.android.server.notification.ManagedServices.UserProfiles;
 import com.android.server.notification.toast.CustomToastRecord;
@@ -725,7 +727,7 @@
 
     private static final int MY_UID = Process.myUid();
     private static final int MY_PID = Process.myPid();
-    private static final IBinder ALLOWLIST_TOKEN = new Binder();
+    static final IBinder ALLOWLIST_TOKEN = new Binder();
     protected RankingHandler mRankingHandler;
     private long mLastOverRateLogTime;
     private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
@@ -996,17 +998,19 @@
     }
 
     /**
-     * This method will update the flags of the summary.
+     * This method will update the flags and/or the icon of the summary.
      * It will set it to FLAG_ONGOING_EVENT if any of its group members
-     * has the same flag. It will delete the flag otherwise
+     * has the same flag. It will delete the flag otherwise.
+     * It will update the summary notification icon if the group children's
+     * icons are different.
      * @param userId user id of the autogroup summary
      * @param pkg package of the autogroup summary
-     * @param flags the new flags for this summary
+     * @param summaryAttr the new flags and/or icon & color for this summary
      * @param isAppForeground true if the app is currently in the foreground.
      */
     @GuardedBy("mNotificationLock")
-    protected void updateAutobundledSummaryFlags(int userId, String pkg, int flags,
-            boolean isAppForeground) {
+    protected void updateAutobundledSummaryLocked(int userId, String pkg,
+            NotificationAttributes summaryAttr, boolean isAppForeground) {
         ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId);
         if (summaries == null) {
             return;
@@ -1020,8 +1024,16 @@
             return;
         }
         int oldFlags = summary.getSbn().getNotification().flags;
-        if (oldFlags != flags) {
-            summary.getSbn().getNotification().flags = flags;
+
+        boolean iconUpdated =
+                !summaryAttr.icon.sameAs(summary.getSbn().getNotification().getSmallIcon())
+                || summaryAttr.iconColor != summary.getSbn().getNotification().color;
+
+        if (oldFlags != summaryAttr.flags || iconUpdated) {
+            summary.getSbn().getNotification().flags =
+                    summaryAttr.flags != GroupHelper.FLAG_INVALID ? summaryAttr.flags : oldFlags;
+            summary.getSbn().getNotification().setSmallIcon(summaryAttr.icon);
+            summary.getSbn().getNotification().color = summaryAttr.iconColor;
             mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground,
                     mPostNotificationTrackerFactory.newTracker(null)));
         }
@@ -2873,7 +2885,8 @@
     private GroupHelper getGroupHelper() {
         mAutoGroupAtCount =
                 getContext().getResources().getInteger(R.integer.config_autoGroupAtCount);
-        return new GroupHelper(mAutoGroupAtCount, new GroupHelper.Callback() {
+        return new GroupHelper(getContext(), getContext().getPackageManager(),
+                mAutoGroupAtCount, new GroupHelper.Callback() {
             @Override
             public void addAutoGroup(String key) {
                 synchronized (mNotificationLock) {
@@ -2890,8 +2903,9 @@
 
             @Override
             public void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
-                    int flags) {
-                NotificationRecord r = createAutoGroupSummary(userId, pkg, triggeringKey, flags);
+                    NotificationAttributes summaryAttr) {
+                NotificationRecord r = createAutoGroupSummary(userId, pkg, triggeringKey,
+                        summaryAttr.flags, summaryAttr.icon, summaryAttr.iconColor);
                 if (r != null) {
                     final boolean isAppForeground =
                             mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
@@ -2908,11 +2922,12 @@
             }
 
             @Override
-            public void updateAutogroupSummary(int userId, String pkg, int flags) {
+            public void updateAutogroupSummary(int userId, String pkg,
+                    NotificationAttributes summaryAttr) {
                 boolean isAppForeground = pkg != null
                         && mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
                 synchronized (mNotificationLock) {
-                    updateAutobundledSummaryFlags(userId, pkg, flags, isAppForeground);
+                    updateAutobundledSummaryLocked(userId, pkg, summaryAttr, isAppForeground);
                 }
             }
         });
@@ -4744,7 +4759,7 @@
                     // Remove background token before returning notification to untrusted app, this
                     // ensures the app isn't able to perform background operations that are
                     // associated with notification interactions.
-                    notification.clearAllowlistToken();
+                    notification.overrideAllowlistToken(null);
                     return new StatusBarNotification(
                             sbn.getPackageName(),
                             sbn.getOpPkg(),
@@ -4880,14 +4895,14 @@
                                 continue;
                             }
                             notificationsRapidlyCleared = notificationsRapidlyCleared
-                                    || isNotificationRecent(r);
+                                    || isNotificationRecent(r.getUpdateTimeMs());
                             cancelNotificationFromListenerLocked(info, callingUid, callingPid,
                                     r.getSbn().getPackageName(), r.getSbn().getTag(),
                                     r.getSbn().getId(), userId, reason);
                         }
                     } else {
                         for (NotificationRecord notificationRecord : mNotificationList) {
-                            if (isNotificationRecent(notificationRecord)) {
+                            if (isNotificationRecent(notificationRecord.getUpdateTimeMs())) {
                                 notificationsRapidlyCleared = true;
                                 break;
                             }
@@ -4913,14 +4928,6 @@
             }
         }
 
-        private boolean isNotificationRecent(@NonNull NotificationRecord notificationRecord) {
-            if (!rapidClearNotificationsByListenerAppOpEnabled()) {
-                return false;
-            }
-            return notificationRecord.getFreshnessMs(System.currentTimeMillis())
-                    < NOTIFICATION_RAPID_CLEAR_THRESHOLD_MS;
-        }
-
         /**
          * Handle request from an approved listener to re-enable itself.
          *
@@ -5044,12 +5051,11 @@
         @Override
         public void snoozeNotificationUntilContextFromListener(INotificationListener token,
                 String key, String snoozeCriterionId) {
+            final int callingUid = Binder.getCallingUid();
             final long identity = Binder.clearCallingIdentity();
             try {
-                synchronized (mNotificationLock) {
-                    final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
-                    snoozeNotificationInt(key, SNOOZE_UNTIL_UNSPECIFIED, snoozeCriterionId, info);
-                }
+                snoozeNotificationInt(callingUid, token, key, SNOOZE_UNTIL_UNSPECIFIED,
+                        snoozeCriterionId);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -5063,12 +5069,10 @@
         @Override
         public void snoozeNotificationUntilFromListener(INotificationListener token, String key,
                 long duration) {
+            final int callingUid = Binder.getCallingUid();
             final long identity = Binder.clearCallingIdentity();
             try {
-                synchronized (mNotificationLock) {
-                    final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
-                    snoozeNotificationInt(key, duration, null, info);
-                }
+                snoozeNotificationInt(callingUid, token, key, duration, null);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -6529,7 +6533,7 @@
 
     // Creates a 'fake' summary for a package that has exceeded the solo-notification limit.
     NotificationRecord createAutoGroupSummary(int userId, String pkg, String triggeringKey,
-            int flagsToSet) {
+            int flagsToSet, Icon summaryIcon, int summaryIconColor) {
         NotificationRecord summaryRecord = null;
         boolean isPermissionFixed = mPermissionHelper.isPermissionFixed(pkg, userId);
         synchronized (mNotificationLock) {
@@ -6555,14 +6559,15 @@
                 final Bundle extras = new Bundle();
                 extras.putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, appInfo);
                 final String channelId = notificationRecord.getChannel().getId();
+
                 final Notification summaryNotification =
-                        new Notification.Builder(getContext(), channelId)
-                                .setSmallIcon(adjustedSbn.getNotification().getSmallIcon())
+                                new Notification.Builder(getContext(), channelId)
+                                .setSmallIcon(summaryIcon)
                                 .setGroupSummary(true)
                                 .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
                                 .setGroup(GroupHelper.AUTOGROUP_KEY)
                                 .setFlag(flagsToSet, true)
-                                .setColor(adjustedSbn.getNotification().color)
+                                .setColor(summaryIconColor)
                                 .build();
                 summaryNotification.extras.putAll(extras);
                 Intent appIntent = getContext().getPackageManager().getLaunchIntentForPackage(pkg);
@@ -7618,6 +7623,8 @@
             }
         }
 
+        notification.overrideAllowlistToken(ALLOWLIST_TOKEN);
+
         // Remote views? Are they too big?
         checkRemoteViews(pkg, tag, id, notification);
     }
@@ -10326,16 +10333,22 @@
         }
     }
 
-    void snoozeNotificationInt(String key, long duration, String snoozeCriterionId,
-            ManagedServiceInfo listener) {
-        if (listener == null) {
-            return;
-        }
-        String listenerName = listener.component.toShortString();
-        if ((duration <= 0 && snoozeCriterionId == null) || key == null) {
-            return;
-        }
+    void snoozeNotificationInt(int callingUid, INotificationListener token, String key,
+            long duration, String snoozeCriterionId) {
+        final String packageName;
+        final long notificationUpdateTimeMs;
+
         synchronized (mNotificationLock) {
+            final ManagedServiceInfo listener = mListeners.checkServiceTokenLocked(token);
+            if (listener == null) {
+                return;
+            }
+            packageName = listener.component.getPackageName();
+            String listenerName = listener.component.toShortString();
+            if ((duration <= 0 && snoozeCriterionId == null) || key == null) {
+                return;
+            }
+
             final NotificationRecord r = findInCurrentAndSnoozedNotificationByKeyLocked(key);
             if (r == null) {
                 return;
@@ -10343,14 +10356,20 @@
             if (!listener.enabledAndUserMatches(r.getSbn().getNormalizedUserId())){
                 return;
             }
+            notificationUpdateTimeMs = r.getUpdateTimeMs();
+
+            if (DBG) {
+                Slog.d(TAG, String.format("snooze event(%s, %d, %s, %s)", key, duration,
+                        snoozeCriterionId, listenerName));
+            }
+            // Needs to post so that it can cancel notifications not yet enqueued.
+            mHandler.post(new SnoozeNotificationRunnable(key, duration, snoozeCriterionId));
         }
 
-        if (DBG) {
-            Slog.d(TAG, String.format("snooze event(%s, %d, %s, %s)", key, duration,
-                    snoozeCriterionId, listenerName));
+        if (isNotificationRecent(notificationUpdateTimeMs)) {
+            mAppOps.noteOpNoThrow(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER,
+                    callingUid, packageName, /* attributionTag= */ null, /* message= */ null);
         }
-        // Needs to post so that it can cancel notifications not yet enqueued.
-        mHandler.post(new SnoozeNotificationRunnable(key, duration, snoozeCriterionId));
     }
 
     void unsnoozeNotificationInt(String key, ManagedServiceInfo listener, boolean muteOnReturn) {
@@ -10362,6 +10381,14 @@
         handleSavePolicyFile();
     }
 
+    private boolean isNotificationRecent(long notificationUpdateTimeMs) {
+        if (!rapidClearNotificationsByListenerAppOpEnabled()) {
+            return false;
+        }
+        return System.currentTimeMillis() - notificationUpdateTimeMs
+                < NOTIFICATION_RAPID_CLEAR_THRESHOLD_MS;
+    }
+
     @GuardedBy("mNotificationLock")
     void cancelAllLocked(int callingUid, int callingPid, int userId, int reason,
             ManagedServiceInfo listener, boolean includeCurrentProfiles, int mustNotHaveFlags) {
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java b/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java
new file mode 100644
index 0000000..1553618
--- /dev/null
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java
@@ -0,0 +1,97 @@
+/*
+ * 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.pm;
+
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+
+import android.annotation.NonNull;
+import android.app.BackgroundInstallControlManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IRemoteCallback;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.ServiceThread;
+
+public class BackgroundInstallControlCallbackHelper {
+
+    @VisibleForTesting static final String FLAGGED_PACKAGE_NAME_KEY = "packageName";
+    @VisibleForTesting static final String FLAGGED_USER_ID_KEY = "userId";
+    private static final String TAG = "BackgroundInstallControlCallbackHelper";
+
+    private final Handler mHandler;
+
+    BackgroundInstallControlCallbackHelper() {
+        HandlerThread backgroundThread =
+                new ServiceThread(
+                        "BackgroundInstallControlCallbackHelperBg",
+                        THREAD_PRIORITY_BACKGROUND,
+                        true);
+        backgroundThread.start();
+        mHandler = new Handler(backgroundThread.getLooper());
+    }
+
+    @NonNull @VisibleForTesting
+    final RemoteCallbackList<IRemoteCallback> mCallbacks = new RemoteCallbackList<>();
+
+    /** Registers callback that gets invoked upon detection of an MBA
+     *
+     * NOTE: The callback is user context agnostic and currently broadcasts to all users of other
+     * users app installs. This is fine because the API is for SystemServer use only.
+     */
+    public void registerBackgroundInstallCallback(IRemoteCallback callback) {
+        synchronized (mCallbacks) {
+            mCallbacks.register(callback, null);
+        }
+    }
+
+    /** Unregisters callback */
+    public void unregisterBackgroundInstallCallback(IRemoteCallback callback) {
+        synchronized (mCallbacks) {
+            mCallbacks.unregister(callback);
+        }
+    }
+
+    /**
+     * Invokes all registered callbacks Callbacks are processed through user provided-threads and
+     * parameters are passed in via {@link BackgroundInstallControlManager} InstallEvent
+     */
+    public void notifyAllCallbacks(int userId, String packageName) {
+        Bundle extras = new Bundle();
+        extras.putCharSequence(FLAGGED_PACKAGE_NAME_KEY, packageName);
+        extras.putInt(FLAGGED_USER_ID_KEY, userId);
+        synchronized (mCallbacks) {
+            mHandler.post(
+                    () ->
+                            mCallbacks.broadcast(
+                                    callback -> {
+                                        try {
+                                            callback.sendResult(extras);
+                                        } catch (RemoteException e) {
+                                            Slog.e(
+                                                    TAG,
+                                                    "error detected: " + e.getLocalizedMessage(),
+                                                    e);
+                                        }
+                                    }));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 200b17b..3468081 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -36,6 +36,7 @@
 import android.os.Build;
 import android.os.Environment;
 import android.os.Handler;
+import android.os.IRemoteCallback;
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemClock;
@@ -93,6 +94,8 @@
     private final File mDiskFile;
     private final Context mContext;
 
+    private final BackgroundInstallControlCallbackHelper mCallbackHelper;
+
     private SparseSetArray<String> mBackgroundInstalledPackages = null;
 
     // User ID -> package name -> set of foreground time frame
@@ -112,6 +115,7 @@
         mHandler = new EventHandler(injector.getLooper(), this);
         mDiskFile = injector.getDiskFile();
         mContext = injector.getContext();
+        mCallbackHelper = injector.getBackgroundInstallControlCallbackHelper();
         UsageStatsManagerInternal usageStatsManagerInternal =
                 injector.getUsageStatsManagerInternal();
         usageStatsManagerInternal.registerListener(
@@ -150,6 +154,16 @@
             }
         }
 
+        @Override
+        public void registerBackgroundInstallCallback(IRemoteCallback callback) {
+            mService.mCallbackHelper.registerBackgroundInstallCallback(callback);
+        }
+
+        @Override
+        public void unregisterBackgroundInstallCallback(IRemoteCallback callback) {
+            mService.mCallbackHelper.unregisterBackgroundInstallCallback(callback);
+        }
+
     }
 
     @RequiresPermission(GET_BACKGROUND_INSTALLED_PACKAGES)
@@ -274,6 +288,7 @@
 
         initBackgroundInstalledPackages();
         mBackgroundInstalledPackages.add(userId, packageName);
+        mCallbackHelper.notifyAllCallbacks(userId, packageName);
         writeBackgroundInstalledPackagesToDisk();
     }
 
@@ -568,6 +583,8 @@
 
         File getDiskFile();
 
+        BackgroundInstallControlCallbackHelper getBackgroundInstallControlCallbackHelper();
+
     }
 
     private static final class InjectorImpl implements Injector {
@@ -617,5 +634,10 @@
             File file = new File(dir, DISK_FILE_NAME);
             return file;
         }
+
+        @Override
+        public BackgroundInstallControlCallbackHelper getBackgroundInstallControlCallbackHelper() {
+            return new BackgroundInstallControlCallbackHelper();
+        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 33f481c..db5acc2 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -592,9 +592,11 @@
             mPm.addAllPackageProperties(pkg);
 
             if (oldPkgSetting == null || oldPkgSetting.getPkg() == null) {
-                mPm.mDomainVerificationManager.addPackage(pkgSetting);
+                mPm.mDomainVerificationManager.addPackage(pkgSetting,
+                        request.getPreVerifiedDomains());
             } else {
-                mPm.mDomainVerificationManager.migrateState(oldPkgSetting, pkgSetting);
+                mPm.mDomainVerificationManager.migrateState(oldPkgSetting, pkgSetting,
+                        request.getPreVerifiedDomains());
             }
 
             int collectionSize = ArrayUtils.size(pkg.getInstrumentations());
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index ebaf516..4fb0c22 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -42,6 +42,7 @@
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.SigningDetails;
 import android.content.pm.parsing.PackageLite;
+import android.content.pm.verify.domain.DomainSet;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Process;
@@ -155,6 +156,9 @@
     @NonNull
     private final ArrayList<String> mWarnings = new ArrayList<>();
 
+    @Nullable
+    private DomainSet mPreVerifiedDomains;
+
     // New install
     InstallRequest(InstallingSession params) {
         mUserId = params.getUser().getIdentifier();
@@ -172,6 +176,7 @@
         mIsInstallInherit = params.mIsInherit;
         mSessionId = params.mSessionId;
         mRequireUserAction = params.mRequireUserAction;
+        mPreVerifiedDomains = params.mPreVerifiedDomains;
     }
 
     // Install existing package as user
@@ -875,6 +880,11 @@
         }
     }
 
+    @Nullable
+    public DomainSet getPreVerifiedDomains() {
+        return mPreVerifiedDomains;
+    }
+
     public void addWarning(@NonNull String warning) {
         mWarnings.add(warning);
     }
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index e970d2c..4cbd3ad 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -40,6 +40,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.SigningDetails;
 import android.content.pm.parsing.PackageLite;
+import android.content.pm.verify.domain.DomainSet;
 import android.os.Environment;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -98,6 +99,8 @@
     final int mSessionId;
     final int mRequireUserAction;
     final boolean mApplicationEnabledSettingPersistent;
+    @Nullable
+    final DomainSet mPreVerifiedDomains;
 
     // For move install
     InstallingSession(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
@@ -130,12 +133,13 @@
         mSessionId = -1;
         mRequireUserAction = USER_ACTION_UNSPECIFIED;
         mApplicationEnabledSettingPersistent = false;
+        mPreVerifiedDomains = null;
     }
 
     InstallingSession(int sessionId, File stagedDir, IPackageInstallObserver2 observer,
             PackageInstaller.SessionParams sessionParams, InstallSource installSource,
             UserHandle user, SigningDetails signingDetails, int installerUid,
-            PackageLite packageLite, PackageManagerService pm) {
+            PackageLite packageLite, DomainSet preVerifiedDomains, PackageManagerService pm) {
         mPm = pm;
         mUser = user;
         mOriginInfo = OriginInfo.fromStagedFile(stagedDir);
@@ -163,6 +167,7 @@
         mSessionId = sessionId;
         mRequireUserAction = sessionParams.requireUserAction;
         mApplicationEnabledSettingPersistent = sessionParams.applicationEnabledSettingPersistent;
+        mPreVerifiedDomains = preVerifiedDomains;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 43328fc..984a629 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -1676,6 +1676,8 @@
         private IntentSender buildAppMarketIntentSenderForUser(@NonNull UserHandle user) {
             Intent appMarketIntent = new Intent(Intent.ACTION_MAIN);
             appMarketIntent.addCategory(Intent.CATEGORY_APP_MARKET);
+            appMarketIntent.setFlags(
+                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
             return buildIntentSenderForUser(appMarketIntent, user);
         }
 
diff --git a/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java b/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java
index d40a715..4b98e34 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java
@@ -19,6 +19,7 @@
 import android.content.pm.PackageInstaller.PreapprovalDetails;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.verify.domain.DomainSet;
 
 import com.android.internal.util.IndentingPrintWriter;
 
@@ -76,6 +77,7 @@
     private final boolean mSessionFailed;
     private final int mSessionErrorCode;
     private final String mSessionErrorMessage;
+    private final String mPreVerifiedDomains;
 
     PackageInstallerHistoricalSession(int sessionId, int userId, int originalInstallerUid,
             String originalInstallerPackageName, InstallSource installSource, int installerUid,
@@ -86,7 +88,7 @@
             String finalMessage, SessionParams params, int parentSessionId,
             int[] childSessionIds, boolean sessionApplied, boolean sessionFailed,
             boolean sessionReady, int sessionErrorCode, String sessionErrorMessage,
-            PreapprovalDetails preapprovalDetails) {
+            PreapprovalDetails preapprovalDetails, DomainSet preVerifiedDomains) {
         this.sessionId = sessionId;
         this.userId = userId;
         this.mOriginalInstallerUid = originalInstallerUid;
@@ -128,6 +130,11 @@
         } else {
             this.mPreapprovalDetails = null;
         }
+        if (preVerifiedDomains != null) {
+            this.mPreVerifiedDomains = String.join(",", preVerifiedDomains.getDomains());
+        } else {
+            this.mPreVerifiedDomains = null;
+        }
     }
 
     void dump(IndentingPrintWriter pw) {
@@ -170,6 +177,7 @@
         pw.printPair("mSessionErrorCode", mSessionErrorCode);
         pw.printPair("mSessionErrorMessage", mSessionErrorMessage);
         pw.printPair("mPreapprovalDetails", mPreapprovalDetails);
+        pw.printPair("mPreVerifiedDomains", mPreVerifiedDomains);
         pw.println();
 
         pw.decreaseIndent();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index abea56b..6e4f199 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1026,7 +1026,7 @@
                 mSilentUpdatePolicy, mInstallThread.getLooper(), mStagingManager, sessionId,
                 userId, callingUid, installSource, params, createdMillis, 0L, stageDir, stageCid,
                 null, null, false, false, false, false, null, SessionInfo.INVALID_ID,
-                false, false, false, PackageManager.INSTALL_UNKNOWN, "");
+                false, false, false, PackageManager.INSTALL_UNKNOWN, "", null);
 
         synchronized (mSessions) {
             mSessions.put(sessionId, session);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 27c3dad..c860b5a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -113,6 +113,7 @@
 import android.content.pm.parsing.PackageLite;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
+import android.content.pm.verify.domain.DomainSet;
 import android.content.res.ApkAssets;
 import android.content.res.AssetManager;
 import android.content.res.Configuration;
@@ -241,6 +242,8 @@
             "whitelisted-restricted-permission";
     private static final String TAG_AUTO_REVOKE_PERMISSIONS_MODE =
             "auto-revoke-permissions-mode";
+
+    static final String TAG_PRE_VERIFIED_DOMAINS = "preVerifiedDomains";
     private static final String ATTR_SESSION_ID = "sessionId";
     private static final String ATTR_USER_ID = "userId";
     private static final String ATTR_INSTALLER_PACKAGE_NAME = "installerPackageName";
@@ -298,6 +301,7 @@
     private static final String ATTR_CHECKSUM_VALUE = "checksumValue";
     private static final String ATTR_APPLICATION_ENABLED_SETTING_PERSISTENT =
             "applicationEnabledSettingPersistent";
+    private static final String ATTR_DOMAIN = "domain";
 
     private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
     private static final int[] EMPTY_CHILD_SESSION_ARRAY = EmptyArray.INT;
@@ -365,6 +369,25 @@
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
     private static final long THROW_EXCEPTION_COMMIT_WITH_IMMUTABLE_PENDING_INTENT = 240618202L;
 
+    /**
+     * Configurable maximum number of pre-verified domains allowed to be added to the session.
+     * Flag type: {@code long}
+     * Namespace: NAMESPACE_PACKAGE_MANAGER_SERVICE
+     */
+    private static final String PROPERTY_PRE_VERIFIED_DOMAINS_COUNT_LIMIT =
+            "pre_verified_domains_count_limit";
+    /**
+     * Configurable maximum string length of each pre-verified domain.
+     * Flag type: {@code long}
+     * Namespace: NAMESPACE_PACKAGE_MANAGER_SERVICE
+     */
+    private static final String PROPERTY_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT =
+            "pre_verified_domain_length_limit";
+    /** Default max number of pre-verified domains */
+    private static final long DEFAULT_PRE_VERIFIED_DOMAINS_COUNT_LIMIT = 1000;
+    /** Default max string length of each pre-verified domain */
+    private static final long DEFAULT_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT = 256;
+
     // TODO: enforce INSTALL_ALLOW_TEST
     // TODO: enforce INSTALL_ALLOW_DOWNGRADE
 
@@ -506,6 +529,9 @@
     @GuardedBy("mLock")
     private int mUserActionRequirement;
 
+    @GuardedBy("mLock")
+    private DomainSet mPreVerifiedDomains;
+
     static class FileEntry {
         private final int mIndex;
         private final InstallationFile mFile;
@@ -936,6 +962,21 @@
                 getInstallSource().mInstallerPackageName, mInstallerUid);
     }
 
+    private boolean isEmergencyInstallerEnabled(String packageName, Computer snapshot) {
+        final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName);
+        if (ps == null || ps.getPkg() == null || !ps.isSystem()) {
+            return false;
+        }
+        String emergencyInstaller = ps.getPkg().getEmergencyInstaller();
+        if (emergencyInstaller == null || !ArrayUtils.contains(
+                snapshot.getPackagesForUid(mInstallerUid),
+                emergencyInstaller)) {
+            return false;
+        }
+        return (snapshot.checkUidPermission(Manifest.permission.EMERGENCY_INSTALL_PACKAGES,
+                mInstallerUid) == PackageManager.PERMISSION_GRANTED);
+    }
+
     private static final int USER_ACTION_NOT_NEEDED = 0;
     private static final int USER_ACTION_REQUIRED = 1;
     private static final int USER_ACTION_PENDING_APK_PARSING = 2;
@@ -1020,6 +1061,8 @@
         final boolean isUpdateOwner = TextUtils.equals(existingUpdateOwnerPackageName,
                 getInstallerPackageName());
         final boolean isSelfUpdate = targetPackageUid == mInstallerUid;
+        final boolean isEmergencyInstall =
+                isEmergencyInstallerEnabled(packageName, snapshot);
         final boolean isPermissionGranted = isInstallPermissionGranted
                 || (isUpdatePermissionGranted && isUpdate)
                 || (isSelfUpdatePermissionGranted && isSelfUpdate)
@@ -1036,7 +1079,7 @@
         // Device owners and affiliated profile owners are allowed to silently install packages, so
         // the permission check is waived if the installer is the device owner.
         final boolean noUserActionNecessary = isInstallerRoot || isInstallerSystem
-                || isInstallerDeviceOwnerOrAffiliatedProfileOwner();
+                || isInstallerDeviceOwnerOrAffiliatedProfileOwner() || isEmergencyInstall;
 
         if (noUserActionNecessary) {
             return userActionNotTypicallyNeededResponse;
@@ -1089,7 +1132,7 @@
             boolean prepared, boolean committed, boolean destroyed, boolean sealed,
             @Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
             boolean isFailed, boolean isApplied, int sessionErrorCode,
-            String sessionErrorMessage) {
+            String sessionErrorMessage, DomainSet preVerifiedDomains) {
         mCallback = callback;
         mContext = context;
         mPm = pm;
@@ -1151,6 +1194,7 @@
         mSessionErrorMessage =
                 sessionErrorMessage != null ? sessionErrorMessage : "";
         mStagedSession = params.isStaged ? new StagedSession() : null;
+        mPreVerifiedDomains = preVerifiedDomains;
 
         if (isDataLoaderInstallation()) {
             if (isApexSession()) {
@@ -1198,7 +1242,7 @@
                     mStageDirInUse, mDestroyed, mFds.size(), mBridges.size(), mFinalStatus,
                     mFinalMessage, params, mParentSessionId, getChildSessionIdsLocked(),
                     mSessionApplied, mSessionFailed, mSessionReady, mSessionErrorCode,
-                    mSessionErrorMessage, mPreapprovalDetails);
+                    mSessionErrorMessage, mPreapprovalDetails, mPreVerifiedDomains);
         }
     }
 
@@ -3135,7 +3179,7 @@
 
         synchronized (mLock) {
             return new InstallingSession(sessionId, stageDir, localObserver, params, mInstallSource,
-                    user, mSigningDetails, mInstallerUid, mPackageLite, mPm);
+                    user, mSigningDetails, mInstallerUid, mPackageLite, mPreVerifiedDomains, mPm);
         }
     }
 
@@ -5024,6 +5068,82 @@
         return (params.installFlags & PackageManager.INSTALL_REQUEST_UPDATE_OWNERSHIP) != 0;
     }
 
+    @Override
+    public void setPreVerifiedDomains(@NonNull DomainSet preVerifiedDomains) {
+        // First check permissions
+        final boolean exemptFromPermissionChecks =
+                (mInstallerUid == Process.ROOT_UID) || (mInstallerUid == Process.SHELL_UID);
+        if (!exemptFromPermissionChecks) {
+            final Computer snapshot = mPm.snapshotComputer();
+            if (PackageManager.PERMISSION_GRANTED != snapshot.checkUidPermission(
+                    Manifest.permission.ACCESS_INSTANT_APPS, mInstallerUid)) {
+                throw new SecurityException("You need android.permission.ACCESS_INSTANT_APPS "
+                        + "permission to set pre-verified domains.");
+            }
+            ComponentName instantAppInstallerComponent = snapshot.getInstantAppInstallerComponent();
+            if (instantAppInstallerComponent == null) {
+                // Shouldn't happen
+                throw new IllegalStateException("Instant app installer is not available. "
+                        + "Only the instant app installer can call this API.");
+            }
+            if (!instantAppInstallerComponent.getPackageName().equals(getInstallerPackageName())) {
+                throw new SecurityException("Only the instant app installer can call this API.");
+            }
+        }
+        // Then check size limits
+        final long preVerifiedDomainsCountLimit = getPreVerifiedDomainsCountLimit();
+        if (preVerifiedDomains.getDomains().size() > preVerifiedDomainsCountLimit) {
+            throw new IllegalArgumentException(
+                    "The number of pre-verified domains have exceeded the maximum of "
+                            + preVerifiedDomainsCountLimit);
+        }
+        final long preVerifiedDomainLengthLimit = getPreVerifiedDomainLengthLimit();
+        for (String domain : preVerifiedDomains.getDomains()) {
+            if (domain.length() > preVerifiedDomainLengthLimit) {
+                throw new IllegalArgumentException(
+                        "Pre-verified domain: [" + domain + " ] exceeds maximum length allowed: "
+                                + preVerifiedDomainLengthLimit);
+            }
+        }
+        // Okay to proceed
+        synchronized (mLock) {
+            mPreVerifiedDomains = preVerifiedDomains;
+        }
+    }
+
+    private static long getPreVerifiedDomainsCountLimit() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return DeviceConfig.getLong(NAMESPACE_PACKAGE_MANAGER_SERVICE,
+                    PROPERTY_PRE_VERIFIED_DOMAINS_COUNT_LIMIT,
+                    DEFAULT_PRE_VERIFIED_DOMAINS_COUNT_LIMIT);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private static long getPreVerifiedDomainLengthLimit() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return DeviceConfig.getLong(NAMESPACE_PACKAGE_MANAGER_SERVICE,
+                    PROPERTY_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT,
+                    DEFAULT_PRE_VERIFIED_DOMAIN_LENGTH_LIMIT);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    @Nullable
+    public DomainSet getPreVerifiedDomains() {
+        assertCallerIsOwnerOrRoot();
+        synchronized (mLock) {
+            assertPreparedAndNotCommittedOrDestroyedLocked("getPreVerifiedDomains");
+            return mPreVerifiedDomains;
+        }
+    }
+
+
     void setSessionReady() {
         synchronized (mLock) {
             // Do not allow destroyed/failed session to change state
@@ -5250,6 +5370,9 @@
         pw.printPair("mSessionErrorCode", mSessionErrorCode);
         pw.printPair("mSessionErrorMessage", mSessionErrorMessage);
         pw.printPair("mPreapprovalDetails", mPreapprovalDetails);
+        if (mPreVerifiedDomains != null) {
+            pw.printPair("mPreVerifiedDomains", mPreVerifiedDomains);
+        }
         pw.println();
 
         pw.decreaseIndent();
@@ -5546,7 +5669,13 @@
                 writeByteArrayAttribute(out, ATTR_SIGNATURE, signature);
                 out.endTag(null, TAG_SESSION_CHECKSUM_SIGNATURE);
             }
-
+            if (mPreVerifiedDomains != null) {
+                for (String domain : mPreVerifiedDomains.getDomains()) {
+                    out.startTag(null, TAG_PRE_VERIFIED_DOMAINS);
+                    writeStringAttribute(out, ATTR_DOMAIN, domain);
+                    out.endTag(null, TAG_PRE_VERIFIED_DOMAINS);
+                }
+            }
         }
 
         out.endTag(null, TAG_SESSION);
@@ -5673,6 +5802,7 @@
         List<InstallationFile> files = new ArrayList<>();
         ArrayMap<String, List<Checksum>> checksums = new ArrayMap<>();
         ArrayMap<String, byte[]> signatures = new ArrayMap<>();
+        ArraySet<String> preVerifiedDomainSet = new ArraySet<>();
         int outerDepth = in.getDepth();
         int type;
         while ((type = in.next()) != XmlPullParser.END_DOCUMENT
@@ -5726,6 +5856,9 @@
                     final byte[] signature = readByteArrayAttribute(in, ATTR_SIGNATURE);
                     signatures.put(fileName1, signature);
                     break;
+                case TAG_PRE_VERIFIED_DOMAINS:
+                    preVerifiedDomainSet.add(readStringAttribute(in, ATTR_DOMAIN));
+                    break;
             }
         }
 
@@ -5769,6 +5902,9 @@
             }
         }
 
+        DomainSet preVerifiedDomains =
+                preVerifiedDomainSet.isEmpty() ? null : new DomainSet(preVerifiedDomainSet);
+
         InstallSource installSource = InstallSource.create(installInitiatingPackageName,
                 installOriginatingPackageName, installerPackageName, installPackageUid,
                 updateOwnerPackageName, installerAttributionTag, params.packageSource);
@@ -5777,6 +5913,6 @@
                 installerUid, installSource, params, createdMillis, committedMillis, stageDir,
                 stageCid, fileArray, checksumsMap, prepared, committed, destroyed, sealed,
                 childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied,
-                sessionErrorCode, sessionErrorMessage);
+                sessionErrorCode, sessionErrorMessage, preVerifiedDomains);
     }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b8960da..afd4fb1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -569,7 +569,8 @@
      * target sdk apps as malware can target older sdk versions to avoid
      * the enforcement of new API behavior.
      */
-    public static final int MIN_INSTALLABLE_TARGET_SDK = Build.VERSION_CODES.M;
+    public static final int MIN_INSTALLABLE_TARGET_SDK =
+            Flags.minTargetSdk24() ? Build.VERSION_CODES.N : Build.VERSION_CODES.M;
 
     // Compilation reasons.
     // TODO(b/260124949): Clean this up with the legacy dexopt code.
@@ -6483,6 +6484,17 @@
         }
 
         @Override
+        @Nullable
+        public ComponentName getDomainVerificationAgent() {
+            final int callerUid = Binder.getCallingUid();
+            if (!PackageManagerServiceUtils.isRootOrShell(callerUid)) {
+                throw new SecurityException("Not allowed to query domain verification agent");
+            }
+            final Computer snapshot = snapshotComputer();
+            return getDomainVerificationAgentComponentNameLPr(snapshot);
+        }
+
+        @Override
         public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                 throws RemoteException {
             try {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 81f9d1b..89589ed 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -107,6 +107,7 @@
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Pair;
 import android.util.PrintWriterPrinter;
@@ -270,6 +271,10 @@
                     return runGetInstallLocation();
                 case "install-add-session":
                     return runInstallAddSession();
+                case "install-set-pre-verified-domains":
+                    return runInstallSetPreVerifiedDomains();
+                case "install-get-pre-verified-domains":
+                    return runInstallGetPreVerifiedDomains();
                 case "move-package":
                     return runMovePackage();
                 case "move-primary-storage":
@@ -391,6 +396,8 @@
                     return runArchive();
                 case "request-unarchive":
                     return runUnarchive();
+                case "get-domain-verification-agent":
+                    return runGetDomainVerificationAgent();
                 default: {
                     if (ART_SERVICE_COMMANDS.contains(cmd)) {
                         if (DexOptHelper.useArtService()) {
@@ -1808,6 +1815,41 @@
                 true /*logSuccess*/);
     }
 
+    private int runInstallSetPreVerifiedDomains() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final int sessionId = Integer.parseInt(getNextArg());
+        final String preVerifiedDomainsStr = getNextArg();
+        final String[] preVerifiedDomains = preVerifiedDomainsStr.split(",");
+        PackageInstaller.Session session = null;
+        try {
+            session = new PackageInstaller.Session(
+                    mInterface.getPackageInstaller().openSession(sessionId));
+            session.setPreVerifiedDomains(new ArraySet<>(preVerifiedDomains));
+        } finally {
+            IoUtils.closeQuietly(session);
+        }
+        return 0;
+    }
+
+    private int runInstallGetPreVerifiedDomains() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final int sessionId = Integer.parseInt(getNextArg());
+        PackageInstaller.Session session = null;
+        try {
+            session = new PackageInstaller.Session(
+                    mInterface.getPackageInstaller().openSession(sessionId));
+            Set<String> preVerifiedDomains = session.getPreVerifiedDomains();
+            if (preVerifiedDomains.isEmpty()) {
+                pw.println("The session doesn't have any pre-verified domains specified.");
+            } else {
+                pw.println(String.join(",", preVerifiedDomains));
+            }
+        } finally {
+            IoUtils.closeQuietly(session);
+        }
+        return 0;
+    }
+
     private int runInstallRemove() throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
 
@@ -4754,6 +4796,19 @@
         return 0;
     }
 
+    private int runGetDomainVerificationAgent() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        try {
+            final ComponentName domainVerificationAgent = mInterface.getDomainVerificationAgent();
+            pw.println(domainVerificationAgent == null
+                    ? "No Domain Verifier available!" : domainVerificationAgent.toString());
+        } catch (Exception e) {
+            pw.println("Failure [" + e.getMessage() + "]");
+            return 1;
+        }
+        return 0;
+    }
+
     @Override
     public void onHelp() {
         final PrintWriter pw = getOutPrintWriter();
@@ -4934,6 +4989,13 @@
         pw.println("  install-add-session MULTI_PACKAGE_SESSION_ID CHILD_SESSION_IDs");
         pw.println("    Add one or more session IDs to a multi-package session.");
         pw.println("");
+        pw.println("  install-set-pre-verified-domains SESSION_ID PRE_VERIFIED_DOMAIN... ");
+        pw.println("    Specify a comma separated list of pre-verified domains for a session.");
+        pw.println("");
+        pw.println("  install-get-pre-verified-domains SESSION_ID");
+        pw.println("    List all the pre-verified domains that are specified in a session.");
+        pw.println("    The result list is comma separated.");
+        pw.println("");
         pw.println("  install-commit SESSION_ID");
         pw.println("    Commit the given active install session, installing the app.");
         pw.println("");
@@ -5147,6 +5209,9 @@
         pw.println("    to unarchive an app to the responsible installer. Options are:");
         pw.println("      --user: request unarchival of the app from the given user.");
         pw.println("");
+        pw.println("  get-domain-verification-agent");
+        pw.println("    Displays the component name of the domain verification agent on device.");
+        pw.println("");
         if (DexOptHelper.useArtService()) {
             printArtServiceHelp();
         } else {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 04e8205..5575f52 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -5044,6 +5044,10 @@
                 pw.print(prefix); pw.print("  updatableSystem=false");
                 pw.println();
             }
+            if (pkg.getEmergencyInstaller() != null) {
+                pw.print(prefix); pw.print("  emergencyInstaller=");
+                pw.println(pkg.getEmergencyInstaller());
+            }
             if (pkg.hasPreserveLegacyExternalStorage()) {
                 pw.print(prefix); pw.print("  hasPreserveLegacyExternalStorage=true");
                 pw.println();
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index e9c6aab..b35f9c2 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -209,9 +209,8 @@
         if (ret == null) {
             ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId);
             mLaunchers.put(key, ret);
-        } else {
-            ret.attemptToRestoreIfNeededAndSave();
         }
+        ret.attemptToRestoreIfNeededAndSave();
         return ret;
     }
 
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index b720304..067a012 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -288,29 +288,6 @@
      * configuration.
      */
     private static UserTypeDetails.Builder getDefaultTypeProfilePrivate() {
-        UserProperties.Builder userPropertiesBuilder = new UserProperties.Builder()
-                .setStartWithParent(true)
-                .setCredentialShareableWithParent(true)
-                .setAuthAlwaysRequiredToDisableQuietMode(true)
-                .setAllowStoppingUserWithDelayedLocking(true)
-                .setMediaSharedWithParent(false)
-                .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
-                .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
-                .setShowInQuietMode(
-                        UserProperties.SHOW_IN_QUIET_MODE_HIDDEN)
-                .setShowInSharingSurfaces(
-                        UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE)
-                .setCrossProfileIntentFilterAccessControl(
-                        UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
-                .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT)
-                .setCrossProfileContentSharingStrategy(
-                        UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT)
-                .setItemsRestrictedOnHomeScreen(true);
-        if (android.multiuser.Flags.supportHidingProfiles()) {
-            userPropertiesBuilder.setProfileApiVisibility(
-                    UserProperties.PROFILE_API_VISIBILITY_HIDDEN);
-        }
-
         return new UserTypeDetails.Builder()
                 .setName(USER_TYPE_PROFILE_PRIVATE)
                 .setBaseType(FLAG_PROFILE)
@@ -329,7 +306,26 @@
                 .setDarkThemeBadgeColors(
                         R.color.white)
                 .setDefaultRestrictions(getDefaultProfileRestrictions())
-                .setDefaultUserProperties(userPropertiesBuilder);
+                .setDefaultUserProperties(new UserProperties.Builder()
+                        .setStartWithParent(true)
+                        .setCredentialShareableWithParent(true)
+                        .setAuthAlwaysRequiredToDisableQuietMode(true)
+                        .setAllowStoppingUserWithDelayedLocking(true)
+                        .setMediaSharedWithParent(false)
+                        .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
+                        .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
+                        .setShowInQuietMode(
+                                UserProperties.SHOW_IN_QUIET_MODE_HIDDEN)
+                        .setShowInSharingSurfaces(
+                                UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE)
+                        .setCrossProfileIntentFilterAccessControl(
+                                UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
+                        .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT)
+                        .setCrossProfileContentSharingStrategy(
+                                UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT)
+                        .setProfileApiVisibility(
+                                UserProperties.PROFILE_API_VISIBILITY_HIDDEN)
+                        .setItemsRestrictedOnHomeScreen(true));
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
index cc26c9b..9159851 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java
@@ -538,9 +538,10 @@
             } else {
                 if (fileInfo.mUserId != userId) {
                     // This should be impossible: private app files are always user-specific and
-                    // can't be accessed from different users.
-                    throw new IllegalArgumentException("Cannot change userId for '" + path
-                            + "' from " + fileInfo.mUserId + " to " + userId);
+                    // can't be accessed from different users. But it does very occasionally happen
+                    // (b/323665257). Ignore such cases - we shouldn't record data from a different
+                    // user.
+                    return false;
                 }
                 // Changing file type (i.e. loading the same file in different ways is possible if
                 // unlikely. We allow it but ignore it.
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
index 53ee189..7ca449a 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
@@ -26,6 +26,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
+import android.content.pm.verify.domain.DomainSet;
 import android.content.pm.verify.domain.DomainVerificationInfo;
 import android.content.pm.verify.domain.DomainVerificationManager;
 import android.content.pm.verify.domain.DomainVerificationState;
@@ -230,13 +231,20 @@
      * broadcast will be sent to the domain verification agent so it may re-run any verification
      * logic for the newly associated domains.
      * <p>
-     * This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal
-     * lock. This should never be called from within the domain verification classes themselves.
+     * Optionally, the caller can specify a set of domains that are already pre-verified by the
+     * installer. These domains, if specified with autoVerify in the manifest, will be regarded as
+     * verified as soon as the app is installed, until the domain verification agent sends back the
+     * real verification results.
+     * <p>
+     * This method will mutate internal {@link DomainVerificationPkgState} and so will hold the
+     * internal lock. This should never be called from within the domain verification classes
+     * themselves.
      * <p>
      * This will NOT call {@link #writeSettings(Computer, TypedXmlSerializer, boolean, int)}. That must be
      * handled by the caller.
      */
-    void addPackage(@NonNull PackageStateInternal newPkgSetting);
+    void addPackage(@NonNull PackageStateInternal newPkgSetting,
+                    @Nullable DomainSet preVerifiedDomains);
 
     /**
      * Migrates verification state from a previous install to a new one. It is expected that the
@@ -245,14 +253,20 @@
      * domains under the assumption that the new package will pass the same server side config as
      * the previous package, as they have matching signatures.
      * <p>
-     * This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal
-     * lock. This should never be called from within the domain verification classes themselves.
+     * Optionally, the caller can specify a set of domains that are already pre-verified by the
+     * installer. These domains, if specified with autoVerify in the manifest, will be regarded as
+     * verified as soon as the app is updated, until the domain verification agent sends back the
+     * real verification results.
+     * <p>
+     * This method will mutate internal {@link DomainVerificationPkgState} and so will hold the
+     * internal lock. This should never be called from within the domain verification classes
+     * themselves.
      * <p>
      * This will NOT call {@link #writeSettings(Computer, TypedXmlSerializer, boolean, int)}. That must be
      * handled by the caller.
      */
     void migrateState(@NonNull PackageStateInternal oldPkgSetting,
-            @NonNull PackageStateInternal newPkgSetting);
+            @NonNull PackageStateInternal newPkgSetting, @Nullable DomainSet preVerifiedDomains);
 
     /**
      * Serializes the entire internal state. This is equivalent to a full backup of the existing
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index 6150099..c796b40 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -32,6 +32,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.verify.domain.DomainOwner;
+import android.content.pm.verify.domain.DomainSet;
 import android.content.pm.verify.domain.DomainVerificationInfo;
 import android.content.pm.verify.domain.DomainVerificationManager;
 import android.content.pm.verify.domain.DomainVerificationState;
@@ -859,7 +860,7 @@
 
     @Override
     public void migrateState(@NonNull PackageStateInternal oldPkgSetting,
-            @NonNull PackageStateInternal newPkgSetting) {
+            @NonNull PackageStateInternal newPkgSetting, @Nullable DomainSet preVerifiedDomains) {
         String pkgName = newPkgSetting.getPackageName();
         boolean sendBroadcast;
 
@@ -935,6 +936,9 @@
 
             sendBroadcast = hasAutoVerifyDomains && needsBroadcast;
 
+            // Apply pre-verified states as the last step of migration
+            applyPreVerifiedState(newStateMap, newAutoVerifyDomains, preVerifiedDomains);
+
             mAttachedPkgStates.put(pkgName, newDomainSetId, new DomainVerificationPkgState(
                     pkgName, newDomainSetId, hasAutoVerifyDomains, newStateMap, newUserStates,
                     null /* signature */));
@@ -947,7 +951,8 @@
 
     // TODO(b/159952358): Handle valid domainSetIds for PackageStateInternals with no AndroidPackage
     @Override
-    public void addPackage(@NonNull PackageStateInternal newPkgSetting) {
+    public void addPackage(@NonNull PackageStateInternal newPkgSetting,
+                           @Nullable DomainSet preVerifiedDomains) {
         // TODO(b/159952358): Optimize packages without any domains. Those wouldn't have to be in
         //  the state map, but it would require handling the "migration" case where an app either
         //  gains or loses all domains.
@@ -1029,6 +1034,9 @@
                             DomainVerificationState.STATE_MIGRATED);
                 }
             }
+
+            // Apply pre-verified states before sending out broadcast
+            applyPreVerifiedState(pkgState.getStateMap(), autoVerifyDomains, preVerifiedDomains);
         }
 
         synchronized (mLock) {
@@ -1040,6 +1048,27 @@
         }
     }
 
+    private void applyPreVerifiedState(ArrayMap<String, Integer> stateMap,
+                                       ArraySet<String> autoVerifyDomains,
+                                       DomainSet preVerifiedDomains) {
+        // If any pre-verified domains are provided, treating them as verified as well. This
+        // allows the app to be opened immediately by the corresponding app links, but the
+        // pre-verified state can still be overwritten by the domain verification agent in the
+        // future.
+        if (preVerifiedDomains != null && !autoVerifyDomains.isEmpty()) {
+            for (String preVerifiedDomain : preVerifiedDomains.getDomains()) {
+                if (autoVerifyDomains.contains(preVerifiedDomain)
+                        && !stateMap.containsKey(preVerifiedDomain)) {
+                    // Only set the pre-verified state if there's no existing state
+                    stateMap.put(preVerifiedDomain, DomainVerificationState.STATE_PRE_VERIFIED);
+                    if (DEBUG_APPROVAL) {
+                        Slog.d(TAG, "Inserted pre-verified domain: " + preVerifiedDomain);
+                    }
+                }
+            }
+        }
+    }
+
     /**
      * Applies any immutable state as the final step when adding or migrating state. Currently only
      * applies {@link SystemConfig#getLinkedApps()}, which approves all domains for a system app.
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
index 466c4c9..13b072b 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
@@ -62,6 +62,7 @@
         pw.println("        - restored: preserved verification from a user data restore");
         pw.println("        - legacy_failure: rejected by a legacy verifier, unknown reason");
         pw.println("        - system_configured: automatically approved by the device config");
+        pw.println("        - pre_verified: the domain was pre-verified by the installer");
         pw.println("        - >= 1024: Custom error code which is specific to the device verifier");
         pw.println("      --user <USER_ID>: include user selections (includes all domains, not");
         pw.println("        just autoVerify ones)");
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index 6546646..b2e01c5 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -21,3 +21,10 @@
     bug: "311793616"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "streamlined_connectivity_battery_stats"
+    namespace: "backstage_power"
+    description: "Feature flag for streamlined connectivity battery stats"
+    bug: "323970018"
+}
diff --git a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
index b2bbcda..e1abae8 100644
--- a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
+++ b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
@@ -32,6 +32,8 @@
 
 import com.android.internal.infra.ServiceConnector;
 
+import java.io.IOException;
+
 /** Manages the connection to the remote wearable sensing service. */
 final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearableSensingService> {
     private static final String TAG =
@@ -56,6 +58,29 @@
     }
 
     /**
+     * Provides a secure connection to the wearable.
+     *
+     * @param secureWearableConnection The secure connection to the wearable
+     * @param callback The callback for service status
+     */
+    public void provideSecureWearableConnection(
+            ParcelFileDescriptor secureWearableConnection, RemoteCallback callback) {
+        if (DEBUG) {
+            Slog.i(TAG, "Providing secure wearable connection.");
+        }
+        var unused = post(
+                service -> {
+                    service.provideSecureWearableConnection(secureWearableConnection, callback);
+                    try {
+                        // close the local fd after it has been sent to the WSS process
+                        secureWearableConnection.close();
+                    } catch (IOException ex) {
+                        Slog.w(TAG, "Unable to close the local parcelFileDescriptor.", ex);
+                    }
+                });
+    }
+
+    /**
      * Provides the implementation a data stream to the wearable.
      *
      * @param parcelFileDescriptor The data stream to the wearable
@@ -66,7 +91,16 @@
         if (DEBUG) {
             Slog.i(TAG, "Providing data stream.");
         }
-        post(service -> service.provideDataStream(parcelFileDescriptor, callback));
+        var unused = post(
+                service -> {
+                    service.provideDataStream(parcelFileDescriptor, callback);
+                    try {
+                        // close the local fd after it has been sent to the WSS process
+                        parcelFileDescriptor.close();
+                    } catch (IOException ex) {
+                        Slog.w(TAG, "Unable to close the local parcelFileDescriptor.", ex);
+                    }
+                });
     }
 
     /**
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
index e73fd0f..a8d6322 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
@@ -22,17 +22,19 @@
 import android.annotation.UserIdInt;
 import android.app.AppGlobals;
 import android.app.ambientcontext.AmbientContextEvent;
+import android.app.wearable.Flags;
 import android.app.wearable.WearableSensingManager;
+import android.companion.CompanionDeviceManager;
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.os.Bundle;
-import android.system.OsConstants;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.SharedMemory;
+import android.system.OsConstants;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
@@ -40,6 +42,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.infra.AbstractPerUserSystemService;
 
+import java.io.IOException;
 import java.io.PrintWriter;
 
 /**
@@ -55,6 +58,10 @@
     RemoteWearableSensingService mRemoteService;
 
     private ComponentName mComponentName;
+    private final Object mSecureChannelLock = new Object();
+
+    @GuardedBy("mSecureChannelLock")
+    private WearableSensingSecureChannel mSecureChannel;
 
     WearableSensingManagerPerUserService(
             @NonNull WearableSensingManagerService master, Object lock, @UserIdInt int userId) {
@@ -76,6 +83,11 @@
                 mRemoteService = null;
             }
         }
+        synchronized (mSecureChannelLock) {
+            if (mSecureChannel != null) {
+                mSecureChannel.close();
+            }
+        }
     }
 
     @GuardedBy("mLock")
@@ -156,6 +168,63 @@
     }
 
     /**
+     * Creates a CompanionDeviceManager secure channel and sends a proxy to the wearable sensing
+     * service.
+     */
+    public void onProvideWearableConnection(
+            ParcelFileDescriptor wearableConnection, RemoteCallback callback) {
+        Slog.i(TAG, "onProvideWearableConnection in per user service.");
+        synchronized (mLock) {
+            if (!setUpServiceIfNeeded()) {
+                Slog.w(TAG, "Detection service is not available at this moment.");
+                notifyStatusCallback(callback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+        }
+        synchronized (mSecureChannelLock) {
+            if (mSecureChannel != null) {
+                // TODO(b/321012559): Kill the WearableSensingService process if it has not been
+                // killed from onError
+                mSecureChannel.close();
+            }
+            try {
+                mSecureChannel =
+                        WearableSensingSecureChannel.create(
+                                getContext().getSystemService(CompanionDeviceManager.class),
+                                wearableConnection,
+                                new WearableSensingSecureChannel.SecureTransportListener() {
+                                    @Override
+                                    public void onSecureTransportAvailable(
+                                            ParcelFileDescriptor secureTransport) {
+                                        Slog.i(TAG, "calling over to remote service.");
+                                        synchronized (mLock) {
+                                            ensureRemoteServiceInitiated();
+                                            mRemoteService.provideSecureWearableConnection(
+                                                    secureTransport, callback);
+                                        }
+                                    }
+
+                                    @Override
+                                    public void onError() {
+                                        // TODO(b/321012559): Kill the WearableSensingService
+                                        // process if mSecureChannel has not been reassigned
+                                        if (Flags.enableProvideWearableConnectionApi()) {
+                                            notifyStatusCallback(
+                                                    callback,
+                                                    WearableSensingManager.STATUS_CHANNEL_ERROR);
+                                        }
+                                    }
+                                });
+            } catch (IOException ex) {
+                Slog.e(TAG, "Unable to create the secure channel.", ex);
+                if (Flags.enableProvideWearableConnectionApi()) {
+                    notifyStatusCallback(callback, WearableSensingManager.STATUS_CHANNEL_ERROR);
+                }
+            }
+        }
+    }
+
+    /**
      * Handles sending the provided data stream for the wearable to the wearable sensing service.
      */
     public void onProvideDataStream(
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index 4cc2c02..28c8f87 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -211,9 +211,27 @@
     private final class WearableSensingManagerInternal extends IWearableSensingManager.Stub {
 
         @Override
+        public void provideWearableConnection(
+                ParcelFileDescriptor wearableConnection, RemoteCallback callback) {
+            Slog.i(TAG, "WearableSensingManagerInternal provideWearableConnection.");
+            Objects.requireNonNull(wearableConnection);
+            Objects.requireNonNull(callback);
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG);
+            if (!mIsServiceEnabled) {
+                Slog.w(TAG, "Service not available.");
+                WearableSensingManagerPerUserService.notifyStatusCallback(
+                        callback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+            callPerUserServiceIfExist(
+                    service -> service.onProvideWearableConnection(wearableConnection, callback),
+                    callback);
+        }
+
+        @Override
         public void provideDataStream(
-                ParcelFileDescriptor parcelFileDescriptor,
-                RemoteCallback callback) {
+                ParcelFileDescriptor parcelFileDescriptor, RemoteCallback callback) {
             Slog.i(TAG, "WearableSensingManagerInternal provideDataStream.");
             Objects.requireNonNull(parcelFileDescriptor);
             Objects.requireNonNull(callback);
diff --git a/services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java b/services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java
new file mode 100644
index 0000000..a16ff51
--- /dev/null
+++ b/services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java
@@ -0,0 +1,350 @@
+/*
+ * 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.wearable;
+
+import android.annotation.NonNull;
+import android.companion.AssociationInfo;
+import android.companion.AssociationRequest;
+import android.companion.CompanionDeviceManager;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.AutoCloseInputStream;
+import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+/**
+ * A wrapper that manages a CompanionDeviceManager secure channel for wearable sensing.
+ *
+ * <p>This wrapper accepts a connection to a wearable from the caller. It then attaches the
+ * connection to the CompanionDeviceManager via {@link
+ * CompanionDeviceManager#attachSystemDataTransport(int, InputStream, OutputStream)}, which will
+ * create an encrypted channel using the provided connection as the raw underlying connection. The
+ * wearable device is expected to attach its side of the raw connection to its
+ * CompanionDeviceManager via the same method so that the two CompanionDeviceManagers on the two
+ * devices can perform attestation and set up the encrypted channel. Attestation requirements are
+ * listed in {@link com.android.server.security.AttestationVerificationPeerDeviceVerifier}.
+ *
+ * <p>When the encrypted channel is available, it will be provided to the caller via the
+ * SecureTransportListener.
+ */
+final class WearableSensingSecureChannel {
+
+    /** A listener for secure transport and its error signal. */
+    interface SecureTransportListener {
+
+        /** Called when the secure transport is available. */
+        void onSecureTransportAvailable(ParcelFileDescriptor secureTransport);
+
+        /**
+         * Called when there is a non-recoverable error. The secure channel will be automatically
+         * closed.
+         */
+        void onError();
+    }
+
+    private static final String TAG = WearableSensingSecureChannel.class.getSimpleName();
+    private static final String CDM_ASSOCIATION_DISPLAY_NAME = "PlaceholderDisplayNameFromWSM";
+    // The batch size of reading from the ParcelFileDescriptor returned to mSecureTransportListener
+    private static final int READ_BUFFER_SIZE = 8192;
+
+    private final Object mLock = new Object();
+    // CompanionDeviceManager (CDM) can continue to call these ExecutorServices even after the
+    // corresponding cleanup methods in CDM have been called (e.g.
+    // removeOnTransportsChangedListener). Since we shut down these ExecutorServices after
+    // clean up, we use SoftShutdownExecutor to suppress RejectedExecutionExceptions.
+    private final SoftShutdownExecutor mMessageFromWearableExecutor =
+            new SoftShutdownExecutor(Executors.newSingleThreadExecutor());
+    private final SoftShutdownExecutor mMessageToWearableExecutor =
+            new SoftShutdownExecutor(Executors.newSingleThreadExecutor());
+    private final SoftShutdownExecutor mLightWeightExecutor =
+            new SoftShutdownExecutor(Executors.newSingleThreadExecutor());
+    private final CompanionDeviceManager mCompanionDeviceManager;
+    private final ParcelFileDescriptor mUnderlyingTransport;
+    private final SecureTransportListener mSecureTransportListener;
+    private final AtomicBoolean mTransportAvailable = new AtomicBoolean(false);
+    private final Consumer<List<AssociationInfo>> mOnTransportsChangedListener =
+            this::onTransportsChanged;
+    private final BiConsumer<Integer, byte[]> mOnMessageReceivedListener = this::onMessageReceived;
+    private final ParcelFileDescriptor mRemoteFd; // To be returned to mSecureTransportListener
+    // read input received from the ParcelFileDescriptor returned to mSecureTransportListener
+    private final InputStream mLocalIn;
+    // send output to the ParcelFileDescriptor returned to mSecureTransportListener
+    private final OutputStream mLocalOut;
+
+    @GuardedBy("mLock")
+    private boolean mClosed = false;
+
+    private Integer mAssociationId = null;
+
+    /**
+     * Creates a WearableSensingSecureChannel. When the secure transport is ready,
+     * secureTransportListener will be notified.
+     *
+     * @param companionDeviceManager The CompanionDeviceManager system service.
+     * @param underlyingTransport The underlying transport to create the secure channel on.
+     * @param secureTransportListener The listener to receive the secure transport when it is ready.
+     * @throws IOException if it cannot create a {@link ParcelFileDescriptor} socket pair.
+     */
+    static WearableSensingSecureChannel create(
+            @NonNull CompanionDeviceManager companionDeviceManager,
+            @NonNull ParcelFileDescriptor underlyingTransport,
+            @NonNull SecureTransportListener secureTransportListener)
+            throws IOException {
+        Objects.requireNonNull(companionDeviceManager);
+        Objects.requireNonNull(underlyingTransport);
+        Objects.requireNonNull(secureTransportListener);
+        ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair();
+        WearableSensingSecureChannel channel =
+                new WearableSensingSecureChannel(
+                        companionDeviceManager,
+                        underlyingTransport,
+                        secureTransportListener,
+                        pair[0],
+                        pair[1]);
+        channel.initialize();
+        return channel;
+    }
+
+    private WearableSensingSecureChannel(
+            CompanionDeviceManager companionDeviceManager,
+            ParcelFileDescriptor underlyingTransport,
+            SecureTransportListener secureTransportListener,
+            ParcelFileDescriptor remoteFd,
+            ParcelFileDescriptor localFd) {
+        mCompanionDeviceManager = companionDeviceManager;
+        mUnderlyingTransport = underlyingTransport;
+        mSecureTransportListener = secureTransportListener;
+        mRemoteFd = remoteFd;
+        mLocalIn = new AutoCloseInputStream(localFd);
+        mLocalOut = new AutoCloseOutputStream(localFd);
+    }
+
+    private void initialize() {
+        final long originalCallingIdentity = Binder.clearCallingIdentity();
+        try {
+            Slog.d(TAG, "Requesting CDM association.");
+            mCompanionDeviceManager.associate(
+                    new AssociationRequest.Builder()
+                            .setDisplayName(CDM_ASSOCIATION_DISPLAY_NAME)
+                            .setSelfManaged(true)
+                            .build(),
+                    mLightWeightExecutor,
+                    new CompanionDeviceManager.Callback() {
+                        @Override
+                        public void onAssociationCreated(AssociationInfo associationInfo) {
+                            WearableSensingSecureChannel.this.onAssociationCreated(
+                                    associationInfo.getId());
+                        }
+
+                        @Override
+                        public void onFailure(CharSequence error) {
+                            Slog.e(
+                                    TAG,
+                                    "Failed to create CompanionDeviceManager association: "
+                                            + error);
+                            onError();
+                        }
+                    });
+        } finally {
+            Binder.restoreCallingIdentity(originalCallingIdentity);
+        }
+    }
+
+    private void onAssociationCreated(int associationId) {
+        Slog.i(TAG, "CDM association created.");
+        synchronized (mLock) {
+            if (mClosed) {
+                return;
+            }
+            mAssociationId = associationId;
+            mCompanionDeviceManager.addOnMessageReceivedListener(
+                    mMessageFromWearableExecutor,
+                    CompanionDeviceManager.MESSAGE_ONEWAY_FROM_WEARABLE,
+                    mOnMessageReceivedListener);
+            mCompanionDeviceManager.addOnTransportsChangedListener(
+                    mLightWeightExecutor, mOnTransportsChangedListener);
+            mCompanionDeviceManager.attachSystemDataTransport(
+                    associationId,
+                    new AutoCloseInputStream(mUnderlyingTransport),
+                    new AutoCloseOutputStream(mUnderlyingTransport));
+        }
+    }
+
+    private void onTransportsChanged(List<AssociationInfo> associationInfos) {
+        synchronized (mLock) {
+            if (mClosed) {
+                return;
+            }
+            if (mAssociationId == null) {
+                Slog.e(TAG, "mAssociationId is null when transport changed");
+                return;
+            }
+        }
+        // Do not call onTransportAvailable() or onError() when holding the lock because it can
+        // cause a deadlock if the callback holds another lock.
+        boolean transportAvailable =
+                associationInfos.stream().anyMatch(info -> info.getId() == mAssociationId);
+        if (transportAvailable && mTransportAvailable.compareAndSet(false, true)) {
+            onTransportAvailable();
+        } else if (!transportAvailable && mTransportAvailable.compareAndSet(true, false)) {
+            Slog.i(TAG, "CDM transport is detached. This is not recoverable.");
+            onError();
+        }
+    }
+
+    private void onTransportAvailable() {
+        // Start sending data received from the remote stream to the wearable.
+        Slog.i(TAG, "Transport available");
+        mMessageToWearableExecutor.execute(
+                () -> {
+                    int[] associationIdsToSendMessageTo = new int[] {mAssociationId};
+                    byte[] buffer = new byte[READ_BUFFER_SIZE];
+                    int readLen;
+                    try {
+                        while ((readLen = mLocalIn.read(buffer)) != -1) {
+                            byte[] data = new byte[readLen];
+                            System.arraycopy(buffer, 0, data, 0, readLen);
+                            Slog.v(TAG, "Sending message to wearable");
+                            mCompanionDeviceManager.sendMessage(
+                                    CompanionDeviceManager.MESSAGE_ONEWAY_TO_WEARABLE,
+                                    data,
+                                    associationIdsToSendMessageTo);
+                        }
+                    } catch (IOException e) {
+                        Slog.i(TAG, "IOException while reading from remote stream.");
+                        onError();
+                        return;
+                    }
+                    Slog.i(
+                            TAG,
+                            "Reached EOF when reading from remote stream. Reporting this as an"
+                                    + " error.");
+                    onError();
+                });
+        mSecureTransportListener.onSecureTransportAvailable(mRemoteFd);
+    }
+
+    private void onMessageReceived(int associationIdForMessage, byte[] data) {
+        if (associationIdForMessage == mAssociationId) {
+            Slog.v(TAG, "Received message from wearable.");
+            try {
+                mLocalOut.write(data);
+                mLocalOut.flush();
+            } catch (IOException e) {
+                Slog.i(
+                        TAG,
+                        "IOException when writing to remote stream. Closing the secure channel.");
+                onError();
+            }
+        } else {
+            Slog.v(
+                    TAG,
+                    "Received CDM message of type MESSAGE_ONEWAY_FROM_WEARABLE, but it is for"
+                        + " another association. Ignoring the message.");
+        }
+    }
+
+    private void onError() {
+        synchronized (mLock) {
+            if (mClosed) {
+                return;
+            }
+        }
+        mSecureTransportListener.onError();
+        close();
+    }
+
+    /** Closes this secure channel and releases all resources. */
+    void close() {
+        synchronized (mLock) {
+            if (mClosed) {
+                return;
+            }
+            Slog.i(TAG, "Closing WearableSensingSecureChannel.");
+            mClosed = true;
+            if (mAssociationId != null) {
+                final long originalCallingIdentity = Binder.clearCallingIdentity();
+                try {
+                    mCompanionDeviceManager.removeOnTransportsChangedListener(
+                            mOnTransportsChangedListener);
+                    mCompanionDeviceManager.removeOnMessageReceivedListener(
+                            CompanionDeviceManager.MESSAGE_ONEWAY_FROM_WEARABLE,
+                            mOnMessageReceivedListener);
+                    mCompanionDeviceManager.detachSystemDataTransport(mAssociationId);
+                    mCompanionDeviceManager.disassociate(mAssociationId);
+                } finally {
+                    Binder.restoreCallingIdentity(originalCallingIdentity);
+                }
+            }
+            try {
+                mLocalIn.close();
+            } catch (IOException ex) {
+                Slog.e(TAG, "Encountered IOException when closing local input stream.", ex);
+            }
+            try {
+                mLocalOut.close();
+            } catch (IOException ex) {
+                Slog.e(TAG, "Encountered IOException when closing local output stream.", ex);
+            }
+            mMessageFromWearableExecutor.shutdown();
+            mMessageToWearableExecutor.shutdown();
+            mLightWeightExecutor.shutdown();
+        }
+    }
+
+    /**
+     * An executor that can be shutdown. Unlike an ExecutorService, it will not throw a
+     * RejectedExecutionException if {@link #execute(Runnable)} is called after shutdown.
+     */
+    private static class SoftShutdownExecutor implements Executor {
+
+        private final ExecutorService mExecutorService;
+
+        SoftShutdownExecutor(ExecutorService executorService) {
+            mExecutorService = executorService;
+        }
+
+        @Override
+        public void execute(Runnable runnable) {
+            try {
+                mExecutorService.execute(runnable);
+            } catch (RejectedExecutionException ex) {
+                Slog.d(TAG, "Received new runnable after shutdown. Ignoring.");
+            }
+        }
+
+        /** Shutdown the underlying ExecutorService. */
+        void shutdown() {
+            mExecutorService.shutdown();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 7b59759..c2117ea 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2441,11 +2441,11 @@
             return false;
         }
 
-        if (mStartingData != null) {
+        if (hasStartingWindow()) {
             return false;
         }
 
-        final WindowState mainWin = findMainWindow();
+        final WindowState mainWin = findMainWindow(false /* includeStartingApp */);
         if (mainWin != null && mainWin.mWinAnimator.getShown()) {
             // App already has a visible window...why would you want a starting window?
             return false;
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index 4ced5d5..f2dc55f 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -333,7 +333,9 @@
         if (aInfo != null && overrideTaskTransition) {
             final int startTasksFromRecentsPerm = ActivityTaskManagerService.checkPermission(
                     START_TASKS_FROM_RECENTS, callingPid, callingUid);
-            if (startTasksFromRecentsPerm != PERMISSION_GRANTED) {
+            // Allow if calling uid is from assistant, or start task from recents
+            if (startTasksFromRecentsPerm != PERMISSION_GRANTED
+                    && !isAssistant(supervisor.mService, callingUid)) {
                 final String msg = "Permission Denial: starting " + getIntentString(intent)
                         + " from " + callerApp + " (pid=" + callingPid
                         + ", uid=" + callingUid + ") with overrideTaskTransition=true";
diff --git a/services/core/java/com/android/server/wm/WindowList.java b/services/core/java/com/android/server/wm/WindowList.java
index dfeba40..1e888f5 100644
--- a/services/core/java/com/android/server/wm/WindowList.java
+++ b/services/core/java/com/android/server/wm/WindowList.java
@@ -24,7 +24,7 @@
  */
 class WindowList<E> extends ArrayList<E> {
 
-    void addFirst(E e) {
+    public void addFirst(E e) {
         add(0, e);
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b3983e7..366e1bb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1114,8 +1114,11 @@
     public static WindowManagerService main(final Context context, final InputManagerService im,
             final boolean showBootMsgs, WindowManagerPolicy policy,
             ActivityTaskManagerService atm) {
-        return main(context, im, showBootMsgs, policy, atm, new DisplayWindowSettingsProvider(),
-                SurfaceControl.Transaction::new, SurfaceControl.Builder::new);
+        final WindowManagerService wms = main(context, im, showBootMsgs, policy, atm,
+                new DisplayWindowSettingsProvider(), SurfaceControl.Transaction::new,
+                SurfaceControl.Builder::new);
+        WindowManagerGlobal.setWindowManagerServiceForSystemProcess(wms);
+        return wms;
     }
 
     /**
@@ -9275,7 +9278,8 @@
         return !fromWin.isFocused();
     }
 
-    private void moveFocusToActivity(@NonNull ActivityRecord activity) {
+    @VisibleForTesting
+    void moveFocusToActivity(@NonNull ActivityRecord activity) {
         moveDisplayToTopInternal(activity.getDisplayId());
         handleTaskFocusChange(activity.getTask(), activity);
     }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f5806c0..68dade0 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3020,8 +3020,10 @@
             return false;
         }
         if (doAnimation) {
-            mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false);
-            if (!isAnimating(TRANSITION | PARENTS)) {
+            // If a hide animation is applied, then let onAnimationFinished
+            // -> checkPolicyVisibilityChange hide the window. Otherwise make doAnimation false
+            // to commit invisible immediately.
+            if (!mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false /* isEntrance */)) {
                 doAnimation = false;
             }
         }
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 09c4f7c..6428591 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -499,10 +499,6 @@
     }
 
     void applyEnterAnimationLocked() {
-        if (mWin.mActivityRecord != null && mWin.mActivityRecord.hasStartingWindow()) {
-            // It's unnecessary to play enter animation below starting window.
-            return;
-        }
         final int transit;
         if (mEnterAnimationPending) {
             mEnterAnimationPending = false;
@@ -513,8 +509,10 @@
 
         // We don't apply animation for application main window here since this window type
         // should be controlled by ActivityRecord in general. Wallpaper is also excluded because
-        // WallpaperController should handle it.
-        if (mAttrType != TYPE_BASE_APPLICATION && !mIsWallpaper) {
+        // WallpaperController should handle it. Also skip play enter animation for the window
+        // below starting window.
+        if (mAttrType != TYPE_BASE_APPLICATION && !mIsWallpaper
+                && !(mWin.mActivityRecord != null && mWin.mActivityRecord.hasStartingWindow())) {
             applyAnimationLocked(transit, true);
         }
 
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index b1349ea..f5ba50d 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -27,6 +27,7 @@
 import android.credentials.selection.RequestInfo;
 import android.os.CancellationSignal;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.service.credentials.CallingAppInfo;
 import android.util.Slog;
 
@@ -149,7 +150,7 @@
     }
 
     @Override
-    public void onUiCancellation(boolean isUserCancellation) {
+    public void onUiCancellation(boolean isUserCancellation, ResultReceiver resultReceiver) {
         // Not needed since UI is not involved
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 3dcf42d..be4b9e1 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -31,6 +31,7 @@
 import android.credentials.selection.RequestInfo;
 import android.os.CancellationSignal;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.service.credentials.CallingAppInfo;
 import android.service.credentials.PermissionUtils;
 import android.util.Slog;
@@ -103,14 +104,16 @@
                 flattenedPrimaryProviders.add(cn.flattenToString());
             }
 
+            final boolean isShowAllOptionsRequested = false;
             mPendingIntent = mCredentialManagerUi.createPendingIntent(
                     RequestInfo.newCreateRequestInfo(
                             mRequestId, mClientRequest,
                             mClientAppInfo.getPackageName(),
                             PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
                                     Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
-                            /*defaultProviderId=*/flattenedPrimaryProviders),
-                    providerDataList, /*isRequestForAllOptions=*/ false);
+                            /*defaultProviderId=*/flattenedPrimaryProviders,
+                            isShowAllOptionsRequested),
+                    providerDataList, /*isRequestForAllOptions=*/ isShowAllOptionsRequested);
             mClientCallback.onPendingIntent(mPendingIntent);
         } catch (RemoteException e) {
             mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
@@ -161,7 +164,7 @@
     }
 
     @Override
-    public void onUiCancellation(boolean isUserCancellation) {
+    public void onUiCancellation(boolean isUserCancellation, ResultReceiver resultReceiver) {
         String exception = CreateCredentialException.TYPE_USER_CANCELED;
         String message = "User cancelled the selector";
         if (!isUserCancellation) {
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 84b5cb7..534c842 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -15,6 +15,8 @@
  */
 package com.android.server.credentials;
 
+import static android.credentials.selection.Constants.EXTRA_FINAL_RESPONSE_RECEIVER;
+
 import android.annotation.NonNull;
 import android.app.PendingIntent;
 import android.content.ComponentName;
@@ -34,7 +36,6 @@
 import android.os.ResultReceiver;
 import android.os.UserHandle;
 import android.service.credentials.CredentialProviderInfoFactory;
-import android.util.Slog;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -79,20 +80,25 @@
                 UserSelectionDialogResult selection = UserSelectionDialogResult
                         .fromResultData(resultData);
                 if (selection != null) {
-                    mCallbacks.onUiSelection(selection);
-                } else {
-                    Slog.i(TAG, "No selection found in UI result");
+                    ResultReceiver resultReceiver = resultData.getParcelable(
+                            EXTRA_FINAL_RESPONSE_RECEIVER,
+                            ResultReceiver.class);
+                    mCallbacks.onUiSelection(selection, resultReceiver);
                 }
                 break;
             case UserSelectionDialogResult.RESULT_CODE_DIALOG_USER_CANCELED:
 
                 mStatus = UiStatus.TERMINATED;
-                mCallbacks.onUiCancellation(/* isUserCancellation= */ true);
+                mCallbacks.onUiCancellation(/* isUserCancellation= */ true,
+                        resultData.getParcelable(EXTRA_FINAL_RESPONSE_RECEIVER,
+                                ResultReceiver.class));
                 break;
             case UserSelectionDialogResult.RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS:
 
                 mStatus = UiStatus.TERMINATED;
-                mCallbacks.onUiCancellation(/* isUserCancellation= */ false);
+                mCallbacks.onUiCancellation(/* isUserCancellation= */ false,
+                        resultData.getParcelable(EXTRA_FINAL_RESPONSE_RECEIVER,
+                                ResultReceiver.class));
                 break;
             case UserSelectionDialogResult.RESULT_CODE_DATA_PARSING_FAILURE:
                 mStatus = UiStatus.TERMINATED;
@@ -116,10 +122,10 @@
      */
     public interface CredentialManagerUiCallback {
         /** Called when the user makes a selection. */
-        void onUiSelection(UserSelectionDialogResult selection);
+        void onUiSelection(UserSelectionDialogResult selection, ResultReceiver resultReceiver);
 
         /** Called when the UI is canceled without a successful provider result. */
-        void onUiCancellation(boolean isUserCancellation);
+        void onUiCancellation(boolean isUserCancellation, ResultReceiver resultReceiver);
 
         /** Called when the selector UI fails to come up (mostly due to parsing issue today). */
         void onUiSelectorInvocationFailure();
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index 1a9a0e6..adb1b72 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
+import android.credentials.Constants;
 import android.credentials.CredentialProviderInfo;
 import android.credentials.GetCandidateCredentialsException;
 import android.credentials.GetCandidateCredentialsResponse;
@@ -29,10 +30,13 @@
 import android.credentials.selection.GetCredentialProviderData;
 import android.credentials.selection.ProviderData;
 import android.credentials.selection.RequestInfo;
+import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.service.credentials.CallingAppInfo;
+import android.service.credentials.CredentialProviderService;
 import android.service.credentials.PermissionUtils;
 import android.util.Slog;
 
@@ -111,13 +115,15 @@
         }
 
         cancelExistingPendingIntent();
+        final boolean isShowAllOptionsRequested = true;
         mPendingIntent = mCredentialManagerUi.createPendingIntent(
                 RequestInfo.newGetRequestInfo(
                         mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
                         PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
-                                Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
+                                Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
+                        isShowAllOptionsRequested),
                 /*providerDataList=*/ null,
-                /*isRequestForAllOptions=*/ true);
+                /*isRequestForAllOptions=*/ isShowAllOptionsRequested);
 
         List<GetCredentialProviderData> candidateProviderDataList = new ArrayList<>();
         for (ProviderData providerData : providerDataList) {
@@ -151,7 +157,8 @@
     }
 
     @Override
-    public void onUiCancellation(boolean isUserCancellation) {
+    public void onUiCancellation(boolean isUserCancellation,
+            @Nullable ResultReceiver finalResponseReceiver) {
         String exception = GetCandidateCredentialsException.TYPE_USER_CANCELED;
         String message = "User cancelled the selector";
         if (!isUserCancellation) {
@@ -159,7 +166,12 @@
             message = "The UI was interrupted - please try again.";
         }
         mRequestSessionMetric.collectFrameworkException(exception);
-        respondToClientWithErrorAndFinish(exception, message);
+        if (finalResponseReceiver != null) {
+            Bundle resultData = new Bundle();
+            finalResponseReceiver.send(Constants.FAILURE_CREDMAN_SELECTOR, resultData);
+        } else {
+            respondToClientWithErrorAndFinish(exception, message);
+        }
     }
 
     @Override
@@ -195,7 +207,16 @@
     public void onFinalResponseReceived(ComponentName componentName,
             GetCredentialResponse response) {
         Slog.d(TAG, "onFinalResponseReceived");
-        respondToClientWithResponseAndFinish(new GetCandidateCredentialsResponse(response));
+        if (this.mFinalResponseReceiver != null) {
+            Slog.d(TAG, "onFinalResponseReceived sending through final receiver");
+            Bundle resultData = new Bundle();
+            resultData.putParcelable(
+                    CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, response);
+            mFinalResponseReceiver.send(Constants.SUCCESS_CREDMAN_SELECTOR, resultData);
+            finishSession(/*propagateCancellation=*/ false);
+        } else {
+            Slog.w(TAG, "onFinalResponseReceived result receiver not found for pinned entry");
+        }
     }
 
     /**
@@ -210,6 +231,5 @@
      */
     public int getAutofillRequestId() {
         return mAutofillRequestId;
-
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index b33f531..a279337 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -31,6 +31,7 @@
 import android.os.Binder;
 import android.os.CancellationSignal;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.service.credentials.CallingAppInfo;
 import android.service.credentials.PermissionUtils;
 import android.util.Slog;
@@ -102,15 +103,17 @@
         Binder.withCleanCallingIdentity(() -> {
             try {
                 cancelExistingPendingIntent();
+                final boolean isShowAllOptionsRequested = false;
                 mPendingIntent = mCredentialManagerUi.createPendingIntent(
                         RequestInfo.newGetRequestInfo(
                                 mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
                                 PermissionUtils.hasPermission(mContext,
                                         mClientAppInfo.getPackageName(),
                                         Manifest.permission
-                                                .CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
+                                                .CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
+                                isShowAllOptionsRequested),
                         providerDataList,
-                        /*isRequestForAllOptions=*/ false);
+                        /*isRequestForAllOptions=*/ isShowAllOptionsRequested);
                 mClientCallback.onPendingIntent(mPendingIntent);
             } catch (RemoteException e) {
                 mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
@@ -163,7 +166,7 @@
     }
 
     @Override
-    public void onUiCancellation(boolean isUserCancellation) {
+    public void onUiCancellation(boolean isUserCancellation, ResultReceiver resultReceiver) {
         String exception = GetCredentialException.TYPE_USER_CANCELED;
         String message = "User cancelled the selector";
         if (!isUserCancellation) {
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
index 30af567..6b313fd 100644
--- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -187,12 +187,14 @@
             }
         }
         if (!providerDataList.isEmpty()) {
+            final boolean isShowAllOptionsRequested = false;
             return mCredentialManagerUi.createPendingIntent(
                     RequestInfo.newGetRequestInfo(
                             mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
                             PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
-                                    Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
-                    providerDataList, /*isRequestForAllOptions=*/ false);
+                                    Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
+                            isShowAllOptionsRequested),
+                    providerDataList, /*isRequestForAllOptions=*/ isShowAllOptionsRequested);
         } else {
             return null;
         }
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index bf7df86..633c9c4 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -17,6 +17,7 @@
 package com.android.server.credentials;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.PendingIntent;
 import android.content.ComponentName;
@@ -33,6 +34,7 @@
 import android.os.IInterface;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.UserHandle;
 import android.service.credentials.CallingAppInfo;
 import android.util.Slog;
@@ -101,6 +103,9 @@
 
     protected PendingIntent mPendingIntent;
 
+    @Nullable
+    protected ResultReceiver mFinalResponseReceiver;
+
     @NonNull
     protected RequestSessionStatus mRequestSessionStatus =
             RequestSessionStatus.IN_PROGRESS;
@@ -219,7 +224,8 @@
     // UI callbacks
 
     @Override // from CredentialManagerUiCallbacks
-    public void onUiSelection(UserSelectionDialogResult selection) {
+    public void onUiSelection(UserSelectionDialogResult selection,
+            ResultReceiver finalResponseReceiver) {
         if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
             Slog.w(TAG, "Request has already been completed. This is strange.");
             return;
@@ -234,6 +240,7 @@
             Slog.w(TAG, "providerSession not found in onUiSelection. This is strange.");
             return;
         }
+        mFinalResponseReceiver = finalResponseReceiver;
         ProviderSessionMetric providerSessionMetric = providerSession.mProviderSessionMetric;
         int initialAuthMetricsProvider = providerSessionMetric.getBrowsedAuthenticationMetric()
                 .size();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 74d544f..6f0985a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -34,6 +34,7 @@
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CAMERA;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CERTIFICATES;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_DEFAULT_SMS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_DISPLAY;
@@ -110,6 +111,8 @@
 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER;
 import static android.app.admin.DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED;
+import static android.app.admin.DevicePolicyManager.CONTENT_PROTECTION_DISABLED;
+import static android.app.admin.DevicePolicyManager.ContentProtectionPolicy;
 import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
 import static android.app.admin.DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL;
 import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
@@ -220,6 +223,7 @@
 import static android.app.admin.ProvisioningException.ERROR_SETTING_PROFILE_OWNER_FAILED;
 import static android.app.admin.ProvisioningException.ERROR_SET_DEVICE_OWNER_FAILED;
 import static android.app.admin.ProvisioningException.ERROR_STARTING_PROFILE_FAILED;
+import static android.app.admin.flags.Flags.backupServiceSecurityLogEventEnabled;
 import static android.app.admin.flags.Flags.dumpsysPolicyEngineMigrationEnabled;
 import static android.app.admin.flags.Flags.policyEngineMigrationV2Enabled;
 import static android.content.Intent.ACTION_MANAGED_PROFILE_AVAILABLE;
@@ -17926,6 +17930,13 @@
                 || isProfileOwner(caller) || isFinancedDeviceOwner(caller));
 
         toggleBackupServiceActive(caller.getUserId(), enabled);
+
+        if (backupServiceSecurityLogEventEnabled()) {
+            if (SecurityLog.isLoggingEnabled()) {
+                SecurityLog.writeEvent(SecurityLog.TAG_BACKUP_SERVICE_TOGGLED,
+                        caller.getPackageName(), caller.getUserId(), enabled ? 1 : 0);
+            }
+        }
     }
 
     @Override
@@ -23165,6 +23176,90 @@
         }
     }
 
+    private EnforcingAdmin enforceCanCallContentProtectionLocked(
+            ComponentName who, String callerPackageName) {
+        CallerIdentity caller = getCallerIdentity(who, callerPackageName);
+        final int userId = caller.getUserId();
+
+        EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+                who,
+                MANAGE_DEVICE_POLICY_CONTENT_PROTECTION,
+                caller.getPackageName(),
+                userId
+        );
+        if ((isDeviceOwner(caller) || isProfileOwner(caller))
+                && !canDPCManagedUserUseLockTaskLocked(userId)) {
+            throw new SecurityException(
+                    "User " + userId + " is not allowed to use content protection");
+        }
+        return enforcingAdmin;
+    }
+
+    private void enforceCanQueryContentProtectionLocked(
+            ComponentName who, String callerPackageName) {
+        CallerIdentity caller = getCallerIdentity(who, callerPackageName);
+        final int userId = caller.getUserId();
+
+        enforceCanQuery(MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, caller.getPackageName(), userId);
+        if ((isDeviceOwner(caller) || isProfileOwner(caller))
+                && !canDPCManagedUserUseLockTaskLocked(userId)) {
+            throw new SecurityException(
+                    "User " + userId + " is not allowed to use content protection");
+        }
+    }
+
+    @Override
+    public void setContentProtectionPolicy(
+            ComponentName who, String callerPackageName, @ContentProtectionPolicy int policy)
+            throws SecurityException {
+        if (!android.view.contentprotection.flags.Flags.manageDevicePolicyEnabled()) {
+            return;
+        }
+
+        CallerIdentity caller = getCallerIdentity(who, callerPackageName);
+        checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_CONTENT_PROTECTION_POLICY);
+
+        EnforcingAdmin enforcingAdmin;
+        synchronized (getLockObject()) {
+            enforcingAdmin = enforceCanCallContentProtectionLocked(who, caller.getPackageName());
+        }
+
+        if (policy == CONTENT_PROTECTION_DISABLED) {
+            mDevicePolicyEngine.removeLocalPolicy(
+                    PolicyDefinition.CONTENT_PROTECTION,
+                    enforcingAdmin,
+                    caller.getUserId());
+        } else {
+            mDevicePolicyEngine.setLocalPolicy(
+                    PolicyDefinition.CONTENT_PROTECTION,
+                    enforcingAdmin,
+                    new IntegerPolicyValue(policy),
+                    caller.getUserId());
+        }
+    }
+
+    @Override
+    public @ContentProtectionPolicy int getContentProtectionPolicy(
+            ComponentName who, String callerPackageName) {
+        if (!android.view.contentprotection.flags.Flags.manageDevicePolicyEnabled()) {
+            return CONTENT_PROTECTION_DISABLED;
+        }
+
+        CallerIdentity caller = getCallerIdentity(who, callerPackageName);
+        final int userHandle = caller.getUserId();
+
+        synchronized (getLockObject()) {
+            enforceCanQueryContentProtectionLocked(who, caller.getPackageName());
+        }
+        Integer policy = mDevicePolicyEngine.getResolvedPolicy(
+                PolicyDefinition.CONTENT_PROTECTION, userHandle);
+        if (policy == null) {
+            return CONTENT_PROTECTION_DISABLED;
+        } else {
+            return policy;
+        }
+    }
+
     @Override
     public ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy() {
         synchronized (getLockObject()) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 0fc8c5e..27f1834 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -341,6 +341,13 @@
                 PolicyEnforcerCallbacks.setUsbDataSignalingEnabled(value, context),
             new BooleanPolicySerializer());
 
+    static PolicyDefinition<Integer> CONTENT_PROTECTION = new PolicyDefinition<>(
+            new NoArgsPolicyKey(DevicePolicyIdentifiers.CONTENT_PROTECTION_POLICY),
+            new MostRecent<>(),
+            POLICY_FLAG_LOCAL_ONLY_POLICY,
+            (Integer value, Context context, Integer userId, PolicyKey policyKey) -> true,
+            new IntegerPolicySerializer());
+
     private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>();
     private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>();
 
@@ -374,6 +381,8 @@
                 PERSONAL_APPS_SUSPENDED);
         POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.USB_DATA_SIGNALING_POLICY,
                 USB_DATA_SIGNALING);
+        POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.CONTENT_PROTECTION_POLICY,
+                CONTENT_PROTECTION);
 
         // User Restriction Policies
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MODIFY_ACCOUNTS, /* flags= */ 0);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b79d20a..c55d709 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -48,6 +48,7 @@
 import android.content.res.Configuration;
 import android.content.res.Resources.Theme;
 import android.credentials.CredentialManager;
+import android.credentials.flags.Flags;
 import android.database.sqlite.SQLiteCompatibilityWalFlags;
 import android.database.sqlite.SQLiteGlobal;
 import android.graphics.GraphicsStatsService;
@@ -2795,9 +2796,14 @@
                     DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CREDENTIAL,
                     CredentialManager.DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER, true);
             if (credentialManagerEnabled) {
-                t.traceBegin("StartCredentialManagerService");
-                mSystemServiceManager.startService(CREDENTIAL_MANAGER_SERVICE_CLASS);
-                t.traceEnd();
+                if(isWatch &&
+                  !android.credentials.flags.Flags.wearCredentialManagerEnabled()) {
+                  Slog.d(TAG, "CredentialManager disabled on wear.");
+                } else {
+                  t.traceBegin("StartCredentialManagerService");
+                  mSystemServiceManager.startService(CREDENTIAL_MANAGER_SERVICE_CLASS);
+                  t.traceEnd();
+                }
             } else {
                 Slog.d(TAG, "CredentialManager disabled.");
             }
diff --git a/services/java/com/android/server/flags.aconfig b/services/java/com/android/server/flags.aconfig
new file mode 100644
index 0000000..4b578af
--- /dev/null
+++ b/services/java/com/android/server/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.server"
+
+flag {
+     namespace: "system_performance"
+     name: "telemetry_apis_service"
+     description: "Control service portion of telemetry APIs feature."
+     is_fixed_read_only: true
+     bug: "324153471"
+}
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 50e426c..68038fa 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -49,7 +49,6 @@
 import com.android.server.wm.ActivityMetricsLaunchObserverRegistry;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
-import java.util.concurrent.ForkJoinPool;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.TimeUnit;
 
@@ -314,7 +313,7 @@
             Log.w(LOG_TAG, "Couldn't get ArtManagerLocal");
             return;
         }
-        aml.setBatchDexoptStartCallback(ForkJoinPool.commonPool(),
+        aml.setBatchDexoptStartCallback(Runnable::run,
                 (snapshot, reason, defaultPackages, builder, passedSignal) -> {
                     traceOnDex2oatStart();
                 });
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/Android.bp b/services/tests/BackgroundInstallControlServiceTests/host/Android.bp
index d479e52..682ed91 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/Android.bp
+++ b/services/tests/BackgroundInstallControlServiceTests/host/Android.bp
@@ -32,6 +32,7 @@
         ":BackgroundInstallControlServiceTestApp",
         ":BackgroundInstallControlMockApp1",
         ":BackgroundInstallControlMockApp2",
+        ":BackgroundInstallControlMockApp3",
     ],
     test_suites: [
         "general-tests",
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/AndroidTest.xml b/services/tests/BackgroundInstallControlServiceTests/host/AndroidTest.xml
index 1e7a78a..a352851 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/AndroidTest.xml
+++ b/services/tests/BackgroundInstallControlServiceTests/host/AndroidTest.xml
@@ -34,6 +34,9 @@
         <option name="push-file"
                 key="BackgroundInstallControlMockApp2.apk"
                 value="/data/local/tmp/BackgroundInstallControlMockApp2.apk" />
+        <option name="push-file"
+            key="BackgroundInstallControlMockApp3.apk"
+            value="/data/local/tmp/BackgroundInstallControlMockApp3.apk" />
     </target_preparer>
 
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java b/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java
index c99e712..5092a46 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java
+++ b/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java
@@ -68,6 +68,20 @@
         assertThat(getDevice().getAppPackageInfo(MOCK_PACKAGE_NAME_2)).isNull();
     }
 
+    @Test
+    public void testRegisterCallback() throws Exception {
+        runDeviceTest(
+                "BackgroundInstallControlServiceTest",
+                "testRegisterBackgroundInstallControlCallback");
+    }
+
+    @Test
+    public void testUnregisterCallback() throws Exception {
+        runDeviceTest(
+                "BackgroundInstallControlServiceTest",
+                "testUnregisterBackgroundInstallControlCallback");
+    }
+
     private void installPackage(String path) throws DeviceNotAvailableException {
         String cmd = "pm install -t --force-queryable " + path;
         CommandResult result = getDevice().executeShellV2Command(cmd);
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java
index b23f591..ac041f4 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java
+++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java
@@ -16,38 +16,59 @@
 
 package com.android.server.pm.test.app;
 
-import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES;
-
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.IBackgroundInstallControlService;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
+import android.os.Bundle;
+import android.os.IRemoteCallback;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.util.Pair;
 
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.ThrowingRunnable;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.FileInputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 @RunWith(AndroidJUnit4.class)
 public class BackgroundInstallControlServiceTest {
     private static final String TAG = "BackgroundInstallControlServiceTest";
+    private static final String ACTION_INSTALL_COMMIT =
+            "com.android.server.pm.test.app.BackgroundInstallControlServiceTest"
+                    + ".ACTION_INSTALL_COMMIT";
     private static final String MOCK_PACKAGE_NAME = "com.android.servicestests.apps.bicmockapp3";
 
+    private static final String TEST_DATA_DIR = "/data/local/tmp/";
+
+    private static final String MOCK_APK_FILE = "BackgroundInstallControlMockApp3.apk";
     private IBackgroundInstallControlService mIBics;
 
     @Before
@@ -74,10 +95,9 @@
                                         PackageManager.MATCH_ALL, Process.myUserHandle()
                                                 .getIdentifier());
                             } catch (RemoteException e) {
-                                throw new RuntimeException(e);
+                                throw e.rethrowFromSystemServer();
                             }
-                        },
-                        GET_BACKGROUND_INSTALLED_PACKAGES);
+                        });
         assertThat(slice).isNotNull();
 
         var packageList = slice.getList();
@@ -94,4 +114,150 @@
                         .collect(Collectors.toSet());
         assertThat(actualPackageNames).containsExactlyElementsIn(expectedPackageNames);
     }
+
+    @Test
+    public void testRegisterBackgroundInstallControlCallback()
+            throws Exception {
+        String testPackageName = "test";
+        int testUserId = 1;
+        ArrayList<Pair<String, Integer>> sharedResource = new ArrayList<>();
+        IRemoteCallback testCallback =
+                new IRemoteCallback.Stub() {
+                    private final ArrayList<Pair<String, Integer>> mArray = sharedResource;
+
+                    @Override
+                    public void sendResult(Bundle data) throws RemoteException {
+                        mArray.add(new Pair(testPackageName, testUserId));
+                    }
+                };
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mIBics,
+                (bics) -> {
+                    try {
+                        bics.registerBackgroundInstallCallback(testCallback);
+                    } catch (RemoteException e) {
+                        throw e.rethrowFromSystemServer();
+                    }
+                });
+        installPackage(TEST_DATA_DIR + MOCK_APK_FILE, MOCK_PACKAGE_NAME);
+
+        assertUntil(() -> sharedResource.size() == 1, 2000);
+        assertThat(sharedResource.get(0).first).isEqualTo(testPackageName);
+        assertThat(sharedResource.get(0).second).isEqualTo(testUserId);
+    }
+
+    @Test
+    public void testUnregisterBackgroundInstallControlCallback() {
+        String testValue = "test";
+        ArrayList<String> sharedResource = new ArrayList<>();
+        IRemoteCallback testCallback =
+                new IRemoteCallback.Stub() {
+                    private final ArrayList<String> mArray = sharedResource;
+
+                    @Override
+                    public void sendResult(Bundle data) throws RemoteException {
+                        mArray.add(testValue);
+                    }
+                };
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mIBics,
+                (bics) -> {
+                    try {
+                        bics.registerBackgroundInstallCallback(testCallback);
+                        bics.unregisterBackgroundInstallCallback(testCallback);
+                    } catch (RemoteException e) {
+                        throw e.rethrowFromSystemServer();
+                    }
+                });
+        installPackage(TEST_DATA_DIR + MOCK_APK_FILE, MOCK_PACKAGE_NAME);
+
+        assertUntil(sharedResource::isEmpty, 2000);
+    }
+
+    private static boolean installPackage(String apkPath, String packageName) {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        final CountDownLatch installLatch = new CountDownLatch(1);
+        final BroadcastReceiver installReceiver =
+                new BroadcastReceiver() {
+                    public void onReceive(Context context, Intent intent) {
+                        int packageInstallStatus =
+                                intent.getIntExtra(
+                                        PackageInstaller.EXTRA_STATUS,
+                                        PackageInstaller.STATUS_FAILURE_INVALID);
+                        if (packageInstallStatus == PackageInstaller.STATUS_SUCCESS) {
+                            installLatch.countDown();
+                        }
+                    }
+                };
+        final IntentFilter intentFilter = new IntentFilter(ACTION_INSTALL_COMMIT);
+        context.registerReceiver(installReceiver, intentFilter, Context.RECEIVER_EXPORTED);
+
+        PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
+        PackageInstaller.SessionParams params =
+                new PackageInstaller.SessionParams(
+                        PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+        params.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED);
+        try {
+            int sessionId = packageInstaller.createSession(params);
+            PackageInstaller.Session session = packageInstaller.openSession(sessionId);
+            OutputStream out = session.openWrite(packageName, 0, -1);
+            FileInputStream fis = new FileInputStream(apkPath);
+            byte[] buffer = new byte[65536];
+            int size;
+            while ((size = fis.read(buffer)) != -1) {
+                out.write(buffer, 0, size);
+            }
+            session.fsync(out);
+            fis.close();
+            out.close();
+
+            runWithShellPermissionIdentity(
+                    () -> {
+                        session.commit(createPendingIntent(context).getIntentSender());
+                        installLatch.await(5, TimeUnit.SECONDS);
+                    });
+            return true;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static PendingIntent createPendingIntent(Context context) {
+        PendingIntent pendingIntent =
+                PendingIntent.getBroadcast(
+                        context,
+                        1,
+                        new Intent(ACTION_INSTALL_COMMIT)
+                                .setPackage(
+                                        BackgroundInstallControlServiceTest.class.getPackageName()),
+                        PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE);
+        return pendingIntent;
+    }
+
+    private static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable command)
+            throws Exception {
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity();
+        try {
+            command.run();
+        } finally {
+            InstrumentationRegistry.getInstrumentation()
+                    .getUiAutomation()
+                    .dropShellPermissionIdentity();
+        }
+    }
+
+    private static void assertUntil(Supplier<Boolean> condition, int timeoutMs) {
+        long endTime = System.currentTimeMillis() + timeoutMs;
+        while (System.currentTimeMillis() <= endTime) {
+            if (condition.get()) return;
+            try {
+                Thread.sleep(10);
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+        assertThat(condition.get()).isTrue();
+    }
 }
\ No newline at end of file
diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/Android.bp b/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/Android.bp
index 7804f4c..39b0ff7 100644
--- a/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/Android.bp
+++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/Android.bp
@@ -50,3 +50,11 @@
         "--rename-manifest-package com.android.servicestests.apps.bicmockapp2",
     ],
 }
+
+android_test_helper_app {
+    name: "BackgroundInstallControlMockApp3",
+    defaults: ["bic-mock-app-defaults"],
+    aaptflags: [
+        "--rename-manifest-package com.android.servicestests.apps.bicmockapp3",
+    ],
+}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
index 811b086..7aa2ff5 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt
@@ -22,7 +22,9 @@
 import android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DENIED
 import android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED
 import android.content.pm.PackageManager
+import android.content.pm.verify.domain.DomainSet
 import android.os.Parcel
+import android.os.Process
 import android.platform.test.annotations.Presubmit
 import android.util.AtomicFile
 import android.util.Slog
@@ -173,7 +175,7 @@
             /* stagingManager */ null,
             /* sessionId */ sessionId,
             /* userId */ 456,
-            /* installerUid */ -1,
+            /* installerUid */ Process.myUid(),
             /* installSource */ installSource,
             /* sessionParams */ params,
             /* createdMillis */ 0L,
@@ -183,8 +185,8 @@
             /* files */ null,
             /* checksums */ null,
             /* prepared */ true,
-            /* committed */ true,
-            /* destroyed */ staged,
+            /* committed */ false,
+            /* destroyed */ false,
             /* sealed */ false, // Setting to true would trigger some PM logic.
             /* childSessionIds */ childSessionIds.toIntArray(),
             /* parentSessionId */ parentSessionId,
@@ -192,7 +194,8 @@
             /* isFailed */ false,
             /* isApplied */ false,
             /* stagedSessionErrorCode */ PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
-            /* stagedSessionErrorMessage */ "some error"
+            /* stagedSessionErrorMessage */ "some error",
+            /* preVerifiedDomains */ DomainSet(setOf("com.foo", "com.bar"))
         )
     }
 
@@ -332,6 +335,7 @@
         assertThat(expected.parentSessionId).isEqualTo(actual.parentSessionId)
         assertThat(expected.childSessionIds).asList()
             .containsExactlyElementsIn(actual.childSessionIds.toList())
+        assertThat(expected.preVerifiedDomains).isEqualTo(actual.preVerifiedDomains)
     }
 
     private fun assertInstallSourcesEquivalent(expected: InstallSource, actual: InstallSource) {
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 ef9c62f..cfe701f 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
@@ -272,7 +272,8 @@
         AndroidPackage::hasPreserveLegacyExternalStorage,
         AndroidPackage::hasRequestForegroundServiceExemption,
         AndroidPackage::hasRequestRawExternalStorageAccess,
-        AndroidPackage::isUpdatableSystem
+        AndroidPackage::isUpdatableSystem,
+        AndroidPackage::getEmergencyInstaller
     )
 
     override fun extraParams() = listOf(
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index d307608..b374af6 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -149,8 +149,8 @@
                 callingUidInt.set(it.callingUid)
                 callingUserIdInt.set(it.callingUserId)
                 service.proxy = it.proxy
-                service.addPackage(visiblePkgState)
-                service.addPackage(invisiblePkgState)
+                service.addPackage(visiblePkgState, null)
+                service.addPackage(invisiblePkgState, null)
                 service.block(it)
             }
 
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
index 5edf30a3..a8100af 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
@@ -566,7 +566,7 @@
     }
 
     private fun DomainVerificationService.addPackages(vararg pkgStates: PackageStateInternal) =
-        pkgStates.forEach(::addPackage)
+        pkgStates.forEach {pkg: PackageStateInternal -> addPackage(pkg, null)}
 
     private fun makeManager(service: DomainVerificationService, userId: Int) =
         DomainVerificationManager(
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
index 85f0125..e0407c1 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
@@ -21,6 +21,7 @@
 import android.content.pm.Signature
 import android.content.pm.SigningDetails
 import android.content.pm.verify.domain.DomainOwner
+import android.content.pm.verify.domain.DomainSet
 import android.content.pm.verify.domain.DomainVerificationInfo.STATE_MODIFIABLE_VERIFIED
 import android.content.pm.verify.domain.DomainVerificationInfo.STATE_NO_RESPONSE
 import android.content.pm.verify.domain.DomainVerificationInfo.STATE_SUCCESS
@@ -48,16 +49,16 @@
 import com.android.server.testutils.spy
 import com.android.server.testutils.whenever
 import com.google.common.truth.Truth.assertThat
-import java.io.ByteArrayInputStream
-import java.io.ByteArrayOutputStream
-import java.security.PublicKey
-import java.util.UUID
 import org.junit.Test
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyLong
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mockito.doReturn
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.security.PublicKey
+import java.util.UUID
 
 class DomainVerificationPackageTest {
 
@@ -88,7 +89,7 @@
     @Test
     fun addPackageFirstTime() {
         val service = makeService(pkg1, pkg2)
-        service.addPackage(pkg1)
+        service.addPackage(pkg1, null)
         val info = service.getInfo(pkg1.packageName)
         assertThat(info.packageName).isEqualTo(pkg1.packageName)
         assertThat(info.identifier).isEqualTo(pkg1.domainSetId)
@@ -120,8 +121,8 @@
             systemConfiguredPackageNames = ArraySet(setOf(pkg1.packageName, pkg2.packageName)),
             pkg1, pkg2
         )
-        service.addPackage(pkg1)
-        service.addPackage(pkg2)
+        service.addPackage(pkg1, null)
+        service.addPackage(pkg2, null)
 
         service.getInfo(pkg1.packageName).apply {
             assertThat(packageName).isEqualTo(pkg1.packageName)
@@ -198,7 +199,7 @@
         val service = makeService(pkg1, pkg2)
         val computer = mockComputer(pkg1, pkg2)
         service.restoreSettings(computer, Xml.resolvePullParser(xml.byteInputStream()))
-        service.addPackage(pkg1)
+        service.addPackage(pkg1, null)
         val info = service.getInfo(pkg1.packageName)
         assertThat(info.packageName).isEqualTo(pkg1.packageName)
         assertThat(info.identifier).isEqualTo(pkg1.domainSetId)
@@ -248,7 +249,7 @@
         val service = makeService(pkg1, pkg2)
         val computer = mockComputer(pkg1, pkg2)
         service.restoreSettings(computer, Xml.resolvePullParser(xml.byteInputStream()))
-        service.addPackage(pkg1)
+        service.addPackage(pkg1, null)
         val info = service.getInfo(pkg1.packageName)
         assertThat(info.packageName).isEqualTo(pkg1.packageName)
         assertThat(info.identifier).isEqualTo(pkg1.domainSetId)
@@ -307,7 +308,7 @@
             service.readSettings(computer, Xml.resolvePullParser(it))
         }
 
-        service.addPackage(pkg1)
+        service.addPackage(pkg1, null)
 
         assertAddPackageActivePendingRestoredState(service)
     }
@@ -321,7 +322,7 @@
             service.readSettings(computer, Xml.resolvePullParser(it))
         }
 
-        service.addPackage(pkg1)
+        service.addPackage(pkg1, null)
 
         val userState = service.getUserState(pkg1.packageName)
         assertThat(userState.packageName).isEqualTo(pkg1.packageName)
@@ -345,11 +346,55 @@
             service.restoreSettings(computer, Xml.resolvePullParser(it))
         }
 
-        service.addPackage(pkg1)
+        service.addPackage(pkg1, null)
 
         assertAddPackageActivePendingRestoredState(service, expectRestore = true)
     }
 
+    @Test
+    fun addPackageWithPreVerifiedDomains() {
+        val service = makeService(pkg1)
+        val pkg1 = mockPkgState(
+                PKG_ONE,
+                UUID_ONE,
+                SIGNATURE_ONE,
+                autoVerifyDomains = listOf(DOMAIN_1, DOMAIN_2),
+                otherDomains = listOf(DOMAIN_3, DOMAIN_4))
+        service.addPackage(pkg1, DomainSet(setOf(DOMAIN_1, DOMAIN_3)))
+        val info = service.getInfo(pkg1.packageName)
+        assertThat(info.packageName).isEqualTo(pkg1.packageName)
+        assertThat(info.identifier).isEqualTo(pkg1.domainSetId)
+        // Test that DOMAIN_1 is pre-verified and DOMAIN_3 is ignored because autoVerify=false
+        assertThat(info.hostToStateMap).containsExactlyEntriesIn(mapOf(
+                DOMAIN_1 to STATE_MODIFIABLE_VERIFIED,
+                DOMAIN_2 to STATE_NO_RESPONSE,
+        ))
+
+        val userState = service.getUserState(pkg1.packageName)
+        assertThat(userState.packageName).isEqualTo(pkg1.packageName)
+        assertThat(userState.identifier).isEqualTo(pkg1.domainSetId)
+        assertThat(userState.isLinkHandlingAllowed).isEqualTo(true)
+        assertThat(userState.user.identifier).isEqualTo(USER_ID)
+        assertThat(userState.hostToStateMap).containsExactlyEntriesIn(mapOf(
+                DOMAIN_1 to DOMAIN_STATE_VERIFIED,
+                DOMAIN_2 to DOMAIN_STATE_NONE,
+        ))
+
+        assertThat(service.queryValidVerificationPackageNames())
+                .containsExactly(pkg1.packageName)
+
+        // Test that the pre-verified state can be overwritten to be disapproved
+        service.setDomainVerificationStatusInternal(
+                PKG_ONE,
+                DomainVerificationState.STATE_DENIED,
+                ArraySet(setOf(DOMAIN_1, DOMAIN_2)))
+        val infoUpdated = service.getInfo(pkg1.packageName)
+        assertThat(infoUpdated.hostToStateMap).containsExactlyEntriesIn(mapOf(
+                DOMAIN_1 to STATE_UNMODIFIABLE,
+                DOMAIN_2 to STATE_UNMODIFIABLE,
+        ))
+    }
+
     /**
      * Shared string that contains invalid [DOMAIN_3] and [DOMAIN_4] which should be stripped from
      * the final state.
@@ -447,7 +492,7 @@
 
         val map = mutableMapOf<String, PackageStateInternal>()
         val service = makeService { map[it] }
-        service.addPackage(pkgBefore)
+        service.addPackage(pkgBefore, null)
 
         // Only insert the package after addPackage call to ensure the service doesn't access
         // a live package inside the addPackage logic. It should only use the provided input.
@@ -482,7 +527,7 @@
 
         map[pkgName] = pkgAfter
 
-        service.migrateState(pkgBefore, pkgAfter)
+        service.migrateState(pkgBefore, pkgAfter, null)
 
         assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
                 DOMAIN_1 to STATE_UNMODIFIABLE,
@@ -503,7 +548,7 @@
 
         val map = mutableMapOf<String, PackageStateInternal>()
         val service = makeService { map[it] }
-        service.addPackage(pkgBefore)
+        service.addPackage(pkgBefore, null)
 
         // Only insert the package after addPackage call to ensure the service doesn't access
         // a live package inside the addPackage logic. It should only use the provided input.
@@ -522,7 +567,7 @@
         // Now remove the package because migrateState shouldn't use it either
         map.remove(pkgName)
 
-        service.migrateState(pkgBefore, pkgAfter)
+        service.migrateState(pkgBefore, pkgAfter, null)
 
         map[pkgName] = pkgAfter
 
@@ -550,7 +595,7 @@
 
         val map = mutableMapOf<String, PackageStateInternal>()
         val service = makeService { map[it] }
-        service.addPackage(pkgBefore)
+        service.addPackage(pkgBefore, null)
 
         // Only insert the package after addPackage call to ensure the service doesn't access
         // a live package inside the addPackage logic. It should only use the provided input.
@@ -571,7 +616,7 @@
         // Now remove the package because migrateState shouldn't use it either
         map.remove(pkgName)
 
-        service.migrateState(pkgBefore, pkgAfter)
+        service.migrateState(pkgBefore, pkgAfter, null)
 
         map[pkgName] = pkgAfter
 
@@ -596,7 +641,7 @@
 
         val map = mutableMapOf<String, PackageStateInternal>()
         val service = makeService { map[it] }
-        service.addPackage(pkgBefore)
+        service.addPackage(pkgBefore, null)
 
         // Only insert the package after addPackage call to ensure the service doesn't access
         // a live package inside the addPackage logic. It should only use the provided input.
@@ -615,7 +660,7 @@
         // Now remove the package because migrateState shouldn't use it either
         map.remove(pkgName)
 
-        service.migrateState(pkgBefore, pkgAfter)
+        service.migrateState(pkgBefore, pkgAfter, null)
 
         map[pkgName] = pkgAfter
 
@@ -640,7 +685,7 @@
 
         val map = mutableMapOf<String, PackageStateInternal>()
         val service = makeService { map[it] }
-        service.addPackage(pkgBefore)
+        service.addPackage(pkgBefore, null)
 
         // Only insert the package after addPackage call to ensure the service doesn't access
         // a live package inside the addPackage logic. It should only use the provided input.
@@ -667,7 +712,7 @@
         // Now remove the package because migrateState shouldn't use it either
         map.remove(pkgName)
 
-        service.migrateState(pkgBefore, pkgAfter)
+        service.migrateState(pkgBefore, pkgAfter, null)
 
         map[pkgName] = pkgAfter
 
@@ -685,6 +730,30 @@
     }
 
     @Test
+    fun migratePackageWithPreVerifiedDomains() {
+        val pkgName = PKG_ONE
+        val pkgBefore = mockPkgState(pkgName, UUID_ONE, SIGNATURE_ONE, emptyList())
+        val pkgAfter = mockPkgState(pkgName, UUID_TWO, SIGNATURE_TWO, listOf(DOMAIN_1, DOMAIN_2))
+
+        val map = mutableMapOf<String, PackageStateInternal>()
+        val service = makeService { map[it] }
+        service.addPackage(pkgBefore, null)
+        service.migrateState(pkgBefore, pkgAfter, DomainSet(setOf(DOMAIN_1, DOMAIN_3)))
+
+        map[pkgName] = pkgAfter
+
+        assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
+                DOMAIN_1 to STATE_MODIFIABLE_VERIFIED,
+                DOMAIN_2 to STATE_NO_RESPONSE,
+        ))
+        assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
+                DOMAIN_1 to DOMAIN_STATE_VERIFIED,
+                DOMAIN_2 to DOMAIN_STATE_NONE,
+        ))
+        assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName)
+    }
+
+    @Test
     fun backupAndRestore() {
         // This test acts as a proxy for true user restore through PackageManager,
         // as that's much harder to test for real.
@@ -694,8 +763,8 @@
             listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3))
         val serviceBefore = makeService(pkg1, pkg2)
         val computerBefore = mockComputer(pkg1, pkg2)
-        serviceBefore.addPackage(pkg1)
-        serviceBefore.addPackage(pkg2)
+        serviceBefore.addPackage(pkg1, null)
+        serviceBefore.addPackage(pkg2, null)
 
         serviceBefore.setStatus(pkg1.domainSetId, setOf(DOMAIN_1), STATE_SUCCESS)
         serviceBefore.setDomainVerificationLinkHandlingAllowed(pkg1.packageName, false, 10)
@@ -748,8 +817,8 @@
 
         val serviceAfter = makeService(pkg1, pkg2)
         val computerAfter = mockComputer(pkg1, pkg2)
-        serviceAfter.addPackage(pkg1)
-        serviceAfter.addPackage(pkg2)
+        serviceAfter.addPackage(pkg1, null)
+        serviceAfter.addPackage(pkg2, null)
 
         // Check the state is default before the restoration applies
         listOf(0, 10).forEach {
@@ -858,8 +927,8 @@
         )
 
         val service = makeService(pkg1, pkg2)
-        service.addPackage(pkg1)
-        service.addPackage(pkg2)
+        service.addPackage(pkg1, null)
+        service.addPackage(pkg2, null)
 
         // Approve domain 1, 3, and 4 for package 2 for both users
         USER_IDS.forEach {
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
index a5c4f6c..9748307 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
@@ -106,7 +106,7 @@
             fun service(name: String, block: DomainVerificationService.() -> Unit) =
                 Params(makeService, name) { service ->
                     service.proxy = proxy
-                    service.addPackage(mockPkgState())
+                    service.addPackage(mockPkgState(), null)
                     service.block()
                 }
 
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
index ae570a3..56ab841 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
@@ -97,8 +97,8 @@
                     }
                 }
             })
-            addPackage(pkg1)
-            addPackage(pkg2)
+            addPackage(pkg1, null)
+            addPackage(pkg2, null)
 
             // Starting state for all tests is to have domain 1 enabled for the first package
             setDomainVerificationUserSelection(UUID_ONE, setOf(DOMAIN_ONE), true, USER_ID)
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index d928306..9f97551 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -73,6 +73,7 @@
         "testng",
         "compatibility-device-util-axt",
         "flag-junit",
+        "am_flags_lib",
     ],
 
     libs: [
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index f875f65..fb47aa8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -38,6 +38,8 @@
 import android.os.HandlerThread;
 import android.os.TestLooperManager;
 import android.os.UserHandle;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
 import android.util.SparseArray;
 
@@ -93,6 +95,9 @@
             .spyStatic(ProcessList.class)
             .build();
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[1];
 
     @Mock
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 75409d9..3f6117b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -75,6 +75,8 @@
 import android.os.PowerExemptionManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pair;
 import android.util.proto.ProtoOutputStream;
@@ -135,6 +137,17 @@
             ProcessStartBehavior.SUCCESS);
 
     /**
+     * Map of processes to behaviors indicating how the new processes should behave as needed
+     * by the tests.
+     */
+    private ArrayMap<String, ProcessBehavior> mNewProcessBehaviors = new ArrayMap<>();
+
+    /**
+     * Map of processes to behaviors indicating how the new process starts should result in.
+     */
+    private ArrayMap<String, ProcessStartBehavior> mNewProcessStartBehaviors = new ArrayMap<>();
+
+    /**
      * Collection of all active processes during current test run.
      */
     private List<ProcessRecord> mActiveProcesses = new ArrayList<>();
@@ -161,15 +174,17 @@
             Log.v(TAG, "Intercepting startProcessLocked() for "
                     + Arrays.toString(invocation.getArguments()));
             assertHealth();
-            final ProcessStartBehavior behavior = mNextProcessStartBehavior
-                    .getAndSet(ProcessStartBehavior.SUCCESS);
+            final String processName = invocation.getArgument(0);
+            final ProcessStartBehavior behavior = mNewProcessStartBehaviors.getOrDefault(
+                    processName, mNextProcessStartBehavior.getAndSet(ProcessStartBehavior.SUCCESS));
             if (behavior == ProcessStartBehavior.FAIL_NULL) {
                 return null;
             }
-            final String processName = invocation.getArgument(0);
             final ApplicationInfo ai = invocation.getArgument(1);
+            final ProcessBehavior processBehavior = mNewProcessBehaviors.getOrDefault(
+                    processName, ProcessBehavior.NORMAL);
             final ProcessRecord res = makeActiveProcessRecord(ai, processName,
-                    ProcessBehavior.NORMAL, UnaryOperator.identity());
+                    processBehavior, UnaryOperator.identity());
             final ProcessRecord deliverRes;
             switch (behavior) {
                 case SUCCESS_PREDECESSOR:
@@ -274,6 +289,8 @@
             assertEquals(app.toShortString(), ProcessList.SCHED_GROUP_UNDEFINED,
                     mQueue.getPreferredSchedulingGroupLocked(app));
         }
+        mNewProcessBehaviors.clear();
+        mNewProcessStartBehaviors.clear();
     }
 
     @Override
@@ -955,6 +972,40 @@
     }
 
     /**
+     * Verify that we handle manifest receivers in a process that always
+     * responds with {@link DeadObjectException} even after restarting.
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_AVOID_REPEATED_BCAST_RE_ENQUEUES)
+    public void testRepeatedDead_Manifest() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        mNewProcessBehaviors.put(PACKAGE_GREEN, ProcessBehavior.DEAD);
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+                withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
+                withPriority(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE), 0))));
+        final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+        enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
+        waitForIdle();
+
+        final ProcessRecord receiverGreenApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
+                getUidForPackage(PACKAGE_GREEN));
+        // Modern queue always kills the target process when broadcast delivery fails, where as
+        // the legacy queue leaves the process killing task to AMS
+        if (mImpl == Impl.MODERN) {
+            assertNull(receiverGreenApp);
+        }
+        final ProcessRecord receiverBlueApp = mAms.getProcessRecordLocked(PACKAGE_BLUE,
+                getUidForPackage(PACKAGE_BLUE));
+        verifyScheduleReceiver(receiverBlueApp, airplane);
+        final ProcessRecord receiverYellowApp = mAms.getProcessRecordLocked(PACKAGE_YELLOW,
+                getUidForPackage(PACKAGE_YELLOW));
+        verifyScheduleReceiver(receiverYellowApp, timezone);
+    }
+
+    /**
      * Verify that we handle the system failing to start a process.
      */
     @Test
@@ -1142,6 +1193,49 @@
     }
 
     /**
+     * Verify that when BroadcastQueue doesn't get notified when a process gets killed repeatedly,
+     * it doesn't get stuck.
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_AVOID_REPEATED_BCAST_RE_ENQUEUES)
+    public void testRepeatedKillWithoutNotify() throws Exception {
+        // Legacy queue does not handle repeated kills that don't get notified.
+        Assume.assumeTrue(mImpl == Impl.MODERN);
+
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+
+        mNewProcessStartBehaviors.put(PACKAGE_GREEN, ProcessStartBehavior.KILLED_WITHOUT_NOTIFY);
+
+        final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+                withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
+                withPriority(makeRegisteredReceiver(receiverBlueApp), 5),
+                withPriority(makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW), 0))));
+
+        final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+        enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_ORANGE, CLASS_ORANGE))));
+
+        waitForIdle();
+        final ProcessRecord receiverGreenApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
+                getUidForPackage(PACKAGE_GREEN));
+        final ProcessRecord receiverYellowApp = mAms.getProcessRecordLocked(PACKAGE_YELLOW,
+                getUidForPackage(PACKAGE_YELLOW));
+        final ProcessRecord receiverOrangeApp = mAms.getProcessRecordLocked(PACKAGE_ORANGE,
+                getUidForPackage(PACKAGE_ORANGE));
+
+        // Modern queue always kills the target process when broadcast delivery fails, where as
+        // the legacy queue leaves the process killing task to AMS
+        if (mImpl == Impl.MODERN) {
+            assertNull(receiverGreenApp);
+        }
+        verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
+        verifyScheduleReceiver(times(1), receiverYellowApp, airplane);
+        verifyScheduleReceiver(times(1), receiverOrangeApp, timezone);
+    }
+
+    /**
      * Verify that a broadcast sent to a frozen app, which gets killed as part of unfreezing
      * process due to pending sync binder transactions, is delivered as expected.
      */
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 3355a6c..fab7610 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -44,6 +44,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
@@ -57,6 +58,7 @@
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.PermissionChecker;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
@@ -65,7 +67,9 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkPolicyManager;
+import android.os.BatteryManager;
 import android.os.BatteryManagerInternal;
+import android.os.BatteryManagerInternal.ChargingPolicyChangeListener;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -89,6 +93,8 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
@@ -107,6 +113,8 @@
     @Mock
     private ActivityManagerInternal mActivityMangerInternal;
     @Mock
+    private BatteryManagerInternal mBatteryManagerInternal;
+    @Mock
     private Context mContext;
     @Mock
     private PackageManagerInternal mPackageManagerInternal;
@@ -114,6 +122,8 @@
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
+    private ChargingPolicyChangeListener mChargingPolicyChangeListener;
+
     private class TestJobSchedulerService extends JobSchedulerService {
         TestJobSchedulerService(Context context) {
             super(context);
@@ -137,7 +147,7 @@
                 .when(() -> LocalServices.getService(ActivityManagerInternal.class));
         doReturn(mock(AppStandbyInternal.class))
                 .when(() -> LocalServices.getService(AppStandbyInternal.class));
-        doReturn(mock(BatteryManagerInternal.class))
+        doReturn(mBatteryManagerInternal)
                 .when(() -> LocalServices.getService(BatteryManagerInternal.class));
         doReturn(mPackageManagerInternal)
                 .when(() -> LocalServices.getService(PackageManagerInternal.class));
@@ -186,8 +196,17 @@
         // Called by DeviceIdlenessTracker
         when(mContext.getSystemService(UiModeManager.class)).thenReturn(mock(UiModeManager.class));
 
+        setChargingPolicy(Integer.MIN_VALUE);
+
+        ArgumentCaptor<ChargingPolicyChangeListener> chargingPolicyChangeListenerCaptor =
+                ArgumentCaptor.forClass(ChargingPolicyChangeListener.class);
+
         mService = new TestJobSchedulerService(mContext);
         mService.waitOnAsyncLoadingForTesting();
+
+        verify(mBatteryManagerInternal).registerChargingPolicyChangeListener(
+                chargingPolicyChangeListenerCaptor.capture());
+        mChargingPolicyChangeListener = chargingPolicyChangeListenerCaptor.getValue();
     }
 
     @After
@@ -1718,6 +1737,127 @@
         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
     }
 
+    @Test
+    public void testBatteryStateTrackerRegistersForImportantIntents() {
+        verify(mContext).registerReceiver(any(), ArgumentMatchers.argThat(filter -> true
+                && filter.hasAction(BatteryManager.ACTION_CHARGING)
+                && filter.hasAction(BatteryManager.ACTION_DISCHARGING)
+                && filter.hasAction(Intent.ACTION_BATTERY_LEVEL_CHANGED)
+                && filter.hasAction(Intent.ACTION_BATTERY_LOW)
+                && filter.hasAction(Intent.ACTION_BATTERY_OKAY)
+                && filter.hasAction(Intent.ACTION_POWER_CONNECTED)
+                && filter.hasAction(Intent.ACTION_POWER_DISCONNECTED)));
+    }
+
+    @Test
+    public void testIsCharging_standardChargingIntent() {
+        JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
+
+        Intent chargingIntent = new Intent(BatteryManager.ACTION_CHARGING);
+        Intent dischargingIntent = new Intent(BatteryManager.ACTION_DISCHARGING);
+        tracker.onReceive(mContext, dischargingIntent);
+        assertFalse(tracker.isCharging());
+        assertFalse(mService.isBatteryCharging());
+
+        tracker.onReceive(mContext, chargingIntent);
+        assertTrue(tracker.isCharging());
+        assertTrue(mService.isBatteryCharging());
+
+        tracker.onReceive(mContext, dischargingIntent);
+        assertFalse(tracker.isCharging());
+        assertFalse(mService.isBatteryCharging());
+    }
+
+    @Test
+    public void testIsCharging_adaptiveCharging_batteryTooLow() {
+        JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
+
+        tracker.onReceive(mContext, new Intent(BatteryManager.ACTION_DISCHARGING));
+        assertFalse(tracker.isCharging());
+        assertFalse(mService.isBatteryCharging());
+
+        setBatteryLevel(15);
+        setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
+        assertFalse(tracker.isCharging());
+        assertFalse(mService.isBatteryCharging());
+
+        tracker.onReceive(mContext, new Intent(Intent.ACTION_POWER_CONNECTED));
+
+        setBatteryLevel(70);
+        assertTrue(tracker.isCharging());
+        assertTrue(mService.isBatteryCharging());
+    }
+
+    @Test
+    public void testIsCharging_adaptiveCharging_chargeBelowThreshold() {
+        JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
+
+        setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
+        tracker.onReceive(mContext, new Intent(Intent.ACTION_POWER_CONNECTED));
+        setBatteryLevel(5);
+
+        tracker.onReceive(mContext, new Intent(BatteryManager.ACTION_CHARGING));
+        assertTrue(tracker.isCharging());
+        assertTrue(mService.isBatteryCharging());
+
+        for (int level = 5; level < 80; ++level) {
+            setBatteryLevel(level);
+            assertTrue(tracker.isCharging());
+            assertTrue(mService.isBatteryCharging());
+        }
+    }
+
+    @Test
+    public void testIsCharging_adaptiveCharging_dischargeAboveThreshold() {
+        JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
+
+        setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
+        tracker.onReceive(mContext, new Intent(Intent.ACTION_POWER_CONNECTED));
+        setBatteryLevel(80);
+
+        tracker.onReceive(mContext, new Intent(BatteryManager.ACTION_DISCHARGING));
+        assertTrue(tracker.isCharging());
+        assertTrue(mService.isBatteryCharging());
+
+        for (int level = 80; level > 60; --level) {
+            setBatteryLevel(level);
+            assertEquals(level >= 70, tracker.isCharging());
+            assertEquals(level >= 70, mService.isBatteryCharging());
+        }
+    }
+
+    @Test
+    public void testIsCharging_adaptiveCharging_notPluggedIn() {
+        JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
+
+        tracker.onReceive(mContext, new Intent(Intent.ACTION_POWER_DISCONNECTED));
+        tracker.onReceive(mContext, new Intent(BatteryManager.ACTION_DISCHARGING));
+        assertFalse(tracker.isCharging());
+        assertFalse(mService.isBatteryCharging());
+
+        setBatteryLevel(15);
+        setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
+        assertFalse(tracker.isCharging());
+        assertFalse(mService.isBatteryCharging());
+
+        setBatteryLevel(50);
+        setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
+        assertFalse(tracker.isCharging());
+        assertFalse(mService.isBatteryCharging());
+
+        setBatteryLevel(70);
+        assertFalse(tracker.isCharging());
+        assertFalse(mService.isBatteryCharging());
+
+        setBatteryLevel(95);
+        assertFalse(tracker.isCharging());
+        assertFalse(mService.isBatteryCharging());
+
+        setBatteryLevel(100);
+        assertFalse(tracker.isCharging());
+        assertFalse(mService.isBatteryCharging());
+    }
+
     /** Tests that rare job batching works as expected. */
     @Test
     public void testConnectivityJobBatching() {
@@ -2257,4 +2397,17 @@
         assertFalse(mService.getPendingJobQueue().contains(job2b));
         assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job2b));
     }
+
+    private void setBatteryLevel(int level) {
+        doReturn(level).when(mBatteryManagerInternal).getBatteryLevel();
+        mService.mBatteryStateTracker
+                .onReceive(mContext, new Intent(Intent.ACTION_BATTERY_LEVEL_CHANGED));
+    }
+
+    private void setChargingPolicy(int policy) {
+        doReturn(policy).when(mBatteryManagerInternal).getChargingPolicy();
+        if (mChargingPolicyChangeListener != null) {
+            mChargingPolicyChangeListener.onChargingPolicyChanged(policy);
+        }
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
index d4ef647..ea937de 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
@@ -25,14 +25,11 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-import static org.mockito.Mockito.verify;
 
 import android.app.AppGlobals;
 import android.app.job.JobInfo;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.os.BatteryManagerInternal;
@@ -49,8 +46,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
@@ -63,7 +58,6 @@
 
     private BatteryController mBatteryController;
     private FlexibilityController mFlexibilityController;
-    private BroadcastReceiver mPowerReceiver;
     private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants();
     private int mSourceUid;
 
@@ -99,10 +93,6 @@
                 .when(() -> LocalServices.getService(PackageManagerInternal.class));
 
         // Initialize real objects.
-        // Capture the listeners.
-        ArgumentCaptor<BroadcastReceiver> receiverCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
-
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mPackageManager.hasSystemFeature(
                 PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(false);
@@ -111,11 +101,6 @@
         mBatteryController = new BatteryController(mJobSchedulerService, mFlexibilityController);
         mBatteryController.startTrackingLocked();
 
-        verify(mContext).registerReceiver(receiverCaptor.capture(),
-                ArgumentMatchers.argThat(filter ->
-                        filter.hasAction(Intent.ACTION_POWER_CONNECTED)
-                                && filter.hasAction(Intent.ACTION_POWER_DISCONNECTED)));
-        mPowerReceiver = receiverCaptor.getValue();
         try {
             mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0);
             // Need to do this since we're using a mock JS and not a real object.
@@ -159,9 +144,11 @@
     }
 
     private void setPowerConnected(boolean connected) {
-        Intent intent = new Intent(
-                connected ? Intent.ACTION_POWER_CONNECTED : Intent.ACTION_POWER_DISCONNECTED);
-        mPowerReceiver.onReceive(mContext, intent);
+        doReturn(connected).when(mJobSchedulerService).isPowerConnected();
+        synchronized (mBatteryController.mLock) {
+            mBatteryController.onBatteryStateChangedLocked();
+        }
+        waitForNonDelayedMessagesProcessed();
     }
 
     private void setUidBias(int uid, int bias) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java
new file mode 100644
index 0000000..574f369
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java
@@ -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.server.pm;
+
+import static com.android.server.pm.BackgroundInstallControlCallbackHelper.FLAGGED_PACKAGE_NAME_KEY;
+import static com.android.server.pm.BackgroundInstallControlCallbackHelper.FLAGGED_USER_ID_KEY;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.os.Bundle;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+
+/** Unit tests for {@link BackgroundInstallControlCallbackHelper} */
+@Presubmit
+@RunWith(JUnit4.class)
+public class BackgroundInstallControlCallbackHelperTest {
+
+    private final IRemoteCallback mCallback =
+            spy(
+                    new IRemoteCallback.Stub() {
+                        @Override
+                        public void sendResult(Bundle extras) {}
+                    });
+
+    private BackgroundInstallControlCallbackHelper mCallbackHelper;
+
+    @Before
+    public void setup() {
+        mCallbackHelper = new BackgroundInstallControlCallbackHelper();
+    }
+
+    @Test
+    public void registerBackgroundInstallControlCallback_registers_successfully() {
+        mCallbackHelper.registerBackgroundInstallCallback(mCallback);
+
+        synchronized (mCallbackHelper.mCallbacks) {
+            assertEquals(1, mCallbackHelper.mCallbacks.getRegisteredCallbackCount());
+            assertEquals(mCallback, mCallbackHelper.mCallbacks.getRegisteredCallbackItem(0));
+        }
+    }
+
+    @Test
+    public void unregisterBackgroundInstallControlCallback_unregisters_successfully() {
+        synchronized (mCallbackHelper.mCallbacks) {
+            mCallbackHelper.mCallbacks.register(mCallback);
+        }
+
+        mCallbackHelper.unregisterBackgroundInstallCallback(mCallback);
+
+        synchronized (mCallbackHelper.mCallbacks) {
+            assertEquals(0, mCallbackHelper.mCallbacks.getRegisteredCallbackCount());
+        }
+    }
+
+    @Test
+    public void notifyAllCallbacks_broadcastsToCallbacks()
+            throws RemoteException {
+        String testPackageName = "testname";
+        int testUserId = 1;
+        mCallbackHelper.registerBackgroundInstallCallback(mCallback);
+
+        mCallbackHelper.notifyAllCallbacks(testUserId, testPackageName);
+
+        ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mCallback, after(1000).times(1)).sendResult(bundleCaptor.capture());
+        Bundle receivedBundle = bundleCaptor.getValue();
+        assertEquals(testPackageName, receivedBundle.getString(FLAGGED_PACKAGE_NAME_KEY));
+        assertEquals(testUserId, receivedBundle.getInt(FLAGGED_USER_ID_KEY));
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
index d2547a3..6f9b8df 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java
@@ -755,7 +755,8 @@
                 /* isFailed */ false,
                 /* isApplied */false,
                 /* stagedSessionErrorCode */ PackageManager.INSTALL_UNKNOWN,
-                /* stagedSessionErrorMessage */ "no error");
+                /* stagedSessionErrorMessage */ "no error",
+                /* preVerifiedDomains */ null);
 
         StagingManager.StagedSession stagedSession = spy(session.mStagedSession);
         doReturn(packageName).when(stagedSession).getPackageName();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
index 2dfabd0..b12d6da 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -893,13 +893,13 @@
 
     @Test
     public void getTokenLocked_windowIsRegistered_shouldReturnToken() {
-        final IBinder token = mA11yWindowManager.getTokenLocked(HOST_WINDOW_ID);
+        final IBinder token = mA11yWindowManager.getLeashTokenLocked(HOST_WINDOW_ID);
         assertEquals(token, mMockHostToken);
     }
 
     @Test
     public void getTokenLocked_windowIsNotRegistered_shouldReturnNull() {
-        final IBinder token = mA11yWindowManager.getTokenLocked(OTHER_WINDOW_ID);
+        final IBinder token = mA11yWindowManager.getLeashTokenLocked(OTHER_WINDOW_ID);
         assertNull(token);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 408442b..35ad55c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -74,7 +74,9 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.keymaster.HardwareAuthenticatorType;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
@@ -83,6 +85,8 @@
 import android.security.KeyStore;
 import android.security.authorization.IKeystoreAuthorization;
 import android.service.gatekeeper.IGateKeeperService;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.WindowManager;
@@ -100,6 +104,7 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.AdditionalMatchers;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
@@ -110,6 +115,8 @@
 
 @Presubmit
 @SmallTest
+@RunWith(AndroidTestingRunner.class)
[email protected]()
 public class BiometricServiceTest {
 
     @Rule
@@ -171,6 +178,8 @@
     private UserManager mUserManager;
     @Mock
     private BiometricCameraManager mBiometricCameraManager;
+    @Mock
+    private BiometricHandlerProvider mBiometricHandlerProvider;
 
     @Mock
     private IKeystoreAuthorization mKeystoreAuthService;
@@ -235,6 +244,14 @@
         when(mInjector.getGateKeeperService()).thenReturn(mGateKeeperService);
         when(mGateKeeperService.getSecureUserId(anyInt())).thenReturn(42L);
 
+        if (com.android.server.biometrics.Flags.deHidl()) {
+            when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
+                    new Handler(TestableLooper.get(this).getLooper()));
+        } else {
+            when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
+                    new Handler(Looper.getMainLooper()));
+        }
+
         final String[] config = {
                 "0:2:15",  // ID0:Fingerprint:Strong
                 "1:8:15",  // ID1:Face:Strong
@@ -312,7 +329,7 @@
         when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
                 .thenReturn(false);
 
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
@@ -333,7 +350,7 @@
         when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
                 .thenReturn(true);
 
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
@@ -360,7 +377,7 @@
     @Test
     public void testAuthenticate_withoutHardware_returnsErrorHardwareNotPresent() throws
             Exception {
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
@@ -377,7 +394,7 @@
     public void testAuthenticate_withoutEnrolled_returnsErrorNoBiometrics() throws Exception {
         when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
 
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
         mBiometricService.mImpl.registerAuthenticator(0 /* id */,
                 TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
@@ -451,7 +468,7 @@
         when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
         when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(false);
 
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
         mBiometricService.mImpl.registerAuthenticator(0 /* id */,
                 TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
@@ -1374,7 +1391,7 @@
 
     @Test
     public void testCanAuthenticate_onlyCredentialRequested() throws Exception {
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         // Credential requested but not set up
@@ -1428,7 +1445,7 @@
 
     @Test
     public void testCanAuthenticate_whenNoBiometricSensor() throws Exception {
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         // When only biometric is requested
@@ -1515,7 +1532,7 @@
 
     @Test
     public void testRegisterAuthenticator_updatesStrengths() throws Exception {
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         verify(mBiometricService.mBiometricStrengthController).startListening();
@@ -1533,7 +1550,7 @@
 
     @Test
     public void testWithDowngradedAuthenticator() throws Exception {
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         final int testId = 0;
@@ -1639,7 +1656,7 @@
 
     @Test(expected = IllegalStateException.class)
     public void testRegistrationWithDuplicateId_throwsIllegalStateException() throws Exception {
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         mBiometricService.mImpl.registerAuthenticator(
@@ -1653,7 +1670,7 @@
     @Test(expected = IllegalArgumentException.class)
     public void testRegistrationWithNullAuthenticator_throwsIllegalArgumentException()
             throws Exception {
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         mBiometricService.mImpl.registerAuthenticator(
@@ -1665,7 +1682,7 @@
     @Test
     public void testRegistrationHappyPath_isOk() throws Exception {
         // This is being tested in many of the other cases, but here's the base case.
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         for (String s : mInjector.getConfiguration(null)) {
@@ -1751,7 +1768,7 @@
         final IBiometricEnabledOnKeyguardCallback callback =
                 mock(IBiometricEnabledOnKeyguardCallback.class);
 
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
 
         when(mUserManager.getAliveUsers()).thenReturn(aliveUsers);
         when(mBiometricService.mSettingObserver.getEnabledOnKeyguard(userInfo1.id))
@@ -1775,7 +1792,7 @@
             throws RemoteException {
         mSetFlagsRule.disableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME);
 
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.mImpl.getLastAuthenticationTime(0, Authenticators.BIOMETRIC_STRONG);
     }
 
@@ -1799,7 +1816,7 @@
         when(mKeystoreAuthService.getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators)))
                 .thenReturn(expectedResult);
 
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
 
         final long result = mBiometricService.mImpl.getLastAuthenticationTime(userId,
                 Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL);
@@ -1822,7 +1839,7 @@
 
     // TODO: Reconcile the registration strength with the injector
     private void setupAuthForOnly(int modality, int strength, boolean enrolled) throws Exception {
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
@@ -1855,7 +1872,7 @@
     // TODO: Reduce duplicated code, currently we cannot start the BiometricService in setUp() for
     // all tests.
     private void setupAuthForMultiple(int[] modalities, int[] strengths) throws RemoteException {
-        mBiometricService = new BiometricService(mContext, mInjector);
+        mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
         mBiometricService.onStart();
 
         when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
@@ -1993,8 +2010,12 @@
         return requestWrapper.eligibleSensors.get(0).getCookie();
     }
 
-    private static void waitForIdle() {
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    private void waitForIdle() {
+        if (com.android.server.biometrics.Flags.deHidl()) {
+            TestableLooper.get(this).processAllMessages();
+        } else {
+            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        }
     }
 
     private byte[] generateRandomHAT() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 7648bd17..9eca93e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -24,19 +24,28 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.common.CommonProps;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
 import android.hardware.biometrics.face.SensorProps;
+import android.hardware.face.FaceAuthenticateOptions;
 import android.hardware.face.HidlFaceSensorConfig;
 import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserManager;
 import android.os.test.TestLooper;
@@ -49,18 +58,23 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
+import com.android.server.biometrics.BiometricHandlerProvider;
 import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -92,6 +106,14 @@
     private BiometricStateCallback mBiometricStateCallback;
     @Mock
     private AuthenticationStateListeners mAuthenticationStateListeners;
+    @Mock
+    private BiometricHandlerProvider mBiometricHandlerProvider;
+    @Mock
+    private Handler mBiometricCallbackHandler;
+    @Mock
+    private BiometricScheduler<IFace, ISession> mScheduler;
+    @Mock
+    AuthSessionCoordinator mAuthSessionCoordinator;
 
     private final TestLooper mLooper = new TestLooper();
     private SensorProps[] mSensorProps;
@@ -109,6 +131,16 @@
         when(mContext.getResources()).thenReturn(mResources);
         when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
                 .thenReturn(FRR_THRESHOLD);
+        when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
+                mBiometricCallbackHandler);
+        when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+        if (Flags.deHidl()) {
+            when(mBiometricHandlerProvider.getFaceHandler()).thenReturn(new Handler(
+                    mLooper.getLooper()));
+        } else {
+            when(mBiometricHandlerProvider.getFaceHandler()).thenReturn(new Handler(
+                    Looper.getMainLooper()));
+        }
 
         final SensorProps sensor1 = new SensorProps();
         sensor1.commonProps = new CommonProps();
@@ -123,7 +155,7 @@
 
         mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback,
                 mAuthenticationStateListeners, mSensorProps, TAG, mLockoutResetDispatcher,
-                mBiometricContext, mDaemon, new Handler(mLooper.getLooper()),
+                mBiometricContext, mDaemon, mBiometricHandlerProvider,
                 false /* resetLockoutRequiresChallenge */, false /* testHalEnabled */);
     }
 
@@ -159,8 +191,7 @@
         mFaceProvider = new FaceProvider(mContext,
                 mBiometricStateCallback, mAuthenticationStateListeners, hidlFaceSensorConfig, TAG,
                 mLockoutResetDispatcher, mBiometricContext, mDaemon,
-                new Handler(mLooper.getLooper()),
-                true /* resetLockoutRequiresChallenge */,
+                mBiometricHandlerProvider, true /* resetLockoutRequiresChallenge */,
                 true /* testHalEnabled */);
 
         assertThat(mFaceProvider.mFaceSensors.get(faceId)
@@ -215,6 +246,54 @@
         }
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testAuthenticateCallbackHandler() {
+        waitForIdle();
+
+        mFaceProvider.mFaceSensors.get(0).setScheduler(mScheduler);
+        mFaceProvider.scheduleAuthenticate(mock(IBinder.class), 0 /* operationId */,
+                0 /* cookie */, new ClientMonitorCallbackConverter(
+                        new IBiometricSensorReceiver.Default()),
+                new FaceAuthenticateOptions.Builder()
+                        .setSensorId(0)
+                        .build(),
+                false /* restricted */, 1 /* statsClient */,
+                true /* allowBackgroundAuthentication */);
+
+        waitForIdle();
+
+        ArgumentCaptor<ClientMonitorCallback> callbackArgumentCaptor = ArgumentCaptor.forClass(
+                ClientMonitorCallback.class);
+        ArgumentCaptor<BaseClientMonitor> clientMonitorArgumentCaptor = ArgumentCaptor.forClass(
+                BaseClientMonitor.class);
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(
+                Message.class);
+
+        verify(mScheduler).scheduleClientMonitor(clientMonitorArgumentCaptor.capture(),
+                callbackArgumentCaptor.capture());
+
+        BaseClientMonitor client = clientMonitorArgumentCaptor.getValue();
+        ClientMonitorCallback callback = callbackArgumentCaptor.getValue();
+        callback.onClientStarted(client);
+
+        verify(mBiometricCallbackHandler).sendMessageAtTime(messageCaptor.capture(), anyLong());
+
+        messageCaptor.getValue().getCallback().run();
+
+        verify(mAuthSessionCoordinator).authStartedFor(anyInt(), anyInt(), anyLong());
+
+        callback.onClientFinished(client, true /* success */);
+
+        verify(mBiometricCallbackHandler, times(2)).sendMessageAtTime(
+                messageCaptor.capture(), anyLong());
+
+        messageCaptor.getValue().getCallback().run();
+
+        verify(mAuthSessionCoordinator).authEndedFor(anyInt(), anyInt(), anyInt(), anyLong(),
+                anyBoolean());
+    }
+
     private void waitForIdle() {
         if (Flags.deHidl()) {
             mLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 9cdaec6..7a77392 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -472,6 +472,7 @@
 
         verify(mUdfpsOverlayController).hideUdfpsOverlay(anyInt());
         verify(mSideFpsController).hide(anyInt());
+        verify(mHal, times(2)).setIgnoreDisplayTouches(false);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index 951c9393..3ee54f5 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -26,6 +26,7 @@
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -327,6 +328,7 @@
 
         verify(mUdfpsOverlayController).hideUdfpsOverlay(anyInt());
         verify(mSideFpsController).hide(anyInt());
+        verify(mHal, times(2)).setIgnoreDisplayTouches(false);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 258be57..0a35037 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -24,21 +24,31 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.common.CommonProps;
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.biometrics.fingerprint.SensorLocation;
 import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.HidlFingerprintSensorConfig;
 import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserManager;
 import android.os.test.TestLooper;
@@ -50,11 +60,16 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.biometrics.BiometricHandlerProvider;
 import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationStateListeners;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.HalClientMonitor;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
 import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
@@ -62,6 +77,7 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -93,6 +109,14 @@
     private BiometricStateCallback mBiometricStateCallback;
     @Mock
     private BiometricContext mBiometricContext;
+    @Mock
+    private BiometricHandlerProvider mBiometricHandlerProvider;
+    @Mock
+    private Handler mBiometricCallbackHandler;
+    @Mock
+    private AuthSessionCoordinator mAuthSessionCoordinator;
+    @Mock
+    private BiometricScheduler<IFingerprint, ISession> mScheduler;
 
     private final TestLooper mLooper = new TestLooper();
 
@@ -109,6 +133,16 @@
         when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
         when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
         when(mDaemon.createSession(anyInt(), anyInt(), any())).thenReturn(mock(ISession.class));
+        when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+        when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
+                mBiometricCallbackHandler);
+        if (Flags.deHidl()) {
+            when(mBiometricHandlerProvider.getFingerprintHandler()).thenReturn(
+                    new Handler(mLooper.getLooper()));
+        } else {
+            when(mBiometricHandlerProvider.getFingerprintHandler()).thenReturn(
+                    new Handler(Looper.getMainLooper()));
+        }
 
         final SensorProps sensor1 = new SensorProps();
         sensor1.commonProps = new CommonProps();
@@ -126,9 +160,8 @@
         mFingerprintProvider = new FingerprintProvider(mContext,
                 mBiometricStateCallback, mAuthenticationStateListeners, mSensorProps, TAG,
                 mLockoutResetDispatcher, mGestureAvailabilityDispatcher, mBiometricContext,
-                mDaemon, new Handler(mLooper.getLooper()),
-                false /* resetLockoutRequiresHardwareAuthToken */,
-                true /* testHalEnabled */);
+                mDaemon, mBiometricHandlerProvider,
+                false /* resetLockoutRequiresHardwareAuthToken */, true /* testHalEnabled */);
     }
 
     @Test
@@ -160,7 +193,7 @@
                 mBiometricStateCallback, mAuthenticationStateListeners,
                 hidlFingerprintSensorConfigs, TAG, mLockoutResetDispatcher,
                 mGestureAvailabilityDispatcher, mBiometricContext, mDaemon,
-                new Handler(mLooper.getLooper()),
+                mBiometricHandlerProvider,
                 false /* resetLockoutRequiresHardwareAuthToken */,
                 true /* testHalEnabled */);
 
@@ -218,6 +251,56 @@
         }
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+    public void testScheduleAuthenticate() {
+        waitForIdle();
+
+        mFingerprintProvider.mFingerprintSensors.get(0).setScheduler(mScheduler);
+        mFingerprintProvider.scheduleAuthenticate(mock(IBinder.class), 0 /* operationId */,
+                0 /* cookie */, new ClientMonitorCallbackConverter(
+                        new IBiometricSensorReceiver.Default()),
+                new FingerprintAuthenticateOptions.Builder()
+                        .setSensorId(0)
+                        .build(),
+                false /* restricted */, 1 /* statsClient */,
+                true /* allowBackgroundAuthentication */);
+
+        waitForIdle();
+
+        ArgumentCaptor<ClientMonitorCallback> callbackArgumentCaptor = ArgumentCaptor.forClass(
+                ClientMonitorCallback.class);
+        ArgumentCaptor<BaseClientMonitor> clientMonitorArgumentCaptor = ArgumentCaptor.forClass(
+                BaseClientMonitor.class);
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(
+                Message.class);
+
+        verify(mScheduler).scheduleClientMonitor(clientMonitorArgumentCaptor.capture(),
+                callbackArgumentCaptor.capture());
+
+        BaseClientMonitor client = clientMonitorArgumentCaptor.getValue();
+        ClientMonitorCallback callback = callbackArgumentCaptor.getValue();
+        callback.onClientStarted(client);
+
+        verify(mBiometricStateCallback).onClientStarted(eq(client));
+        verify(mBiometricCallbackHandler).sendMessageAtTime(messageCaptor.capture(), anyLong());
+
+        messageCaptor.getValue().getCallback().run();
+
+        verify(mAuthSessionCoordinator).authStartedFor(anyInt(), anyInt(), anyLong());
+
+        callback.onClientFinished(client, true /* success */);
+
+        verify(mBiometricStateCallback).onClientFinished(eq(client), eq(true /* success */));
+        verify(mBiometricCallbackHandler, times(2)).sendMessageAtTime(
+                messageCaptor.capture(), anyLong());
+
+        messageCaptor.getValue().getCallback().run();
+
+        verify(mAuthSessionCoordinator).authEndedFor(anyInt(), anyInt(), anyInt(), anyLong(),
+                anyBoolean());
+    }
+
     private void waitForIdle() {
         if (Flags.deHidl()) {
             mLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index 5943832..07e6ab2 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -19,11 +19,10 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertThrows;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.startsWith;
-import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
 
 import android.hardware.display.DisplayManagerInternal;
@@ -86,9 +85,8 @@
         LocalServices.removeServiceForTest(InputManagerInternal.class);
         LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
 
-        final DisplayInfo displayInfo = new DisplayInfo();
-        displayInfo.uniqueId = "uniqueId";
-        doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
+        setUpDisplay(1 /* displayId */);
+        setUpDisplay(2 /* displayId */);
         LocalServices.removeServiceForTest(DisplayManagerInternal.class);
         LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
 
@@ -100,6 +98,16 @@
                 threadVerifier);
     }
 
+    void setUpDisplay(int displayId) {
+        final String uniqueId = "uniqueId:" + displayId;
+        doAnswer((inv) -> {
+            final DisplayInfo displayInfo = new DisplayInfo();
+            displayInfo.uniqueId = uniqueId;
+            return displayInfo;
+        }).when(mDisplayManagerInternalMock).getDisplayInfo(eq(displayId));
+        mInputManagerMockHelper.addDisplayIdMapping(uniqueId, displayId);
+    }
+
     @After
     public void tearDown() {
         mInputManagerMockHelper.tearDown();
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
index 3722247..74e854e4 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
@@ -20,7 +20,6 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.when;
 
 import android.hardware.input.IInputDevicesChangedListener;
@@ -28,12 +27,15 @@
 import android.hardware.input.InputManagerGlobal;
 import android.os.RemoteException;
 import android.testing.TestableLooper;
+import android.view.Display;
 import android.view.InputDevice;
 
 import org.mockito.invocation.InvocationOnMock;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.stream.IntStream;
 
@@ -49,6 +51,10 @@
     private final InputManagerGlobal.TestSession mInputManagerGlobalSession;
     private final List<InputDevice> mDevices = new ArrayList<>();
     private IInputDevicesChangedListener mDevicesChangedListener;
+    private final Map<String /* uniqueId */, Integer /* displayId */> mDisplayIdMapping =
+            new HashMap<>();
+    private final Map<String /* phys */, String /* uniqueId */> mUniqueIdAssociation =
+            new HashMap<>();
 
     InputManagerMockHelper(TestableLooper testableLooper,
             InputController.NativeWrapper nativeWrapperMock, IInputManager iInputManagerMock)
@@ -73,8 +79,10 @@
         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
         doAnswer(inv -> mDevices.get(inv.getArgument(0)))
                 .when(mIInputManagerMock).getInputDevice(anyInt());
-        doNothing().when(mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
-        doNothing().when(mIInputManagerMock).removeUniqueIdAssociation(anyString());
+        doAnswer(inv -> mUniqueIdAssociation.put(inv.getArgument(0), inv.getArgument(1))).when(
+                mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
+        doAnswer(inv -> mUniqueIdAssociation.remove(inv.getArgument(0))).when(
+                mIInputManagerMock).removeUniqueIdAssociation(anyString());
 
         // Set a new instance of InputManager for testing that uses the IInputManager mock as the
         // interface to the server.
@@ -87,17 +95,25 @@
         }
     }
 
+    public void addDisplayIdMapping(String uniqueId, int displayId) {
+        mDisplayIdMapping.put(uniqueId, displayId);
+    }
+
     private long handleNativeOpenInputDevice(InvocationOnMock inv) {
         Objects.requireNonNull(mDevicesChangedListener,
                 "InputController did not register an InputDevicesChangedListener.");
 
+        final String phys = inv.getArgument(3);
         final InputDevice device = new InputDevice.Builder()
                 .setId(mDevices.size())
                 .setName(inv.getArgument(0))
                 .setVendorId(inv.getArgument(1))
                 .setProductId(inv.getArgument(2))
-                .setDescriptor(inv.getArgument(3))
+                .setDescriptor(phys)
                 .setExternal(true)
+                .setAssociatedDisplayId(
+                        mDisplayIdMapping.getOrDefault(mUniqueIdAssociation.get(phys),
+                                Display.INVALID_DISPLAY))
                 .build();
 
         mDevices.add(device);
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 5442af8..157e893 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
@@ -39,6 +39,7 @@
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.argThat;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -2015,6 +2016,13 @@
                 eq(virtualDevice), any(), any())).thenReturn(displayId);
         virtualDevice.createVirtualDisplay(VIRTUAL_DISPLAY_CONFIG, mVirtualDisplayCallback,
                 NONBLOCKED_APP_PACKAGE_NAME);
+        final String uniqueId = UNIQUE_ID + displayId;
+        doAnswer(inv -> {
+            final DisplayInfo displayInfo = new DisplayInfo();
+            displayInfo.uniqueId = uniqueId;
+            return displayInfo;
+        }).when(mDisplayManagerInternalMock).getDisplayInfo(eq(displayId));
+        mInputManagerMockHelper.addDisplayIdMapping(uniqueId, displayId);
     }
 
     private ComponentName getPermissionDialogComponent() {
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 8656f60..bf87e3a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
@@ -111,6 +111,8 @@
     private UsageStatsManagerInternal mUsageStatsManagerInternal;
     @Mock
     private PermissionManagerServiceInternal mPermissionManager;
+    @Mock
+    private BackgroundInstallControlCallbackHelper mCallbackHelper;
 
     @Captor
     private ArgumentCaptor<PackageManagerInternal.PackageListObserver> mPackageListObserverCaptor;
@@ -982,5 +984,11 @@
         public File getDiskFile() {
             return mFile;
         }
+
+
+        @Override
+        public BackgroundInstallControlCallbackHelper getBackgroundInstallControlCallbackHelper() {
+            return mCallbackHelper;
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index 3778a32..f1d3ba9 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -23,7 +23,6 @@
 import android.content.pm.UserProperties;
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.Xml;
 
 import androidx.test.filters.MediumTest;
@@ -32,7 +31,6 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -54,13 +52,10 @@
 @RunWith(AndroidJUnit4.class)
 @MediumTest
 public class UserManagerServiceUserPropertiesTest {
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     /** Test that UserProperties can properly read the xml information that it writes. */
     @Test
     public void testWriteReadXml() throws Exception {
-        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         final UserProperties defaultProps = new UserProperties.Builder()
                 .setShowInLauncher(21)
                 .setStartWithParent(false)
@@ -123,7 +118,6 @@
     /** Tests parcelling an object in which all properties are present. */
     @Test
     public void testParcelUnparcel() throws Exception {
-        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         final UserProperties originalProps = new UserProperties.Builder()
                 .setShowInLauncher(2145)
                 .build();
@@ -134,7 +128,6 @@
     /** Tests copying a UserProperties object varying permissions. */
     @Test
     public void testCopyLacksPermissions() throws Exception {
-        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         final UserProperties defaultProps = new UserProperties.Builder()
                 .setShowInLauncher(2145)
                 .setStartWithParent(true)
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 6cdbc74..3047bcf 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -41,7 +41,6 @@
 import android.os.Bundle;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.ArrayMap;
 
 import androidx.test.InstrumentationRegistry;
@@ -51,7 +50,6 @@
 import com.android.frameworks.servicestests.R;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -73,11 +71,8 @@
     public void setup() {
         mResources = InstrumentationRegistry.getTargetContext().getResources();
     }
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     @Test
     public void testUserTypeBuilder_createUserType() {
-        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         final Bundle restrictions = makeRestrictionsBundle("r1", "r2");
         final Bundle systemSettings = makeSettingsBundle("s1", "s2");
         final Bundle secureSettings = makeSettingsBundle("secure_s1", "secure_s2");
@@ -207,7 +202,6 @@
 
     @Test
     public void testUserTypeBuilder_defaults() {
-        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         UserTypeDetails type = new UserTypeDetails.Builder()
                 .setName("name") // Required (no default allowed)
                 .setBaseType(FLAG_FULL) // Required (no default allowed)
@@ -321,7 +315,6 @@
     /** Tests {@link UserTypeFactory#customizeBuilders} for a reasonable xml file. */
     @Test
     public void testUserTypeFactoryCustomize_profile() throws Exception {
-        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         final String userTypeAosp1 = "android.test.1"; // Profile user that is not customized
         final String userTypeAosp2 = "android.test.2"; // Profile user that is customized
         final String userTypeOem1 = "custom.test.1"; // Custom-defined profile
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 9323b48..df2069e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -39,7 +39,6 @@
 import android.os.UserManager;
 import android.platform.test.annotations.Postsubmit;
 import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
@@ -56,7 +55,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -99,8 +97,6 @@
     private UserSwitchWaiter mUserSwitchWaiter;
     private UserRemovalWaiter mUserRemovalWaiter;
     private int mOriginalCurrentUserId;
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Before
     public void setUp() throws Exception {
@@ -172,7 +168,6 @@
 
     @Test
     public void testCloneUser() throws Exception {
-        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         assumeCloneEnabled();
         UserHandle mainUser = mUserManager.getMainUser();
         assumeTrue("Main user is null", mainUser != null);
@@ -229,7 +224,8 @@
                 .isEqualTo(cloneUserProperties.getCrossProfileContentSharingStrategy());
         assertThrows(SecurityException.class, cloneUserProperties::getDeleteAppWithParent);
         assertThrows(SecurityException.class, cloneUserProperties::getAlwaysVisible);
-        assertThrows(SecurityException.class, cloneUserProperties::getProfileApiVisibility);
+        assertThat(typeProps.getProfileApiVisibility()).isEqualTo(
+                cloneUserProperties.getProfileApiVisibility());
         compareDrawables(mUserManager.getUserBadge(),
                 Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain()));
 
@@ -311,7 +307,6 @@
 
     @Test
     public void testPrivateProfile() throws Exception {
-        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         UserHandle mainUser = mUserManager.getMainUser();
         assumeTrue("Main user is null", mainUser != null);
         // Get the default properties for private profile user type.
@@ -353,8 +348,8 @@
         assertThrows(SecurityException.class, privateProfileUserProperties::getDeleteAppWithParent);
         assertThrows(SecurityException.class,
                 privateProfileUserProperties::getAllowStoppingUserWithDelayedLocking);
-        assertThrows(SecurityException.class,
-                privateProfileUserProperties::getProfileApiVisibility);
+        assertThat(typeProps.getProfileApiVisibility()).isEqualTo(
+                privateProfileUserProperties.getProfileApiVisibility());
         assertThrows(SecurityException.class,
                 privateProfileUserProperties::areItemsRestrictedOnHomeScreen);
         compareDrawables(mUserManager.getUserBadge(),
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java
index e075379..c0ea157 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java
@@ -106,13 +106,13 @@
     }
 
     @Test
-    public void testRecord_changeUserForFile_throws() {
+    public void testRecord_changeUserForFile_ignored() {
         Entry entry1 = new Entry("owning.package1", "/path/file1", 'D', 10, "loading.package1");
         Entry entry2 = new Entry("owning.package1", "/path/file1", 'D', 20, "loading.package1");
 
         PackageDynamicCodeLoading info = makePackageDcl(entry1);
 
-        assertThrows(() -> record(info, entry2));
+        assertThat(record(info, entry2)).isFalse();
         assertHasEntries(info, entry1);
     }
 
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 516fb4a..5eb76e3 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -15,37 +15,56 @@
  */
 package com.android.server.notification;
 
+import static android.app.Notification.COLOR_DEFAULT;
 import static android.app.Notification.FLAG_AUTO_CANCEL;
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.Notification.FLAG_CAN_COLORIZE;
 import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
 import static android.app.Notification.FLAG_NO_CLEAR;
 import static android.app.Notification.FLAG_ONGOING_EVENT;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 
 import static com.android.server.notification.GroupHelper.BASE_FLAGS;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertEquals;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
 import android.annotation.SuppressLint;
 import android.app.Notification;
+import android.content.pm.PackageManager;
+import android.graphics.Color;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArrayMap;
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.R;
 import com.android.server.UiServiceTestCase;
+import com.android.server.notification.GroupHelper.NotificationAttributes;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -59,23 +78,37 @@
 @SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the class.
 @RunWith(AndroidJUnit4.class)
 public class GroupHelperTest extends UiServiceTestCase {
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
     private @Mock GroupHelper.Callback mCallback;
+    private @Mock PackageManager mPackageManager;
 
     private final static int AUTOGROUP_AT_COUNT = 7;
     private GroupHelper mGroupHelper;
+    private @Mock Icon mSmallIcon;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        mGroupHelper = new GroupHelper(AUTOGROUP_AT_COUNT, mCallback);
+        mGroupHelper = new GroupHelper(getContext(), mPackageManager, AUTOGROUP_AT_COUNT,
+                mCallback);
+
+        NotificationRecord r = mock(NotificationRecord.class);
+        StatusBarNotification sbn = getSbn("package", 0, "0", UserHandle.SYSTEM);
+        when(r.getNotification()).thenReturn(sbn.getNotification());
+        when(r.getSbn()).thenReturn(sbn);
+        when(mSmallIcon.sameAs(mSmallIcon)).thenReturn(true);
     }
 
     private StatusBarNotification getSbn(String pkg, int id, String tag,
-            UserHandle user, String groupKey) {
+            UserHandle user, String groupKey, Icon smallIcon, int iconColor) {
         Notification.Builder nb = new Notification.Builder(getContext(), "test_channel_id")
                 .setContentTitle("A")
-                .setWhen(1205);
+                .setWhen(1205)
+                .setSmallIcon(smallIcon)
+                .setColor(iconColor);
         if (groupKey != null) {
             nb.setGroup(groupKey);
         }
@@ -84,23 +117,32 @@
     }
 
     private StatusBarNotification getSbn(String pkg, int id, String tag,
+            UserHandle user, String groupKey) {
+        return getSbn(pkg, id, tag, user, groupKey, mSmallIcon, Notification.COLOR_DEFAULT);
+    }
+
+    private StatusBarNotification getSbn(String pkg, int id, String tag,
             UserHandle user) {
         return getSbn(pkg, id, tag, user, null);
     }
 
+    private NotificationAttributes getNotificationAttributes(int flags) {
+        return new NotificationAttributes(flags, mSmallIcon, COLOR_DEFAULT);
+    }
+
     @Test
     public void testGetAutogroupSummaryFlags_noChildren() {
-        ArrayMap<String, Integer> children = new ArrayMap<>();
+        ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
 
         assertEquals(BASE_FLAGS, mGroupHelper.getAutogroupSummaryFlags(children));
     }
 
     @Test
     public void testGetAutogroupSummaryFlags_oneOngoing() {
-        ArrayMap<String, Integer> children = new ArrayMap<>();
-        children.put("a", 0);
-        children.put("b", FLAG_ONGOING_EVENT);
-        children.put("c", FLAG_BUBBLE);
+        ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+        children.put("a", getNotificationAttributes(0));
+        children.put("b", getNotificationAttributes(FLAG_ONGOING_EVENT));
+        children.put("c", getNotificationAttributes(FLAG_BUBBLE));
 
         assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
                 mGroupHelper.getAutogroupSummaryFlags(children));
@@ -108,10 +150,10 @@
 
     @Test
     public void testGetAutogroupSummaryFlags_oneOngoingNoClear() {
-        ArrayMap<String, Integer> children = new ArrayMap<>();
-        children.put("a", 0);
-        children.put("b", FLAG_ONGOING_EVENT|FLAG_NO_CLEAR);
-        children.put("c", FLAG_BUBBLE);
+        ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+        children.put("a", getNotificationAttributes(0));
+        children.put("b", getNotificationAttributes(FLAG_ONGOING_EVENT | FLAG_NO_CLEAR));
+        children.put("c", getNotificationAttributes(FLAG_BUBBLE));
 
         assertEquals(FLAG_NO_CLEAR | FLAG_ONGOING_EVENT | BASE_FLAGS,
                 mGroupHelper.getAutogroupSummaryFlags(children));
@@ -119,10 +161,10 @@
 
     @Test
     public void testGetAutogroupSummaryFlags_oneOngoingBubble() {
-        ArrayMap<String, Integer> children = new ArrayMap<>();
-        children.put("a", 0);
-        children.put("b", FLAG_ONGOING_EVENT | FLAG_BUBBLE);
-        children.put("c", FLAG_BUBBLE);
+        ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+        children.put("a", getNotificationAttributes(0));
+        children.put("b", getNotificationAttributes(FLAG_ONGOING_EVENT | FLAG_BUBBLE));
+        children.put("c", getNotificationAttributes(FLAG_BUBBLE));
 
         assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
                 mGroupHelper.getAutogroupSummaryFlags(children));
@@ -130,11 +172,11 @@
 
     @Test
     public void testGetAutogroupSummaryFlags_multipleOngoing() {
-        ArrayMap<String, Integer> children = new ArrayMap<>();
-        children.put("a", 0);
-        children.put("b", FLAG_ONGOING_EVENT);
-        children.put("c", FLAG_BUBBLE);
-        children.put("d", FLAG_ONGOING_EVENT);
+        ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+        children.put("a", getNotificationAttributes(0));
+        children.put("b", getNotificationAttributes(FLAG_ONGOING_EVENT));
+        children.put("c", getNotificationAttributes(FLAG_BUBBLE));
+        children.put("d", getNotificationAttributes(FLAG_ONGOING_EVENT));
 
         assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
                 mGroupHelper.getAutogroupSummaryFlags(children));
@@ -142,10 +184,10 @@
 
     @Test
     public void testGetAutogroupSummaryFlags_oneAutoCancel() {
-        ArrayMap<String, Integer> children = new ArrayMap<>();
-        children.put("a", 0);
-        children.put("b", FLAG_AUTO_CANCEL);
-        children.put("c", FLAG_BUBBLE);
+        ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+        children.put("a", getNotificationAttributes(0));
+        children.put("b", getNotificationAttributes(FLAG_AUTO_CANCEL));
+        children.put("c", getNotificationAttributes(FLAG_BUBBLE));
 
         assertEquals(BASE_FLAGS,
                 mGroupHelper.getAutogroupSummaryFlags(children));
@@ -153,11 +195,11 @@
 
     @Test
     public void testGetAutogroupSummaryFlags_allAutoCancel() {
-        ArrayMap<String, Integer> children = new ArrayMap<>();
-        children.put("a", FLAG_AUTO_CANCEL);
-        children.put("b", FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE);
-        children.put("c", FLAG_AUTO_CANCEL);
-        children.put("d", FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE);
+        ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+        children.put("a", getNotificationAttributes(FLAG_AUTO_CANCEL));
+        children.put("b", getNotificationAttributes(FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE));
+        children.put("c", getNotificationAttributes(FLAG_AUTO_CANCEL));
+        children.put("d", getNotificationAttributes(FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE));
 
         assertEquals(FLAG_AUTO_CANCEL | BASE_FLAGS,
                 mGroupHelper.getAutogroupSummaryFlags(children));
@@ -165,11 +207,12 @@
 
     @Test
     public void testGetAutogroupSummaryFlags_allAutoCancelOneOngoing() {
-        ArrayMap<String, Integer> children = new ArrayMap<>();
-        children.put("a", FLAG_AUTO_CANCEL);
-        children.put("b", FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE);
-        children.put("c", FLAG_AUTO_CANCEL);
-        children.put("d", FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE | FLAG_ONGOING_EVENT);
+        ArrayMap<String, NotificationAttributes> children = new ArrayMap<>();
+        children.put("a", getNotificationAttributes(FLAG_AUTO_CANCEL));
+        children.put("b", getNotificationAttributes(FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE));
+        children.put("c", getNotificationAttributes(FLAG_AUTO_CANCEL));
+        children.put("d", getNotificationAttributes(
+                FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE | FLAG_ONGOING_EVENT));
 
         assertEquals(FLAG_AUTO_CANCEL| FLAG_ONGOING_EVENT | BASE_FLAGS,
                 mGroupHelper.getAutogroupSummaryFlags(children));
@@ -230,11 +273,11 @@
                     getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false);
         }
         verify(mCallback, times(1)).addAutoGroupSummary(
-                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+                anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
-        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
     }
 
     @Test
@@ -248,11 +291,11 @@
             mGroupHelper.onNotificationPosted(sbn, false);
         }
         verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
-                eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+                eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
-        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
     }
 
     @Test
@@ -266,11 +309,11 @@
             mGroupHelper.onNotificationPosted(sbn, false);
         }
         verify(mCallback, times(1)).addAutoGroupSummary(
-                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+                anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
-        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
     }
 
     @Test
@@ -282,11 +325,11 @@
             mGroupHelper.onNotificationPosted(sbn, false);
         }
         verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
-                eq(BASE_FLAGS | FLAG_AUTO_CANCEL));
+                eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL)));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
-        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
     }
 
     @Test
@@ -301,11 +344,11 @@
             mGroupHelper.onNotificationPosted(sbn, false);
         }
         verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
-                eq(BASE_FLAGS | FLAG_AUTO_CANCEL | FLAG_NO_CLEAR));
+                eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL | FLAG_NO_CLEAR)));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
-        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
     }
 
     @Test
@@ -329,8 +372,8 @@
         mGroupHelper.onNotificationPosted(notifications.get(0), true);
 
         // Summary should keep FLAG_ONGOING_EVENT if any child has it
-        verify(mCallback).updateAutogroupSummary(
-                anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+                eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
     }
 
     @Test
@@ -355,7 +398,8 @@
         mGroupHelper.onNotificationRemoved(notifications.get(0));
 
         // Summary is no longer ongoing
-        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+                eq(getNotificationAttributes(BASE_FLAGS)));
     }
 
     @Test
@@ -378,8 +422,8 @@
         mGroupHelper.onNotificationPosted(notifications.get(0), true);
 
         // Summary is now ongoing
-        verify(mCallback).updateAutogroupSummary(
-                anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+                eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
     }
 
     @Test
@@ -403,8 +447,8 @@
         mGroupHelper.onNotificationPosted(sbn, true);
 
         // Summary is now ongoing
-        verify(mCallback).updateAutogroupSummary(
-                anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+                eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT)));
     }
 
     @Test
@@ -430,7 +474,8 @@
         mGroupHelper.onNotificationPosted(sbn, true);
 
         // Summary is no longer ongoing
-        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+                eq(getNotificationAttributes(BASE_FLAGS)));
     }
 
     @Test
@@ -455,7 +500,7 @@
         mGroupHelper.onNotificationRemoved(notifications.get(1));
 
         // Summary is still ongoing
-        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
     }
 
     @Test
@@ -479,7 +524,8 @@
         mGroupHelper.onNotificationPosted(notifications.get(0), true);
 
         // Summary should no longer be autocancelable
-        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+                eq(getNotificationAttributes(BASE_FLAGS)));
     }
 
     @Test
@@ -505,8 +551,8 @@
         mGroupHelper.onNotificationPosted(notifications.get(0), true);
 
         // Summary should now autocancelable
-        verify(mCallback).updateAutogroupSummary(
-                anyInt(), anyString(), eq(BASE_FLAGS | FLAG_AUTO_CANCEL));
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+                eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL)));
     }
 
     @Test
@@ -530,7 +576,7 @@
         mGroupHelper.onNotificationPosted(sbn, true);
 
         // Summary should be still be autocancelable
-        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
     }
 
     @Test
@@ -552,7 +598,7 @@
         mGroupHelper.onNotificationRemoved(notifications.get(0));
 
         // Summary should still be autocancelable
-        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any());
     }
 
     @Test
@@ -565,7 +611,7 @@
             mGroupHelper.onNotificationPosted(sbn, false);
         }
         verify(mCallback, times(1)).addAutoGroupSummary(
-                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+                anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -593,7 +639,7 @@
             mGroupHelper.onNotificationPosted(sbn, false);
         }
         verify(mCallback, times(1)).addAutoGroupSummary(
-                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+                anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -622,7 +668,7 @@
             mGroupHelper.onNotificationPosted(sbn, false);
         }
         verify(mCallback, times(1)).addAutoGroupSummary(
-                anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+                anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
         verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -646,8 +692,213 @@
         verify(mCallback, times(1)).addAutoGroup(sbn.getKey());
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
-        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
-        verify(mCallback, never()).addAutoGroupSummary(
-                anyInt(), anyString(), anyString(), anyInt());
+        verify(mCallback).updateAutogroupSummary(anyInt(), anyString(),
+                eq(getNotificationAttributes(BASE_FLAGS)));
+        verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(), any());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+    public void testAddSummary_sameIcon_sameColor() {
+        final String pkg = "package";
+        final Icon icon = mock(Icon.class);
+        when(icon.sameAs(icon)).thenReturn(true);
+        final int iconColor = Color.BLUE;
+        final NotificationAttributes attr = new NotificationAttributes(BASE_FLAGS, icon, iconColor);
+
+        // Add notifications with same icon and color
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
+                    icon, iconColor);
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+        // Check that the summary would have the same icon and color
+        verify(mCallback, times(1)).addAutoGroupSummary(
+                anyInt(), eq(pkg), anyString(), eq(attr));
+        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+
+        // After auto-grouping, add new notification with the same color
+        StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT,
+                String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, icon, iconColor);
+        mGroupHelper.onNotificationPosted(sbn, true);
+
+        // Check that the summary was updated
+        //NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, icon, iconColor);
+        verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(attr));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+    public void testAddSummary_diffIcon_diffColor() {
+        final String pkg = "package";
+        final Icon initialIcon = mock(Icon.class);
+        when(initialIcon.sameAs(initialIcon)).thenReturn(true);
+        final int initialIconColor = Color.BLUE;
+
+        // Spy GroupHelper for getMonochromeAppIcon
+        final Icon monochromeIcon = mock(Icon.class);
+        when(monochromeIcon.sameAs(monochromeIcon)).thenReturn(true);
+        GroupHelper groupHelper = spy(mGroupHelper);
+        doReturn(monochromeIcon).when(groupHelper).getMonochromeAppIcon(eq(pkg));
+
+        final NotificationAttributes initialAttr = new NotificationAttributes(BASE_FLAGS,
+                initialIcon, initialIconColor);
+
+        // Add notifications with same icon and color
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
+                    initialIcon, initialIconColor);
+            groupHelper.onNotificationPosted(sbn, false);
+        }
+        // Check that the summary would have the same icon and color
+        verify(mCallback, times(1)).addAutoGroupSummary(
+                anyInt(), eq(pkg), anyString(), eq(initialAttr));
+        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+
+        // After auto-grouping, add new notification with a different color
+        final Icon newIcon = mock(Icon.class);
+        final int newIconColor = Color.YELLOW;
+        StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT,
+                String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, newIcon,
+                newIconColor);
+        groupHelper.onNotificationPosted(sbn, true);
+
+        // Summary should be updated to the default color and the icon to the monochrome icon
+        NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, monochromeIcon,
+                COLOR_DEFAULT);
+        verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(newAttr));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+    public void testAutoGrouped_diffIcon_diffColor_removeChild_updateTo_sameIcon_sameColor() {
+        final String pkg = "package";
+        final Icon initialIcon = mock(Icon.class);
+        when(initialIcon.sameAs(initialIcon)).thenReturn(true);
+        final int initialIconColor = Color.BLUE;
+        final NotificationAttributes initialAttr = new NotificationAttributes(
+                GroupHelper.FLAG_INVALID, initialIcon, initialIconColor);
+
+        // Add AUTOGROUP_AT_COUNT-1 notifications with same icon and color
+        ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+            StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
+                    initialIcon, initialIconColor);
+            notifications.add(sbn);
+        }
+        // And an additional notification with different icon and color
+        final int lastIdx = AUTOGROUP_AT_COUNT - 1;
+        StatusBarNotification newSbn = getSbn(pkg, lastIdx,
+                String.valueOf(lastIdx), UserHandle.SYSTEM, null, mock(Icon.class),
+                Color.YELLOW);
+        notifications.add(newSbn);
+        for (StatusBarNotification sbn: notifications) {
+            mGroupHelper.onNotificationPosted(sbn, false);
+        }
+
+        // Remove last notification (the only one with different icon and color)
+        mGroupHelper.onNotificationRemoved(notifications.get(lastIdx));
+
+        // Summary should be updated to the common icon and color
+        verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(initialAttr));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+    public void testAutobundledSummaryIcon_sameIcon() {
+        final String pkg = "package";
+        final Icon icon = mock(Icon.class);
+        when(icon.sameAs(icon)).thenReturn(true);
+
+        // Create notifications with the same icon
+        List<NotificationAttributes> childrenAttr = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            childrenAttr.add(new NotificationAttributes(0, icon, COLOR_DEFAULT));
+        }
+
+        //Check that the generated summary icon is the same as the child notifications'
+        Icon summaryIcon = mGroupHelper.getAutobundledSummaryIconAndColor(pkg, childrenAttr).icon;
+        assertThat(summaryIcon).isEqualTo(icon);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+    public void testAutobundledSummaryIcon_diffIcon() {
+        final String pkg = "package";
+        // Spy GroupHelper for getMonochromeAppIcon
+        final Icon monochromeIcon = mock(Icon.class);
+        GroupHelper groupHelper = spy(mGroupHelper);
+        doReturn(monochromeIcon).when(groupHelper).getMonochromeAppIcon(eq(pkg));
+
+        // Create notifications with different icons
+        List<NotificationAttributes> childrenAttr = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), COLOR_DEFAULT));
+        }
+
+        // Check that the generated summary icon is the monochrome icon
+        Icon summaryIcon = groupHelper.getAutobundledSummaryIconAndColor(pkg, childrenAttr).icon;
+        assertThat(summaryIcon).isEqualTo(monochromeIcon);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+    public void testAutobundledSummaryIconColor_sameColor() {
+        final String pkg = "package";
+        final int iconColor = Color.BLUE;
+        // Create notifications with the same icon color
+        List<NotificationAttributes> childrenAttr = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), iconColor));
+        }
+
+        // Check that the generated summary icon color is the same as the child notifications'
+        int summaryIconColor = mGroupHelper.getAutobundledSummaryIconAndColor(pkg,
+                childrenAttr).iconColor;
+        assertThat(summaryIconColor).isEqualTo(iconColor);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+    public void testAutobundledSummaryIconColor_diffColor() {
+        final String pkg = "package";
+        // Create notifications with different icon colors
+        List<NotificationAttributes> childrenAttr = new ArrayList<>();
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+            childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), i));
+        }
+
+        // Check that the generated summary icon color is the default color
+        int summaryIconColor = mGroupHelper.getAutobundledSummaryIconAndColor(pkg,
+                childrenAttr).iconColor;
+        assertThat(summaryIconColor).isEqualTo(Notification.COLOR_DEFAULT);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+    public void testMonochromeAppIcon_adaptiveIconExists() throws Exception {
+        final String pkg = "testPackage";
+        final int monochromeIconResId = 1234;
+        AdaptiveIconDrawable adaptiveIcon = mock(AdaptiveIconDrawable.class);
+        Drawable monochromeIcon = mock(Drawable.class);
+        when(mPackageManager.getApplicationIcon(pkg)).thenReturn(adaptiveIcon);
+        when(adaptiveIcon.getMonochrome()).thenReturn(monochromeIcon);
+        when(adaptiveIcon.getSourceDrawableResId()).thenReturn(monochromeIconResId);
+        assertThat(mGroupHelper.getMonochromeAppIcon(pkg).getResId())
+                .isEqualTo(monochromeIconResId);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
+    public void testMonochromeAppIcon_adaptiveIconMissing_fallback() throws Exception {
+        final String pkg = "testPackage";
+        final int fallbackIconResId = R.drawable.ic_notification_summary_auto;
+        when(mPackageManager.getApplicationIcon(pkg)).thenReturn(mock(Drawable.class));
+        assertThat(mGroupHelper.getMonochromeAppIcon(pkg).getResId())
+                .isEqualTo(fallbackIconResId);
     }
 }
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 8261dee..77be01c 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -279,6 +279,7 @@
 import com.android.server.job.JobSchedulerInternal;
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
+import com.android.server.notification.GroupHelper.NotificationAttributes;
 import com.android.server.notification.NotificationManagerService.NotificationAssistants;
 import com.android.server.notification.NotificationManagerService.NotificationListeners;
 import com.android.server.notification.NotificationManagerService.PostNotificationTracker;
@@ -322,6 +323,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
+import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -658,7 +660,8 @@
         // NOTE: Prefer using the @EnableFlag annotation where possible. Do not add any android.app
         //  flags here.
         mSetFlagsRule.disableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER,
-                Flags.FLAG_POLITE_NOTIFICATIONS);
+                Flags.FLAG_POLITE_NOTIFICATIONS, Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE);
+
         initNMS();
     }
 
@@ -2332,8 +2335,9 @@
         mService.mAutobundledSummaries.put(0, new ArrayMap<>());
         mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey());
 
-        mService.updateAutobundledSummaryFlags(
-                0, "pkg", GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT, false);
+        mService.updateAutobundledSummaryLocked(0, "pkg",
+                new NotificationAttributes(GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT,
+                    mock(Icon.class), 0), false);
         waitForIdle();
 
         assertTrue(summary.getSbn().isOngoing());
@@ -2350,7 +2354,9 @@
         mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey());
         mService.mSummaryByGroupKey.put("pkg", summary);
 
-        mService.updateAutobundledSummaryFlags(0, "pkg", GroupHelper.BASE_FLAGS, false);
+        mService.updateAutobundledSummaryLocked(0, "pkg",
+                new NotificationAttributes(GroupHelper.BASE_FLAGS,
+                    mock(Icon.class), 0), false);
         waitForIdle();
 
         assertFalse(summary.getSbn().isOngoing());
@@ -3427,8 +3433,8 @@
         when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
         when(mPermissionHelper.isPermissionFixed(PKG, temp.getUserId())).thenReturn(true);
 
-        NotificationRecord r = mService.createAutoGroupSummary(
-                temp.getUserId(), temp.getSbn().getPackageName(), temp.getKey(), 0);
+        NotificationRecord r = mService.createAutoGroupSummary(temp.getUserId(),
+                temp.getSbn().getPackageName(), temp.getKey(), 0, mock(Icon.class), 0);
 
         assertThat(r.isImportanceFixed()).isTrue();
     }
@@ -4116,7 +4122,8 @@
         when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
         when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
 
-        mService.snoozeNotificationInt(r.getKey(), 1000, null, mListener);
+        mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+                r.getKey(), 1000, null);
 
         verify(mWorkerHandler, never()).post(
                 any(NotificationManagerService.SnoozeNotificationRunnable.class));
@@ -4134,13 +4141,118 @@
         when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
         when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
 
-        mService.snoozeNotificationInt(r2.getKey(), 1000, null, mListener);
+        mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+                r2.getKey(), 1000, null);
 
         verify(mWorkerHandler).post(
                 any(NotificationManagerService.SnoozeNotificationRunnable.class));
     }
 
     @Test
+    public void snoozeNotificationInt_rapidSnooze_new() {
+        mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
+                .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
+
+        // Create recent notification.
+        final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+                System.currentTimeMillis());
+        mService.addNotification(nr1);
+
+        mListener = mock(ManagedServices.ManagedServiceInfo.class);
+        mListener.component = new ComponentName(PKG, PKG);
+        when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
+        when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+        mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+                nr1.getKey(), 1000, null);
+
+        verify(mWorkerHandler).post(
+                any(NotificationManagerService.SnoozeNotificationRunnable.class));
+        // Ensure cancel event is logged.
+        verify(mAppOpsManager).noteOpNoThrow(
+                AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER, mUid, PKG, null,
+                null);
+    }
+
+    @Test
+    public void snoozeNotificationInt_rapidSnooze_old() {
+        mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
+                .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
+
+        // Create old notification.
+        final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+                System.currentTimeMillis() - 60000);
+        mService.addNotification(nr1);
+
+        mListener = mock(ManagedServices.ManagedServiceInfo.class);
+        mListener.component = new ComponentName(PKG, PKG);
+        when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
+        when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+        mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+                nr1.getKey(), 1000, null);
+
+        verify(mWorkerHandler).post(
+                any(NotificationManagerService.SnoozeNotificationRunnable.class));
+        // Ensure cancel event is not logged.
+        verify(mAppOpsManager, never()).noteOpNoThrow(
+                eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(),
+                any(), any());
+    }
+
+    @Test
+    public void snoozeNotificationInt_rapidSnooze_new_flagDisabled() {
+        mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags
+                .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
+
+        // Create recent notification.
+        final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+                System.currentTimeMillis());
+        mService.addNotification(nr1);
+
+        mListener = mock(ManagedServices.ManagedServiceInfo.class);
+        mListener.component = new ComponentName(PKG, PKG);
+        when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
+        when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+        mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+                nr1.getKey(), 1000, null);
+
+        verify(mWorkerHandler).post(
+                any(NotificationManagerService.SnoozeNotificationRunnable.class));
+        // Ensure cancel event is not logged.
+        verify(mAppOpsManager, never()).noteOpNoThrow(
+                eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(),
+                any(), any());
+    }
+
+    @Test
+    public void snoozeNotificationInt_rapidSnooze_old_flagDisabled() {
+        mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags
+                .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
+
+        // Create old notification.
+        final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+                System.currentTimeMillis() - 60000);
+        mService.addNotification(nr1);
+
+        mListener = mock(ManagedServices.ManagedServiceInfo.class);
+        mListener.component = new ComponentName(PKG, PKG);
+        when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
+        when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+        mService.snoozeNotificationInt(Binder.getCallingUid(), mock(INotificationListener.class),
+                nr1.getKey(), 1000, null);
+
+        verify(mWorkerHandler).post(
+                any(NotificationManagerService.SnoozeNotificationRunnable.class));
+        // Ensure cancel event is not logged.
+        verify(mAppOpsManager, never()).noteOpNoThrow(
+                eq(AppOpsManager.OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER), anyInt(), anyString(),
+                any(), any());
+    }
+
+    @Test
     public void testSnoozeRunnable_tooManySnoozed_singleNotification() {
         final NotificationRecord notification = generateNotificationRecord(
                 mTestNotificationChannel, 1, null, true);
@@ -9741,8 +9853,11 @@
             throws RemoteException {
         IRingtonePlayer mockPlayer = mock(IRingtonePlayer.class);
         when(mAudioManager.getRingtonePlayer()).thenReturn(mockPlayer);
-        // Set up volume to be above 0 for the sound to actually play
+        // Set up volume to be above 0, and for AudioManager to signal playback should happen,
+        // for the sound to actually play
         when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10);
+        when(mAudioManager.shouldNotificationSoundPlay(any(android.media.AudioAttributes.class)))
+                .thenReturn(true);
 
         setUpPrefsForBubbles(PKG, mUid,
                 true /* global */,
@@ -11962,7 +12077,7 @@
         // add summary
         mService.addNotification(mService.createAutoGroupSummary(nr1.getUserId(),
                 nr1.getSbn().getPackageName(), nr1.getKey(),
-                GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT));
+                GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT, mock(Icon.class), 0));
 
         // cancel both children
         mBinderService.cancelNotificationWithTag(PKG, PKG, nr0.getSbn().getTag(),
@@ -11989,8 +12104,9 @@
         // add notifications + summary for USER_SYSTEM
         mService.addNotification(nr0);
         mService.addNotification(nr1);
-        mService.addNotification(mService.createAutoGroupSummary(nr1.getUserId(),
-                nr1.getSbn().getPackageName(), nr1.getKey(), GroupHelper.BASE_FLAGS));
+        mService.addNotification(
+                mService.createAutoGroupSummary(nr1.getUserId(), nr1.getSbn().getPackageName(),
+                nr1.getKey(), GroupHelper.BASE_FLAGS, mock(Icon.class), 0));
 
         // add notifications + summary for USER_ALL
         NotificationRecord nr0_all =
@@ -12000,8 +12116,10 @@
 
         mService.addNotification(nr0_all);
         mService.addNotification(nr1_all);
-        mService.addNotification(mService.createAutoGroupSummary(nr0_all.getUserId(),
-                nr0_all.getSbn().getPackageName(), nr0_all.getKey(), GroupHelper.BASE_FLAGS));
+        mService.addNotification(
+                mService.createAutoGroupSummary(nr0_all.getUserId(),
+                nr0_all.getSbn().getPackageName(),
+                nr0_all.getKey(), GroupHelper.BASE_FLAGS, mock(Icon.class), 0));
 
         // cancel both children for USER_ALL
         mBinderService.cancelNotificationWithTag(PKG, PKG, nr0_all.getSbn().getTag(),
@@ -12854,6 +12972,35 @@
     }
 
     @Test
+    public void fixNotification_customAllowlistToken()
+            throws Exception {
+        Notification n = new Notification.Builder(mContext, "test")
+                .build();
+        try {
+            Field allowlistToken = Class.forName("android.app.Notification").
+                    getDeclaredField("mAllowlistToken");
+            allowlistToken.setAccessible(true);
+            allowlistToken.set(n, new Binder());
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+        mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
+
+        IBinder actual = null;
+        try {
+            Field allowlistToken = Class.forName("android.app.Notification").
+                    getDeclaredField("mAllowlistToken");
+            allowlistToken.setAccessible(true);
+            actual = (IBinder) allowlistToken.get(n);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+        assertTrue(mService.ALLOWLIST_TOKEN == actual);
+    }
+
+    @Test
     public void testCancelAllNotifications_IgnoreUserInitiatedJob() throws Exception {
         when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
                 .thenReturn(true);
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 c44be7b..c29547f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -56,7 +56,6 @@
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.util.ArraySet;
 import android.view.WindowManager;
 import android.window.BackAnimationAdapter;
@@ -632,8 +631,8 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG)
     public void testAdjacentFocusInActivityEmbedding() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG);
         Task task = createTask(mDefaultDisplay);
         TaskFragment primary = createTaskFragmentWithActivity(task);
         TaskFragment secondary = createTaskFragmentWithActivity(task);
@@ -645,7 +644,7 @@
         doReturn(primary).when(windowState).getTaskFragment();
 
         startBackNavigation();
-        verify(mWm).moveFocusToAdjacentWindow(any(), anyInt());
+        verify(mWm).moveFocusToActivity(any());
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index a0562aa..f02dd3f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -236,6 +236,26 @@
         assertTrue(window.isOnScreen());
         window.hide(false /* doAnimation */, false /* requestAnim */);
         assertFalse(window.isOnScreen());
+
+        // Verifies that a window without animation can be hidden even if its parent is animating.
+        window.show(false /* doAnimation */, false /* requestAnim */);
+        assertTrue(window.isVisibleByPolicy());
+        window.getParent().startAnimation(mTransaction, mock(AnimationAdapter.class),
+                false /* hidden */, SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFORM);
+        window.mAttrs.windowAnimations = 0;
+        window.hide(true /* doAnimation */, true /* requestAnim */);
+        assertFalse(window.isSelfAnimating(0, SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION));
+        assertFalse(window.isVisibleByPolicy());
+        assertFalse(window.isOnScreen());
+
+        // Verifies that a window with animation can be hidden after the hide animation is finished.
+        window.show(false /* doAnimation */, false /* requestAnim */);
+        window.mAttrs.windowAnimations = android.R.style.Animation_Dialog;
+        window.hide(true /* doAnimation */, true /* requestAnim */);
+        assertTrue(window.isSelfAnimating(0, SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION));
+        assertTrue(window.isVisibleByPolicy());
+        window.cancelAnimation();
+        assertFalse(window.isVisibleByPolicy());
     }
 
     @Test
diff --git a/services/usage/Android.bp b/services/usage/Android.bp
index 867773d..2c1095a 100644
--- a/services/usage/Android.bp
+++ b/services/usage/Android.bp
@@ -18,5 +18,8 @@
     name: "services.usage",
     defaults: ["platform_service_defaults"],
     srcs: [":services.usage-sources"],
-    libs: ["services.core"],
+    libs: [
+        "services.core",
+        "service-art.stubs.system_server",
+    ],
 }
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 2445f51..44f4068 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -19,7 +19,9 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import static com.android.internal.util.ArrayUtils.defeatNullable;
+import static com.android.server.pm.DexOptHelper.getArtManagerLocal;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import static com.android.server.pm.PackageManagerServiceUtils.getPackageManagerLocal;
 import static com.android.server.usage.StorageStatsManagerLocal.StorageStatsAugmenter;
 
 import android.Manifest;
@@ -76,6 +78,9 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
+import com.android.server.art.ArtManagerLocal;
+import com.android.server.art.model.ArtManagedFileStats;
+import com.android.server.pm.PackageManagerLocal.FilteredSnapshot;
 import com.android.server.IoThread;
 import com.android.server.LocalManagerRegistry;
 import com.android.server.LocalServices;
@@ -449,7 +454,7 @@
                     }
                     if (Flags.getAppBytesByDataTypeApi()) {
                         computeAppStatsByDataTypes(
-                            stats, appInfo.sourceDir);
+                            stats, appInfo.sourceDir, packageNames[i]);
                     }
                 }
             } catch (NameNotFoundException e) {
@@ -592,6 +597,9 @@
         res.codeBytes = stats.codeSize + stats.externalCodeSize;
         res.dataBytes = stats.dataSize + stats.externalDataSize;
         res.cacheBytes = stats.cacheSize + stats.externalCacheSize;
+        res.dexoptBytes = stats.dexoptSize;
+        res.curProfBytes = stats.curProfSize;
+        res.refProfBytes = stats.refProfSize;
         res.apkBytes = stats.apkSize;
         res.libBytes = stats.libSize;
         res.dmBytes = stats.dmSize;
@@ -946,7 +954,7 @@
     }
 
     private void computeAppStatsByDataTypes(
-        PackageStats stats, String sourceDirName) {
+        PackageStats stats, String sourceDirName, String packageName) {
 
         // Get apk, lib, dm file sizes.
         File srcDir = new File(sourceDirName);
@@ -958,5 +966,24 @@
         stats.apkSize += getFileBytesInDir(srcDir, ".apk");
         stats.dmSize += getFileBytesInDir(srcDir, ".dm");
         stats.libSize += getDirBytes(new File(sourceDirName + "/lib/"));
+
+        // Get dexopt, current profle and reference profile sizes.
+        if (SystemProperties.getBoolean("dalvik.vm.features.art_managed_file_stats", false)) {
+            ArtManagedFileStats artManagedFileStats;
+            try (var snapshot = getPackageManagerLocal().withFilteredSnapshot()) {
+                artManagedFileStats =
+                    getArtManagerLocal().getArtManagedFileStats(snapshot, packageName);
+            }
+
+            stats.dexoptSize +=
+                artManagedFileStats
+                    .getTotalSizeBytesByType(ArtManagedFileStats.TYPE_DEXOPT_ARTIFACT);
+            stats.refProfSize +=
+                artManagedFileStats
+                    .getTotalSizeBytesByType(ArtManagedFileStats.TYPE_REF_PROFILE);
+            stats.curProfSize +=
+                artManagedFileStats
+                    .getTotalSizeBytesByType(ArtManagedFileStats.TYPE_CUR_PROFILE);
+        }
     }
 }
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index b7706a9..6bdc43e 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -810,6 +810,9 @@
          * If this setter method is never called or cleared using
          * {@link #clearSimultaneousCallingRestriction()}, there is no restriction and all
          * {@link PhoneAccount}s registered to Telecom by this package support simultaneous calling.
+         * If this setter is called and set as an empty Set, then this {@link PhoneAccount} does
+         * not support simultaneous calling with any other {@link PhoneAccount}s registered by the
+         * same application.
          * <p>
          * Note: Simultaneous calling restrictions can only be placed on {@link PhoneAccount}s that
          * were registered by the same application. Simultaneous calling across applications is
diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java
index b59e855..5af2c34 100644
--- a/telephony/java/android/service/euicc/EuiccService.java
+++ b/telephony/java/android/service/euicc/EuiccService.java
@@ -19,6 +19,7 @@
 
 import android.Manifest;
 import android.annotation.CallSuper;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -38,6 +39,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.lang.annotation.Retention;
@@ -759,6 +762,20 @@
     public abstract int onRetainSubscriptionsForFactoryReset(int slotId);
 
     /**
+     * Return the available memory in bytes of the eUICC.
+     *
+     * @param slotId ID of the SIM slot being queried.
+     * @return the available memory in bytes.
+     * @see android.telephony.euicc.EuiccManager#getAvailableMemoryInBytes
+     */
+    @FlaggedApi(Flags.FLAG_ESIM_AVAILABLE_MEMORY)
+    public long onGetAvailableMemoryInBytes(int slotId) {
+        // stub implementation, LPA needs to implement this
+        throw new UnsupportedOperationException("The connected LPA does not implement"
+                + "EuiccService#onGetAvailableMemoryInBytes(int)");
+    }
+
+    /**
      * Dump to a provided printWriter.
      */
     public void dump(@NonNull PrintWriter printWriter) {
@@ -834,6 +851,22 @@
         }
 
         @Override
+        @FlaggedApi(Flags.FLAG_ESIM_AVAILABLE_MEMORY)
+        public void getAvailableMemoryInBytes(
+                int slotId, IGetAvailableMemoryInBytesCallback callback) {
+            mExecutor.execute(
+                    () -> {
+                        long availableMemoryInBytes =
+                                EuiccService.this.onGetAvailableMemoryInBytes(slotId);
+                        try {
+                            callback.onSuccess(availableMemoryInBytes);
+                        } catch (RemoteException e) {
+                            // Can't communicate with the phone process; ignore.
+                        }
+                    });
+        }
+
+        @Override
         public void startOtaIfNecessary(
                 int slotId, IOtaStatusChangedCallback statusChangedCallback) {
             mExecutor.execute(new Runnable() {
diff --git a/telephony/java/android/service/euicc/IEuiccService.aidl b/telephony/java/android/service/euicc/IEuiccService.aidl
index f8d5ae9..0f8c72b 100644
--- a/telephony/java/android/service/euicc/IEuiccService.aidl
+++ b/telephony/java/android/service/euicc/IEuiccService.aidl
@@ -19,6 +19,7 @@
 import android.service.euicc.IDeleteSubscriptionCallback;
 import android.service.euicc.IDownloadSubscriptionCallback;
 import android.service.euicc.IEraseSubscriptionsCallback;
+import android.service.euicc.IGetAvailableMemoryInBytesCallback;
 import android.service.euicc.IGetDefaultDownloadableSubscriptionListCallback;
 import android.service.euicc.IGetDownloadableSubscriptionMetadataCallback;
 import android.service.euicc.IGetEidCallback;
@@ -60,4 +61,5 @@
     void retainSubscriptionsForFactoryReset(
             int slotId, in IRetainSubscriptionsForFactoryResetCallback callback);
     void dump(in IEuiccServiceDumpResultCallback callback);
-}
\ No newline at end of file
+    void getAvailableMemoryInBytes(int slotId, in IGetAvailableMemoryInBytesCallback callback);
+}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt b/telephony/java/android/service/euicc/IGetAvailableMemoryInBytesCallback.aidl
similarity index 66%
rename from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt
rename to telephony/java/android/service/euicc/IGetAvailableMemoryInBytesCallback.aidl
index 52bbfaa8..bd6d19b 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/model/PasswordUiModel.kt
+++ b/telephony/java/android/service/euicc/IGetAvailableMemoryInBytesCallback.aidl
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2023 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.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0N
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.ui.model
+package android.service.euicc;
 
-data class PasswordUiModel(val email: String)
+/** @hide */
+oneway interface IGetAvailableMemoryInBytesCallback {
+    void onSuccess(long availableMemoryInBytes);
+}
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index a188581..82ed340 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -40,6 +40,7 @@
 import android.telephony.SubscriptionManager.ProfileClass;
 import android.telephony.SubscriptionManager.SimDisplayNameSource;
 import android.telephony.SubscriptionManager.SubscriptionType;
+import android.telephony.SubscriptionManager.TransferStatus;
 import android.telephony.SubscriptionManager.UsageSetting;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
@@ -236,6 +237,11 @@
     @UsageSetting
     private final int mUsageSetting;
 
+    /**
+     * Subscription's transfer status
+     */
+    private final int mTransferStatus;
+
     // Below are the fields that do not exist in the database.
 
     /**
@@ -393,6 +399,7 @@
         this.mUsageSetting = usageSetting;
         this.mIsOnlyNonTerrestrialNetwork = false;
         this.mServiceCapabilities = 0;
+        this.mTransferStatus = 0;
     }
 
     /**
@@ -433,6 +440,7 @@
         this.mUsageSetting = builder.mUsageSetting;
         this.mIsOnlyNonTerrestrialNetwork = builder.mIsOnlyNonTerrestrialNetwork;
         this.mServiceCapabilities = builder.mServiceCapabilities;
+        this.mTransferStatus = builder.mTransferStatus;
     }
 
     /**
@@ -928,6 +936,19 @@
         return SubscriptionManager.getServiceCapabilitiesSet(mServiceCapabilities);
     }
 
+    /**
+     * Get the transfer status for this subscription.
+     *
+     * @return The transfer status for this subscription.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+    @SystemApi
+    public @TransferStatus int getTransferStatus() {
+        return mTransferStatus;
+    }
+
     @NonNull
     public static final Parcelable.Creator<SubscriptionInfo> CREATOR =
             new Parcelable.Creator<SubscriptionInfo>() {
@@ -967,6 +988,7 @@
                     .setOnlyNonTerrestrialNetwork(source.readBoolean())
                     .setServiceCapabilities(
                             SubscriptionManager.getServiceCapabilitiesSet(source.readInt()))
+                    .setTransferStatus(source.readInt())
                     .build();
         }
 
@@ -1010,6 +1032,7 @@
         dest.writeInt(mUsageSetting);
         dest.writeBoolean(mIsOnlyNonTerrestrialNetwork);
         dest.writeInt(mServiceCapabilities);
+        dest.writeInt(mTransferStatus);
     }
 
     @Override
@@ -1075,6 +1098,7 @@
                 + " isOnlyNonTerrestrialNetwork=" + mIsOnlyNonTerrestrialNetwork
                 + " serviceCapabilities=" + SubscriptionManager.getServiceCapabilitiesSet(
                 mServiceCapabilities).toString()
+                + " transferStatus=" + mTransferStatus
                 + "]";
     }
 
@@ -1101,7 +1125,8 @@
                 that.mCarrierConfigAccessRules) && Objects.equals(mGroupUuid, that.mGroupUuid)
                 && mCountryIso.equals(that.mCountryIso) && mGroupOwner.equals(that.mGroupOwner)
                 && mIsOnlyNonTerrestrialNetwork == that.mIsOnlyNonTerrestrialNetwork
-                && mServiceCapabilities == that.mServiceCapabilities;
+                && mServiceCapabilities == that.mServiceCapabilities
+                && mTransferStatus == that.mTransferStatus;
     }
 
     @Override
@@ -1110,7 +1135,8 @@
                 mDisplayNameSource, mIconTint, mNumber, mDataRoaming, mMcc, mMnc, mIsEmbedded,
                 mCardString, mIsOpportunistic, mGroupUuid, mCountryIso, mCarrierId, mProfileClass,
                 mType, mGroupOwner, mAreUiccApplicationsEnabled, mPortIndex, mUsageSetting, mCardId,
-                mIsGroupDisabled, mIsOnlyNonTerrestrialNetwork, mServiceCapabilities);
+                mIsGroupDisabled, mIsOnlyNonTerrestrialNetwork, mServiceCapabilities,
+                mTransferStatus);
         result = 31 * result + Arrays.hashCode(mEhplmns);
         result = 31 * result + Arrays.hashCode(mHplmns);
         result = 31 * result + Arrays.hashCode(mNativeAccessRules);
@@ -1314,6 +1340,8 @@
          */
         private boolean mIsOnlyNonTerrestrialNetwork = false;
 
+        private int mTransferStatus = 0;
+
         /**
          * Service capabilities bitmasks the subscription supports.
          */
@@ -1363,6 +1391,7 @@
             mUsageSetting = info.mUsageSetting;
             mIsOnlyNonTerrestrialNetwork = info.mIsOnlyNonTerrestrialNetwork;
             mServiceCapabilities = info.mServiceCapabilities;
+            mTransferStatus = info.mTransferStatus;
         }
 
         /**
@@ -1785,6 +1814,18 @@
             mServiceCapabilities = combinedCapabilities;
             return this;
         }
+         /**
+         * Set subscription's transfer status
+         *
+         * @param status Subscription's transfer status
+         * @return The builder.
+         */
+        @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+        @NonNull
+        public Builder setTransferStatus(@TransferStatus int status) {
+            mTransferStatus = status;
+            return this;
+        }
 
         /**
          * Build the {@link SubscriptionInfo}.
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 1749545..3fde3b6 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1148,6 +1148,14 @@
      */
     public static final String SERVICE_CAPABILITIES = SimInfo.COLUMN_SERVICE_CAPABILITIES;
 
+    /**
+     * TelephonyProvider column name to identify eSIM's transfer status.
+     * By default, it's disabled.
+     * <P>Type: INTEGER (int)</P>
+     * @hide
+     */
+    public static final String TRANSFER_STATUS = SimInfo.COLUMN_TRANSFER_STATUS;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"USAGE_SETTING_"},
@@ -1453,6 +1461,41 @@
     private static final LruCache<Pair<String, Configuration>, Resources> sResourcesCache =
             new LruCache<>(MAX_RESOURCE_CACHE_ENTRY_COUNT);
 
+
+    /**
+     * The profile has not been transferred or converted to an eSIM.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+    @SystemApi
+    public static final int TRANSFER_STATUS_NONE = 0;
+
+    /**
+     * The existing profile of the old device has been transferred to an eSIM of the new device.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+    @SystemApi
+    public static final int TRANSFER_STATUS_TRANSFERRED_OUT = 1;
+
+    /**
+     * The existing profile of the same device has been converted to an eSIM of the same device
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+    @SystemApi
+    public static final int TRANSFER_STATUS_CONVERTED = 2;
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"TRANSFER_STATUS"},
+            value = {
+                    TRANSFER_STATUS_NONE,
+                    TRANSFER_STATUS_TRANSFERRED_OUT,
+                    TRANSFER_STATUS_CONVERTED,
+            })
+    public @interface TransferStatus {}
+
+
     /**
      * A listener class for monitoring changes to {@link SubscriptionInfo} records.
      * <p>
@@ -4716,4 +4759,27 @@
     public static int serviceCapabilityToBitmask(@ServiceCapability int capability) {
         return 1 << (capability - 1);
     }
+
+    /**
+     * Set the transfer status of the subscriptionInfo of the subId.
+     * @param subscriptionId The unique SubscriptionInfo key in database.
+     * @param status The transfer status to change.
+     *
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+    @SystemApi
+    @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+    public void setTransferStatus(int subscriptionId, @TransferStatus int status) {
+        try {
+            ISub iSub = TelephonyManager.getSubscriptionService();
+            if (iSub != null) {
+                iSub.setTransferStatus(subscriptionId, status);
+            }
+        } catch (RemoteException ex) {
+            logd("setTransferStatus for subId = " + subscriptionId + " failed.");
+            throw ex.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 9b8e62f..7935d24 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -16,12 +16,14 @@
 package android.telephony.euicc;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
+import android.annotation.SuppressAutoDoc;
 import android.annotation.SystemApi;
 import android.app.Activity;
 import android.app.PendingIntent;
@@ -50,6 +52,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -861,6 +864,10 @@
      */
     public static final int ERROR_INVALID_PORT = 10017;
 
+    /** Temporary failure to retrieve available memory because eUICC is not ready. */
+    @FlaggedApi(Flags.FLAG_ESIM_AVAILABLE_MEMORY)
+    public static final long EUICC_MEMORY_FIELD_UNAVAILABLE = -1L;
+
     /**
      * Apps targeting on Android T and beyond will get exception whenever switchToSubscription
      * without portIndex is called for disable subscription.
@@ -961,6 +968,35 @@
     }
 
     /**
+     * Returns the available memory in bytes of the eUICC.
+     *
+     * @return the available memory in bytes. May be {@link #EUICC_MEMORY_FIELD_UNAVAILABLE} if the
+     *     eUICC is not ready. Check {@link #isEnabled} for more information.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC} or
+     *          device doesn't support querying this information from the eUICC.
+     */
+    @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
+    @FlaggedApi(Flags.FLAG_ESIM_AVAILABLE_MEMORY)
+    @RequiresPermission(
+            anyOf = {
+                Manifest.permission.READ_PHONE_STATE,
+                Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+                "carrier privileges"
+            })
+    public long getAvailableMemoryInBytes() {
+        if (!isEnabled()) {
+            return EUICC_MEMORY_FIELD_UNAVAILABLE;
+        }
+        try {
+            return getIEuiccController()
+                    .getAvailableMemoryInBytes(mCardId, mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns the current status of eUICC OTA.
      *
      * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
@@ -1707,4 +1743,57 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Sets the supported carrier ids for pSIM conversion.
+     *
+     * <p>Any existing pSIM conversion supported carrier list will be replaced
+     * by the {@code carrierIds} set here.
+     *
+     * @param carrierIds is a list of carrierIds that supports pSIM conversion
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
+     * @throws IllegalStateException if this method is called when {@link #isEnabled} is false.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+    @SystemApi
+    @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+    public void setPsimConversionSupportedCarriers(@NonNull Set<Integer> carrierIds) {
+        if (!isEnabled()) {
+            throw new IllegalStateException("Euicc is not enabled");
+        }
+        try {
+            int[] arr = carrierIds.stream().mapToInt(Integer::intValue).toArray();
+            getIEuiccController().setPsimConversionSupportedCarriers(arr);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Returns whether the given carrier id supports pSIM conversion or not.
+     *
+     * @param carrierId to check whether pSIM conversion is supported or not
+     * @return whether the given carrier id supports pSIM conversion or not,
+     *         or false if {@link #isEnabled} is false
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+    @SystemApi
+    @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+    public boolean isPsimConversionSupported(int carrierId) {
+        if (!isEnabled()) {
+            return false;
+        }
+        try {
+            return getIEuiccController().isPsimConversionSupported(carrierId);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/satellite/EnableRequestAttributes.java b/telephony/java/android/telephony/satellite/EnableRequestAttributes.java
new file mode 100644
index 0000000..bc9d230
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/EnableRequestAttributes.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 android.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.telephony.flags.Flags;
+
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * EnableRequestAttributes is used to store the attributes of the request
+ * {@link SatelliteManager#requestEnabled(EnableRequestAttributes, Executor, Consumer)}
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+public class EnableRequestAttributes {
+    /** {@code true} to enable satellite and {@code false} to disable satellite */
+    private boolean mIsEnabled;
+    /**
+     * {@code true} to enable demo mode and {@code false} to disable. When disabling satellite,
+     * {@code mIsDemoMode} is always considered as {@code false} by Telephony.
+     */
+    private boolean mIsDemoMode;
+    /**
+     * {@code true} means satellite is enabled for emergency mode, {@code false} otherwise. When
+     * disabling satellite, {@code isEmergencyMode} is always considered as {@code false} by
+     * Telephony.
+     */
+    private boolean mIsEmergencyMode;
+
+    /**
+     * Constructor from builder.
+     *
+     * @param builder Builder of {@link EnableRequestAttributes}.
+     */
+    private EnableRequestAttributes(@NonNull Builder builder) {
+        this.mIsEnabled = builder.mIsEnabled;
+        this.mIsDemoMode = builder.mIsDemoMode;
+        this.mIsEmergencyMode = builder.mIsEmergencyMode;
+    }
+
+    /**
+     * @return Whether satellite is to be enabled
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public boolean isEnabled() {
+        return mIsEnabled;
+    }
+
+    /**
+     * @return Whether demo mode is to be enabled
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public boolean isDemoMode() {
+        return mIsDemoMode;
+    }
+
+    /**
+     * @return Whether satellite is to be enabled for emergency mode
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public boolean isEmergencyMode() {
+        return mIsEmergencyMode;
+    }
+
+    /**
+     * The builder class of {@link EnableRequestAttributes}
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final class Builder {
+        private boolean mIsEnabled;
+        private boolean mIsDemoMode = false;
+        private boolean mIsEmergencyMode = false;
+
+        @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+        public Builder(boolean isEnabled) {
+            mIsEnabled = isEnabled;
+        }
+
+        /**
+         * Set demo mode
+         *
+         * @param isDemoMode {@code true} to enable demo mode and {@code false} to disable. When
+         *                   disabling satellite, {@code isDemoMode} is always considered as
+         *                   {@code false} by Telephony.
+         * @return The builder object
+         */
+        @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+        @NonNull
+        public Builder setDemoMode(boolean isDemoMode) {
+            if (mIsEnabled) {
+                mIsDemoMode = isDemoMode;
+            }
+            return this;
+        }
+
+        /**
+         * Set emergency mode
+         *
+         * @param isEmergencyMode {@code true} means satellite is enabled for emergency mode,
+         *                        {@code false} otherwise. When disabling satellite,
+         *                        {@code isEmergencyMode} is always considered as {@code false} by
+         *                        Telephony.
+         * @return The builder object
+         */
+        @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+        @NonNull
+        public Builder setEmergencyMode(boolean isEmergencyMode) {
+            if (mIsEnabled) {
+                mIsEmergencyMode = isEmergencyMode;
+            }
+            return this;
+        }
+
+        /**
+         * Build the {@link EnableRequestAttributes}
+         *
+         * @return The {@link EnableRequestAttributes} instance.
+         */
+        @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+        @NonNull
+        public EnableRequestAttributes build() {
+            return new EnableRequestAttributes(this);
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index b97822a..2c141cf 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -162,6 +162,13 @@
 
     /**
      * Bundle key to get the response from
+     * {@link #requestIsEmergencyModeEnabled(Executor, OutcomeReceiver)}.
+     * @hide
+     */
+    public static final String KEY_EMERGENCY_MODE_ENABLED = "emergency_mode_enabled";
+
+    /**
+     * Bundle key to get the response from
      * {@link #requestIsSupported(Executor, OutcomeReceiver)}.
      * @hide
      */
@@ -341,6 +348,13 @@
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_ILLEGAL_STATE = 23;
 
+    /**
+     * Telephony framework timeout to receive ACK or response from the satellite modem after
+     * sending a request to the modem.
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final int SATELLITE_RESULT_MODEM_TIMEOUT = 24;
+
     /** @hide */
     @IntDef(prefix = {"SATELLITE_RESULT_"}, value = {
             SATELLITE_RESULT_SUCCESS,
@@ -366,7 +380,8 @@
             SATELLITE_RESULT_NOT_SUPPORTED,
             SATELLITE_RESULT_REQUEST_IN_PROGRESS,
             SATELLITE_RESULT_MODEM_BUSY,
-            SATELLITE_RESULT_ILLEGAL_STATE
+            SATELLITE_RESULT_ILLEGAL_STATE,
+            SATELLITE_RESULT_MODEM_TIMEOUT
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SatelliteResult {}
@@ -482,9 +497,7 @@
      * aligned with the satellite, user can send a message and also receive a reply in demo mode.
      * If enableSatellite is {@code false}, enableDemoMode has no impact on the behavior.
      *
-     * @param enableSatellite {@code true} to enable the satellite modem and
-     *                        {@code false} to disable.
-     * @param enableDemoMode {@code true} to enable demo mode and {@code false} to disable.
+     * @param attributes The attributes of the enable request.
      * @param executor The executor on which the error code listener will be called.
      * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
@@ -493,9 +506,10 @@
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    public void requestEnabled(boolean enableSatellite, boolean enableDemoMode,
+    public void requestEnabled(@NonNull EnableRequestAttributes attributes,
             @NonNull @CallbackExecutor Executor executor,
             @SatelliteResult @NonNull Consumer<Integer> resultListener) {
+        Objects.requireNonNull(attributes);
         Objects.requireNonNull(executor);
         Objects.requireNonNull(resultListener);
 
@@ -509,8 +523,8 @@
                                 () -> resultListener.accept(result)));
                     }
                 };
-                telephony.requestSatelliteEnabled(mSubId, enableSatellite, enableDemoMode,
-                        errorCallback);
+                telephony.requestSatelliteEnabled(mSubId, attributes.isEnabled(),
+                        attributes.isDemoMode(), attributes.isEmergencyMode(), errorCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -631,6 +645,61 @@
     }
 
     /**
+     * Request to get whether the satellite service is enabled for emergency mode.
+     *
+     * @param executor The executor on which the callback will be called.
+     * @param callback The callback object to which the result will be delivered.
+     *                 If the request is successful, {@link OutcomeReceiver#onResult(Object)}
+     *                 will return a {@code boolean} with value {@code true} if satellite is enabled
+     *                 for emergency mode and {@code false} otherwise.
+     *                 If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
+     *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public void requestIsEmergencyModeEnabled(@NonNull @CallbackExecutor Executor executor,
+            @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                ResultReceiver receiver = new ResultReceiver(null) {
+                    @Override
+                    protected void onReceiveResult(int resultCode, Bundle resultData) {
+                        if (resultCode == SATELLITE_RESULT_SUCCESS) {
+                            if (resultData.containsKey(KEY_EMERGENCY_MODE_ENABLED)) {
+                                boolean isEmergencyModeEnabled =
+                                        resultData.getBoolean(KEY_EMERGENCY_MODE_ENABLED);
+                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                        callback.onResult(isEmergencyModeEnabled)));
+                            } else {
+                                loge("KEY_EMERGENCY_MODE_ENABLED does not exist.");
+                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                        callback.onError(new SatelliteException(
+                                                SATELLITE_RESULT_REQUEST_FAILED))));
+                            }
+                        } else {
+                            executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                    callback.onError(new SatelliteException(resultCode))));
+                        }
+                    }
+                };
+                telephony.requestIsEmergencyModeEnabled(mSubId, receiver);
+            } else {
+                executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                        new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+            }
+        } catch (RemoteException ex) {
+            loge("requestIsEmergencyModeEnabled() RemoteException: " + ex);
+            ex.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Request to get whether the satellite service is supported on the device.
      *
      * <p>
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 3f41d56..cc770aa 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -376,4 +376,12 @@
      * @param data with the sim specific configs to be backed up.
      */
     void restoreAllSimSpecificSettingsFromBackup(in byte[] data);
+
+    /**
+     * Set the transfer status of the subscriptionInfo that corresponds to subId.
+     * @param subId The unique SubscriptionInfo key in database.
+     * @param status The transfer status to change. This value must be one of the following.
+     */
+    @EnforcePermission("WRITE_EMBEDDED_SUBSCRIPTIONS")
+    void setTransferStatus(int subId, int status);
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 213fbc5..bd47b1f 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2742,14 +2742,19 @@
      * Request to enable or disable the satellite modem.
      *
      * @param subId The subId of the subscription to enable or disable the satellite modem for.
-     * @param enable True to enable the satellite modem and false to disable.
-     * @param isDemoModeEnabled True if demo mode is enabled and false otherwise.
+     * @param enableSatellite True to enable the satellite modem and false to disable.
+     * @param enableDemoMode True if demo mode is enabled and false otherwise. When
+     *                       disabling satellite, {@code enableDemoMode} is always considered as
+     *                       {@code false} by Telephony.
+     * @param isEmergency {@code true} means satellite is enabled for emergency mode, {@code false}
+     *                    otherwise. When disabling satellite, {@code isEmergency} is always
+     *                    considered as {@code false} by Telephony.
      * @param callback The callback to get the result of the request.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestSatelliteEnabled(int subId, boolean enable, boolean isDemoModeEnabled,
-            in IIntegerConsumer callback);
+    void requestSatelliteEnabled(int subId, boolean enableSatellite, boolean enableDemoMode,
+            boolean isEmergency, in IIntegerConsumer callback);
 
     /**
      * Request to get whether the satellite modem is enabled.
@@ -2775,6 +2780,18 @@
     void requestIsDemoModeEnabled(int subId, in ResultReceiver receiver);
 
     /**
+     * Request to get whether the satellite service is enabled with emergency mode.
+     *
+     * @param subId The subId of the subscription to request whether the satellite demo mode is
+     *              enabled for.
+     * @param receiver Result receiver to get the error code of the request and whether the
+     *                 satellite is enabled with emergency mode.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    void requestIsEmergencyModeEnabled(int subId, in ResultReceiver receiver);
+
+    /**
      * Request to get whether the satellite service is supported on the device.
      *
      * @param subId The subId of the subscription to check whether satellite is supported for.
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index 07f2916..a9ebd5c 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -250,9 +250,6 @@
      */
     public static final int DOMAIN_NON_3GPP_PS = 4;
 
-    /** Key to enable comparison of domain selection results from legacy and new code. */
-    public static final String EXTRA_COMPARE_DOMAIN = "compare_domain";
-
     /** The key to specify the emergency service category */
     public static final String EXTRA_EMERGENCY_SERVICE_CATEGORY = "emergency_service_category";
 }
diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
index 19f1a5b..053bc7d 100644
--- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
@@ -57,4 +57,7 @@
     boolean isSimPortAvailable(int cardId, int portIndex, String callingPackage);
     boolean hasCarrierPrivilegesForPackageOnAnyPhone(String callingPackage);
     boolean isCompatChangeEnabled(String callingPackage, long changeId);
+    void setPsimConversionSupportedCarriers(in int[] carrierIds);
+    boolean isPsimConversionSupported(in int carrierId);
+    long getAvailableMemoryInBytes(int cardId, String callingPackage);
 }
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 73cc2f2..f628af1 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -340,6 +340,14 @@
         wmHelper.StateSyncBuilder().withPipGone().withHomeActivityVisible().waitForAndVerify()
     }
 
+    open fun tapPipToShowMenu(wmHelper: WindowManagerStateHelper) {
+        val windowRect = getWindowRect(wmHelper)
+        uiDevice.click(windowRect.centerX(), windowRect.centerY())
+        // search and interact with the dismiss button
+        val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
+        uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
+    }
+
     /** Close the pip window by pressing the expand button */
     fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) {
         val windowRect = getWindowRect(wmHelper)