Merge changes from topic "persistent_device_id_in_api" into main

* changes:
  New permission system APIs with persistent device Id
  Create a system API to check permission by persistent device Id
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 9ee74e3..1c6df75 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -56,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}",
@@ -1159,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 a148cbd..9d2147c 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -361,7 +361,10 @@
     previous_api: ":android.api.public.latest",
     merge_annotations_dirs: ["metalava-manual"],
     defaults_visibility: ["//frameworks/base/api"],
-    visibility: ["//frameworks/base/api"],
+    visibility: [
+        "//frameworks/base/api",
+        "//frameworks/base/core/api",
+    ],
 }
 
 // We resolve dependencies on APIs in modules by depending on a prebuilt of the whole
diff --git a/api/api.go b/api/api.go
index fa2be21..c733f5b 100644
--- a/api/api.go
+++ b/api/api.go
@@ -130,7 +130,7 @@
 	Scope string
 }
 
-func createMergedTxt(ctx android.LoadHookContext, txt MergedTxtDefinition) {
+func createMergedTxt(ctx android.LoadHookContext, txt MergedTxtDefinition, stubsTypeSuffix string, doDist bool) {
 	metalavaCmd := "$(location metalava)"
 	// Silence reflection warnings. See b/168689341
 	metalavaCmd += " -J--add-opens=java.base/java.util=ALL-UNNAMED "
@@ -140,7 +140,7 @@
 	if txt.Scope != "public" {
 		filename = txt.Scope + "-" + filename
 	}
-	moduleName := ctx.ModuleName() + "-" + filename
+	moduleName := ctx.ModuleName() + stubsTypeSuffix + filename
 
 	props := genruleProps{}
 	props.Name = proptools.StringPtr(moduleName)
@@ -148,17 +148,19 @@
 	props.Out = []string{filename}
 	props.Cmd = proptools.StringPtr(metalavaCmd + "$(in) --out $(out)")
 	props.Srcs = append([]string{txt.BaseTxt}, createSrcs(txt.Modules, txt.ModuleTag)...)
-	props.Dists = []android.Dist{
-		{
-			Targets: []string{"droidcore"},
-			Dir:     proptools.StringPtr("api"),
-			Dest:    proptools.StringPtr(filename),
-		},
-		{
-			Targets: []string{"api_txt", "sdk"},
-			Dir:     proptools.StringPtr("apistubs/android/" + txt.Scope + "/api"),
-			Dest:    proptools.StringPtr(txt.DistFilename),
-		},
+	if doDist {
+		props.Dists = []android.Dist{
+			{
+				Targets: []string{"droidcore"},
+				Dir:     proptools.StringPtr("api"),
+				Dest:    proptools.StringPtr(filename),
+			},
+			{
+				Targets: []string{"api_txt", "sdk"},
+				Dir:     proptools.StringPtr("apistubs/android/" + txt.Scope + "/api"),
+				Dest:    proptools.StringPtr(txt.DistFilename),
+			},
+		}
 	}
 	props.Visibility = []string{"//visibility:public"}
 	ctx.CreateModule(genrule.GenRuleFactory, &props)
@@ -343,7 +345,7 @@
 	ctx.CreateModule(android.FileGroupFactory, &props)
 }
 
-func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) {
+func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string, baseTxtModulePrefix, stubsTypeSuffix string, doDist bool) {
 	var textFiles []MergedTxtDefinition
 
 	tagSuffix := []string{".api.txt}", ".removed-api.txt}"}
@@ -352,7 +354,7 @@
 		textFiles = append(textFiles, MergedTxtDefinition{
 			TxtFilename:  f,
 			DistFilename: distFilename[i],
-			BaseTxt:      ":non-updatable-" + f,
+			BaseTxt:      ":" + baseTxtModulePrefix + f,
 			Modules:      bootclasspath,
 			ModuleTag:    "{.public" + tagSuffix[i],
 			Scope:        "public",
@@ -360,7 +362,7 @@
 		textFiles = append(textFiles, MergedTxtDefinition{
 			TxtFilename:  f,
 			DistFilename: distFilename[i],
-			BaseTxt:      ":non-updatable-system-" + f,
+			BaseTxt:      ":" + baseTxtModulePrefix + "system-" + f,
 			Modules:      bootclasspath,
 			ModuleTag:    "{.system" + tagSuffix[i],
 			Scope:        "system",
@@ -368,7 +370,7 @@
 		textFiles = append(textFiles, MergedTxtDefinition{
 			TxtFilename:  f,
 			DistFilename: distFilename[i],
-			BaseTxt:      ":non-updatable-module-lib-" + f,
+			BaseTxt:      ":" + baseTxtModulePrefix + "module-lib-" + f,
 			Modules:      bootclasspath,
 			ModuleTag:    "{.module-lib" + tagSuffix[i],
 			Scope:        "module-lib",
@@ -376,14 +378,14 @@
 		textFiles = append(textFiles, MergedTxtDefinition{
 			TxtFilename:  f,
 			DistFilename: distFilename[i],
-			BaseTxt:      ":non-updatable-system-server-" + f,
+			BaseTxt:      ":" + baseTxtModulePrefix + "system-server-" + f,
 			Modules:      system_server_classpath,
 			ModuleTag:    "{.system-server" + tagSuffix[i],
 			Scope:        "system-server",
 		})
 	}
 	for _, txt := range textFiles {
-		createMergedTxt(ctx, txt)
+		createMergedTxt(ctx, txt, stubsTypeSuffix, doDist)
 	}
 }
 
@@ -465,7 +467,8 @@
 		bootclasspath = append(bootclasspath, a.properties.Conditional_bootclasspath...)
 		sort.Strings(bootclasspath)
 	}
-	createMergedTxts(ctx, bootclasspath, system_server_classpath)
+	createMergedTxts(ctx, bootclasspath, system_server_classpath, "non-updatable-", "-", false)
+	createMergedTxts(ctx, bootclasspath, system_server_classpath, "non-updatable-exportable-", "-exportable-", true)
 
 	createMergedPublicStubs(ctx, bootclasspath)
 	createMergedSystemStubs(ctx, bootclasspath)
diff --git a/core/api/Android.bp b/core/api/Android.bp
index 8d8a82b..77594b7 100644
--- a/core/api/Android.bp
+++ b/core/api/Android.bp
@@ -96,3 +96,54 @@
     name: "non-updatable-test-lint-baseline.txt",
     srcs: ["test-lint-baseline.txt"],
 }
+
+// Exportable stub artifacts
+filegroup {
+    name: "non-updatable-exportable-current.txt",
+    srcs: [":api-stubs-docs-non-updatable{.exportable.api.txt}"],
+}
+
+filegroup {
+    name: "non-updatable-exportable-removed.txt",
+    srcs: [":api-stubs-docs-non-updatable{.exportable.removed-api.txt}"],
+}
+
+filegroup {
+    name: "non-updatable-exportable-system-current.txt",
+    srcs: [":system-api-stubs-docs-non-updatable{.exportable.api.txt}"],
+}
+
+filegroup {
+    name: "non-updatable-exportable-system-removed.txt",
+    srcs: [":system-api-stubs-docs-non-updatable{.exportable.removed-api.txt}"],
+}
+
+filegroup {
+    name: "non-updatable-exportable-module-lib-current.txt",
+    srcs: [":module-lib-api-stubs-docs-non-updatable{.exportable.api.txt}"],
+}
+
+filegroup {
+    name: "non-updatable-exportable-module-lib-removed.txt",
+    srcs: [":module-lib-api-stubs-docs-non-updatable{.exportable.removed-api.txt}"],
+}
+
+filegroup {
+    name: "non-updatable-exportable-test-current.txt",
+    srcs: [":test-api-stubs-docs-non-updatable{.exportable.api.txt}"],
+}
+
+filegroup {
+    name: "non-updatable-exportable-test-removed.txt",
+    srcs: [":test-api-stubs-docs-non-updatable{.exportable.removed-api.txt}"],
+}
+
+filegroup {
+    name: "non-updatable-exportable-system-server-current.txt",
+    srcs: [":services-non-updatable-stubs{.exportable.api.txt}"],
+}
+
+filegroup {
+    name: "non-updatable-exportable-system-server-removed.txt",
+    srcs: [":services-non-updatable-stubs{.exportable.removed-api.txt}"],
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index c896d5c..f41982f 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
@@ -1375,6 +1375,7 @@
     field public static final int reqTouchScreen = 16843303; // 0x1010227
     field public static final int requestLegacyExternalStorage = 16844291; // 0x1010603
     field public static final int requestRawExternalStorageAccess = 16844357; // 0x1010645
+    field @FlaggedApi("android.security.content_uri_permission_apis") public static final int requireContentUriPermissionFromCaller;
     field public static final int requireDeviceScreenOn = 16844317; // 0x101061d
     field public static final int requireDeviceUnlock = 16843756; // 0x10103ec
     field public static final int required = 16843406; // 0x101028e
@@ -1568,7 +1569,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 +1891,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 +7896,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 +7947,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);
@@ -8067,8 +8071,8 @@
     method public boolean isUsbDataSignalingEnabled();
     method public boolean isUsingUnifiedPassword(@NonNull android.content.ComponentName);
     method @NonNull public java.util.List<android.os.UserHandle> listForegroundAffiliatedUsers();
-    method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCK, conditional=true) public void lockNow();
-    method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCK, conditional=true) public void lockNow(int);
+    method @RequiresPermission(value="android.permission.LOCK_DEVICE", conditional=true) public void lockNow();
+    method @RequiresPermission(value="android.permission.LOCK_DEVICE", conditional=true) public void lockNow(int);
     method public int logoutUser(@NonNull android.content.ComponentName);
     method public void reboot(@NonNull android.content.ComponentName);
     method public void removeActiveAdmin(@NonNull android.content.ComponentName);
@@ -8100,6 +8104,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 +8530,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
@@ -24706,6 +24712,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);
@@ -40954,6 +40961,14 @@
 
 }
 
+package android.service.persistentdata {
+
+  @FlaggedApi("android.security.frp_enforcement") public class PersistentDataBlockManager {
+    method @FlaggedApi("android.security.frp_enforcement") public boolean isFactoryResetProtectionActive();
+  }
+
+}
+
 package android.service.quickaccesswallet {
 
   public interface GetWalletCardsCallback {
@@ -46298,6 +46313,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();
@@ -46330,6 +46346,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";
@@ -53618,8 +53635,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();
@@ -53629,7 +53646,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();
@@ -53645,7 +53662,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();
@@ -53677,7 +53694,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);
@@ -53699,9 +53716,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);
@@ -53713,8 +53730,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-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 c3eaedf..f30c8cf 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";
@@ -112,6 +113,7 @@
     field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA";
     field public static final String COMPANION_APPROVE_WIFI_CONNECTIONS = "android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS";
     field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS";
+    field @FlaggedApi("android.security.frp_enforcement") public static final String CONFIGURE_FACTORY_RESET_PROTECTION = "android.permission.CONFIGURE_FACTORY_RESET_PROTECTION";
     field public static final String CONFIGURE_INTERACT_ACROSS_PROFILES = "android.permission.CONFIGURE_INTERACT_ACROSS_PROFILES";
     field @Deprecated public static final String CONNECTIVITY_INTERNAL = "android.permission.CONNECTIVITY_INTERNAL";
     field public static final String CONNECTIVITY_USE_RESTRICTED_NETWORKS = "android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS";
@@ -3150,14 +3152,38 @@
 
 package android.app.wearable {
 
+  @FlaggedApi("android.app.wearable.enable_data_request_observer_api") public final class WearableSensingDataRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getDataSize();
+    method public int getDataType();
+    method public static int getMaxRequestSize();
+    method public static int getRateLimit();
+    method @NonNull public static java.time.Duration getRateLimitWindowSize();
+    method @NonNull public android.os.PersistableBundle getRequestDetails();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.wearable.WearableSensingDataRequest> CREATOR;
+  }
+
+  @FlaggedApi("android.app.wearable.enable_data_request_observer_api") public static final class WearableSensingDataRequest.Builder {
+    ctor public WearableSensingDataRequest.Builder(int);
+    method @NonNull public android.app.wearable.WearableSensingDataRequest build();
+    method @NonNull public android.app.wearable.WearableSensingDataRequest.Builder setRequestDetails(@NonNull android.os.PersistableBundle);
+  }
+
   public class WearableSensingManager {
+    method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @Nullable public static android.app.wearable.WearableSensingDataRequest getDataRequestFromIntent(@NonNull android.content.Intent);
     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>);
+    method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void registerDataRequestObserver(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void unregisterDataRequestObserver(int, @NonNull android.app.PendingIntent, @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
     field public static final int STATUS_UNSUPPORTED = 2; // 0x2
+    field @FlaggedApi("android.app.wearable.enable_data_request_observer_api") public static final int STATUS_UNSUPPORTED_DATA_TYPE = 8; // 0x8
     field @FlaggedApi("android.app.wearable.enable_unsupported_operation_status_code") public static final int STATUS_UNSUPPORTED_OPERATION = 6; // 0x6
     field public static final int STATUS_WEARABLE_UNAVAILABLE = 4; // 0x4
   }
@@ -4249,7 +4275,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();
@@ -4259,9 +4284,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
@@ -4415,8 +4437,9 @@
   }
 
   @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class CancelSelectionRequest implements android.os.Parcelable {
+    ctor public CancelSelectionRequest(@NonNull android.credentials.selection.RequestToken, boolean, @NonNull String);
     method public int describeContents();
-    method @NonNull public String getAppPackageName();
+    method @NonNull public String getPackageName();
     method @NonNull public android.credentials.selection.RequestToken getRequestToken();
     method public boolean shouldShowCancellationExplanation();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -4498,10 +4521,10 @@
 
   @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class RequestInfo implements android.os.Parcelable {
     method public int describeContents();
-    method @NonNull public String getAppPackageName();
     method @Nullable public android.credentials.CreateCredentialRequest getCreateCredentialRequest();
     method @NonNull public java.util.List<java.lang.String> getDefaultProviderIds();
     method @Nullable public android.credentials.GetCredentialRequest getGetCredentialRequest();
+    method @NonNull public String getPackageName();
     method @NonNull public java.util.List<java.lang.String> getRegistryProviderIds();
     method @NonNull public android.credentials.selection.RequestToken getRequestToken();
     method @NonNull public String getType();
@@ -4685,7 +4708,7 @@
 package android.hardware.camera2.extension {
 
   @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract class AdvancedExtender {
-    ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") protected AdvancedExtender(@NonNull android.hardware.camera2.CameraManager);
+    ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public AdvancedExtender(@NonNull android.hardware.camera2.CameraManager);
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.List<android.hardware.camera2.CaptureRequest.Key> getAvailableCaptureRequestKeys(@NonNull String);
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.List<android.hardware.camera2.CaptureResult.Key> getAvailableCaptureResultKeys(@NonNull String);
     method @FlaggedApi("com.android.internal.camera.flags.camera_extensions_characteristics_get") @NonNull public abstract java.util.List<android.util.Pair<android.hardware.camera2.CameraCharacteristics.Key,java.lang.Object>> getAvailableCharacteristicsKeyValues();
@@ -4693,23 +4716,23 @@
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract android.hardware.camera2.extension.SessionProcessor getSessionProcessor();
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.Map<java.lang.Integer,java.util.List<android.util.Size>> getSupportedCaptureOutputResolutions(@NonNull String);
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.Map<java.lang.Integer,java.util.List<android.util.Size>> getSupportedPreviewOutputResolutions(@NonNull String);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void init(@NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap);
+    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void initialize(@NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap);
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract boolean isExtensionAvailable(@NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap);
   }
 
   @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract class CameraExtensionService extends android.app.Service {
     ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") protected CameraExtensionService();
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.os.IBinder onBind(@Nullable android.content.Intent);
+    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract android.hardware.camera2.extension.AdvancedExtender onInitializeAdvancedExtension(int);
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract boolean onRegisterClient(@NonNull android.os.IBinder);
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void onUnregisterClient(@NonNull android.os.IBinder);
   }
 
   @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class CameraOutputSurface {
-    ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public CameraOutputSurface(@NonNull android.view.Surface, @Nullable android.util.Size);
+    ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public CameraOutputSurface(@NonNull android.view.Surface, @NonNull android.util.Size);
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int getImageFormat();
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @Nullable public android.util.Size getSize();
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @Nullable public android.view.Surface getSurface();
+    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.util.Size getSize();
+    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.view.Surface getSurface();
   }
 
   @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class CharacteristicsMap {
@@ -4727,10 +4750,10 @@
 
   @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class RequestProcessor {
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void abortCaptures();
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int setRepeating(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @Nullable java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback);
+    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int setRepeating(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback) throws android.hardware.camera2.CameraAccessException;
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void stopRepeating();
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int submit(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @Nullable java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int submitBurst(@NonNull java.util.List<android.hardware.camera2.extension.RequestProcessor.Request>, @Nullable java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback);
+    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int submit(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback) throws android.hardware.camera2.CameraAccessException;
+    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int submitBurst(@NonNull java.util.List<android.hardware.camera2.extension.RequestProcessor.Request>, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback) throws android.hardware.camera2.CameraAccessException;
   }
 
   @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final class RequestProcessor.Request {
@@ -4749,15 +4772,15 @@
   }
 
   @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract class SessionProcessor {
-    ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") protected SessionProcessor();
+    ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public SessionProcessor();
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void deInitSession(@NonNull android.os.IBinder);
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract android.hardware.camera2.extension.ExtensionConfiguration initSession(@NonNull android.os.IBinder, @NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap, @NonNull android.hardware.camera2.extension.CameraOutputSurface, @NonNull android.hardware.camera2.extension.CameraOutputSurface);
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void onCaptureSessionEnd();
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void onCaptureSessionStart(@NonNull android.hardware.camera2.extension.RequestProcessor, @NonNull String);
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void setParameters(@NonNull android.hardware.camera2.CaptureRequest);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract int startCapture(@Nullable java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract int startRepeating(@Nullable java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback);
-    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract int startTrigger(@NonNull android.hardware.camera2.CaptureRequest, @Nullable java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback);
+    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract int startMultiFrameCapture(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback);
+    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract int startRepeating(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback);
+    method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract int startTrigger(@NonNull android.hardware.camera2.CaptureRequest, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback);
     method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void stopRepeating();
   }
 
@@ -6954,6 +6977,8 @@
   @FlaggedApi("android.media.audiopolicy.enable_fade_manager_configuration") public final class FadeManagerConfiguration implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public java.util.List<android.media.AudioAttributes> getAudioAttributesWithVolumeShaperConfigs();
+    method public static long getDefaultFadeInDurationMillis();
+    method public static long getDefaultFadeOutDurationMillis();
     method public long getFadeInDelayForOffenders();
     method public long getFadeInDurationForAudioAttributes(@NonNull android.media.AudioAttributes);
     method public long getFadeInDurationForUsage(int);
@@ -6979,7 +7004,6 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.media.FadeManagerConfiguration> CREATOR;
     field public static final long DURATION_NOT_SET = 0L; // 0x0L
     field public static final int FADE_STATE_DISABLED = 0; // 0x0
-    field public static final int FADE_STATE_ENABLED_AUTO = 2; // 0x2
     field public static final int FADE_STATE_ENABLED_DEFAULT = 1; // 0x1
     field public static final String TAG = "FadeManagerConfiguration";
     field public static final int VOLUME_SHAPER_SYSTEM_FADE_ID = 2; // 0x2
@@ -6994,10 +7018,10 @@
     method @NonNull public android.media.FadeManagerConfiguration.Builder addUnfadeableContentType(int);
     method @NonNull public android.media.FadeManagerConfiguration.Builder addUnfadeableUid(int);
     method @NonNull public android.media.FadeManagerConfiguration build();
-    method @NonNull public android.media.FadeManagerConfiguration.Builder clearFadeableUsage(int);
-    method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableAudioAttributes(@NonNull android.media.AudioAttributes);
-    method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableContentType(int);
-    method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableUid(int);
+    method @NonNull public android.media.FadeManagerConfiguration.Builder clearFadeableUsages();
+    method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableAudioAttributes();
+    method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableContentTypes();
+    method @NonNull public android.media.FadeManagerConfiguration.Builder clearUnfadeableUids();
     method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDelayForOffenders(long);
     method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDurationForAudioAttributes(@NonNull android.media.AudioAttributes, long);
     method @NonNull public android.media.FadeManagerConfiguration.Builder setFadeInDurationForUsage(int, long);
@@ -12409,6 +12433,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);
@@ -12679,13 +12704,15 @@
 
 package android.service.persistentdata {
 
-  public class PersistentDataBlockManager {
+  @FlaggedApi("android.security.frp_enforcement") public class PersistentDataBlockManager {
+    method @FlaggedApi("android.security.frp_enforcement") @RequiresPermission(android.Manifest.permission.CONFIGURE_FACTORY_RESET_PROTECTION) public boolean deactivateFactoryResetProtection(@NonNull byte[]);
     method @RequiresPermission(android.Manifest.permission.ACCESS_PDB_STATE) public int getDataBlockSize();
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public int getFlashLockState();
     method public long getMaximumDataBlockSize();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public boolean getOemUnlockEnabled();
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_PDB_STATE) public String getPersistentDataPackageName();
     method public byte[] read();
+    method @FlaggedApi("android.security.frp_enforcement") public boolean setFactoryResetProtectionSecret(@NonNull byte[]);
     method @Deprecated @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void setOemUnlockEnabled(boolean);
     method @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void wipe();
     method public int write(byte[]);
@@ -13432,12 +13459,24 @@
 
 package android.service.wearable {
 
+  @FlaggedApi("android.app.wearable.enable_data_request_observer_api") public interface WearableSensingDataRequester {
+    method public void requestData(@NonNull android.app.wearable.WearableSensingDataRequest, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    field public static final int STATUS_OBSERVER_CANCELLED = 2; // 0x2
+    field public static final int STATUS_SUCCESS = 1; // 0x1
+    field public static final int STATUS_TOO_FREQUENT = 4; // 0x4
+    field public static final int STATUS_TOO_LARGE = 3; // 0x3
+    field public static final int STATUS_UNKNOWN = 0; // 0x0
+  }
+
   public abstract class WearableSensingService extends android.app.Service {
     ctor public WearableSensingService();
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method @BinderThread public abstract void onDataProvided(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @BinderThread public void onDataRequestObserverRegistered(int, @NonNull String, @NonNull android.service.wearable.WearableSensingDataRequester, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @BinderThread public void onDataRequestObserverUnregistered(int, @NonNull String, @NonNull android.service.wearable.WearableSensingDataRequester, @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";
@@ -13613,6 +13652,13 @@
     method public final void addExistingConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.Connection, @NonNull android.telecom.Conference);
   }
 
+  public final class DisconnectCause implements android.os.Parcelable {
+    ctor @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public DisconnectCause(int, @NonNull CharSequence, @NonNull CharSequence, @NonNull String, int, int, int, @Nullable android.telephony.ims.ImsReasonInfo);
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @Nullable public android.telephony.ims.ImsReasonInfo getImsReasonInfo();
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public int getTelephonyDisconnectCause();
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public int getTelephonyPreciseDisconnectCause();
+  }
+
   public abstract class InCallService extends android.app.Service {
     method @Deprecated public android.telecom.Phone getPhone();
     method @Deprecated public void onPhoneCreated(android.telecom.Phone);
@@ -13852,7 +13898,7 @@
     method @Deprecated public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsForPackage();
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isInEmergencyCall();
-    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean isInSelfManagedCall(@NonNull String, @NonNull android.os.UserHandle, boolean);
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isInSelfManagedCall(@NonNull String, @NonNull android.os.UserHandle, boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUserSelectedOutgoingPhoneAccount(@Nullable android.telecom.PhoneAccountHandle);
     field public static final String ACTION_CURRENT_TTY_MODE_CHANGED = "android.telecom.action.CURRENT_TTY_MODE_CHANGED";
@@ -14185,12 +14231,12 @@
     method @NonNull public android.telephony.DataThrottlingRequest.Builder setDataThrottlingAction(int);
   }
 
-  @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public class DomainSelectionService extends android.app.Service {
+  @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public abstract class DomainSelectionService extends android.app.Service {
     ctor public DomainSelectionService();
     method public void onBarringInfoUpdated(int, int, @NonNull android.telephony.BarringInfo);
-    method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent);
+    method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
     method @NonNull public java.util.concurrent.Executor onCreateExecutor();
-    method public void onDomainSelection(@NonNull android.telephony.DomainSelectionService.SelectionAttributes, @NonNull android.telephony.TransportSelectorCallback);
+    method public abstract void onDomainSelection(@NonNull android.telephony.DomainSelectionService.SelectionAttributes, @NonNull android.telephony.TransportSelectorCallback);
     method public void onServiceStateUpdated(int, int, @NonNull android.telephony.ServiceState);
     field public static final int SCAN_TYPE_FULL_SERVICE = 2; // 0x2
     field public static final int SCAN_TYPE_LIMITED_SERVICE = 1; // 0x1
@@ -14204,7 +14250,7 @@
     method @Nullable public android.net.Uri getAddress();
     method @Nullable public String getCallId();
     method public int getCsDisconnectCause();
-    method @Nullable public android.telephony.EmergencyRegResult getEmergencyRegResult();
+    method @Nullable public android.telephony.EmergencyRegistrationResult getEmergencyRegistrationResult();
     method @Nullable public android.telephony.ims.ImsReasonInfo getPsDisconnectCause();
     method public int getSelectorType();
     method public int getSlotIndex();
@@ -14220,13 +14266,13 @@
   @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final class DomainSelectionService.SelectionAttributes.Builder {
     ctor public DomainSelectionService.SelectionAttributes.Builder(int, int, int);
     method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes build();
-    method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setAddress(@NonNull android.net.Uri);
-    method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setCallId(@NonNull String);
+    method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setAddress(@Nullable android.net.Uri);
+    method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setCallId(@Nullable String);
     method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setCsDisconnectCause(int);
     method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setEmergency(boolean);
-    method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setEmergencyRegResult(@NonNull android.telephony.EmergencyRegResult);
+    method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setEmergencyRegistrationResult(@Nullable android.telephony.EmergencyRegistrationResult);
     method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setExitedFromAirplaneMode(boolean);
-    method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setPsDisconnectCause(@NonNull android.telephony.ims.ImsReasonInfo);
+    method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setPsDisconnectCause(@Nullable android.telephony.ims.ImsReasonInfo);
     method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setTestEmergencyNumber(boolean);
     method @NonNull public android.telephony.DomainSelectionService.SelectionAttributes.Builder setVideoCall(boolean);
   }
@@ -14236,7 +14282,7 @@
     method public void reselectDomain(@NonNull android.telephony.DomainSelectionService.SelectionAttributes);
   }
 
-  @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public final class EmergencyRegResult implements android.os.Parcelable {
+  @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public final class EmergencyRegistrationResult implements android.os.Parcelable {
     method public int describeContents();
     method public int getAccessNetwork();
     method @NonNull public String getCountryIso();
@@ -14249,7 +14295,7 @@
     method public boolean isEmcBearerSupported();
     method public boolean isVopsSupported();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.EmergencyRegResult> CREATOR;
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.EmergencyRegistrationResult> CREATOR;
   }
 
   public final class ImsiEncryptionInfo implements android.os.Parcelable {
@@ -15321,7 +15367,7 @@
 
   @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public interface WwanSelectorCallback {
     method public void onDomainSelected(int, boolean);
-    method public void onRequestEmergencyNetworkScan(@NonNull java.util.List<java.lang.Integer>, int, boolean, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.telephony.EmergencyRegResult>);
+    method public void onRequestEmergencyNetworkScan(@NonNull java.util.List<java.lang.Integer>, int, boolean, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.telephony.EmergencyRegistrationResult>);
   }
 
 }
@@ -17389,6 +17435,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();
@@ -17454,10 +17513,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>);
@@ -17519,6 +17579,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 4a048bd..fc095d4 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
@@ -1282,10 +1283,6 @@
     field public static final int RESULT_CODE_DIALOG_USER_CANCELED = 0; // 0x0
   }
 
-  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class CancelSelectionRequest implements android.os.Parcelable {
-    ctor @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public CancelSelectionRequest(@NonNull android.os.IBinder, boolean, @NonNull String);
-  }
-
   @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class CreateCredentialProviderData extends android.credentials.selection.ProviderData implements android.os.Parcelable {
     ctor public CreateCredentialProviderData(@NonNull String, @NonNull java.util.List<android.credentials.selection.Entry>, @Nullable android.credentials.selection.Entry);
     method @Nullable public android.credentials.selection.Entry getRemoteEntry();
@@ -1882,6 +1879,7 @@
     method @FlaggedApi("android.media.audio.focus_freeze_test_api") @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public long getFocusUnmuteDelayAfterFadeOutForTest();
     method @Nullable public static android.media.AudioHalVersionInfo getHalVersion();
     method public static final int[] getPublicStreamTypes();
+    method @FlaggedApi("android.media.audiopolicy.audio_mix_test_api") @NonNull public java.util.List<android.media.audiopolicy.AudioMix> getRegisteredPolicyMixes();
     method @NonNull public java.util.List<java.lang.Integer> getReportedSurroundFormats();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public float getRs2Value();
     method public int getStreamMinVolumeInt(int);
@@ -2055,6 +2053,10 @@
 
 package android.media.audiopolicy {
 
+  public class AudioPolicy {
+    method @FlaggedApi("android.media.audiopolicy.audio_mix_test_api") @NonNull public java.util.List<android.media.audiopolicy.AudioMix> getMixes();
+  }
+
   public static class AudioPolicy.Builder {
     method @NonNull public android.media.audiopolicy.AudioPolicy.Builder setIsTestFocusPolicy(boolean);
   }
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 5e904ef9..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/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 084c71f..a8d183a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -5954,6 +5954,20 @@
     }
 
     /**
+     * Used by {@link com.android.systemui.theme.ThemeOverlayController} to notify of color
+     * palette readiness.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.SET_THEME_OVERLAY_CONTROLLER_READY)
+    public void setThemeOverlayReady(boolean readiness) {
+        try {
+            getService().setThemeOverlayReady(readiness);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Resets the state of the {@link com.android.server.am.AppErrors} instance.
      * This is intended for use with CTS only.
      * @hide
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 232fc92..0ae2e01 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -1258,4 +1258,11 @@
      */
     public abstract boolean clearApplicationUserData(String packageName, boolean keepState,
             boolean isRestore, IPackageDataObserver observer, int userId);
+
+    /**
+     * Returns current state of {@link com.android.systemui.theme.ThemeOverlayController} color
+     * palette readiness.
+     * @hide
+     */
+    public abstract boolean getThemeOverlayReadiness();
 }
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index b063d04..ceeaf5d 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -17,7 +17,6 @@
 package android.app;
 
 import android.app.ActivityManager;
-import android.app.ActivityManager.PendingIntentInfo;
 import android.app.ActivityTaskManager;
 import android.app.ApplicationStartInfo;
 import android.app.ApplicationErrorReport;
@@ -553,6 +552,14 @@
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     boolean isTopOfTask(in IBinder token);
     void bootAnimationComplete();
+
+    /**
+     * Used by {@link com.android.systemui.theme.ThemeOverlayController} to notify of color
+     * palette readiness.
+     * @throws RemoteException
+     */
+    void setThemeOverlayReady(boolean readiness);
+
     @UnsupportedAppUsage
     void registerTaskStackListener(in ITaskStackListener listener);
     void unregisterTaskStackListener(in ITaskStackListener listener);
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..c8762c6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -18,12 +18,14 @@
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.LOCK_DEVICE;
 import static android.Manifest.permission.MANAGE_DEVICE_ADMINS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL;
 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 +55,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 +3826,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 +3874,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 +4101,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 +4124,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.
@@ -6330,10 +6416,10 @@
      * (PIN, pattern, or password). This API is intended for use only by device admins.
      * <p>
      * From version {@link android.os.Build.VERSION_CODES#R} onwards, the caller must either have
-     * the LOCK_DEVICE permission or the device must have the device admin feature; if neither is
-     * true, then the method will return without completing any action. Before version
-     * {@link android.os.Build.VERSION_CODES#R}, the device needed the device admin feature,
-     * regardless of the caller's permissions.
+     * the LOCK_DEVICE permission or the device must have the
+     * device admin feature; if neither is true, then the method will return without completing
+     * any action. Before version {@link android.os.Build.VERSION_CODES#R},
+     * the device needed the device admin feature, regardless of the caller's permissions.
      * <p>
      * The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK}
      * to be able to call this method; if it has not, a security exception will be thrown.
@@ -6353,7 +6439,8 @@
      * @throws SecurityException if the calling application does not own an active administrator
      *             that uses {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK}
      */
-    @RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK, conditional = true)
+    @SuppressLint("RequiresPermission")
+    @RequiresPermission(value = LOCK_DEVICE, conditional = true)
     public void lockNow() {
         lockNow(0);
     }
@@ -6364,14 +6451,13 @@
      * <p>
      * This method secures the device in response to an urgent situation, such as a lost or stolen
      * device. After this method is called, the device must be unlocked using strong authentication
-     * (PIN, pattern, or password). This API is for use only by device admins and holders of the
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK} permission.
+     * (PIN, pattern, or password). This API is intended for use only by device admins.
      * <p>
      * From version {@link android.os.Build.VERSION_CODES#R} onwards, the caller must either have
-     * the LOCK_DEVICE permission or the device must have the device admin feature; if neither is
-     * true, then the method will return without completing any action. Before version
-     * {@link android.os.Build.VERSION_CODES#R}, the device needed the device admin feature,
-     * regardless of the caller's permissions.
+     * the LOCK_DEVICE permission or the device must have the
+     * device admin feature; if neither is true, then the method will return without completing any
+     * action. Before version {@link android.os.Build.VERSION_CODES#R}, the device needed the device
+     * admin feature, regardless of the caller's permissions.
      * <p>
      * A calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK}
      * to be able to call this method; if it has not, a security exception will be thrown.
@@ -6400,7 +6486,7 @@
      * @param flags May be 0 or {@link #FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY}.
      * @throws SecurityException if the calling application does not own an active administrator
      *             that uses {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} and the does not hold
-     *             the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK} permission, or
+     *             the {@link android.Manifest.permission#LOCK_DEVICE} permission, or
      *             the {@link #FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY} flag is passed by an
      *             application that is not a profile owner of a managed profile.
      * @throws IllegalArgumentException if the {@link #FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY} flag is
@@ -6409,7 +6495,7 @@
      *             flag is passed when {@link #getStorageEncryptionStatus} does not return
      *             {@link #ENCRYPTION_STATUS_ACTIVE_PER_USER}.
      */
-    @RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK, conditional = true)
+    @RequiresPermission(value = LOCK_DEVICE, conditional = true)
     public void lockNow(@LockNowFlag int flags) {
         if (mService != null) {
             try {
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/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/wearable/IWearableSensingManager.aidl b/core/java/android/app/wearable/IWearableSensingManager.aidl
index ff37bd8..3cbc8a2 100644
--- a/core/java/android/app/wearable/IWearableSensingManager.aidl
+++ b/core/java/android/app/wearable/IWearableSensingManager.aidl
@@ -16,6 +16,7 @@
 
 package android.app.wearable;
 
+import android.app.PendingIntent;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.os.RemoteCallback;
@@ -28,7 +29,13 @@
  */
 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);
+     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
+     void registerDataRequestObserver(int dataType, in PendingIntent dataRequestPendingIntent, in RemoteCallback statusCallback);
+     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
+     void unregisterDataRequestObserver(int dataType, in PendingIntent dataRequestPendingIntent, in RemoteCallback statusCallback);
 }
\ No newline at end of file
diff --git a/core/java/android/app/wearable/WearableSensingDataRequest.java b/core/java/android/app/wearable/WearableSensingDataRequest.java
new file mode 100644
index 0000000..9329b37
--- /dev/null
+++ b/core/java/android/app/wearable/WearableSensingDataRequest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.wearable;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+
+import java.time.Duration;
+
+/**
+ * Data class for a data request for wearable sensing.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
+@SystemApi
+public final class WearableSensingDataRequest implements Parcelable {
+    private static final int MAX_REQUEST_SIZE = 200;
+    private static final Duration RATE_LIMIT_WINDOW_SIZE = Duration.ofMinutes(1);
+    private static final int RATE_LIMIT = 30;
+
+    private final int mDataType;
+    @NonNull private final PersistableBundle mRequestDetails;
+
+    private WearableSensingDataRequest(int dataType, @NonNull PersistableBundle requestDetails) {
+        mDataType = dataType;
+        mRequestDetails = requestDetails;
+    }
+
+    /** Returns the data type this request is for. */
+    public int getDataType() {
+        return mDataType;
+    }
+
+    /** Returns the details for this request. */
+    @NonNull
+    public PersistableBundle getRequestDetails() {
+        return mRequestDetails;
+    }
+
+    /** Returns the data size of this object when it is parcelled. */
+    public int getDataSize() {
+        Parcel parcel = Parcel.obtain();
+        try {
+            writeToParcel(parcel, describeContents());
+            return parcel.dataSize();
+        } finally {
+            parcel.recycle();
+        }
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mDataType);
+        dest.writeTypedObject(mRequestDetails, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "WearableSensingDataRequest { "
+                + "dataType = "
+                + mDataType
+                + ", "
+                + "requestDetails = "
+                + mRequestDetails
+                + " }";
+    }
+
+    /**
+     * Returns a String representation of this data request that shows its contents.
+     *
+     * @hide
+     */
+    public String toExpandedString() {
+        if (mRequestDetails != null) {
+            // Trigger unparcelling so that its individual fields will be listed in toString
+            boolean unused =
+                    mRequestDetails.getBoolean(
+                            "PlaceholderForWearableSensingDataRequest#toExpandedString()");
+        }
+        return toString();
+    }
+
+    /**
+     * The bundle key for this class of object, used in {@code RemoteCallback#sendResult}.
+     *
+     * @hide
+     */
+    public static final String REQUEST_BUNDLE_KEY =
+            "android.app.wearable.WearableSensingDataRequestBundleKey";
+
+    /**
+     * The bundle key for the status callback for a data request, used in {@code
+     * RemoteCallback#sendResult}.
+     *
+     * @hide
+     */
+    public static final String REQUEST_STATUS_CALLBACK_BUNDLE_KEY =
+            "android.app.wearable.WearableSensingDataRequestStatusCallbackBundleKey";
+
+    public static final @NonNull Parcelable.Creator<WearableSensingDataRequest> CREATOR =
+            new Parcelable.Creator<WearableSensingDataRequest>() {
+                @Override
+                public WearableSensingDataRequest[] newArray(int size) {
+                    return new WearableSensingDataRequest[size];
+                }
+
+                @Override
+                public WearableSensingDataRequest createFromParcel(@NonNull Parcel in) {
+                    int dataType = in.readInt();
+                    PersistableBundle requestDetails =
+                            in.readTypedObject(PersistableBundle.CREATOR);
+                    return new WearableSensingDataRequest(dataType, requestDetails);
+                }
+            };
+
+    /**
+     * Returns the maximum allowed size of a WearableSensingDataRequest when it is parcelled.
+     * Instances that exceed this size can be constructed, but will be rejected by the system when
+     * they leave the isolated WearableSensingService.
+     */
+    public static int getMaxRequestSize() {
+        return MAX_REQUEST_SIZE;
+    }
+
+    /**
+     * Returns the rolling time window used to perform rate limiting on data requests leaving the
+     * WearableSensingService.
+     */
+    @NonNull
+    public static Duration getRateLimitWindowSize() {
+        return RATE_LIMIT_WINDOW_SIZE;
+    }
+
+    /**
+     * Returns the number of data requests allowed to leave the WearableSensingService in each
+     * {@link #getRateLimitWindowSize()}.
+     */
+    public static int getRateLimit() {
+        return RATE_LIMIT;
+    }
+
+    /** A builder for WearableSensingDataRequest. */
+    @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
+    public static final class Builder {
+        private int mDataType;
+        private PersistableBundle mRequestDetails;
+
+        public Builder(int dataType) {
+            mDataType = dataType;
+        }
+
+        /** Sets the request details. */
+        public @NonNull Builder setRequestDetails(@NonNull PersistableBundle requestDetails) {
+            mRequestDetails = requestDetails;
+            return this;
+        }
+
+        /** Builds the WearableSensingDataRequest. */
+        public @NonNull WearableSensingDataRequest build() {
+            if (mRequestDetails == null) {
+                mRequestDetails = PersistableBundle.EMPTY;
+            }
+            return new WearableSensingDataRequest(mDataType, mRequestDetails);
+        }
+    }
+}
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
index eca0039..077f7b5 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -25,8 +25,11 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.app.PendingIntent;
 import android.app.ambientcontext.AmbientContextEvent;
+import android.companion.CompanionDeviceManager;
 import android.content.Context;
+import android.content.Intent;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
@@ -36,6 +39,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;
@@ -55,7 +60,6 @@
  *
  * @hide
  */
-
 @SystemApi
 @SystemService(Context.WEARABLE_SENSING_SERVICE)
 public class WearableSensingManager {
@@ -68,6 +72,14 @@
     public static final String STATUS_RESPONSE_BUNDLE_KEY =
             "android.app.wearable.WearableSensingStatusBundleKey";
 
+    /**
+     * The Intent extra key for the data request in the Intent sent to the PendingIntent registered
+     * with {@link #registerDataRequestObserver(int, PendingIntent, Executor, Consumer)}.
+     *
+     * @hide
+     */
+    public static final String EXTRA_WEARABLE_SENSING_DATA_REQUEST =
+            "android.app.wearable.extra.WEARABLE_SENSING_DATA_REQUEST";
 
     /**
      * An unknown status.
@@ -104,22 +116,54 @@
      * The value of the status code that indicates the method called is not supported by the
      * implementation of {@link WearableSensingService}.
      */
+
     @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;
+
+    /** The value of the status code that indicates the provided data type is not supported. */
+    @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
+    public static final int STATUS_UNSUPPORTED_DATA_TYPE = 8;
+
     /** @hide */
-    @IntDef(prefix = { "STATUS_" }, value = {
-            STATUS_UNKNOWN,
-            STATUS_SUCCESS,
-            STATUS_UNSUPPORTED,
-            STATUS_SERVICE_UNAVAILABLE,
-            STATUS_WEARABLE_UNAVAILABLE,
-            STATUS_ACCESS_DENIED,
-            STATUS_UNSUPPORTED_OPERATION
-    })
+    @IntDef(
+            prefix = {"STATUS_"},
+            value = {
+                STATUS_UNKNOWN,
+                STATUS_SUCCESS,
+                STATUS_UNSUPPORTED,
+                STATUS_SERVICE_UNAVAILABLE,
+                STATUS_WEARABLE_UNAVAILABLE,
+                STATUS_ACCESS_DENIED,
+                STATUS_UNSUPPORTED_OPERATION,
+                STATUS_CHANNEL_ERROR,
+                STATUS_UNSUPPORTED_DATA_TYPE
+            })
     @Retention(RetentionPolicy.SOURCE)
     public @interface StatusCode {}
 
+    /**
+     * Retrieves a {@link WearableSensingDataRequest} from the Intent sent to the PendingIntent
+     * provided to {@link #registerDataRequestObserver(int, PendingIntent, Executor, Consumer)}.
+     *
+     * @param intent The Intent received from the PendingIntent.
+     * @return The WearableSensingDataRequest in the provided Intent, or null if the Intent does not
+     *     contain a WearableSensingDataRequest.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
+    @Nullable
+    public static WearableSensingDataRequest getDataRequestFromIntent(@NonNull Intent intent) {
+        return intent.getParcelableExtra(
+                EXTRA_WEARABLE_SENSING_DATA_REQUEST, WearableSensingDataRequest.class);
+    }
+
     private final Context mContext;
     private final IWearableSensingManager mService;
 
@@ -132,6 +176,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 +247,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 +281,117 @@
             @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();
         }
     }
 
+    /**
+     * Registers a data request observer for the provided data type.
+     *
+     * <p>When data is requested, the provided {@code dataRequestPendingIntent} will be invoked. A
+     * {@link WearableSensingDataRequest} can be extracted from the Intent sent to {@code
+     * dataRequestPendingIntent} by calling {@link #getDataRequestFromIntent(Intent)}. The observer
+     * can then provide the requested data via {@link #provideData(PersistableBundle, SharedMemory,
+     * Executor, Consumer)}.
+     *
+     * <p>There is no limit to the number of observers registered for a data type. How they are
+     * handled depends on the implementation of WearableSensingService.
+     *
+     * <p>When the observer is no longer needed, {@link #unregisterDataRequestObserver(int,
+     * PendingIntent, Executor, Consumer)} should be called with the same {@code
+     * dataRequestPendingIntent}. It should be done regardless of the status code returned from
+     * {@code statusConsumer} in order to clean up housekeeping data for the {@code
+     * dataRequestPendingIntent} maintained by the system.
+     *
+     * <p>Example:
+     *
+     * <pre>{@code
+     * // Create a PendingIntent for MyDataRequestBroadcastReceiver
+     * Intent intent =
+     *         new Intent(actionString).setClass(context, MyDataRequestBroadcastReceiver.class);
+     * PendingIntent pendingIntent = PendingIntent.getBroadcast(
+     *         context, 0, intent, PendingIntent.FLAG_MUTABLE);
+     *
+     * // Register the PendingIntent as a data request observer
+     * wearableSensingManager.registerDataRequestObserver(
+     *         dataType, pendingIntent, executor, statusConsumer);
+     *
+     * // Within MyDataRequestBroadcastReceiver, receive the broadcast Intent and extract the
+     * // WearableSensingDataRequest
+     * {@literal @}Override
+     * public void onReceive(Context context, Intent intent) {
+     *     WearableSensingDataRequest dataRequest =
+     *             WearableSensingManager.getDataRequestFromIntent(intent);
+     *     // After parsing the dataRequest, provide the data
+     *     wearableSensingManager.provideData(data, sharedMemory, executor, statusConsumer);
+     * }
+     * }</pre>
+     *
+     * @param dataType The data type to listen to. Values are defined by the application that
+     *     implements {@link WearableSensingService}.
+     * @param dataRequestPendingIntent A mutable {@link PendingIntent} that will be invoked when
+     *     data is requested. See {@link #getDataRequestFromIntent(Intent)}. Activities are not
+     *     allowed to be launched using this PendingIntent.
+     * @param executor Executor on which to run the consumer callback.
+     * @param statusConsumer A consumer that handles the status code for the observer registration.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
+    @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
+    public void registerDataRequestObserver(
+            int dataType,
+            @NonNull PendingIntent dataRequestPendingIntent,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull @StatusCode Consumer<Integer> statusConsumer) {
+        try {
+            RemoteCallback statusCallback = createStatusCallback(executor, statusConsumer);
+            mService.registerDataRequestObserver(
+                    dataType, dataRequestPendingIntent, statusCallback);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unregisters a previously registered data request observer. If the provided {@link
+     * PendingIntent} was not registered, or is already unregistered, the {@link
+     * WearableSensingService} will not be notified.
+     *
+     * @param dataType The data type the observer is for.
+     * @param dataRequestPendingIntent The observer to unregister.
+     * @param executor Executor on which to run the consumer callback.
+     * @param statusConsumer A consumer that handles the status code for the observer
+     *     unregistration.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
+    @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
+    public void unregisterDataRequestObserver(
+            int dataType,
+            @NonNull PendingIntent dataRequestPendingIntent,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull @StatusCode Consumer<Integer> statusConsumer) {
+        try {
+            RemoteCallback statusCallback = createStatusCallback(executor, statusConsumer);
+            mService.unregisterDataRequestObserver(
+                    dataType, dataRequestPendingIntent, statusCallback);
+        } 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/app/wearable/flags.aconfig b/core/java/android/app/wearable/flags.aconfig
index 5e8bdb5..b4f628f 100644
--- a/core/java/android/app/wearable/flags.aconfig
+++ b/core/java/android/app/wearable/flags.aconfig
@@ -22,6 +22,13 @@
 }
 
 flag {
+    name: "enable_restart_wss_process"
+    namespace: "machine_learning"
+    description: "When this flag is on, the system will restart the WearableSensingService process before providing it with a new secure wearable connection."
+    bug: "301427767"
+}
+
+flag {
     name: "enable_hotword_wearable_sensing_api"
     namespace: "machine_learning"
     description: "This flag enables the APIs related to hotword in WearableSensingManager and WearableSensingService."
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 9fe8af5..a64ee5b 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -239,6 +239,110 @@
     public String requiredDisplayCategory;
 
     /**
+     * Constant corresponding to {@code none} in the
+     * {@link android.R.attr#requireContentUriPermissionFromCaller} attribute.
+     * @hide
+     */
+    public static final int CONTENT_URI_PERMISSION_NONE = 0;
+
+    /**
+     * Constant corresponding to {@code read} in the
+     * {@link android.R.attr#requireContentUriPermissionFromCaller} attribute.
+     * @hide
+     */
+    public static final int CONTENT_URI_PERMISSION_READ = 1;
+
+    /**
+     * Constant corresponding to {@code write} in the
+     * {@link android.R.attr#requireContentUriPermissionFromCaller} attribute.
+     * @hide
+     */
+    public static final int CONTENT_URI_PERMISSION_WRITE = 2;
+
+    /**
+     * Constant corresponding to {@code readOrWrite} in the
+     * {@link android.R.attr#requireContentUriPermissionFromCaller} attribute.
+     * @hide
+     */
+    public static final int CONTENT_URI_PERMISSION_READ_OR_WRITE = 3;
+
+    /**
+     * Constant corresponding to {@code readAndWrite} in the
+     * {@link android.R.attr#requireContentUriPermissionFromCaller} attribute.
+     * @hide
+     */
+    public static final int CONTENT_URI_PERMISSION_READ_AND_WRITE = 4;
+
+    /** @hide */
+    @SuppressWarnings("SwitchIntDef")
+    public static boolean isRequiredContentUriPermissionRead(
+            @RequiredContentUriPermission int permission) {
+        return switch (permission) {
+            case CONTENT_URI_PERMISSION_READ_AND_WRITE, CONTENT_URI_PERMISSION_READ_OR_WRITE,
+                    CONTENT_URI_PERMISSION_READ -> true;
+            default -> false;
+        };
+    }
+
+    /** @hide */
+    @SuppressWarnings("SwitchIntDef")
+    public static boolean isRequiredContentUriPermissionWrite(
+            @RequiredContentUriPermission int permission) {
+        return switch (permission) {
+            case CONTENT_URI_PERMISSION_READ_AND_WRITE, CONTENT_URI_PERMISSION_READ_OR_WRITE,
+                    CONTENT_URI_PERMISSION_WRITE -> true;
+            default -> false;
+        };
+    }
+
+    /** @hide */
+    @IntDef(prefix = "CONTENT_URI_PERMISSION_", value = {
+            CONTENT_URI_PERMISSION_NONE,
+            CONTENT_URI_PERMISSION_READ,
+            CONTENT_URI_PERMISSION_WRITE,
+            CONTENT_URI_PERMISSION_READ_OR_WRITE,
+            CONTENT_URI_PERMISSION_READ_AND_WRITE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RequiredContentUriPermission {
+    }
+
+    private String requiredContentUriPermissionToFullString(
+            @RequiredContentUriPermission int permission) {
+        return switch (permission) {
+            case CONTENT_URI_PERMISSION_NONE -> "CONTENT_URI_PERMISSION_NONE";
+            case CONTENT_URI_PERMISSION_READ -> "CONTENT_URI_PERMISSION_READ";
+            case CONTENT_URI_PERMISSION_WRITE -> "CONTENT_URI_PERMISSION_WRITE";
+            case CONTENT_URI_PERMISSION_READ_OR_WRITE -> "CONTENT_URI_PERMISSION_READ_OR_WRITE";
+            case CONTENT_URI_PERMISSION_READ_AND_WRITE -> "CONTENT_URI_PERMISSION_READ_AND_WRITE";
+            default -> "unknown=" + permission;
+        };
+    }
+
+    /** @hide */
+    public static String requiredContentUriPermissionToShortString(
+            @RequiredContentUriPermission int permission) {
+        return switch (permission) {
+            case CONTENT_URI_PERMISSION_NONE -> "none";
+            case CONTENT_URI_PERMISSION_READ -> "read";
+            case CONTENT_URI_PERMISSION_WRITE -> "write";
+            case CONTENT_URI_PERMISSION_READ_OR_WRITE -> "read or write";
+            case CONTENT_URI_PERMISSION_READ_AND_WRITE -> "read and write";
+            default -> "unknown=" + permission;
+        };
+    }
+
+    /**
+     * Specifies permissions necessary to launch this activity via
+     * {@link android.content.Context#startActivity} when passing content URIs. The default value is
+     * {@code none}, meaning no specific permissions are required. Setting this attribute restricts
+     * activity invocation based on the invoker's permissions.
+     * @hide
+     */
+    @RequiredContentUriPermission
+    public int requireContentUriPermissionFromCaller;
+
+    /**
      * Activity can not be resized and always occupies the fullscreen area with all windows fully
      * visible.
      * @hide
@@ -1590,6 +1694,7 @@
         mMinAspectRatio = orig.mMinAspectRatio;
         supportsSizeChanges = orig.supportsSizeChanges;
         requiredDisplayCategory = orig.requiredDisplayCategory;
+        requireContentUriPermissionFromCaller = orig.requireContentUriPermissionFromCaller;
     }
 
     /**
@@ -1946,6 +2051,11 @@
         if (requiredDisplayCategory != null) {
             pw.println(prefix + "requiredDisplayCategory=" + requiredDisplayCategory);
         }
+        if ((dumpFlags & DUMP_FLAG_DETAILS) != 0) {
+            pw.println(prefix + "requireContentUriPermissionFromCaller="
+                    + requiredContentUriPermissionToFullString(
+                            requireContentUriPermissionFromCaller));
+        }
         super.dumpBack(pw, prefix, dumpFlags);
     }
 
@@ -1993,6 +2103,7 @@
         dest.writeBoolean(supportsSizeChanges);
         sForStringSet.parcel(mKnownActivityEmbeddingCerts, dest, flags);
         dest.writeString8(requiredDisplayCategory);
+        dest.writeInt(requireContentUriPermissionFromCaller);
     }
 
     /**
@@ -2119,6 +2230,7 @@
             mKnownActivityEmbeddingCerts = null;
         }
         requiredDisplayCategory = source.readString8();
+        requireContentUriPermissionFromCaller = source.readInt();
     }
 
     /**
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/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/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/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/CancelSelectionRequest.java b/core/java/android/credentials/selection/CancelSelectionRequest.java
index 2662d76..55acfdb 100644
--- a/core/java/android/credentials/selection/CancelSelectionRequest.java
+++ b/core/java/android/credentials/selection/CancelSelectionRequest.java
@@ -21,7 +21,6 @@
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
-import android.annotation.TestApi;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -59,7 +58,7 @@
     private final boolean mShouldShowCancellationExplanation;
 
     @NonNull
-    private final String mAppPackageName;
+    private final String mPackageName;
 
     /**
      * Returns the request token matching the user request that should be cancelled.
@@ -85,8 +84,8 @@
      * metadata (e.g. "Cancelled by `App Name`").
      */
     @NonNull
-    public String getAppPackageName() {
-        return mAppPackageName;
+    public String getPackageName() {
+        return mPackageName;
     }
 
     /**
@@ -98,33 +97,36 @@
         return mShouldShowCancellationExplanation;
     }
 
+
     /**
      * Constructs a {@link CancelSelectionRequest}.
      *
-     * @hide
+     * @param requestToken request token matching the app request that should be cancelled
+     * @param shouldShowCancellationExplanation whether the UI should display some informational
+     *                                          cancellation message before closing
+     * @param packageName package that is invoking this request
+     *
      */
-    @TestApi
-    @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
-    public CancelSelectionRequest(@NonNull IBinder token, boolean shouldShowCancellationExplanation,
-            @NonNull String appPackageName) {
-        mToken = token;
+    public CancelSelectionRequest(@NonNull RequestToken requestToken,
+            boolean shouldShowCancellationExplanation, @NonNull String packageName) {
+        mToken = requestToken.getToken();
         mShouldShowCancellationExplanation = shouldShowCancellationExplanation;
-        mAppPackageName = appPackageName;
+        mPackageName = packageName;
     }
 
     private CancelSelectionRequest(@NonNull Parcel in) {
         mToken = in.readStrongBinder();
         AnnotationValidations.validate(NonNull.class, null, mToken);
         mShouldShowCancellationExplanation = in.readBoolean();
-        mAppPackageName = in.readString8();
-        AnnotationValidations.validate(NonNull.class, null, mAppPackageName);
+        mPackageName = in.readString8();
+        AnnotationValidations.validate(NonNull.class, null, mPackageName);
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeStrongBinder(mToken);
         dest.writeBoolean(mShouldShowCancellationExplanation);
-        dest.writeString8(mAppPackageName);
+        dest.writeString8(mPackageName);
     }
 
     @Override
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/IntentFactory.java b/core/java/android/credentials/selection/IntentFactory.java
index 1837976..ac2bae4 100644
--- a/core/java/android/credentials/selection/IntentFactory.java
+++ b/core/java/android/credentials/selection/IntentFactory.java
@@ -210,7 +210,8 @@
                                                 .config_credentialManagerDialogComponent));
         intent.setComponent(componentName);
         intent.putExtra(CancelSelectionRequest.EXTRA_CANCEL_UI_REQUEST,
-                new CancelSelectionRequest(requestToken, shouldShowCancellationUi, appPackageName));
+                new CancelSelectionRequest(new RequestToken(requestToken), shouldShowCancellationUi,
+                        appPackageName));
         return intent;
     }
 
diff --git a/core/java/android/credentials/selection/RequestInfo.java b/core/java/android/credentials/selection/RequestInfo.java
index 60bbae6..16d0802 100644
--- a/core/java/android/credentials/selection/RequestInfo.java
+++ b/core/java/android/credentials/selection/RequestInfo.java
@@ -106,7 +106,7 @@
     private final String mType;
 
     @NonNull
-    private final String mAppPackageName;
+    private final String mPackageName;
 
     private final boolean mHasPermissionToOverrideDefault;
 
@@ -172,8 +172,8 @@
 
     /** Returns the display name of the app that made this request. */
     @NonNull
-    public String getAppPackageName() {
-        return mAppPackageName;
+    public String getPackageName() {
+        return mPackageName;
     }
 
     /**
@@ -248,7 +248,7 @@
             boolean isShowAllOptionsRequested) {
         mToken = token;
         mType = type;
-        mAppPackageName = appPackageName;
+        mPackageName = appPackageName;
         mCreateCredentialRequest = createCredentialRequest;
         mGetCredentialRequest = getCredentialRequest;
         mHasPermissionToOverrideDefault = hasPermissionToOverrideDefault;
@@ -270,8 +270,8 @@
         AnnotationValidations.validate(NonNull.class, null, mToken);
         mType = type;
         AnnotationValidations.validate(NonNull.class, null, mType);
-        mAppPackageName = appPackageName;
-        AnnotationValidations.validate(NonNull.class, null, mAppPackageName);
+        mPackageName = appPackageName;
+        AnnotationValidations.validate(NonNull.class, null, mPackageName);
         mCreateCredentialRequest = createCredentialRequest;
         mGetCredentialRequest = getCredentialRequest;
         mHasPermissionToOverrideDefault = in.readBoolean();
@@ -284,7 +284,7 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeStrongBinder(mToken);
         dest.writeString8(mType);
-        dest.writeString8(mAppPackageName);
+        dest.writeString8(mPackageName);
         dest.writeTypedObject(mCreateCredentialRequest, flags);
         dest.writeTypedObject(mGetCredentialRequest, flags);
         dest.writeBoolean(mHasPermissionToOverrideDefault);
diff --git a/core/java/android/credentials/selection/RequestToken.java b/core/java/android/credentials/selection/RequestToken.java
index 27b83f8..f1953ce 100644
--- a/core/java/android/credentials/selection/RequestToken.java
+++ b/core/java/android/credentials/selection/RequestToken.java
@@ -30,6 +30,11 @@
  * To compare if two requests pertain to the same session, compare their RequestTokens using
  * the {@link RequestToken#equals(Object)} method.
  *
+ * For example, when receiving a {@link android.credentials.selection.CancelSelectionRequest},
+ * the developer should use {@link RequestToken#getToken()} to retrieve the token from request and
+ * compare whether it is equal with the cached token using {@link RequestToken#equals(Object)}. Only
+ * cancel the request when two tokens are the same.
+ *
  * @hide
  */
 @SystemApi
@@ -39,6 +44,12 @@
     @NonNull
     private final IBinder mToken;
 
+    /** @hide **/
+    @NonNull
+    public IBinder getToken() {
+        return mToken;
+    }
+
     /** @hide */
     @TestApi
     @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
diff --git a/core/java/android/hardware/camera2/extension/AdvancedExtender.java b/core/java/android/hardware/camera2/extension/AdvancedExtender.java
index 6653577..4895f38 100644
--- a/core/java/android/hardware/camera2/extension/AdvancedExtender.java
+++ b/core/java/android/hardware/camera2/extension/AdvancedExtender.java
@@ -43,8 +43,9 @@
  *
  * <p>This advanced contract empowers implementations to gain access to
  * more Camera2 capability. This includes: (1) Add custom surfaces with
- * specific formats like YUV, RAW, RAW_DEPTH. (2) Access to
- * the capture request callbacks as well as all the images retrieved of
+ * specific formats like {@link android.graphics.ImageFormat#YUV_420_888},
+ * {@link android.graphics.ImageFormat#RAW10}, {@link android.graphics.ImageFormat#RAW_DEPTH10}.
+ * (2) Access to the capture request callbacks as well as all the images retrieved of
  * various image formats. (3)
  * Able to triggers single or repeating request with the capabilities to
  * specify target surfaces, template id and parameters.
@@ -60,8 +61,14 @@
     private CameraUsageTracker mCameraUsageTracker;
     private static final String TAG = "AdvancedExtender";
 
+
+    /**
+     * Initialize a camera extension advanced extender instance.
+     *
+     * @param cameraManager the system camera manager
+     */
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
-    protected AdvancedExtender(@NonNull CameraManager cameraManager) {
+    public AdvancedExtender(@NonNull CameraManager cameraManager) {
         mCameraManager = cameraManager;
         try {
             String [] cameraIds = mCameraManager.getCameraIdListNoLazy();
@@ -87,6 +94,14 @@
         mCameraUsageTracker = tracker;
     }
 
+    /**
+     * Returns the camera metadata vendor id, that can be used to
+     * configure and enable vendor tag support for a particular
+     * camera metadata buffer.
+     *
+     * @param cameraId           The camera2 id string of the camera.
+     * @return the camera metadata vendor Id associated with the given camera
+     */
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public long getMetadataVendorId(@NonNull String cameraId) {
         long vendorId = mMetadataVendorIdMap.containsKey(cameraId) ?
@@ -131,12 +146,15 @@
      *                           CameraCharacteristics.
      */
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
-    public abstract void init(@NonNull String cameraId, @NonNull CharacteristicsMap map);
+    public abstract void initialize(@NonNull String cameraId, @NonNull CharacteristicsMap map);
 
     /**
      * Returns supported output format/size map for preview. The format
-     * could be PRIVATE or YUV_420_888. Implementations must support
-     * PRIVATE format at least.
+     * could be {@link android.graphics.ImageFormat#PRIVATE} or
+     * {@link android.graphics.ImageFormat#YUV_420_888}. Implementations must support
+     * {@link android.graphics.ImageFormat#PRIVATE} format at least.
+     * An example of how the map is parsed can be found in
+     * {@link #initializeParcelable(Map)}
      *
      * <p>The preview surface format in the CameraCaptureSession may not
      * be identical to the supported preview output format returned here.
@@ -149,11 +167,16 @@
 
     /**
      * Returns supported output format/size map for image capture. OEM is
-     * required to support both JPEG and YUV_420_888 format output.
+     * required to support both {@link android.graphics.ImageFormat#JPEG} and
+     * {@link android.graphics.ImageFormat#YUV_420_888} format output.
+     * An example of how the map is parsed can be found in
+     * {@link #initializeParcelable(Map)}
      *
      * <p>The surface created with this supported
      * format/size could be either added in CameraCaptureSession with HAL
-     * processing OR it  configures intermediate surfaces(YUV/RAW..) and
+     * processing OR it  configures intermediate surfaces(
+     * {@link android.graphics.ImageFormat#YUV_420_888}/
+     * {@link android.graphics.ImageFormat#RAW10}..) and
      * writes the output to the output surface.
      * @param cameraId           The camera2 id string of the camera.
      */
@@ -256,7 +279,7 @@
 
         @Override
         public void init(String cameraId, Map<String, CameraMetadataNative> charsMapNative) {
-            AdvancedExtender.this.init(cameraId, new CharacteristicsMap(charsMapNative));
+            AdvancedExtender.this.initialize(cameraId, new CharacteristicsMap(charsMapNative));
         }
 
         @Override
diff --git a/core/java/android/hardware/camera2/extension/CameraExtensionService.java b/core/java/android/hardware/camera2/extension/CameraExtensionService.java
index fa0d14a..01698d5 100644
--- a/core/java/android/hardware/camera2/extension/CameraExtensionService.java
+++ b/core/java/android/hardware/camera2/extension/CameraExtensionService.java
@@ -23,6 +23,7 @@
 import android.app.AppOpsManager;
 import android.app.Service;
 import android.content.Intent;
+import android.hardware.camera2.CameraExtensionCharacteristics.Extension;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
@@ -92,7 +93,7 @@
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     @Override
     @NonNull
-    public IBinder onBind(@Nullable Intent intent) {
+    public final IBinder onBind(@Nullable Intent intent) {
         if (mCameraUsageTracker == null) {
             mCameraUsageTracker = new CameraTracker();
         }
@@ -153,21 +154,21 @@
         }
 
         @Override
-        public IPreviewExtenderImpl initializePreviewExtension(int extensionType)
+        public IPreviewExtenderImpl initializePreviewExtension(@Extension int extensionType)
                 throws RemoteException {
             // Basic Extension API is not supported
             return null;
         }
 
         @Override
-        public IImageCaptureExtenderImpl initializeImageExtension(int extensionType)
+        public IImageCaptureExtenderImpl initializeImageExtension(@Extension int extensionType)
                 throws RemoteException {
             // Basic Extension API is not supported
             return null;
         }
 
         @Override
-        public IAdvancedExtenderImpl initializeAdvancedExtension(int extensionType)
+        public IAdvancedExtenderImpl initializeAdvancedExtension(@Extension int extensionType)
                 throws RemoteException {
             AdvancedExtender extender =  CameraExtensionService.this.onInitializeAdvancedExtension(
                     extensionType);
@@ -205,5 +206,5 @@
      */
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     @NonNull
-    public abstract AdvancedExtender onInitializeAdvancedExtension(int extensionType);
+    public abstract AdvancedExtender onInitializeAdvancedExtension(@Extension int extensionType);
 }
diff --git a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
index f98ebee..b4fe7fe 100644
--- a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
+++ b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
@@ -18,8 +18,8 @@
 
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.graphics.ImageFormat;
 import android.hardware.camera2.utils.SurfaceUtils;
 import android.util.Size;
 import android.view.Surface;
@@ -28,6 +28,14 @@
 
 
 /**
+ * Helper method used to describe a single camera output
+ * {@link Surface}.
+ *
+ * <p>Instances of this class can be used as arguments when
+ * initializing {@link ExtensionOutputConfiguration}.</p>
+ *
+ * @see ExtensionConfiguration
+ * @see ExtensionOutputConfiguration
  * @hide
  */
 @SystemApi
@@ -40,27 +48,39 @@
        mOutputSurface = surface;
     }
 
+    /**
+     * Initialize a camera output surface instance
+     *
+     * @param surface      Output {@link Surface} to be
+     *                     configured as camera output
+     * @param size         Requested size of the camera
+     *                     output
+     */
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public CameraOutputSurface(@NonNull Surface surface,
-            @Nullable Size size ) {
+            @NonNull Size size) {
         mOutputSurface = new OutputSurface();
         mOutputSurface.surface = surface;
         mOutputSurface.imageFormat = SurfaceUtils.getSurfaceFormat(surface);
-        if (size != null) {
-            mOutputSurface.size = new android.hardware.camera2.extension.Size();
-            mOutputSurface.size.width = size.getWidth();
-            mOutputSurface.size.height = size.getHeight();
-        }
+        mOutputSurface.size = new android.hardware.camera2.extension.Size();
+        mOutputSurface.size.width = size.getWidth();
+        mOutputSurface.size.height = size.getHeight();
     }
 
+    /**
+     * Return the current output {@link Surface}
+     */
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
-    @Nullable
+    @NonNull
     public Surface getSurface() {
         return mOutputSurface.surface;
     }
 
+    /**
+     * Return the current requested output size
+     */
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
-    @Nullable
+    @NonNull
     public android.util.Size getSize() {
         if (mOutputSurface.size != null) {
             return new Size(mOutputSurface.size.width, mOutputSurface.size.height);
@@ -68,8 +88,11 @@
         return null;
     }
 
+    /**
+     * Return the current surface output {@link android.graphics.ImageFormat}
+     */
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
-    public int getImageFormat() {
+    public @ImageFormat.Format int getImageFormat() {
         return mOutputSurface.imageFormat;
     }
 }
diff --git a/core/java/android/hardware/camera2/extension/CharacteristicsMap.java b/core/java/android/hardware/camera2/extension/CharacteristicsMap.java
index af83595..495abc8 100644
--- a/core/java/android/hardware/camera2/extension/CharacteristicsMap.java
+++ b/core/java/android/hardware/camera2/extension/CharacteristicsMap.java
@@ -30,12 +30,22 @@
 import java.util.Set;
 
 /**
+ * Helper class used to forward the current
+ * system camera characteristics information.
+ *
  * @hide
  */
 @SystemApi
 @FlaggedApi(Flags.FLAG_CONCERT_MODE)
 public class CharacteristicsMap {
     private final HashMap<String, CameraCharacteristics> mCharMap;
+
+    /**
+     * Initialize a camera characteristics map instance
+     *
+     * @param charsMap       Maps camera ids to respective
+     *                       {@link CameraCharacteristics}
+     */
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     CharacteristicsMap(@NonNull Map<String, CameraMetadataNative> charsMap) {
         mCharMap = new HashMap<>();
@@ -44,12 +54,26 @@
         }
     }
 
+    /**
+     * Return the set of camera ids stored in the characteristics map
+     *
+     * @return Set of the camera ids stored in the map
+     */
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     @NonNull
     public Set<String> getCameraIds() {
         return mCharMap.keySet();
     }
 
+    /**
+     * Return the corresponding {@link CameraCharacteristics} given
+     * a valid camera id
+     *
+     * @param cameraId Camera device id
+     *
+     * @return Valid {@link CameraCharacteristics} instance of null
+     *         in case the camera id is not part of the map
+     */
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     @Nullable
     public CameraCharacteristics get(@NonNull String cameraId) {
diff --git a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
index 2d9ab76..96c88e6 100644
--- a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
+++ b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
@@ -20,7 +20,9 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CaptureRequest;
+import android.os.IBinder;
 
 import com.android.internal.camera.flags.Flags;
 
@@ -28,6 +30,15 @@
 import java.util.List;
 
 /**
+ * Helper class used to guide the camera framework when
+ * initializing the internal camera capture session.
+ * It contains all required internal outputs, parameters,
+ * modes and settings.
+ *
+ * <p>Extension must decide the final set of output surfaces
+ * and pass an instance of ExtensionConfiguration as part
+ * of the result during calls to {@link SessionProcessor#initSession}.</p>
+ *
  * @hide
  */
 @SystemApi
@@ -38,9 +49,25 @@
     private final List<ExtensionOutputConfiguration> mOutputs;
     private final CaptureRequest mSessionParameters;
 
+    /**
+     * Initialize an extension configuration instance
+     *
+     * @param sessionType       The type of camera capture session
+     *                          operating mode to be used
+     * @param sessionTemplateId The request template id to be used
+     *                          for generating the session parameter
+     *                          capture request
+     * @param outputs           List of {@link ExtensionOutputConfiguration}
+     *                          camera outputs to be configured
+     *                          as part of the capture session
+     * @param sessionParams     An optional set of camera capture
+     *                          session parameter values
+     */
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
-    public ExtensionConfiguration(int sessionType, int sessionTemplateId, @NonNull
-            List<ExtensionOutputConfiguration> outputs, @Nullable CaptureRequest sessionParams) {
+    public ExtensionConfiguration(@CameraDevice.SessionOperatingMode int sessionType,
+            @CameraDevice.RequestTemplate int sessionTemplateId,
+            @NonNull List<ExtensionOutputConfiguration> outputs,
+            @Nullable CaptureRequest sessionParams) {
         mSessionType = sessionType;
         mSessionTemplateId = sessionTemplateId;
         mOutputs = outputs;
diff --git a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
index 85d180d..9dc6d7b 100644
--- a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
+++ b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
@@ -27,6 +27,10 @@
 import java.util.List;
 
 /**
+ * Helper class used to describe a single camera
+ * output configuration that is intended to be configured
+ * internally by the extension implementation.
+ *
  * @hide
  */
 @SystemApi
@@ -37,6 +41,21 @@
     private final int mOutputConfigId;
     private final int mSurfaceGroupId;
 
+    /**
+     * Initialize an extension output configuration instance
+     *
+     * @param outputs           List of camera {@link CameraOutputSurface outputs}.
+     *                          The list may include more than one entry
+     *                          only in case of shared camera outputs.
+     *                          In all other cases the list will only include
+     *                          a single entry.
+     * @param outputConfigId    Unique output configuration id used to identify
+     *                          this particular configuration.
+     * @param physicalCameraId  In case of physical camera capture, this field
+     *                          must contain a valid physical camera id.
+     * @param surfaceGroupId    In case of surface group, this field must
+     *                          contain the surface group id
+     */
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public ExtensionOutputConfiguration(@NonNull List<CameraOutputSurface> outputs,
             int outputConfigId, @Nullable String physicalCameraId, int surfaceGroupId) {
diff --git a/core/java/android/hardware/camera2/extension/RequestProcessor.java b/core/java/android/hardware/camera2/extension/RequestProcessor.java
index bf5ea12..0ad27c2 100644
--- a/core/java/android/hardware/camera2/extension/RequestProcessor.java
+++ b/core/java/android/hardware/camera2/extension/RequestProcessor.java
@@ -20,7 +20,9 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CaptureFailure;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
@@ -69,6 +71,8 @@
          *                  regular request, or the timestamp at the input
          *                  image's start of capture for a
          *                  reprocess request, in nanoseconds.
+         *                  The timestamp matches with and uses the same
+         *                  time base as {@link CaptureResult#SENSOR_TIMESTAMP}.
          * @param frameNumber the frame number for this capture
          *
          */
@@ -225,11 +229,12 @@
     public final static class Request {
         private final List<Integer> mOutputIds;
         private final List<Pair<CaptureRequest.Key, Object>> mParameters;
-        private final int mTemplateId;
+        private final @CameraDevice.RequestTemplate int mTemplateId;
 
         @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         public Request(@NonNull List<Integer> outputConfigIds,
-                @NonNull List<Pair<CaptureRequest.Key, Object>> parameters, int templateId) {
+                @NonNull List<Pair<CaptureRequest.Key, Object>> parameters,
+                @CameraDevice.RequestTemplate int templateId) {
             mOutputIds = outputConfigIds;
             mParameters = parameters;
             mTemplateId = templateId;
@@ -255,7 +260,10 @@
         }
 
         /**
-         * Gets the template id.
+         * Gets the request {@link android.hardware.camera2.CameraDevice.RequestTemplate template}
+         * id.
+         *
+         * @see CameraDevice.RequestTemplate
          */
         @FlaggedApi(Flags.FLAG_CONCERT_MODE)
         Integer getTemplateId() {
@@ -310,26 +318,32 @@
      * Submit a capture request.
      * @param request  Capture request to queued in the Camera2 session
      * @param executor the executor which will be used for
-     *                 invoking the callbacks or null to use the
-     *                 current thread's looper
+     *                 invoking the callbacks
      * @param callback Request callback implementation
-     * @return the id of the capture sequence or -1 in case the processor
-     *         encounters a fatal error or receives an invalid argument.
+     * @return the id of the capture sequence
      */
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
-    public int submit(@NonNull Request request, @Nullable Executor executor,
-            @NonNull RequestCallback callback) {
+    public int submit(@NonNull Request request, @NonNull Executor executor,
+            @NonNull RequestCallback callback) throws CameraAccessException {
         ArrayList<Request> requests = new ArrayList<>(1);
         requests.add(0, request);
         List<android.hardware.camera2.extension.Request> parcelableRequests =
                 Request.initializeParcelable(mVendorId, requests);
 
+        int ret = -1;
         try {
-            return mRequestProcessor.submit(parcelableRequests.get(0),
+            ret = mRequestProcessor.submit(parcelableRequests.get(0),
                     new RequestCallbackImpl(requests, callback, executor));
         } catch (RemoteException e) {
             throw new RuntimeException(e);
         }
+
+        if (ret == -1) {
+            throw new CameraAccessException(CameraAccessException.CAMERA_ERROR,
+                    "Failed to submit capture request");
+        }
+
+        return ret;
     }
 
     /**
@@ -337,24 +351,30 @@
      * @param requests List of capture requests to be queued in the
      *                 Camera2 session
      * @param executor the executor which will be used for
-     *                 invoking the callbacks or null to use the
-     *                 current thread's looper
+     *                 invoking the callbacks
      * @param callback Request callback implementation
-     * @return the id of the capture sequence or -1 in case the processor
-     *         encounters a fatal error or receives an invalid argument.
+     * @return the id of the capture sequence
      */
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
-    public int submitBurst(@NonNull List<Request> requests, @Nullable Executor executor,
-            @NonNull RequestCallback callback) {
+    public int submitBurst(@NonNull List<Request> requests, @NonNull Executor executor,
+            @NonNull RequestCallback callback) throws CameraAccessException {
         List<android.hardware.camera2.extension.Request> parcelableRequests =
                 Request.initializeParcelable(mVendorId, requests);
 
+        int ret = -1;
         try {
-            return mRequestProcessor.submitBurst(parcelableRequests,
+            ret = mRequestProcessor.submitBurst(parcelableRequests,
                     new RequestCallbackImpl(requests, callback, executor));
         } catch (RemoteException e) {
             throw new RuntimeException(e);
         }
+
+        if (ret == -1) {
+            throw new CameraAccessException(CameraAccessException.CAMERA_ERROR,
+                    "Failed to submit burst request");
+        }
+
+        return ret;
     }
 
     /**
@@ -362,26 +382,32 @@
      * @param request  Repeating capture request to be se in the
      *                 Camera2 session
      * @param executor the executor which will be used for
-     *                 invoking the callbacks or null to use the
-     *                 current thread's looper
+     *                 invoking the callbacks
      * @param callback Request callback implementation
-     * @return the id of the capture sequence or -1 in case the processor
-     *         encounters a fatal error or receives an invalid argument.
+     * @return the id of the capture sequence
      */
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
-    public int setRepeating(@NonNull Request request, @Nullable Executor executor,
-            @NonNull RequestCallback callback) {
+    public int setRepeating(@NonNull Request request, @NonNull Executor executor,
+            @NonNull RequestCallback callback) throws CameraAccessException {
         ArrayList<Request> requests = new ArrayList<>(1);
         requests.add(0, request);
         List<android.hardware.camera2.extension.Request> parcelableRequests =
                 Request.initializeParcelable(mVendorId, requests);
 
+        int ret = -1;
         try {
-            return mRequestProcessor.setRepeating(parcelableRequests.get(0),
+            ret = mRequestProcessor.setRepeating(parcelableRequests.get(0),
                     new RequestCallbackImpl(requests, callback, executor));
         } catch (RemoteException e) {
             throw new RuntimeException(e);
         }
+        if (ret == -1) {
+            throw new CameraAccessException(CameraAccessException.CAMERA_ERROR,
+                    "Failed to set the repeating request");
+
+        }
+
+        return ret;
     }
 
     /**
@@ -414,7 +440,7 @@
         private final Executor mExecutor;
 
         public RequestCallbackImpl(@NonNull List<Request> requests,
-                @NonNull RequestCallback callback, @Nullable Executor executor) {
+                @NonNull RequestCallback callback, @NonNull Executor executor) {
             mCallback = callback;
             mRequests = requests;
             mExecutor = executor;
@@ -425,13 +451,8 @@
             if (mRequests.get(requestId) != null) {
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    if (mExecutor != null) {
-                        mExecutor.execute(() -> mCallback.onCaptureStarted(
-                                mRequests.get(requestId), frameNumber, timestamp));
-                    } else {
-                        mCallback.onCaptureStarted(mRequests.get(requestId), frameNumber,
-                                timestamp);
-                    }
+                    mExecutor.execute(() -> mCallback.onCaptureStarted(
+                            mRequests.get(requestId), frameNumber, timestamp));
                 } finally {
                     Binder.restoreCallingIdentity(ident);
                 }
@@ -448,14 +469,9 @@
                         partialResult.frameNumber);
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    if (mExecutor != null) {
-                        mExecutor.execute(
-                                () -> mCallback.onCaptureProgressed(mRequests.get(requestId),
-                                        result));
-                    } else {
-                        mCallback.onCaptureProgressed(mRequests.get(requestId), result);
-                    }
-
+                    mExecutor.execute(
+                            () -> mCallback.onCaptureProgressed(mRequests.get(requestId),
+                                    result));
                 } finally {
                     Binder.restoreCallingIdentity(ident);
                 }
@@ -489,13 +505,9 @@
                         physicalResults);
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    if (mExecutor != null) {
-                        mExecutor.execute(
-                                () -> mCallback.onCaptureCompleted(mRequests.get(requestId),
-                                        result));
-                    } else {
-                        mCallback.onCaptureCompleted(mRequests.get(requestId), result);
-                    }
+                    mExecutor.execute(
+                            () -> mCallback.onCaptureCompleted(mRequests.get(requestId),
+                                    result));
                 } finally {
                     Binder.restoreCallingIdentity(ident);
                 }
@@ -515,12 +527,8 @@
                                 captureFailure.errorPhysicalCameraId);
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    if (mExecutor != null) {
-                        mExecutor.execute(() -> mCallback.onCaptureFailed(mRequests.get(requestId),
-                                failure));
-                    } else {
-                        mCallback.onCaptureFailed(mRequests.get(requestId), failure);
-                    }
+                    mExecutor.execute(() -> mCallback.onCaptureFailed(mRequests.get(requestId),
+                            failure));
                 } finally {
                     Binder.restoreCallingIdentity(ident);
                 }
@@ -534,14 +542,9 @@
             if (mRequests.get(requestId) != null) {
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    if (mExecutor != null) {
-                        mExecutor.execute(
-                                () -> mCallback.onCaptureBufferLost(mRequests.get(requestId),
-                                        frameNumber, outputStreamId));
-                    } else {
-                        mCallback.onCaptureBufferLost(mRequests.get(requestId), frameNumber,
-                                outputStreamId);
-                    }
+                    mExecutor.execute(
+                            () -> mCallback.onCaptureBufferLost(mRequests.get(requestId),
+                                    frameNumber, outputStreamId));
                 } finally {
                     Binder.restoreCallingIdentity(ident);
                 }
@@ -554,12 +557,8 @@
         public void onCaptureSequenceCompleted(int sequenceId, long frameNumber) {
             final long ident = Binder.clearCallingIdentity();
             try {
-                if (mExecutor != null) {
-                    mExecutor.execute(() -> mCallback.onCaptureSequenceCompleted(sequenceId,
-                            frameNumber));
-                } else {
-                    mCallback.onCaptureSequenceCompleted(sequenceId, frameNumber);
-                }
+                mExecutor.execute(() -> mCallback.onCaptureSequenceCompleted(sequenceId,
+                        frameNumber));
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -569,11 +568,7 @@
         public void onCaptureSequenceAborted(int sequenceId) {
             final long ident = Binder.clearCallingIdentity();
             try {
-                if (mExecutor != null) {
-                    mExecutor.execute(() -> mCallback.onCaptureSequenceAborted(sequenceId));
-                } else {
-                    mCallback.onCaptureSequenceAborted(sequenceId);
-                }
+                mExecutor.execute(() -> mCallback.onCaptureSequenceAborted(sequenceId));
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
diff --git a/core/java/android/hardware/camera2/extension/SessionProcessor.java b/core/java/android/hardware/camera2/extension/SessionProcessor.java
index 9c5136b..e7cc5303 100644
--- a/core/java/android/hardware/camera2/extension/SessionProcessor.java
+++ b/core/java/android/hardware/camera2/extension/SessionProcessor.java
@@ -18,13 +18,15 @@
 
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.impl.CameraExtensionUtils.HandlerExecutor;
 import android.hardware.camera2.impl.CameraMetadataNative;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -60,7 +62,7 @@
  * to the repeating request and single requests but the implementation can
  * choose to apply some of them only.
  *
- * (5) {@link #startCapture}: It is called when apps want
+ * (5) {@link #startMultiFrameCapture}: It is called when apps want
  * to start a multi-frame image capture.  {@link CaptureCallback} will be
  * called to report the status and the output image will be written to the
  * capture output surface specified in {@link #initSession}.
@@ -78,8 +80,11 @@
     private static final String TAG = "SessionProcessor";
     private CameraUsageTracker mCameraUsageTracker;
 
+    /**
+     * Initialize a session process instance
+     */
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
-    protected SessionProcessor() {}
+    public SessionProcessor() {}
 
     void setCameraUsageTracker(CameraUsageTracker tracker) {
         mCameraUsageTracker = tracker;
@@ -87,7 +92,7 @@
 
     /**
      * Callback for notifying the status of {@link
-     * #startCapture} and {@link #startRepeating}.
+     * #startMultiFrameCapture} and {@link #startRepeating}.
      * @hide
      */
     @SystemApi
@@ -175,7 +180,7 @@
          * @param requestId  the capture request id that generated the
          *                   capture results. This is the return value of
          *                   either {@link #startRepeating} or {@link
-         *                   #startCapture}.
+         *                   #startMultiFrameCapture}.
          * @param results  The supported capture results. Do note
          *                  that if results 'android.jpeg.quality' and
          *                  android.jpeg.orientation' are present in the
@@ -252,7 +257,14 @@
      * until onCaptureSessionEnd is called.
      * @param requestProcessor The request processor to be used for
      *                         managing capture requests
-     * @param statsKey         Unique key for telemetry
+     * @param statsKey         Unique key that is associated with the
+     *                         current Camera2 session and used by the
+     *                         framework telemetry. The id can be referenced
+     *                         by the extension, in case there is additional
+     *                         extension specific telemetry that needs
+     *                         to be linked to the regular capture session.
+     *
+     *
      */
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public abstract void onCaptureSessionStart(@NonNull RequestProcessor requestProcessor,
@@ -275,13 +287,12 @@
      * repeating request when needed later.
      *
      * @param executor the executor which will be used for
-     *                 invoking the callbacks or null to use the
-     *                 current thread's looper
+     *                 invoking the callbacks
      * @param callback a callback to report the status.
      * @return the id of the capture sequence.
      */
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
-    public abstract int startRepeating(@Nullable Executor executor,
+    public abstract int startRepeating(@NonNull Executor executor,
             @NonNull CaptureCallback callback);
 
     /**
@@ -309,13 +320,12 @@
      * immediately.
      *
      * @param executor the executor which will be used for
-     *                 invoking the callbacks or null to use the
-     *                 current thread's looper
+     *                 invoking the callbacks
      * @param callback a callback to report the status.
      * @return the id of the capture sequence.
      */
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
-    public abstract int startCapture(@Nullable Executor executor,
+    public abstract int startMultiFrameCapture(@NonNull Executor executor,
             @NonNull CaptureCallback callback);
 
     /**
@@ -340,15 +350,14 @@
      * @param captureRequest Capture request that includes the respective
      *                       triggers.
      * @param executor the executor which will be used for
-     *                 invoking the callbacks or null to use the
-     *                 current thread's looper
+     *                 invoking the callbacks
      * @param callback a callback to report the status.
      * @return the id of the capture sequence.
      *
      */
     @FlaggedApi(Flags.FLAG_CONCERT_MODE)
     public abstract int startTrigger(@NonNull CaptureRequest captureRequest,
-            @Nullable Executor executor, @NonNull CaptureCallback callback);
+            @NonNull Executor executor, @NonNull CaptureCallback callback);
 
     private final class SessionProcessorImpl extends ISessionProcessorImpl.Stub {
         private long mVendorId = -1;
@@ -401,7 +410,8 @@
 
         @Override
         public int startRepeating(ICaptureCallback callback) throws RemoteException {
-            return SessionProcessor.this.startRepeating(/*executor*/ null,
+            return SessionProcessor.this.startRepeating(
+                    new HandlerExecutor(new Handler(Looper.getMainLooper())),
                     new CaptureCallbackImpl(callback));
         }
 
@@ -413,7 +423,8 @@
         @Override
         public int startCapture(ICaptureCallback callback, boolean isPostviewRequested)
                 throws RemoteException {
-            return SessionProcessor.this.startCapture(/*executor*/ null,
+            return SessionProcessor.this.startMultiFrameCapture(
+                    new HandlerExecutor(new Handler(Looper.getMainLooper())),
                     new CaptureCallbackImpl(callback));
         }
 
@@ -425,7 +436,8 @@
         @Override
         public int startTrigger(CaptureRequest captureRequest, ICaptureCallback callback)
                 throws RemoteException {
-            return SessionProcessor.this.startTrigger(captureRequest, /*executor*/ null,
+            return SessionProcessor.this.startTrigger(captureRequest,
+                    new HandlerExecutor(new Handler(Looper.getMainLooper())),
                     new CaptureCallbackImpl(callback));
         }
 
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/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/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 6c728a4..abfa4e3 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -122,3 +122,11 @@
     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/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/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/persistentdata/IPersistentDataBlockService.aidl b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
index 11e5ad8..21801c0 100644
--- a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
+++ b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
@@ -38,5 +38,33 @@
     int getFlashLockState();
     boolean hasFrpCredentialHandle();
     String getPersistentDataPackageName();
-}
 
+    /**
+     * Returns true if Factory Reset Protection (FRP) is active, meaning the device rebooted and has
+     * not been able to transition to the FRP inactive state.
+     */
+    boolean isFactoryResetProtectionActive();
+
+    /**
+     * Attempts to deactivate Factory Reset Protection (FRP) with the provided secret.  If the
+     * provided secret matches the stored FRP secret, FRP is deactivated and the method returns
+     * true.  Otherwise, FRP state remains unchanged and the method returns false.
+     */
+    boolean deactivateFactoryResetProtection(in byte[] secret);
+
+    /**
+     * Stores the provided Factory Reset Protection (FRP) secret as the secret to be used for future
+     * FRP deactivation.  The secret must be 32 bytes in length.  Setting the all-zeros "default"
+     * value disables the FRP feature entirely.
+     *
+     * It's the responsibility of the caller to ensure that copies of the FRP secret are stored
+     * securely where they can be recovered and used to deactivate FRP after an untrusted reset.
+     * This method will store a copy in /data/system and use that to automatically deactivate FRP
+     * until /data is wiped.
+     *
+     * Note that this method does nothing if FRP is currently active.
+     *
+     * Returns true if the secret was successfully changed, false otherwise.
+     */
+    boolean setFactoryResetProtectionSecret(in byte[] secret);
+}
diff --git a/core/java/android/service/persistentdata/PersistentDataBlockManager.java b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
index 6da3206..9b9cc19 100644
--- a/core/java/android/service/persistentdata/PersistentDataBlockManager.java
+++ b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
@@ -16,6 +16,7 @@
 
 package android.service.persistentdata;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
@@ -24,30 +25,17 @@
 import android.annotation.SystemService;
 import android.content.Context;
 import android.os.RemoteException;
+import android.security.Flags;
 import android.service.oemlock.OemLockManager;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * Interface for reading and writing data blocks to a persistent partition.
- *
- * Allows writing one block at a time. Namely, each time
- * {@link PersistentDataBlockManager#write(byte[])}
- * is called, it will overwite the data that was previously written on the block.
- *
- * Clients can query the size of the currently written block via
- * {@link PersistentDataBlockManager#getDataBlockSize()}.
- *
- * Clients can query the maximum size for a block via
- * {@link PersistentDataBlockManager#getMaximumDataBlockSize()}
- *
- * Clients can read the currently written block by invoking
- * {@link PersistentDataBlockManager#read()}.
- *
- * @hide
+ * Interface to the persistent data partition.  Provides access to information about the state
+ * of factory reset protection.
  */
-@SystemApi
+@FlaggedApi(Flags.FLAG_FRP_ENFORCEMENT)
 @SystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE)
 public class PersistentDataBlockManager {
     private static final String TAG = PersistentDataBlockManager.class.getSimpleName();
@@ -55,18 +43,32 @@
 
     /**
      * Indicates that the device's bootloader lock state is UNKNOWN.
+     *
+     * @hide
      */
+    @SystemApi
     public static final int FLASH_LOCK_UNKNOWN = -1;
     /**
      * Indicates that the device's bootloader is UNLOCKED.
+     *
+     * @hide
      */
+    @SystemApi
     public static final int FLASH_LOCK_UNLOCKED = 0;
     /**
      * Indicates that the device's bootloader is LOCKED.
+     *
+     * @hide
      */
+    @SystemApi
     public static final int FLASH_LOCK_LOCKED = 1;
 
-    /** @removed mistakenly exposed previously */
+    /**
+     * @removed mistakenly exposed previously
+     *
+     * @hide
+     */
+    @SystemApi
     @IntDef(prefix = { "FLASH_LOCK_" }, value = {
             FLASH_LOCK_UNKNOWN,
             FLASH_LOCK_LOCKED,
@@ -75,7 +77,9 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface FlashLockState {}
 
-    /** @hide */
+    /**
+     * @hide
+     */
     public PersistentDataBlockManager(IPersistentDataBlockService service) {
         sService = service;
     }
@@ -91,7 +95,10 @@
      * in which case -1 will be returned.
      *
      * @param data the data to write
+     *
+     * @hide
      */
+    @SystemApi
     @SuppressLint("RequiresPermission")
     public int write(byte[] data) {
         try {
@@ -103,7 +110,10 @@
 
     /**
      * Returns the data block stored on the persistent partition.
+     *
+     * @hide
      */
+    @SystemApi
     @SuppressLint("RequiresPermission")
     public byte[] read() {
         try {
@@ -117,7 +127,10 @@
      * Retrieves the size of the block currently written to the persistent partition.
      *
      * Return -1 on error.
+     *
+     * @hide
      */
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.ACCESS_PDB_STATE)
     public int getDataBlockSize() {
         try {
@@ -131,7 +144,10 @@
      * Retrieves the maximum size allowed for a data block.
      *
      * Returns -1 on error.
+     *
+     * @hide
      */
+    @SystemApi
     @SuppressLint("RequiresPermission")
     public long getMaximumDataBlockSize() {
         try {
@@ -146,7 +162,10 @@
      * will erase all data written to the persistent data partition.
      * It will also prevent any further {@link #write} operation until reboot,
      * in order to prevent a potential race condition. See b/30352311.
+     *
+     * @hide
      */
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.OEM_UNLOCK_STATE)
     public void wipe() {
         try {
@@ -160,7 +179,11 @@
      * Writes a byte enabling or disabling the ability to "OEM unlock" the device.
      *
      * @deprecated use {@link OemLockManager#setOemUnlockAllowedByUser(boolean)} instead.
+     *
+     * @hide
      */
+    @SystemApi
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.OEM_UNLOCK_STATE)
     public void setOemUnlockEnabled(boolean enabled) {
         try {
@@ -174,7 +197,11 @@
      * Returns whether or not "OEM unlock" is enabled or disabled on this device.
      *
      * @deprecated use {@link OemLockManager#isOemUnlockAllowedByUser()} instead.
+     *
+     * @hide
      */
+    @SystemApi
+    @Deprecated
     @RequiresPermission(anyOf = {
             android.Manifest.permission.READ_OEM_UNLOCK_STATE,
             android.Manifest.permission.OEM_UNLOCK_STATE
@@ -193,7 +220,10 @@
      * @return {@link #FLASH_LOCK_LOCKED} if device bootloader is locked,
      * {@link #FLASH_LOCK_UNLOCKED} if device bootloader is unlocked, or {@link #FLASH_LOCK_UNKNOWN}
      * if this information cannot be ascertained on this device.
+     *
+     * @hide
      */
+    @SystemApi
     @RequiresPermission(anyOf = {
             android.Manifest.permission.READ_OEM_UNLOCK_STATE,
             android.Manifest.permission.OEM_UNLOCK_STATE
@@ -222,4 +252,73 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Returns true if FactoryResetProtection (FRP) is active, meaning the device rebooted and has
+     * not been able to deactivate FRP because the deactivation secrets were wiped by an untrusted
+     * factory reset.
+     */
+    @FlaggedApi(Flags.FLAG_FRP_ENFORCEMENT)
+    public boolean isFactoryResetProtectionActive() {
+        try {
+            return sService.isFactoryResetProtectionActive();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Attempt to deactivate FRP with the provided secret.  If the provided secret matches the
+     * stored FRP secret, FRP is deactivated and the method returns true.  Otherwise, FRP state
+     * remains unchanged and the method returns false.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_FRP_ENFORCEMENT)
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.CONFIGURE_FACTORY_RESET_PROTECTION)
+    public boolean deactivateFactoryResetProtection(@NonNull byte[] secret) {
+        try {
+            return sService.deactivateFactoryResetProtection(secret);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Store the provided FRP secret as the secret to be used for future FRP deactivation.  The
+     * secret must be 32 bytes in length.  Setting the all-zeros "default" value disables the FRP
+     * feature entirely.
+     *
+     * To ensure that the device doesn't end up in a bad state if a crash occurs, this method
+     * should be used in a three-step process:
+     *
+     * 1.  Generate a new secret and securely store any necessary copies (e.g. by encrypting them
+     *     and calling #write with a new data block that contains both the old encrypted secret
+     *     copies and the new ones).
+     * 2.  Call this method to set the new FRP secret.  This will also write the copy used during
+     *     normal boot.
+     * 3.  Delete any old FRP secret copies (e.g. by calling #write with a new data block that
+     *     contains only the new encrypted secret copies).
+     *
+     * Note that this method does nothing if FRP is currently active.
+     *
+     * This method does not require any permission, but can be called only by the
+     * PersistentDataBlockService's authorized caller UID.
+     *
+     * Returns true if the new secret was successfully written.  Returns false if FRP is currently
+     * active.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_FRP_ENFORCEMENT)
+    @SystemApi
+    @SuppressLint("RequiresPermission")
+    public boolean setFactoryResetProtectionSecret(@NonNull byte[] secret) {
+        try {
+            return sService.setFactoryResetProtectionSecret(secret);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/service/voice/OWNERS b/core/java/android/service/voice/OWNERS
index ec44100..763c79e 100644
--- a/core/java/android/service/voice/OWNERS
+++ b/core/java/android/service/voice/OWNERS
@@ -4,4 +4,4 @@
 
 # The owner here should not be assist owner
 liangyuchen@google.com
-tuanng@google.com
+adudani@google.com
diff --git a/core/java/android/service/wearable/IWearableSensingService.aidl b/core/java/android/service/wearable/IWearableSensingService.aidl
index 44a13c4..0556188 100644
--- a/core/java/android/service/wearable/IWearableSensingService.aidl
+++ b/core/java/android/service/wearable/IWearableSensingService.aidl
@@ -28,8 +28,11 @@
  * @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 registerDataRequestObserver(int dataType, in RemoteCallback dataRequestCallback, int dataRequestObserverId, in String packageName, in RemoteCallback statusCallback);
+    void unregisterDataRequestObserver(int dataType, int dataRequestObserverId, in String packageName, in RemoteCallback statusCallback);
     void startDetection(in AmbientContextEventRequest request, in String packageName,
             in RemoteCallback detectionResultCallback, in RemoteCallback statusCallback);
     void stopDetection(in String packageName);
diff --git a/core/java/android/service/wearable/WearableSensingDataRequester.java b/core/java/android/service/wearable/WearableSensingDataRequester.java
new file mode 100644
index 0000000..5a8104f
--- /dev/null
+++ b/core/java/android/service/wearable/WearableSensingDataRequester.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.wearable;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.wearable.Flags;
+import android.app.wearable.WearableSensingDataRequest;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.function.Consumer;
+
+/**
+ * An interface to request wearable sensing data.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
+@SystemApi
+public interface WearableSensingDataRequester {
+
+    /** An unknown status. */
+    int STATUS_UNKNOWN = 0;
+
+    /** The value of the status code that indicates success. */
+    int STATUS_SUCCESS = 1;
+
+    /**
+     * The value of the status code that indicates the request is rejected because the data request
+     * observer PendingIntent has been cancelled.
+     */
+    int STATUS_OBSERVER_CANCELLED = 2;
+
+    /**
+     * The value of the status code that indicates the request is rejected because it is larger than
+     * {@link WearableSensingDataRequest#getMaxRequestSize()}.
+     */
+    int STATUS_TOO_LARGE = 3;
+
+    /**
+     * The value of the status code that indicates the request is rejected because it exceeds the
+     * rate limit. See {@link WearableSensingDataRequest#getRateLimit()}.
+     */
+    int STATUS_TOO_FREQUENT = 4;
+
+    /** @hide */
+    @IntDef(
+            prefix = {"STATUS_"},
+            value = {
+                STATUS_UNKNOWN,
+                STATUS_SUCCESS,
+                STATUS_OBSERVER_CANCELLED,
+                STATUS_TOO_LARGE,
+                STATUS_TOO_FREQUENT
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface StatusCode {}
+
+    /**
+     * Sends a data request. See {@link WearableSensingService#onDataRequestObserverRegistered(int,
+     * String, WearableSensingDataRequester, Consumer)} for size and rate restrictions on data
+     * requests.
+     *
+     * @param dataRequest The data request to send.
+     * @param statusConsumer A consumer that handles the status code for the data request.
+     */
+    void requestData(
+            @NonNull WearableSensingDataRequest dataRequest,
+            @NonNull @StatusCode Consumer<Integer> statusConsumer);
+}
diff --git a/core/java/android/service/wearable/WearableSensingService.java b/core/java/android/service/wearable/WearableSensingService.java
index e7e44a5..d25cff7 100644
--- a/core/java/android/service/wearable/WearableSensingService.java
+++ b/core/java/android/service/wearable/WearableSensingService.java
@@ -17,12 +17,15 @@
 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.WearableSensingDataRequest;
 import android.app.wearable.WearableSensingManager;
 import android.content.Intent;
 import android.os.Bundle;
@@ -34,11 +37,13 @@
 import android.service.ambientcontext.AmbientContextDetectionResult;
 import android.service.ambientcontext.AmbientContextDetectionServiceStatus;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /**
@@ -87,6 +92,9 @@
     public static final String SERVICE_INTERFACE =
             "android.service.wearable.WearableSensingService";
 
+    private final SparseArray<WearableSensingDataRequester> mDataRequestObserverIdToRequesterMap =
+            new SparseArray<>();
+
     @Nullable
     @Override
     public final IBinder onBind(@NonNull Intent intent) {
@@ -94,17 +102,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 +127,87 @@
                         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 registerDataRequestObserver(
+                        int dataType,
+                        RemoteCallback dataRequestCallback,
+                        int dataRequestObserverId,
+                        String packageName,
+                        RemoteCallback statusCallback) {
+                    Objects.requireNonNull(dataRequestCallback);
+                    Objects.requireNonNull(statusCallback);
+                    WearableSensingDataRequester dataRequester;
+                    synchronized (mDataRequestObserverIdToRequesterMap) {
+                        dataRequester =
+                                mDataRequestObserverIdToRequesterMap.get(dataRequestObserverId);
+                        if (dataRequester == null) {
+                            dataRequester = createDataRequester(dataRequestCallback);
+                            mDataRequestObserverIdToRequesterMap.put(
+                                    dataRequestObserverId, dataRequester);
+                        }
+                    }
+                    Consumer<Integer> statusConsumer = createWearableStatusConsumer(statusCallback);
+                    WearableSensingService.this.onDataRequestObserverRegistered(
+                            dataType, packageName, dataRequester, statusConsumer);
+                }
+
+                @Override
+                public void unregisterDataRequestObserver(
+                        int dataType,
+                        int dataRequestObserverId,
+                        String packageName,
+                        RemoteCallback statusCallback) {
+                    WearableSensingDataRequester dataRequester;
+                    synchronized (mDataRequestObserverIdToRequesterMap) {
+                        dataRequester =
+                                mDataRequestObserverIdToRequesterMap.get(dataRequestObserverId);
+                        if (dataRequester == null) {
+                            Slog.w(
+                                    TAG,
+                                    "dataRequestObserverId not found, cannot unregister data"
+                                            + " request observer.");
+                            return;
+                        }
+                        mDataRequestObserverIdToRequesterMap.remove(dataRequestObserverId);
+                    }
+                    Consumer<Integer> statusConsumer = createWearableStatusConsumer(statusCallback);
+                    WearableSensingService.this.onDataRequestObserverUnregistered(
+                            dataType, packageName, dataRequester, statusConsumer);
+                }
+
+                /** {@inheritDoc} */
+                @Override
+                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 +222,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 +249,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.
@@ -198,19 +285,19 @@
             @NonNull Consumer<Integer> statusConsumer);
 
     /**
-     * Called when configurations and read-only data in a {@link PersistableBundle}
-     * can be used by the WearableSensingService and sends the result to the {@link Consumer}
-     * right after the call. It is dependent on the application to define the type of data to
-     * provide. This is used by applications that will also provide an implementation of an isolated
-     * WearableSensingService. If the data was provided successfully
-     * {@link WearableSensingManager#STATUS_SUCCESS} will be provided.
+     * Called when configurations and read-only data in a {@link PersistableBundle} can be used by
+     * the WearableSensingService and sends the result to the {@link Consumer} right after the call.
+     * It is dependent on the application to define the type of data to provide. This is used by
+     * applications that will also provide an implementation of an isolated WearableSensingService.
+     * If the data was provided successfully {@link WearableSensingManager#STATUS_SUCCESS} will be
+     * provided.
      *
      * @param data Application configuration data to provide to the {@link WearableSensingService}.
-     *             PersistableBundle does not allow any remotable objects or other contents
-     *             that can be used to communicate with other processes.
-     * @param sharedMemory The unrestricted data blob to
-     *                     provide to the {@link WearableSensingService}. Use this to provide the
-     *                     sensing models data or other such data to the trusted process.
+     *     PersistableBundle does not allow any remotable objects or other contents that can be used
+     *     to communicate with other processes.
+     * @param sharedMemory The unrestricted data blob to provide to the {@link
+     *     WearableSensingService}. Use this to provide the sensing models data or other such data
+     *     to the trusted process.
      * @param statusConsumer the consumer for the service status.
      */
     @BinderThread
@@ -220,6 +307,68 @@
             @NonNull Consumer<Integer> statusConsumer);
 
     /**
+     * Called when a data request observer is registered. Each request must not be larger than
+     * {@link WearableSensingDataRequest#getMaxRequestSize()}. In addition, at most {@link
+     * WearableSensingDataRequester#getRateLimit()} requests can be sent every rolling {@link
+     * WearableSensingDataRequester#getRateLimitWindowSize()}. Requests that are too large or too
+     * frequent will be dropped by the system. See {@link
+     * WearableSensingDataRequester#requestData(WearableSensingDataRequest, Consumer)} for details
+     * about the status code returned for each request.
+     *
+     * <p>The implementing class should override this method. After the data requester is received,
+     * it should send a {@link WearableSensingManager#STATUS_SUCCESS} status code to the {@code
+     * statusConsumer} unless it encounters an error condition described by a status code listed in
+     * {@link WearableSensingManager}, such as {@link
+     * WearableSensingManager#STATUS_WEARABLE_UNAVAILABLE}, in which case it should return the
+     * corresponding status code.
+     *
+     * @param dataType The data type the observer is registered for. Values are defined by the
+     *     application that implements this class.
+     * @param packageName The package name of the app that will receive the requests.
+     * @param dataRequester A handle to the observer registered. It can be used to request data of
+     *     the specified data type.
+     * @param statusConsumer the consumer for the status of the data request observer registration.
+     *     This is different from the status for each data request.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
+    @BinderThread
+    public void onDataRequestObserverRegistered(
+            int dataType,
+            @NonNull String packageName,
+            @NonNull WearableSensingDataRequester dataRequester,
+            @NonNull Consumer<Integer> statusConsumer) {
+        statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION);
+    }
+
+    /**
+     * Called when a data request observer is unregistered.
+     *
+     * <p>The implementing class should override this method. It should send a {@link
+     * WearableSensingManager#STATUS_SUCCESS} status code to the {@code statusConsumer} unless it
+     * encounters an error condition described by a status code listed in {@link
+     * WearableSensingManager}, such as {@link WearableSensingManager#STATUS_WEARABLE_UNAVAILABLE},
+     * in which case it should return the corresponding status code.
+     *
+     * @param dataType The data type the observer is for.
+     * @param packageName The package name of the app that will receive the requests sent to the
+     *     dataRequester.
+     * @param dataRequester A handle to the observer to be unregistered. It is the exact same
+     *     instance provided in a previous {@link #onDataRequestConsumerRegistered(int, String,
+     *     WearableSensingDataRequester, Consumer)} invocation.
+     * @param statusConsumer the consumer for the status of the data request observer
+     *     unregistration. This is different from the status for each data request.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
+    @BinderThread
+    public void onDataRequestObserverUnregistered(
+            int dataType,
+            @NonNull String packageName,
+            @NonNull WearableSensingDataRequester dataRequester,
+            @NonNull Consumer<Integer> statusConsumer) {
+        statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION);
+    }
+
+    /**
      * Called when a client app requests starting detection of the events in the request. The
      * implementation should keep track of whether the user has explicitly consented to detecting
      * the events using on-going ambient sensor (e.g. microphone), and agreed to share the
@@ -275,4 +424,32 @@
         }
         return intArray;
     }
+
+    private static WearableSensingDataRequester createDataRequester(
+            RemoteCallback dataRequestCallback) {
+        return (request, requestStatusConsumer) -> {
+            Bundle bundle = new Bundle();
+            bundle.putParcelable(WearableSensingDataRequest.REQUEST_BUNDLE_KEY, request);
+            RemoteCallback requestStatusCallback =
+                    new RemoteCallback(
+                            requestStatusBundle -> {
+                                requestStatusConsumer.accept(
+                                        requestStatusBundle.getInt(
+                                                WearableSensingManager.STATUS_RESPONSE_BUNDLE_KEY));
+                            });
+            bundle.putParcelable(
+                    WearableSensingDataRequest.REQUEST_STATUS_CALLBACK_BUNDLE_KEY,
+                    requestStatusCallback);
+            dataRequestCallback.sendResult(bundle);
+        };
+    }
+
+    @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/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index 27c509a..52f4855 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -183,6 +183,7 @@
      * {@link SurfaceControlViewHost} instances.
      *
      * <p>This token should be passed to {@link SurfaceControlViewHost}'s constructor.
+     * This token will be {@code null} if the window does not have an input channel.
      *
      * @return The SurfaceControlViewHost link token.
      */
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index 9b21b76..270bf8b 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -32,6 +32,7 @@
 import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.VectorDrawable;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -173,6 +174,8 @@
     private Bitmap mBitmapFrames[];
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private int mDurationPerFrame;
+    @SuppressWarnings("unused")
+    private boolean mDrawNativeDropShadow;
 
     private PointerIcon(int type) {
         mType = type;
@@ -231,9 +234,15 @@
             typeIndex = getSystemIconTypeIndex(TYPE_DEFAULT);
         }
 
-        final int defStyle = useLargeIcons
-                ? com.android.internal.R.style.LargePointer
-                : com.android.internal.R.style.Pointer;
+        final int defStyle;
+        // TODO(b/305193969): Use scaled vectors when large icons are requested.
+        if (useLargeIcons) {
+            defStyle = com.android.internal.R.style.LargePointer;
+        } else if (android.view.flags.Flags.enableVectorCursors()) {
+            defStyle = com.android.internal.R.style.VectorPointer;
+        } else {
+            defStyle = com.android.internal.R.style.Pointer;
+        }
         TypedArray a = context.obtainStyledAttributes(null,
                 com.android.internal.R.styleable.Pointer,
                 0, defStyle);
@@ -333,6 +342,7 @@
                                     Bitmap.CREATOR.createFromParcel(in),
                                     in.readFloat(),
                                     in.readFloat());
+                    icon.mDrawNativeDropShadow = in.readBoolean();
                     return icon;
                 }
 
@@ -362,6 +372,7 @@
         mBitmap.writeToParcel(out, flags);
         out.writeFloat(mHotSpotX);
         out.writeFloat(mHotSpotY);
+        out.writeBoolean(mDrawNativeDropShadow);
     }
 
     @Override
@@ -415,6 +426,16 @@
         return scaled;
     }
 
+    private BitmapDrawable getBitmapDrawableFromVectorDrawable(Resources resources,
+            VectorDrawable vectorDrawable) {
+        Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(),
+                vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+        vectorDrawable.draw(canvas);
+        return new BitmapDrawable(resources, bitmap);
+    }
+
     private void loadResource(Context context, Resources resources, @XmlRes int resourceId) {
         final XmlResourceParser parser = resources.getXml(resourceId);
         final int bitmapRes;
@@ -476,6 +497,10 @@
                 }
             }
         }
+        if (drawable instanceof VectorDrawable) {
+            mDrawNativeDropShadow = true;
+            drawable = getBitmapDrawableFromVectorDrawable(resources, (VectorDrawable) drawable);
+        }
         if (!(drawable instanceof BitmapDrawable)) {
             throw new IllegalArgumentException("<pointer-icon> bitmap attribute must "
                     + "refer to a bitmap drawable.");
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5c5817f..a9f1897 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5543,7 +5543,7 @@
 
     // The preferred frame rate of the view that is mainly used for
     // touch boosting, view velocity handling, and TextureView.
-    private float mPreferredFrameRate = REQUESTED_FRAME_RATE_CATEGORY_DEFAULT;
+    private float mPreferredFrameRate = Float.NaN;
 
     private int mInfrequentUpdateCount = 0;
     private long mLastUpdateTimeMillis = 0;
@@ -33186,25 +33186,27 @@
         float sizePercentage = getSizePercentage();
         int frameRateCateogry = calculateFrameRateCategory(sizePercentage);
         if (viewRootImpl != null && sizePercentage > 0) {
-            if (mPreferredFrameRate < 0) {
-                if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
-                    frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
-                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
-                    frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
-                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
-                    frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
-                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
-                    frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
-                }
-            } else {
-                viewRootImpl.votePreferredFrameRate(mPreferredFrameRate);
-            }
-            viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
-            mLastFrameRateCategory = frameRateCateogry;
-
             if (sToolkitMetricsForFrameRateDecisionFlagValue) {
                 viewRootImpl.recordViewPercentage(sizePercentage);
             }
+            if (!Float.isNaN(mPreferredFrameRate)) {
+                if (mPreferredFrameRate < 0) {
+                    if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
+                        frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+                    } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
+                        frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
+                    } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
+                        frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
+                    } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
+                        frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
+                    }
+                } else {
+                    viewRootImpl.votePreferredFrameRate(mPreferredFrameRate);
+                    return;
+                }
+            }
+            viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
+            mLastFrameRateCategory = frameRateCateogry;
         }
     }
 
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..3e7a9cb 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"
@@ -24,3 +17,11 @@
     bug: "316170253"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "enable_vector_cursors"
+    namespace: "systemui"
+    description: "Feature flag to enable vector drawables in addition to bitmaps for PointerIcon."
+    bug: "305193969"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/view/flags/window_insets.aconfig b/core/java/android/view/flags/window_insets.aconfig
new file mode 100644
index 0000000..201b7ad
--- /dev/null
+++ b/core/java/android/view/flags/window_insets.aconfig
@@ -0,0 +1,9 @@
+package: "android.view.flags"
+
+flag {
+    name: "customizable_window_headers"
+    namespace: "lse_desktop_experience"
+    description: "Flag to control the caption bar appearance and to fit app content in its empty space"
+    bug: "316387515"
+    is_fixed_read_only: true
+}
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java
index b0f3578..a051c1b 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java
@@ -89,4 +89,7 @@
      */
     @Nullable
     String getRequiredDisplayCategory();
+
+    /** Gets the permissions necessary for launching the activity when using content URIs. */
+    int getRequireContentUriPermissionFromCaller();
 }
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java
index 2f977ee..1218793 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java
@@ -36,9 +36,9 @@
 import android.text.TextUtils;
 import android.util.ArraySet;
 
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
-import com.android.internal.pm.pkg.parsing.ParsingUtils;
 
 import java.util.Collections;
 import java.util.Locale;
@@ -97,6 +97,8 @@
     @Nullable
     private String mRequiredDisplayCategory;
 
+    private int mRequireContentUriPermissionFromCaller;
+
     public ParsedActivityImpl(ParsedActivityImpl other) {
         super(other);
         this.theme = other.theme;
@@ -124,6 +126,7 @@
         this.windowLayout = other.windowLayout;
         this.mKnownActivityEmbeddingCerts = other.mKnownActivityEmbeddingCerts;
         this.mRequiredDisplayCategory = other.mRequiredDisplayCategory;
+        this.mRequireContentUriPermissionFromCaller = other.mRequireContentUriPermissionFromCaller;
     }
 
     /**
@@ -192,6 +195,8 @@
         alias.setDirectBootAware(target.isDirectBootAware());
         alias.setProcessName(target.getProcessName());
         alias.setRequiredDisplayCategory(target.getRequiredDisplayCategory());
+        alias.setRequireContentUriPermissionFromCaller(
+                target.getRequireContentUriPermissionFromCaller());
         return alias;
 
         // Not all attributes from the target ParsedActivity are copied to the alias.
@@ -320,6 +325,7 @@
         }
         sForStringSet.parcel(this.mKnownActivityEmbeddingCerts, dest, flags);
         dest.writeString8(this.mRequiredDisplayCategory);
+        dest.writeInt(this.mRequireContentUriPermissionFromCaller);
     }
 
     public ParsedActivityImpl() {
@@ -355,6 +361,7 @@
         }
         this.mKnownActivityEmbeddingCerts = sForStringSet.unparcel(in);
         this.mRequiredDisplayCategory = in.readString8();
+        this.mRequireContentUriPermissionFromCaller = in.readInt();
     }
 
     @NonNull
@@ -412,7 +419,8 @@
             int rotationAnimation,
             int colorMode,
             @Nullable ActivityInfo.WindowLayout windowLayout,
-            @Nullable String requiredDisplayCategory) {
+            @Nullable String requiredDisplayCategory,
+            int requireContentUriPermissionFromCaller) {
         this.theme = theme;
         this.uiOptions = uiOptions;
         this.targetActivity = targetActivity;
@@ -438,6 +446,7 @@
         this.colorMode = colorMode;
         this.windowLayout = windowLayout;
         this.mRequiredDisplayCategory = requiredDisplayCategory;
+        this.mRequireContentUriPermissionFromCaller = requireContentUriPermissionFromCaller;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -563,6 +572,11 @@
     }
 
     @DataClass.Generated.Member
+    public int getRequireContentUriPermissionFromCaller() {
+        return mRequireContentUriPermissionFromCaller;
+    }
+
+    @DataClass.Generated.Member
     public @NonNull ParsedActivityImpl setTheme( int value) {
         theme = value;
         return this;
@@ -694,11 +708,17 @@
         return this;
     }
 
+    @DataClass.Generated.Member
+    public @NonNull ParsedActivityImpl setRequireContentUriPermissionFromCaller( int value) {
+        mRequireContentUriPermissionFromCaller = value;
+        return this;
+    }
+
     @DataClass.Generated(
-            time = 1701338377709L,
+            time = 1706180262165L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java",
-            inputSignatures = "private  int theme\nprivate  int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate  int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate  int launchMode\nprivate  int documentLaunchMode\nprivate  int maxRecents\nprivate  int configChanges\nprivate  int softInputMode\nprivate  int persistableMode\nprivate  int lockTaskLaunchMode\nprivate  int screenOrientation\nprivate  int resizeMode\nprivate  float maxAspectRatio\nprivate  float minAspectRatio\nprivate  boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate  int rotationAnimation\nprivate  int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mRequiredDisplayCategory\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedActivityImpl> CREATOR\npublic static @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.internal.pm.pkg.component.ParsedActivity)\npublic  com.android.internal.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic  com.android.internal.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic  com.android.internal.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic  com.android.internal.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic  void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.internal.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+            inputSignatures = "private  int theme\nprivate  int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate  int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate  int launchMode\nprivate  int documentLaunchMode\nprivate  int maxRecents\nprivate  int configChanges\nprivate  int softInputMode\nprivate  int persistableMode\nprivate  int lockTaskLaunchMode\nprivate  int screenOrientation\nprivate  int resizeMode\nprivate  float maxAspectRatio\nprivate  float minAspectRatio\nprivate  boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate  int rotationAnimation\nprivate  int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mRequiredDisplayCategory\nprivate  int mRequireContentUriPermissionFromCaller\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedActivityImpl> CREATOR\npublic static @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.internal.pm.pkg.component.ParsedActivity)\npublic  com.android.internal.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic  com.android.internal.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic  com.android.internal.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic  com.android.internal.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic  void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.internal.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
index c3f7dab..9f71d88 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
@@ -241,6 +241,10 @@
 
             activity.setRequiredDisplayCategory(requiredDisplayCategory);
 
+            activity.setRequireContentUriPermissionFromCaller(sa.getInt(
+                    R.styleable.AndroidManifestActivity_requireContentUriPermissionFromCaller,
+                    ActivityInfo.CONTENT_URI_PERMISSION_NONE));
+
             return parseActivityOrAlias(activity, pkg, tag, parser, res, sa, receiver,
                     false /*isAlias*/, visibleToEphemeral, input,
                     R.styleable.AndroidManifestActivity_parentActivityName,
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/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
index 3ab6c47..a7260bb 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -289,6 +289,7 @@
         try {
             byte[] bytes = readAllBytes(fd);
             buffer.reset();
+            buffer.mBuffer.resize(bytes.length);
             System.arraycopy(bytes, 0, buffer.mBuffer.mBuffer, 0, bytes.length);
             buffer.mBuffer.mSize = bytes.length;
         } catch (Exception e) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
index 3e701c1..4518d94 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
@@ -37,7 +37,7 @@
         this(BUFFER_SIZE);
     }
 
-    private void resize(int need) {
+    public void resize(int need) {
         if (mSize + need >= mMaxSize) {
             mMaxSize = Math.max(mMaxSize * 2, mSize + need);
             mBuffer = Arrays.copyOf(mBuffer, mMaxSize);
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 3fc1683..240028c 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -98,6 +98,7 @@
         "libminikin",
         "libz",
         "server_configurable_flags",
+        "android.media.audiopolicy-aconfig-cc",
     ],
 
     static_libs: [
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 969e47b..070d07c 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -22,6 +22,7 @@
 #include <android/media/INativeSpatializerCallback.h>
 #include <android/media/ISpatializer.h>
 #include <android/media/audio/common/AudioConfigBase.h>
+#include <android_media_audiopolicy.h>
 #include <android_os_Parcel.h>
 #include <audiomanager/AudioManager.h>
 #include <jni.h>
@@ -55,6 +56,8 @@
 
 // ----------------------------------------------------------------------------
 
+namespace audio_flags = android::media::audiopolicy;
+
 using namespace android;
 using media::audio::common::AudioConfigBase;
 
@@ -145,6 +148,7 @@
 } gAudioPatchFields;
 
 static jclass gAudioMixClass;
+static jmethodID gAudioMixCstor;
 static struct {
     jfieldID    mRule;
     jfieldID    mFormat;
@@ -165,7 +169,15 @@
     // other fields unused by JNI
 } gAudioFormatFields;
 
+static jclass gAudioAttributesClass;
+static jmethodID gAudioAttributesCstor;
+static struct {
+    jfieldID mSource;
+    jfieldID mUsage;
+} gAudioAttributesFields;
+
 static jclass gAudioMixingRuleClass;
+static jmethodID gAudioMixingRuleCstor;
 static struct {
     jfieldID    mCriteria;
     jfieldID    mAllowPrivilegedPlaybackCapture;
@@ -174,6 +186,8 @@
 } gAudioMixingRuleFields;
 
 static jclass gAudioMixMatchCriterionClass;
+static jmethodID gAudioMixMatchCriterionAttrCstor;
+static jmethodID gAudioMixMatchCriterionIntPropCstor;
 static struct {
     jfieldID    mAttr;
     jfieldID    mIntProp;
@@ -2087,6 +2101,39 @@
                           channelMask, channelIndexMask);
 }
 
+jint nativeAudioConfigToJavaAudioFormat(JNIEnv *env, const audio_config_t *nConfigBase,
+                                        jobject *jAudioFormat, bool isInput) {
+    if (!audio_flags::audio_mix_test_api()) {
+        return AUDIO_JAVA_INVALID_OPERATION;
+    }
+
+    if (nConfigBase == nullptr) {
+        return AUDIO_JAVA_BAD_VALUE;
+    }
+    int propertyMask = AUDIO_FORMAT_HAS_PROPERTY_ENCODING | AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE;
+    int channelMask = 0;
+    int channelIndexMask = 0;
+    switch (audio_channel_mask_get_representation(nConfigBase->channel_mask)) {
+        case AUDIO_CHANNEL_REPRESENTATION_POSITION:
+            channelMask = isInput ? inChannelMaskFromNative(nConfigBase->channel_mask)
+                                  : outChannelMaskFromNative(nConfigBase->channel_mask);
+            propertyMask |= AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK;
+            break;
+        case AUDIO_CHANNEL_REPRESENTATION_INDEX:
+            channelIndexMask = audio_channel_mask_get_bits(nConfigBase->channel_mask);
+            propertyMask |= AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK;
+            break;
+        default:
+            // This must not happen
+            break;
+    }
+
+    *jAudioFormat = env->NewObject(gAudioFormatClass, gAudioFormatCstor, propertyMask,
+                                   audioFormatFromNative(nConfigBase->format),
+                                   nConfigBase->sample_rate, channelMask, channelIndexMask);
+    return AUDIO_JAVA_SUCCESS;
+}
+
 jint convertAudioMixerAttributesToNative(JNIEnv *env, const jobject jAudioMixerAttributes,
                                          audio_mixer_attributes_t *nMixerAttributes) {
     ScopedLocalRef<jobject> jFormat(env,
@@ -2179,6 +2226,88 @@
     return AUDIO_JAVA_SUCCESS;
 }
 
+static jint nativeAudioMixToJavaAudioMixingRule(JNIEnv *env, const AudioMix &nAudioMix,
+                                                jobject *jAudioMixingRule) {
+    if (!audio_flags::audio_mix_test_api()) {
+        return AUDIO_JAVA_INVALID_OPERATION;
+    }
+
+    jobject jAudioMixMatchCriterionList = env->NewObject(gArrayListClass, gArrayListMethods.cstor);
+    for (const auto &criteria : nAudioMix.mCriteria) {
+        jobject jAudioAttributes = NULL;
+        jobject jMixMatchCriterion = NULL;
+        jobject jValueInteger = NULL;
+        switch (criteria.mRule) {
+            case RULE_MATCH_UID:
+                jValueInteger = env->NewObject(gIntegerClass, gIntegerCstor, criteria.mValue.mUid);
+                jMixMatchCriterion = env->NewObject(gAudioMixMatchCriterionClass,
+                                                    gAudioMixMatchCriterionIntPropCstor,
+                                                    jValueInteger, criteria.mRule);
+                break;
+            case RULE_MATCH_USERID:
+                jValueInteger =
+                        env->NewObject(gIntegerClass, gIntegerCstor, criteria.mValue.mUserId);
+                jMixMatchCriterion = env->NewObject(gAudioMixMatchCriterionClass,
+                                                    gAudioMixMatchCriterionIntPropCstor,
+                                                    jValueInteger, criteria.mRule);
+                break;
+            case RULE_MATCH_AUDIO_SESSION_ID:
+                jValueInteger = env->NewObject(gIntegerClass, gIntegerCstor,
+                                               criteria.mValue.mAudioSessionId);
+                jMixMatchCriterion = env->NewObject(gAudioMixMatchCriterionClass,
+                                                    gAudioMixMatchCriterionIntPropCstor,
+                                                    jValueInteger, criteria.mRule);
+                break;
+            case RULE_MATCH_ATTRIBUTE_USAGE:
+                jAudioAttributes = env->NewObject(gAudioAttributesClass, gAudioAttributesCstor);
+                env->SetIntField(jAudioAttributes, gAudioAttributesFields.mUsage,
+                                 criteria.mValue.mUsage);
+                jMixMatchCriterion = env->NewObject(gAudioMixMatchCriterionClass,
+                                                    gAudioMixMatchCriterionAttrCstor,
+                                                    jMixMatchCriterion, criteria.mRule);
+                break;
+            case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
+                jAudioAttributes = env->NewObject(gAudioAttributesClass, gAudioAttributesCstor);
+                env->SetIntField(jAudioAttributes, gAudioAttributesFields.mSource,
+                                 criteria.mValue.mSource);
+                jMixMatchCriterion = env->NewObject(gAudioMixMatchCriterionClass,
+                                                    gAudioMixMatchCriterionAttrCstor,
+                                                    jMixMatchCriterion, criteria.mRule);
+                break;
+        }
+        env->CallBooleanMethod(jAudioMixMatchCriterionList, gArrayListMethods.add,
+                               jMixMatchCriterion);
+    }
+
+    *jAudioMixingRule = env->NewObject(gAudioMixingRuleClass, gAudioMixingRuleCstor,
+                                       nAudioMix.mMixType, jAudioMixMatchCriterionList,
+                                       nAudioMix.mAllowPrivilegedMediaPlaybackCapture,
+                                       nAudioMix.mVoiceCommunicationCaptureAllowed);
+    return AUDIO_JAVA_SUCCESS;
+}
+
+static jint convertAudioMixFromNative(JNIEnv *env, jobject *jAudioMix, const AudioMix &nAudioMix) {
+    if (!audio_flags::audio_mix_test_api()) {
+        return AUDIO_JAVA_INVALID_OPERATION;
+    }
+    jobject jAudioMixingRule = NULL;
+    int status = nativeAudioMixToJavaAudioMixingRule(env, nAudioMix, &jAudioMixingRule);
+    if (status != AUDIO_JAVA_SUCCESS) {
+        return status;
+    }
+    jobject jAudioFormat = NULL;
+    status = nativeAudioConfigToJavaAudioFormat(env, &nAudioMix.mFormat, &jAudioFormat, false);
+    if (status != AUDIO_JAVA_SUCCESS) {
+        return status;
+    }
+
+    jstring deviceAddress = env->NewStringUTF(nAudioMix.mDeviceAddress.c_str());
+    *jAudioMix = env->NewObject(gAudioMixClass, gAudioMixCstor, jAudioMixingRule, jAudioFormat,
+                                nAudioMix.mRouteFlags, nAudioMix.mCbFlags, nAudioMix.mDeviceType,
+                                deviceAddress);
+    return AUDIO_JAVA_SUCCESS;
+}
+
 static jint convertAudioMixToNative(JNIEnv *env, AudioMix *nAudioMix, const jobject jAudioMix) {
     nAudioMix->mMixType = env->GetIntField(jAudioMix, gAudioMixFields.mMixType);
     nAudioMix->mRouteFlags = env->GetIntField(jAudioMix, gAudioMixFields.mRouteFlags);
@@ -2252,6 +2381,34 @@
     return nativeToJavaStatus(status);
 }
 
+static jint android_media_AudioSystem_getRegisteredPolicyMixes(JNIEnv *env, jobject clazz,
+                                                               jobject jMixes) {
+    if (!audio_flags::audio_mix_test_api()) {
+        return AUDIO_JAVA_INVALID_OPERATION;
+    }
+
+    status_t status;
+    std::vector<AudioMix> mixes;
+    ALOGV("AudioSystem::getRegisteredPolicyMixes");
+    status = AudioSystem::getRegisteredPolicyMixes(mixes);
+    ALOGV("AudioSystem::getRegisteredPolicyMixes() returned %zu mixes. Status=%d", mixes.size(),
+          status);
+    if (status != NO_ERROR) {
+        return nativeToJavaStatus(status);
+    }
+
+    for (const auto &mix : mixes) {
+        jobject jAudioMix = NULL;
+        int conversionStatus = convertAudioMixFromNative(env, &jAudioMix, mix);
+        if (conversionStatus != AUDIO_JAVA_SUCCESS) {
+            return conversionStatus;
+        }
+        env->CallBooleanMethod(jMixes, gListMethods.add, jAudioMix);
+    }
+
+    return AUDIO_JAVA_SUCCESS;
+}
+
 static jint android_media_AudioSystem_updatePolicyMixes(JNIEnv *env, jobject clazz,
                                                         jobjectArray mixes,
                                                         jobjectArray updatedMixingRules) {
@@ -3251,6 +3408,8 @@
          MAKE_AUDIO_SYSTEM_METHOD(getAudioHwSyncForSession),
          MAKE_JNI_NATIVE_METHOD("registerPolicyMixes", "(Ljava/util/ArrayList;Z)I",
                                 android_media_AudioSystem_registerPolicyMixes),
+         MAKE_JNI_NATIVE_METHOD("getRegisteredPolicyMixes", "(Ljava/util/List;)I",
+                                android_media_AudioSystem_getRegisteredPolicyMixes),
          MAKE_JNI_NATIVE_METHOD("updatePolicyMixes",
                                 "([Landroid/media/audiopolicy/AudioMix;[Landroid/media/audiopolicy/"
                                 "AudioMixingRule;)I",
@@ -3499,6 +3658,11 @@
 
     jclass audioMixClass = FindClassOrDie(env, "android/media/audiopolicy/AudioMix");
     gAudioMixClass = MakeGlobalRefOrDie(env, audioMixClass);
+    if (audio_flags::audio_mix_test_api()) {
+        gAudioMixCstor = GetMethodIDOrDie(env, audioMixClass, "<init>",
+                                          "(Landroid/media/audiopolicy/AudioMixingRule;Landroid/"
+                                          "media/AudioFormat;IIILjava/lang/String;)V");
+    }
     gAudioMixFields.mRule = GetFieldIDOrDie(env, audioMixClass, "mRule",
                                                 "Landroid/media/audiopolicy/AudioMixingRule;");
     gAudioMixFields.mFormat = GetFieldIDOrDie(env, audioMixClass, "mFormat",
@@ -3521,6 +3685,10 @@
 
     jclass audioMixingRuleClass = FindClassOrDie(env, "android/media/audiopolicy/AudioMixingRule");
     gAudioMixingRuleClass = MakeGlobalRefOrDie(env, audioMixingRuleClass);
+    if (audio_flags::audio_mix_test_api()) {
+        gAudioMixingRuleCstor = GetMethodIDOrDie(env, audioMixingRuleClass, "<init>",
+                                                 "(ILjava/util/Collection;ZZ)V");
+    }
     gAudioMixingRuleFields.mCriteria = GetFieldIDOrDie(env, audioMixingRuleClass, "mCriteria",
                                                        "Ljava/util/ArrayList;");
     gAudioMixingRuleFields.mAllowPrivilegedPlaybackCapture =
@@ -3529,9 +3697,24 @@
     gAudioMixingRuleFields.mVoiceCommunicationCaptureAllowed =
             GetFieldIDOrDie(env, audioMixingRuleClass, "mVoiceCommunicationCaptureAllowed", "Z");
 
+    if (audio_flags::audio_mix_test_api()) {
+        jclass audioAttributesClass = FindClassOrDie(env, "android/media/AudioAttributes");
+        gAudioAttributesClass = MakeGlobalRefOrDie(env, audioAttributesClass);
+        gAudioAttributesCstor = GetMethodIDOrDie(env, gAudioAttributesClass, "<init>", "()V");
+        gAudioAttributesFields.mSource = GetFieldIDOrDie(env, gAudioAttributesClass, "mUsage", "I");
+        gAudioAttributesFields.mUsage = GetFieldIDOrDie(env, gAudioAttributesClass, "mSource", "I");
+    }
+
     jclass audioMixMatchCriterionClass =
                 FindClassOrDie(env, "android/media/audiopolicy/AudioMixingRule$AudioMixMatchCriterion");
     gAudioMixMatchCriterionClass = MakeGlobalRefOrDie(env,audioMixMatchCriterionClass);
+    if (audio_flags::audio_mix_test_api()) {
+        gAudioMixMatchCriterionAttrCstor =
+                GetMethodIDOrDie(env, gAudioMixMatchCriterionClass, "<init>",
+                                 "(Landroid/media/AudioAttributes;I)V");
+        gAudioMixMatchCriterionIntPropCstor = GetMethodIDOrDie(env, gAudioMixMatchCriterionClass,
+                                                               "<init>", "(Ljava/lang/Integer;I)V");
+    }
     gAudioMixMatchCriterionFields.mAttr = GetFieldIDOrDie(env, audioMixMatchCriterionClass, "mAttr",
                                                        "Landroid/media/AudioAttributes;");
     gAudioMixMatchCriterionFields.mIntProp = GetFieldIDOrDie(env, audioMixMatchCriterionClass, "mIntProp",
diff --git a/core/jni/android_view_PointerIcon.cpp b/core/jni/android_view_PointerIcon.cpp
index c6a3b52..86b0009 100644
--- a/core/jni/android_view_PointerIcon.cpp
+++ b/core/jni/android_view_PointerIcon.cpp
@@ -37,6 +37,7 @@
     jfieldID mHotSpotY;
     jfieldID mBitmapFrames;
     jfieldID mDurationPerFrame;
+    jfieldID mDrawNativeDropShadow;
 } gPointerIconClassInfo;
 
 
@@ -51,6 +52,8 @@
             env->GetIntField(pointerIconObj, gPointerIconClassInfo.mType));
     icon.hotSpotX = env->GetFloatField(pointerIconObj, gPointerIconClassInfo.mHotSpotX);
     icon.hotSpotY = env->GetFloatField(pointerIconObj, gPointerIconClassInfo.mHotSpotY);
+    icon.drawNativeDropShadow =
+            env->GetBooleanField(pointerIconObj, gPointerIconClassInfo.mDrawNativeDropShadow);
 
     ScopedLocalRef<jobject> bitmapObj(
             env, env->GetObjectField(pointerIconObj, gPointerIconClassInfo.mBitmap));
@@ -95,6 +98,9 @@
     gPointerIconClassInfo.mBitmapFrames = GetFieldIDOrDie(env, gPointerIconClassInfo.clazz,
             "mBitmapFrames", "[Landroid/graphics/Bitmap;");
 
+    gPointerIconClassInfo.mDrawNativeDropShadow =
+            GetFieldIDOrDie(env, gPointerIconClassInfo.clazz, "mDrawNativeDropShadow", "Z");
+
     gPointerIconClassInfo.mDurationPerFrame = GetFieldIDOrDie(env, gPointerIconClassInfo.clazz,
             "mDurationPerFrame", "I");
 
diff --git a/core/jni/android_view_PointerIcon.h b/core/jni/android_view_PointerIcon.h
index ee446fb..1b6a397 100644
--- a/core/jni/android_view_PointerIcon.h
+++ b/core/jni/android_view_PointerIcon.h
@@ -39,6 +39,7 @@
     float hotSpotY;
     std::vector<graphics::Bitmap> bitmapFrames;
     int32_t durationPerFrame;
+    bool drawNativeDropShadow;
 
     inline bool isNullIcon() { return style == PointerIconStyle::TYPE_NULL; }
 
@@ -49,6 +50,7 @@
         hotSpotY = 0;
         bitmapFrames.clear();
         durationPerFrame = 0;
+        drawNativeDropShadow = false;
     }
 };
 
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 58166bf..0938ce17 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -2416,6 +2416,11 @@
                          const std::vector<int>& fds_to_ignore,
                          bool is_priority_fork,
                          bool purge) {
+  ATRACE_CALL();
+  if (is_priority_fork) {
+    setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MAX);
+  }
+
   SetSignalHandlers();
 
   // Curry a failure function.
@@ -2501,6 +2506,10 @@
   // We blocked SIGCHLD prior to a fork, we unblock it here.
   UnblockSignal(SIGCHLD, fail_fn);
 
+  if (is_priority_fork && pid != 0) {
+    setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_DEFAULT);
+  }
+
   return pid;
 }
 
@@ -2570,6 +2579,7 @@
         JNIEnv* env, jclass, uid_t uid, gid_t gid, jintArray gids,
         jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities,
         jlong effective_capabilities) {
+  ATRACE_CALL();
   std::vector<int> fds_to_close(MakeUsapPipeReadFDVector()),
                    fds_to_ignore(fds_to_close);
 
@@ -2645,6 +2655,7 @@
                                                          jintArray managed_session_socket_fds,
                                                          jboolean args_known,
                                                          jboolean is_priority_fork) {
+  ATRACE_CALL();
   std::vector<int> session_socket_fds =
       ExtractJIntArray(env, "USAP", nullptr, managed_session_socket_fds)
           .value_or(std::vector<int>());
@@ -2660,6 +2671,7 @@
                     bool args_known,
                     bool is_priority_fork,
                     bool purge) {
+  ATRACE_CALL();
 
   std::vector<int> fds_to_close(MakeUsapPipeReadFDVector()),
                    fds_to_ignore(fds_to_close);
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..a425bb0 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2772,6 +2772,12 @@
     <permission android:name="android.permission.OEM_UNLOCK_STATE"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows configuration of factory reset protection
+         @FlaggedApi("android.security.frp_enforcement")
+         @hide <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.CONFIGURE_FACTORY_RESET_PROTECTION"
+        android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi @hide Allows querying state of PersistentDataBlock
    <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.ACCESS_PDB_STATE"
@@ -3216,6 +3222,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" />
@@ -7555,6 +7568,11 @@
     <permission android:name="android.permission.RESET_APP_ERRORS"
         android:protectionLevel="signature" />
 
+    <!-- @hide Allows ThemeOverlayController to delay launch of Home / SetupWizard on boot, ensuring
+         Theme Palettes and Colors are ready  -->
+    <permission android:name="android.permission.SET_THEME_OVERLAY_CONTROLLER_READY"
+        android:protectionLevel="signature|setup" />
+
     <!-- @hide Allows an application to create/destroy input consumer. -->
     <permission android:name="android.permission.INPUT_CONSUMER"
                 android:protectionLevel="signature" />
diff --git a/core/res/res/drawable/pointer_alias_vector.xml b/core/res/res/drawable/pointer_alias_vector.xml
new file mode 100644
index 0000000..74dd6a0
--- /dev/null
+++ b/core/res/res/drawable/pointer_alias_vector.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:fillColor="#00FFFFFF"
+        android:pathData="M14.494 12.779a5.2 5.2 0 0 1-1.771 1.414 5.2 5.2 0 0 1-1.68.489l.81 1.658a1.968 1.968 0 0 0 3.536-1.728zM12.03 8.291l-.81-1.658a1.968 1.968 0 0 0-3.536 1.728l.896 1.833a5.2 5.2 0 0 1 1.77-1.414 5.2 5.2 0 0 1 1.68-.489" />
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="m18.323 13.178-.975-1.995a5.2 5.2 0 0 0-1.704-1.978 5.2 5.2 0 0 0-.517-2.01L14.152 5.2a5.232 5.232 0 0 0-9.401 4.594l.975 1.995a5.2 5.2 0 0 0 1.704 1.978 5.2 5.2 0 0 0 .517 2.01l.975 1.995a5.233 5.233 0 0 0 9.401-4.594m-2.843 6.1a4.23 4.23 0 0 1-5.66-1.944l-.975-1.995a4.2 4.2 0 0 1-.431-1.838l-.001-.276-.234-.146a4.2 4.2 0 0 1-1.555-1.729L5.65 9.355a4.232 4.232 0 1 1 7.604-3.716l.975 1.995c.29.594.428 1.22.431 1.838l.001.276.234.146c.648.405 1.194.99 1.555 1.728l.975 1.995a4.234 4.234 0 0 1-1.945 5.661" />
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="M15.313 12.177a3 3 0 0 0-.416-.633l-.459-.534-.353.609a4.2 4.2 0 0 1-1.801 1.675 4.2 4.2 0 0 1-1.977.429l-.704-.02.213.671q.066.208.164.409l.975 1.995a2.967 2.967 0 1 0 5.332-2.606zm-.827 5.066a1.97 1.97 0 0 1-2.632-.904l-.81-1.658a5.2 5.2 0 0 0 1.68-.489 5.2 5.2 0 0 0 1.771-1.414l.896 1.833a1.97 1.97 0 0 1-.905 2.632m-3.697-7.565a4.2 4.2 0 0 1 1.977-.429l.704.02-.213-.671a3 3 0 0 0-.164-.409l-.975-1.995A2.967 2.967 0 1 0 6.785 8.8l.975 1.995q.172.35.416.633l.459.534.353-.609a4.2 4.2 0 0 1 1.801-1.675m-2.21.516-.895-1.833a1.968 1.968 0 1 1 3.536-1.728l.81 1.658a5.2 5.2 0 0 0-1.68.489 5.2 5.2 0 0 0-1.771 1.414m3.151 1.965a3 3 0 0 0 1.02-.818l.755-.95-1.205.142a2.97 2.97 0 0 0-1.975 1.1l-.755.95 1.205-.142c.324-.039.646-.132.955-.282" />
+    <path
+        android:fillColor="#000000"
+        android:pathData="M16.449 11.622a4.2 4.2 0 0 0-1.555-1.728l-.234-.146-.001-.276a4.2 4.2 0 0 0-.431-1.838l-.975-1.995a4.232 4.232 0 1 0-7.604 3.716l.975 1.995a4.2 4.2 0 0 0 1.555 1.729l.234.146.001.276c.002.617.141 1.244.431 1.838l.975 1.995a4.232 4.232 0 1 0 7.604-3.716zm-7.814.34-.459-.534a3 3 0 0 1-.416-.633L6.785 8.8a2.967 2.967 0 1 1 5.332-2.606l.975 1.995q.098.202.164.409l.214.672-.704-.02a4.2 4.2 0 0 0-1.977.429 4.2 4.2 0 0 0-1.801 1.675zm1.689-.33a2.97 2.97 0 0 1 1.975-1.1l1.205-.142-.755.95a2.95 2.95 0 0 1-1.02.818 3 3 0 0 1-.955.281l-1.204.143zm4.601 6.51a2.967 2.967 0 0 1-3.969-1.363l-.975-1.995a3 3 0 0 1-.164-.409l-.213-.671.704.02a4.2 4.2 0 0 0 1.977-.429 4.2 4.2 0 0 0 1.801-1.675l.353-.609.459.534q.245.284.416.633l.975 1.995a2.97 2.97 0 0 1-1.364 3.969" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_alias_vector_icon.xml b/core/res/res/drawable/pointer_alias_vector_icon.xml
new file mode 100644
index 0000000..6057a2e
--- /dev/null
+++ b/core/res/res/drawable/pointer_alias_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_alias_vector"
+    android:hotSpotX="11.5dp"
+    android:hotSpotY="11.5dp" />
diff --git a/core/res/res/drawable/pointer_all_scroll_vector.xml b/core/res/res/drawable/pointer_all_scroll_vector.xml
new file mode 100644
index 0000000..1692e5e
--- /dev/null
+++ b/core/res/res/drawable/pointer_all_scroll_vector.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:pathData="M12.93 4.54a1.06 1.06 0 0 0-1.85 0L9.32 7.6c-.4.71.1 1.6.92 1.6h.82v1.86H9.2v-.84c0-.82-.88-1.33-1.6-.93l-3.06 1.76c-.7.41-.7 1.44 0 1.85l3.07 1.76c.7.4 1.6-.1 1.6-.93v-.79h1.86v1.87h-.82c-.81 0-1.33.88-.92 1.6l1.76 3.06c.4.71 1.44.71 1.85 0l1.75-3.07c.41-.7-.1-1.6-.92-1.6h-.82v-1.86h1.86v.8c0 .81.89 1.32 1.6.92l3.07-1.76c.7-.41.7-1.44 0-1.85L16.4 9.3c-.71-.4-1.6.1-1.6.93v.84h-1.86V9.2h.82c.82 0 1.33-.89.92-1.6l-1.75-3.06z"
+        android:fillColor="#000000"/>
+    <path
+        android:pathData="M12 4c.36 0 .72.18.93.54l1.75 3.06c.41.71-.1 1.6-.92 1.6h-.82v1.86h1.86v-.84a1.07 1.07 0 0 1 1.6-.92l3.06 1.75c.72.41.72 1.44 0 1.85l-3.06 1.76a1.07 1.07 0 0 1-1.6-.92v-.8h-1.86v1.87h.82c.82 0 1.33.88.92 1.6l-1.75 3.06a1.07 1.07 0 0 1-1.85 0L9.32 16.4c-.4-.7.1-1.6.93-1.6h.81v-1.86H9.2v.8a1.07 1.07 0 0 1-1.6.92L4.54 12.9a1.06 1.06 0 0 1 0-1.85L7.6 9.3a1.07 1.07 0 0 1 1.6.92v.85h1.86V9.2h-.82c-.81 0-1.33-.89-.92-1.6l1.76-3.06c.2-.36.56-.54.92-.54m0-1c-.74 0-1.41.39-1.79 1.04L8.45 7.1c-.18.33-.28.7-.27 1.05h-.05c-.36 0-.71.1-1.03.28l-3.06 1.76a2.05 2.05 0 0 0 0 3.58l3.06 1.75c.32.18.67.28 1.03.28h.05c-.01.38.08.76.28 1.1l1.75 3.07c.38.65 1.05 1.03 1.8 1.03s1.41-.38 1.78-1.03l1.76-3.07c.2-.34.3-.72.28-1.1h.04c.36 0 .71-.1 1.03-.28l3.06-1.75a2.07 2.07 0 0 0 0-3.58L16.9 8.43a2.07 2.07 0 0 0-1.03-.28h-.04c0-.36-.09-.72-.28-1.05L13.8 4.04A2.04 2.04 0 0 0 12 3z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_all_scroll_vector_icon.xml b/core/res/res/drawable/pointer_all_scroll_vector_icon.xml
new file mode 100644
index 0000000..d64b99a
--- /dev/null
+++ b/core/res/res/drawable/pointer_all_scroll_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_all_scroll_vector"
+    android:hotSpotX="12dp"
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_arrow_vector.xml b/core/res/res/drawable/pointer_arrow_vector.xml
new file mode 100644
index 0000000..562f0c0
--- /dev/null
+++ b/core/res/res/drawable/pointer_arrow_vector.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:pathData="M16.34 11.18 6.77 4.02a1.78 1.78 0 0 0-1.88-.17c-.63.31-1 .91-1 1.6l.01 11.96c0 .9.6 1.46 1.15 1.67a1.74 1.74 0 0 0 1.98-.45l2.96-3.19c.3-.32.7-.52 1.13-.56l4.33-.47a1.8 1.8 0 0 0 .89-3.23z"
+        android:fillColor="#000000"/>
+    <path
+        android:pathData="M16.94 10.38 7.37 3.22a2.77 2.77 0 0 0-2.93-.27 2.75 2.75 0 0 0-1.55 2.51l.01 11.95a2.78 2.78 0 0 0 2.82 2.8c.77 0 1.5-.32 2.03-.9l2.97-3.19a.8.8 0 0 1 .5-.25l4.34-.46a2.76 2.76 0 0 0 2.4-2.05 2.8 2.8 0 0 0-1.02-2.98zM17 13.1a1.77 1.77 0 0 1-1.55 1.31l-4.33.47a1.8 1.8 0 0 0-1.13.56l-2.97 3.2c-.4.42-.86.57-1.3.57-.24 0-.48-.05-.68-.13a1.77 1.77 0 0 1-1.14-1.67V5.46a1.81 1.81 0 0 1 1.8-1.8c.38 0 .75.11 1.07.36l9.57 7.16c.72.54.81 1.35.66 1.92z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_arrow_vector_icon.xml b/core/res/res/drawable/pointer_arrow_vector_icon.xml
new file mode 100644
index 0000000..b7a8992
--- /dev/null
+++ b/core/res/res/drawable/pointer_arrow_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_arrow_vector"
+    android:hotSpotX="4.5dp"
+    android:hotSpotY="3.5dp" />
diff --git a/core/res/res/drawable/pointer_cell_vector.xml b/core/res/res/drawable/pointer_cell_vector.xml
new file mode 100644
index 0000000..044a4f4
--- /dev/null
+++ b/core/res/res/drawable/pointer_cell_vector.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="M19 9.667h-4.668V5a2 2 0 0 0-2-2h-.667a2 2 0 0 0-2 2v4.667H5a2 2 0 0 0-2 2v.667a2 2 0 0 0 2 2h4.665V19a2 2 0 0 0 2 2h.667a2 2 0 0 0 2-2v-4.666H19a2 2 0 0 0 2-2v-.667a2 2 0 0 0-2-2m1 2.667a1 1 0 0 1-1 1h-5.668V19a1 1 0 0 1-1 1h-.667a1 1 0 0 1-1-1v-5.666H5a1 1 0 0 1-1-1v-.667a1 1 0 0 1 1-1h5.665V5a1 1 0 0 1 1-1h.667a1 1 0 0 1 1 1v5.667H19a1 1 0 0 1 1 1z" />
+    <path
+        android:fillColor="#000000"
+        android:pathData="M19 10.667h-5.668V5a1 1 0 0 0-1-1h-.667a1 1 0 0 0-1 1v5.667H5a1 1 0 0 0-1 1v.667a1 1 0 0 0 1 1h5.665V19a1 1 0 0 0 1 1h.667a1 1 0 0 0 1-1v-5.666H19a1 1 0 0 0 1-1v-.667a1 1 0 0 0-1-1" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_cell_vector_icon.xml b/core/res/res/drawable/pointer_cell_vector_icon.xml
new file mode 100644
index 0000000..9e0f632
--- /dev/null
+++ b/core/res/res/drawable/pointer_cell_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_cell_vector"
+    android:hotSpotX="12dp"
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_context_menu_vector.xml b/core/res/res/drawable/pointer_context_menu_vector.xml
new file mode 100644
index 0000000..8e954d2
--- /dev/null
+++ b/core/res/res/drawable/pointer_context_menu_vector.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <group>
+        <path android:fillColor="#FFFFFF" android:pathData="M19.475 2.604h-2.66c-.842 0-1.527.685-1.527 1.527v2.66c0 .842.685 1.527 1.527 1.527h2.66c.842 0 1.527-.685 1.527-1.527v-2.66c0-.842-.685-1.527-1.527-1.527m.67 4.187c0 .37-.3.67-.67.67h-2.66a.67.67 0 0 1-.67-.67v-2.66c0-.37.3-.67.67-.67h2.66c.37 0 .67.3.67.67z" />
+        <path android:fillColor="#FFFFFF" android:pathData="M19.175 4.17h-2.067a.3.3 0 1 0 0 .6h2.067a.3.3 0 1 0 0-.6m0 .886h-2.067a.3.3 0 1 0 0 .6h2.067a.3.3 0 1 0 0-.6m0 .868h-2.067a.3.3 0 1 0 0 .6h2.067a.3.3 0 1 0 0-.6" />
+    </group>
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="M16.938 10.38 7.372 3.216a2.77 2.77 0 0 0-2.931-.262A2.75 2.75 0 0 0 2.894 5.46l.009 11.951a2.785 2.785 0 0 0 1.776 2.604c.33.129.691.197 1.044.197a2.75 2.75 0 0 0 2.031-.897l2.969-3.193a.8.8 0 0 1 .5-.25l4.336-.467c1.397-.15 2.157-1.153 2.401-2.041a2.785 2.785 0 0 0-1.022-2.984m.058 2.718c-.157.571-.645 1.216-1.544 1.312l-4.335.467a1.8 1.8 0 0 0-1.126.563l-2.97 3.193a1.74 1.74 0 0 1-1.298.578 1.9 1.9 0 0 1-.678-.128c-.551-.217-1.141-.771-1.142-1.674l-.009-11.95c0-.697.371-1.299.994-1.611.262-.131.538-.196.813-.196.377 0 .75.123 1.072.365l9.566 7.163c.723.542.814 1.346.657 1.918" />
+    <path
+        android:fillColor="#000000"
+        android:pathData="M16.339 11.18 6.773 4.017a1.78 1.78 0 0 0-1.072-.365c-.274 0-.551.065-.813.196a1.77 1.77 0 0 0-.994 1.611l.009 11.951c0 .903.59 1.457 1.142 1.674.2.078.433.128.678.128.434 0 .906-.155 1.298-.578l2.97-3.193a1.8 1.8 0 0 1 1.126-.563l4.335-.467c.899-.097 1.387-.741 1.544-1.312.157-.573.066-1.377-.657-1.919" />
+    <path
+        android:fillColor="#000000"
+        android:pathData="M19.475 3.461h-2.66c-.37 0-.67.3-.67.67v2.66c0 .37.3.67.67.67h2.66c.37 0 .67-.3.67-.67v-2.66a.67.67 0 0 0-.67-.67m-.3 3.062h-2.067a.3.3 0 1 1 0-.6h2.067a.3.3 0 1 1 0 .6m0-.868h-2.067a.3.3 0 1 1 0-.6h2.067a.3.3 0 1 1 0 .6m0-.885h-2.067a.3.3 0 1 1 0-.6h2.067a.3.3 0 1 1 0 .6" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_context_menu_vector_icon.xml b/core/res/res/drawable/pointer_context_menu_vector_icon.xml
new file mode 100644
index 0000000..90f9043
--- /dev/null
+++ b/core/res/res/drawable/pointer_context_menu_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_context_menu_vector"
+    android:hotSpotX="4.5dp"
+    android:hotSpotY="3.5dp" />
diff --git a/core/res/res/drawable/pointer_copy_vector.xml b/core/res/res/drawable/pointer_copy_vector.xml
new file mode 100644
index 0000000..b1e8995
--- /dev/null
+++ b/core/res/res/drawable/pointer_copy_vector.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <group>
+        <path android:fillColor="#FFFFFF" android:pathData="M17.5 2c-2.104 0-3.861 1.457-4.351 3.41A4.5 4.5 0 0 0 13 6.5c0 .344.047.675.12.997-.062-.002-.122-.009-.185-.009q-.225 0-.446.018V6.484a1 1 0 0 0-2 0v1.57a5.7 5.7 0 0 0-.997.625V7.583a1 1 0 0 0-2 0v4.205l-.697-.713c-.482-.494-1.265-.494-1.747 0s-.482 1.294 0 1.787l3.847 3.936q.056.057.117.106a5.58 5.58 0 0 0 3.922 1.613c3.045 0 5.563-2.469 5.563-5.514q0-.192-.013-.38v-1.739a4.4 4.4 0 0 0 1-.37C20.969 9.778 22 8.265 22 6.5 22 4.019 19.981 2 17.5 2m1.985 7.364a3.6 3.6 0 0 1-1 .478A3.5 3.5 0 0 1 17.5 10a3.5 3.5 0 0 1-3.486-3.358C14.012 6.594 14 6.549 14 6.5c0-.328.06-.639.145-.941C14.559 4.088 15.898 3 17.5 3 19.43 3 21 4.57 21 6.5a3.47 3.47 0 0 1-1.515 2.864" />
+        <path android:fillColor="#FFFFFF" android:pathData="M19.299 6H18V4.7a.5.5 0 0 0-1 0V6h-1.301a.5.5 0 0 0 0 1H17v1.3a.5.5 0 0 0 1 0V7h1.299a.5.5 0 0 0 0-1" />
+    </group>
+    <path
+        android:fillColor="#000000"
+        android:pathData="M18.485 10.884v1.739q.013.189.013.38c0 3.045-2.518 5.514-5.563 5.514a5.58 5.58 0 0 1-3.922-1.613 1 1 0 0 1-.117-.106l-3.847-3.936c-.482-.494-.482-1.294 0-1.787s1.265-.494 1.747 0l.697.713V7.583a1 1 0 0 1 2 0v1.096q.463-.364.997-.625v-1.57a1 1 0 0 1 2 0v1.022q.22-.018.446-.018c.062 0 .123.007.185.009A4.4 4.4 0 0 1 13 6.5c0-.378.061-.739.149-1.09a1.97 1.97 0 0 0-1.659-.926c-.903 0-1.658.603-1.906 1.425a1.997 1.997 0 0 0-3.091 1.674v2.206a2.2 2.2 0 0 0-2.159.586 2.285 2.285 0 0 0 0 3.185l3.847 3.936q.063.061.13.118l-.001.001a6.58 6.58 0 0 0 4.624 1.902c3.586 0 6.563-2.905 6.563-6.514q-.001-.192-.013-.381v-2.108c-.316.156-.645.29-.999.37" />
+    <path
+        android:fillColor="#1FA54A"
+        android:pathData="M17.5 3C15.57 3 14 4.57 14 6.5s1.57 3.5 3.5 3.5S21 8.43 21 6.5 19.43 3 17.5 3m1.799 4H18v1.3a.5.5 0 0 1-1 0V7h-1.301a.5.5 0 0 1 0-1H17V4.7a.5.5 0 0 1 1 0V6h1.299a.5.5 0 0 1 0 1" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_copy_vector_icon.xml b/core/res/res/drawable/pointer_copy_vector_icon.xml
new file mode 100644
index 0000000..fe2db15
--- /dev/null
+++ b/core/res/res/drawable/pointer_copy_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_copy_vector"
+    android:hotSpotX="8.5dp"
+    android:hotSpotY="7.5dp" />
diff --git a/core/res/res/drawable/pointer_crosshair_vector.xml b/core/res/res/drawable/pointer_crosshair_vector.xml
new file mode 100644
index 0000000..b2e7e8a
--- /dev/null
+++ b/core/res/res/drawable/pointer_crosshair_vector.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="M19.25 10.25h-5.5v-5.5a1.75 1.75 0 0 0-3.5 0v5.5h-5.5a1.75 1.75 0 0 0 0 3.5h5.5v5.5a1.75 1.75 0 0 0 3.5 0v-5.5h5.5a1.75 1.75 0 0 0 0-3.5m0 2.5h-6.5v6.5a.75.75 0 0 1-1.5 0v-6.5h-6.5a.75.75 0 0 1 0-1.5h6.5v-6.5a.75.75 0 0 1 1.5 0v6.5h6.5a.75.75 0 0 1 0 1.5" />
+    <path
+        android:fillType="evenOdd"
+        android:fillColor="#000000"
+        android:pathData="M19.25 11.25h-6.5v-6.5a.75.75 0 0 0-1.5 0v6.5h-6.5a.75.75 0 0 0 0 1.5h6.5v6.5a.75.75 0 0 0 1.5 0v-6.5h6.5a.75.75 0 0 0 0-1.5" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_crosshair_vector_icon.xml b/core/res/res/drawable/pointer_crosshair_vector_icon.xml
new file mode 100644
index 0000000..d938514
--- /dev/null
+++ b/core/res/res/drawable/pointer_crosshair_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_crosshair_vector"
+    android:hotSpotX="12dp"
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_grab_vector.xml b/core/res/res/drawable/pointer_grab_vector.xml
new file mode 100644
index 0000000..7d9f048
--- /dev/null
+++ b/core/res/res/drawable/pointer_grab_vector.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M20.442 7.562a2 2 0 0 0-2-2c-.366 0-.705.106-1 .277V4.686a2 2 0 0 0-2-2 2 2 0 0 0-1.004.279 1.995 1.995 0 0 0-3.986-.06 2 2 0 0 0-1.006-.28 2 2 0 0 0-2 2v6.501l-.247-.253a2.216 2.216 0 0 0-3.178 0 2.286 2.286 0 0 0 0 3.186l5.106 5.224q.063.061.131.118l-.001.001a6.58 6.58 0 0 0 4.624 1.901c3.587 0 6.565-2.906 6.565-6.516q0-.105-.004-.21m-6.561 5.727a5.58 5.58 0 0 1-3.922-1.613 1 1 0 0 1-.117-.106l-5.106-5.224a1.286 1.286 0 0 1 0-1.788 1.215 1.215 0 0 1 1.747 0l1.962 2.008V4.625a1 1 0 0 1 2 0v5.833q.463-.362.996-.623V3a1 1 0 0 1 2 0v6.29a6 6 0 0 1 1 .011V4.686a1 1 0 0 1 2 0v5.21c.357.185.693.408 1 .663V7.562a1 1 0 0 1 2 0v7.019h.001-.001q.004.104.004.207c.001 3.046-2.518 5.516-5.564 5.516" />
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="M19.442 14.581V7.562a1 1 0 0 0-2 0v2.997a5.7 5.7 0 0 0-1-.663v-5.21a1 1 0 0 0-2 0v4.615a5.5 5.5 0 0 0-1-.011V3a1 1 0 0 0-2 0v6.835a5.6 5.6 0 0 0-.996.623V4.625a1 1 0 0 0-2 0v8.955l-1.962-2.008a1.215 1.215 0 0 0-1.747 0 1.286 1.286 0 0 0 0 1.788l5.106 5.224q.056.057.117.106a5.58 5.58 0 0 0 3.922 1.613c3.046 0 5.565-2.469 5.565-5.516z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_grab_vector_icon.xml b/core/res/res/drawable/pointer_grab_vector_icon.xml
new file mode 100644
index 0000000..6ff7082
--- /dev/null
+++ b/core/res/res/drawable/pointer_grab_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_grab_vector"
+    android:hotSpotX="9.5dp"
+    android:hotSpotY="4.5dp" />
diff --git a/core/res/res/drawable/pointer_grabbing_vector.xml b/core/res/res/drawable/pointer_grabbing_vector.xml
new file mode 100644
index 0000000..9c96103
--- /dev/null
+++ b/core/res/res/drawable/pointer_grabbing_vector.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M19.485 12.622V8.508a2 2 0 0 0-3.12-1.657 1.993 1.993 0 0 0-2.99-1.006 1.99 1.99 0 0 0-1.886-1.361c-.903 0-1.658.603-1.906 1.425a2 2 0 0 0-3.09 1.674v2.206a2.2 2.2 0 0 0-2.159.586 2.285 2.285 0 0 0 0 3.185l3.847 3.936q.063.061.13.118l-.001.001a6.58 6.58 0 0 0 4.624 1.902c3.586 0 6.563-2.905 6.563-6.514a5 5 0 0 0-.012-.381m-6.55 5.895a5.58 5.58 0 0 1-3.922-1.613 1 1 0 0 1-.117-.106l-3.847-3.936c-.482-.494-.482-1.294 0-1.787s1.265-.494 1.747 0l.697.713V7.583a1 1 0 0 1 2 0v1.096q.463-.364.997-.625v-1.57a1 1 0 0 1 2 0v1.022a5.5 5.5 0 0 1 .996.009v-.007a1 1 0 0 1 2 0v.599q.537.277 1 .66v-.259a1 1 0 0 1 2 0v4.115q.013.189.013.38c-.001 3.045-2.518 5.514-5.564 5.514" />
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="M18.485 12.622V8.508a1 1 0 0 0-2 0v.259a5.6 5.6 0 0 0-1-.66v-.599a1 1 0 0 0-2 0v.008a5.6 5.6 0 0 0-.996-.009V6.484a1 1 0 0 0-2 0v1.57a5.7 5.7 0 0 0-.997.625V7.583a1 1 0 0 0-2 0v4.205l-.697-.713c-.482-.494-1.265-.494-1.747 0s-.482 1.294 0 1.787l3.847 3.936q.056.057.117.106a5.58 5.58 0 0 0 3.922 1.613c3.045 0 5.563-2.469 5.563-5.514a5 5 0 0 0-.012-.381" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_grabbing_vector_icon.xml b/core/res/res/drawable/pointer_grabbing_vector_icon.xml
new file mode 100644
index 0000000..903c693
--- /dev/null
+++ b/core/res/res/drawable/pointer_grabbing_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_grabbing_vector"
+    android:hotSpotX="8.5dp"
+    android:hotSpotY="7.5dp" />
diff --git a/core/res/res/drawable/pointer_hand_vector.xml b/core/res/res/drawable/pointer_hand_vector.xml
new file mode 100644
index 0000000..79792f8
--- /dev/null
+++ b/core/res/res/drawable/pointer_hand_vector.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M20.492 15.197v-4.198A1.995 1.995 0 0 0 18.5 9.001c-.413 0-.797.126-1.115.342a1.99 1.99 0 0 0-1.873-1.341c-.411 0-.792.125-1.109.339a1.99 1.99 0 0 0-1.879-1.361c-.363 0-.699.105-.992.275V3.998A1.99 1.99 0 0 0 9.542 2c-1.1 0-1.992.895-1.992 1.998v7.831l-.242-.249a2.2 2.2 0 0 0-3.164 0 2.29 2.29 0 0 0 0 3.183l5.084 5.219q.063.061.13.118l-.001.001A6.54 6.54 0 0 0 13.963 22c3.572 0 6.537-2.903 6.537-6.509q0-.148-.008-.294m-6.529 5.804a5.55 5.55 0 0 1-3.906-1.611 1 1 0 0 1-.117-.106l-5.084-5.219a1.286 1.286 0 0 1 0-1.786 1.21 1.21 0 0 1 1.74 0l1.95 2.002V3.998c0-.552.446-.999.996-.999s.996.447.996.999v7.17l.011-.007a.495.495 0 0 0 .989-.037V8.939a.992.992 0 0 1 1.984.039v.796l-.007 1.386a.5.5 0 0 0 .495.502h.003a.5.5 0 0 0 .498-.497l.006-1.157h.001V10a.997.997 0 1 1 1.991 0v.601l.004.003v1.02q.001.107.042.199a.5.5 0 0 0 .153.187l.031.021a.5.5 0 0 0 .231.083c.014.001.026.008.04.008a.5.5 0 0 0 .498-.5v-.642a.996.996 0 0 1 .993-.98c.55 0 .996.447.996.999v4.199a6 6 0 0 1 .008.293c-.001 3.043-2.509 5.51-5.542 5.51" />
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="M19.496 10.999A.997.997 0 0 0 18.5 10a.995.995 0 0 0-.992.98v.644a.5.5 0 0 1-.498.5c-.014 0-.026-.007-.04-.008a.493.493 0 0 1-.457-.491v-1.02l-.004-.003V10c0-.552-.446-.999-.996-.999s-.996.447-.996.999v.008h-.001l-.005 1.003-.001.154a.5.5 0 0 1-.498.497h-.003a.5.5 0 0 1-.495-.502l.001-.159.006-1.227v-.796a.997.997 0 0 0-.996-.999.993.993 0 0 0-.988.96v2.185a.496.496 0 0 1-.989.037l-.011.007v-7.17a.997.997 0 0 0-.996-.999.997.997 0 0 0-.996.999V14.28l-1.95-2.002a1.21 1.21 0 0 0-1.74 0 1.286 1.286 0 0 0 0 1.786l5.084 5.219q.056.057.117.106A5.54 5.54 0 0 0 13.962 21c3.033 0 5.541-2.467 5.541-5.51a6 6 0 0 0-.008-.293z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_hand_vector_icon.xml b/core/res/res/drawable/pointer_hand_vector_icon.xml
new file mode 100644
index 0000000..c59c8dc
--- /dev/null
+++ b/core/res/res/drawable/pointer_hand_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_hand_vector"
+    android:hotSpotX="9.5dp"
+    android:hotSpotY="2.5dp" />
diff --git a/core/res/res/drawable/pointer_handwriting_vector.xml b/core/res/res/drawable/pointer_handwriting_vector.xml
new file mode 100644
index 0000000..09f3e31
--- /dev/null
+++ b/core/res/res/drawable/pointer_handwriting_vector.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <group>
+        <path android:fillColor="#FFFFFF" android:pathData="M20.12 6.8 18.7 5.38c-.57-.57-1.32-.88-2.12-.88s-1.56.31-2.13.88l-7.16 7.16-.29.29V5c0-1.1-.9-2-2-2s-2 .9-2 2v14c0 1.1.9 2 2 2s2-.9 2-2v-.5h5.67l.29-.29 7.16-7.16c.57-.57.88-1.32.88-2.12s-.31-1.57-.88-2.13M6 19c0 .55-.45 1-1 1s-1-.45-1-1V5c0-.55.45-1 1-1s1 .45 1 1zm13.41-8.66-7.16 7.16H8v-4.25l7.16-7.16c.39-.39.9-.59 1.41-.59h.01c.51 0 1.02.2 1.41.59l1.42 1.42c.78.78.78 2.05 0 2.83" />
+        <path android:fillColor="#FFFFFF" android:pathData="m16.431 7.64-6.29 6.29 1.43 1.43 6.29-6.29-1.42-1.43z" />
+    </group>
+    <path
+        android:fillColor="#000000"
+        android:pathData="M5 4c-.55 0-1 .45-1 1v14c0 .55.45 1 1 1s1-.45 1-1V5c0-.55-.45-1-1-1m14.41 3.51-1.42-1.42c-.39-.39-.9-.59-1.41-.59h-.01c-.51 0-1.02.2-1.41.59L8 13.25v4.25h4.25l7.16-7.16c.78-.78.78-2.05 0-2.83m-7.839 7.85-1.43-1.43 6.29-6.29h.01l1.42 1.43z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_handwriting_vector_icon.xml b/core/res/res/drawable/pointer_handwriting_vector_icon.xml
new file mode 100644
index 0000000..14a8700
--- /dev/null
+++ b/core/res/res/drawable/pointer_handwriting_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_handwriting_vector"
+    android:hotSpotX="8.25dp"
+    android:hotSpotY="23.75dp" />
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_help_vector.xml b/core/res/res/drawable/pointer_help_vector.xml
new file mode 100644
index 0000000..6b7fd9f
--- /dev/null
+++ b/core/res/res/drawable/pointer_help_vector.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M16.339 11.18 6.773 4.017a1.78 1.78 0 0 0-1.072-.365c-.274 0-.551.065-.813.196a1.77 1.77 0 0 0-.994 1.611l.009 11.951c0 .903.59 1.457 1.142 1.674.2.078.433.128.678.128.434 0 .906-.155 1.298-.578l2.97-3.193a1.8 1.8 0 0 1 1.126-.563l4.335-.467c.899-.097 1.387-.741 1.544-1.312.157-.573.066-1.377-.657-1.919" />
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="M16.94 10.38 7.37 3.22a2.77 2.77 0 0 0-2.93-.27A2.75 2.75 0 0 0 2.9 5.46l.01 11.95a2.79 2.79 0 0 0 2.82 2.8c.78 0 1.5-.32 2.03-.9l2.97-3.19a.8.8 0 0 1 .5-.25l4.34-.46a2.76 2.76 0 0 0 2.4-2.05 2.8 2.8 0 0 0-1.02-2.98zM17 13.1a1.77 1.77 0 0 1-1.55 1.31l-4.33.47a1.8 1.8 0 0 0-1.13.56l-2.97 3.2c-.4.42-.86.57-1.3.57-.24 0-.48-.05-.68-.13a1.77 1.77 0 0 1-1.14-1.67V5.46a1.81 1.81 0 0 1 1.8-1.8c.38 0 .75.11 1.07.36l9.57 7.16c.72.54.81 1.35.66 1.92zm2.64-10.83a2.5 2.5 0 0 0-1.84-.72 3 3 0 0 0-2.83 1.93l-.39.94.96.37.86.32.12.05-.02.03c-.22.4-.3.82-.3 1.33v.94a1.56 1.56 0 0 0 .4 1.47 1.54 1.54 0 0 0 2.24.01 1.55 1.55 0 0 0 .28-1.84v-.52c0-.1.02-.17.03-.25l.16-.15c.32-.25.6-.56.78-.93.18-.37.26-.76.26-1.16 0-.68-.21-1.32-.7-1.82zm-1.5 5.96a.55.55 0 0 1-.82 0 .56.56 0 0 1-.17-.4c0-.16.06-.3.17-.4a.55.55 0 0 1 .41-.18c.15 0 .28.06.4.17a.55.55 0 0 1 0 .81zm1.05-3.42c-.1.22-.28.42-.52.6-.26.22-.42.42-.47.6-.05.18-.08.37-.08.57l-.93-.06c0-.38.07-.62.19-.86.13-.24.3-.46.54-.66.17-.13.3-.28.4-.43s.14-.3.14-.46c0-.2-.08-.37-.22-.5s-.31-.17-.52-.17c-.2 0-.39.06-.56.18-.17.13-.3.31-.4.56l-.87-.33a2.03 2.03 0 0 1 1.91-1.3c.48 0 .86.14 1.13.42.28.28.41.65.41 1.12 0 .26-.05.5-.15.72z" />
+    <path
+        android:fillColor="#000000"
+        android:pathData="M17.73 7.254a.55.55 0 0 0-.407.169.55.55 0 0 0-.169.407q0 .225.169.401a.55.55 0 0 0 .808 0 .56.56 0 0 0 .175-.413.53.53 0 0 0-.175-.394.56.56 0 0 0-.401-.17m1.202-4.288q-.413-.42-1.126-.419-.651 0-1.164.357a2.1 2.1 0 0 0-.751.945l.864.326q.15-.363.407-.551a.93.93 0 0 1 .557-.188q.313 0 .526.182c.213.182.213.286.213.495q0 .226-.144.457a1.4 1.4 0 0 1-.394.432q-.35.3-.538.657c-.125.238-.187.485-.187.86l.926.06q0-.3.081-.57t.469-.595q.363-.276.519-.601t.156-.726q-.002-.701-.414-1.121" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_help_vector_icon.xml b/core/res/res/drawable/pointer_help_vector_icon.xml
new file mode 100644
index 0000000..78cc3e9
--- /dev/null
+++ b/core/res/res/drawable/pointer_help_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_help_vector"
+    android:hotSpotX="4.5dp"
+    android:hotSpotY="3.5dp" />
diff --git a/core/res/res/drawable/pointer_horizontal_double_arrow_vector.xml b/core/res/res/drawable/pointer_horizontal_double_arrow_vector.xml
new file mode 100644
index 0000000..d1aea9e
--- /dev/null
+++ b/core/res/res/drawable/pointer_horizontal_double_arrow_vector.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="m19.963 10.185-3.065-1.758c-1.327-.761-2.96.14-3.072 1.633h-3.651c-.113-1.492-1.746-2.394-3.072-1.633l-3.065 1.758c-1.383.793-1.383 2.786 0 3.579l3.065 1.758c1.311.752 2.918-.12 3.065-1.581h3.666c.147 1.46 1.754 2.333 3.065 1.581l3.065-1.758c1.382-.793 1.382-2.786-.001-3.579m-.498 2.712L16.4 14.655a1.065 1.065 0 0 1-1.596-.922v-.791H9.195v.791c0 .818-.886 1.33-1.596.922l-3.065-1.758a1.063 1.063 0 0 1 0-1.845l3.065-1.758a1.065 1.065 0 0 1 1.596.922v.843h5.609v-.843c0-.818.886-1.33 1.596-.922l3.065 1.758a1.063 1.063 0 0 1 0 1.845" />
+    <path
+        android:fillColor="#000000"
+        android:pathData="M19.465 11.052 16.4 9.294a1.065 1.065 0 0 0-1.596.922v.843H9.195v-.843c0-.818-.886-1.33-1.596-.922l-3.065 1.758a1.063 1.063 0 0 0 0 1.845l3.065 1.758a1.065 1.065 0 0 0 1.596-.922v-.791h5.609v.791c0 .818.886 1.33 1.596.922l3.065-1.758a1.063 1.063 0 0 0 0-1.845" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_horizontal_double_arrow_vector_icon.xml b/core/res/res/drawable/pointer_horizontal_double_arrow_vector_icon.xml
new file mode 100644
index 0000000..cee5f919
--- /dev/null
+++ b/core/res/res/drawable/pointer_horizontal_double_arrow_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_horizontal_double_arrow_vector"
+    android:hotSpotX="12dp"
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_nodrop_vector.xml b/core/res/res/drawable/pointer_nodrop_vector.xml
new file mode 100644
index 0000000..3a38bab
--- /dev/null
+++ b/core/res/res/drawable/pointer_nodrop_vector.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <group>
+        <path android:fillColor="#FFFFFF" android:pathData="M17.5 1.953c-2.108 0-3.869 1.449-4.382 3.398a4.5 4.5 0 0 0-.165 1.148c0 .343.045.674.117.995-.045-.001-.09-.007-.135-.007q-.225 0-.446.018V6.484a1 1 0 0 0-2 0v1.57a5.7 5.7 0 0 0-.997.625V7.583a1 1 0 0 0-2 0v4.205l-.697-.713c-.482-.494-1.265-.494-1.747 0s-.482 1.294 0 1.787l3.847 3.936q.056.057.117.106a5.58 5.58 0 0 0 3.922 1.613c3.045 0 5.563-2.469 5.563-5.514q0-.192-.013-.38v-1.69a4.5 4.5 0 0 0 1-.366c1.51-.739 2.562-2.275 2.562-4.066A4.55 4.55 0 0 0 17.5 1.953m0 8.047C15.57 10 14 8.43 14 6.5S15.57 3 17.5 3 21 4.57 21 6.5 19.43 10 17.5 10" />
+        <path android:fillColor="#FFFFFF" android:pathData="M17.5 4c-.493 0-.95.148-1.337.395l3.442 3.442C19.852 7.45 20 6.993 20 6.5 20 5.121 18.879 4 17.5 4M15 6.5C15 7.879 16.121 9 17.5 9c.525 0 1.011-.164 1.413-.441l-3.472-3.472A2.5 2.5 0 0 0 15 6.5" />
+    </group>
+    <path
+        android:fillColor="#000000"
+        android:pathData="M18.485 10.932v1.69q.013.188.013.38c0 3.045-2.518 5.514-5.563 5.514a5.58 5.58 0 0 1-3.922-1.613 1 1 0 0 1-.117-.106l-3.847-3.936c-.482-.494-.482-1.294 0-1.787s1.265-.494 1.747 0l.697.713V7.583a1 1 0 0 1 2 0v1.096q.463-.364.997-.625v-1.57a1 1 0 0 1 2 0v1.022q.22-.018.446-.018c.046 0 .09.006.135.007a4.5 4.5 0 0 1-.117-.995c0-.399.068-.779.165-1.148a1.97 1.97 0 0 0-1.629-.867c-.903 0-1.658.603-1.906 1.425a2 2 0 0 0-1.091-.327 2 2 0 0 0-2 2v2.206a2.2 2.2 0 0 0-2.159.586 2.285 2.285 0 0 0 0 3.185l3.847 3.936q.063.061.13.118l-.001.001a6.58 6.58 0 0 0 4.624 1.902c3.586 0 6.563-2.905 6.563-6.514q-.001-.192-.013-.381v-2.056a4.5 4.5 0 0 1-.999.366" />
+    <path
+        android:fillColor="#B22A25"
+        android:pathData="M17.5 3C15.57 3 14 4.57 14 6.5s1.57 3.5 3.5 3.5S21 8.43 21 6.5 19.43 3 17.5 3m0 6A2.5 2.5 0 0 1 15 6.5c0-.525.164-1.011.441-1.413l3.472 3.472A2.5 2.5 0 0 1 17.5 9m2.105-1.163-3.442-3.442A2.5 2.5 0 0 1 17.5 4C18.879 4 20 5.121 20 6.5c0 .493-.148.95-.395 1.337" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_nodrop_vector_icon.xml b/core/res/res/drawable/pointer_nodrop_vector_icon.xml
new file mode 100644
index 0000000..ceba002
--- /dev/null
+++ b/core/res/res/drawable/pointer_nodrop_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_nodrop_vector"
+    android:hotSpotX="8.5dp"
+    android:hotSpotY="7.5dp" />
diff --git a/core/res/res/drawable/pointer_spot_anchor_vector.xml b/core/res/res/drawable/pointer_spot_anchor_vector.xml
new file mode 100644
index 0000000..54de2ae
--- /dev/null
+++ b/core/res/res/drawable/pointer_spot_anchor_vector.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <group>
+        <path android:fillColor="#ADC6E7" android:pathData="M12 3c-4.963 0-9 4.038-9 9 0 4.963 4.037 9 9 9s9-4.037 9-9c0-4.962-4.037-9-9-9m0 17c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8" />
+        <path android:fillColor="#ADC6E7" android:pathData="M12 5c-3.859 0-7 3.14-7 7s3.141 7 7 7 7-3.141 7-7-3.141-7-7-7m0 13c-3.309 0-6-2.691-6-6s2.691-6 6-6 6 2.691 6 6-2.691 6-6 6" />
+    </group>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_spot_anchor_vector_icon.xml b/core/res/res/drawable/pointer_spot_anchor_vector_icon.xml
new file mode 100644
index 0000000..83b767c
--- /dev/null
+++ b/core/res/res/drawable/pointer_spot_anchor_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_spot_anchor_vector"
+    android:hotSpotX="12dp"
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_spot_hover_vector.xml b/core/res/res/drawable/pointer_spot_hover_vector.xml
new file mode 100644
index 0000000..ef596c4
--- /dev/null
+++ b/core/res/res/drawable/pointer_spot_hover_vector.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <group>
+        <path android:fillColor="#ADC6E7" android:pathData="M12 3c-4.963 0-9 4.038-9 9 0 4.963 4.037 9 9 9s9-4.037 9-9c0-4.962-4.037-9-9-9m0 17c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8" />
+        <path android:fillColor="#ADC6E7" android:pathData="M12 7c-2.757 0-5 2.243-5 5s2.243 5 5 5 5-2.243 5-5-2.243-5-5-5m0 9c-2.206 0-4-1.794-4-4s1.794-4 4-4 4 1.794 4 4-1.794 4-4 4" />
+    </group>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_spot_hover_vector_icon.xml b/core/res/res/drawable/pointer_spot_hover_vector_icon.xml
new file mode 100644
index 0000000..f892958
--- /dev/null
+++ b/core/res/res/drawable/pointer_spot_hover_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_spot_hover_vector"
+    android:hotSpotX="12dp"
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_spot_touch_vector.xml b/core/res/res/drawable/pointer_spot_touch_vector.xml
new file mode 100644
index 0000000..afd2956
--- /dev/null
+++ b/core/res/res/drawable/pointer_spot_touch_vector.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:fillColor="#ADC6E7"
+        android:pathData="M21 12c0-4.963-4.038-9-9-9s-9 4.037-9 9 4.038 9 9 9 9-4.037 9-9m-9 8c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_spot_touch_vector_icon.xml b/core/res/res/drawable/pointer_spot_touch_vector_icon.xml
new file mode 100644
index 0000000..7b96938
--- /dev/null
+++ b/core/res/res/drawable/pointer_spot_touch_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_spot_touch_vector"
+    android:hotSpotX="12dp"
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_text_vector.xml b/core/res/res/drawable/pointer_text_vector.xml
new file mode 100644
index 0000000..9e44f28
--- /dev/null
+++ b/core/res/res/drawable/pointer_text_vector.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M12 3c-.551 0-1 .448-1 1v14a1.001 1.001 0 0 0 2 0V4c0-.552-.449-1-1-1" />
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="M12 2c-1.103 0-2 .897-2 2v14c0 1.103.897 2 2 2s2-.897 2-2V4c0-1.103-.897-2-2-2m1 16a1.001 1.001 0 0 1-2 0V4a1.001 1.001 0 0 1 2 0z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_text_vector_icon.xml b/core/res/res/drawable/pointer_text_vector_icon.xml
new file mode 100644
index 0000000..b03f8da
--- /dev/null
+++ b/core/res/res/drawable/pointer_text_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_text_vector"
+    android:hotSpotX="12dp"
+    android:hotSpotY="11dp" />
diff --git a/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_vector.xml b/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_vector.xml
new file mode 100644
index 0000000..e5d5301
--- /dev/null
+++ b/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_vector.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="m18.896 16.365-.924-3.41c-.398-1.467-2.169-1.985-3.305-1.035L12.08 9.333c.952-1.136.434-2.908-1.034-3.306l-3.41-.924c-1.539-.416-2.948.993-2.532 2.532l.924 3.41c.398 1.468 2.17 1.986 3.306 1.034l2.586 2.586c-.953 1.136-.435 2.91 1.033 3.307l3.41.924c1.54.417 2.949-.992 2.533-2.531m-2.27 1.566-3.41-.924a1.065 1.065 0 0 1-.476-1.781l.579-.579-3.966-3.966-.579.579a1.066 1.066 0 0 1-1.781-.476L6.07 7.373a1.063 1.063 0 0 1 1.304-1.304l3.41.924a1.065 1.065 0 0 1 .476 1.781l-.578.578 3.966 3.966.577-.577a1.066 1.066 0 0 1 1.781.477l.924 3.41a1.062 1.062 0 0 1-1.304 1.303" />
+    <path
+        android:fillType="evenOdd"
+        android:fillColor="#000000"
+        android:pathData="M6.07 7.373a1.063 1.063 0 0 1 1.304-1.304l3.41.924a1.065 1.065 0 0 1 .476 1.781l-.578.578 3.966 3.966.577-.577a1.066 1.066 0 0 1 1.781.476l.924 3.41a1.063 1.063 0 0 1-1.304 1.304l-3.41-.924a1.065 1.065 0 0 1-.476-1.781l.579-.579-3.966-3.966-.579.579a1.066 1.066 0 0 1-1.781-.476z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_vector_icon.xml b/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_vector_icon.xml
new file mode 100644
index 0000000..7fd2a7f
--- /dev/null
+++ b/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_top_left_diagonal_double_arrow_vector"
+    android:hotSpotX="12dp"
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_vector.xml b/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_vector.xml
new file mode 100644
index 0000000..e6f7aaf
--- /dev/null
+++ b/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_vector.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="m16.365 5.104-3.41.924c-1.468.398-1.986 2.171-1.033 3.307l-2.586 2.586c-1.136-.952-2.909-.434-3.306 1.034l-.924 3.41c-.417 1.539.992 2.948 2.531 2.531l3.41-.924c1.468-.398 1.986-2.17 1.034-3.306l2.587-2.587c1.136.951 2.908.432 3.305-1.035l.924-3.41c.415-1.538-.994-2.947-2.532-2.53m1.565 2.269-.924 3.41a1.065 1.065 0 0 1-1.781.476l-.577-.577-3.966 3.966.578.578a1.066 1.066 0 0 1-.476 1.781l-3.41.924a1.063 1.063 0 0 1-1.304-1.304l.924-3.41a1.066 1.066 0 0 1 1.781-.477l.578.578 3.966-3.966-.579-.579a1.066 1.066 0 0 1 .476-1.781l3.41-.924a1.063 1.063 0 0 1 1.304 1.305" />
+    <path
+        android:fillColor="#000000"
+        android:pathData="m16.626 6.069-3.41.924a1.065 1.065 0 0 0-.476 1.781l.579.579-3.966 3.966-.579-.579a1.066 1.066 0 0 0-1.781.477l-.924 3.41a1.063 1.063 0 0 0 1.304 1.304l3.41-.924a1.065 1.065 0 0 0 .476-1.781l-.578-.578 3.966-3.966.577.577a1.066 1.066 0 0 0 1.781-.476l.924-3.41a1.062 1.062 0 0 0-1.303-1.304" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_vector_icon.xml b/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_vector_icon.xml
new file mode 100644
index 0000000..d2516b1
--- /dev/null
+++ b/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_top_right_diagonal_double_arrow_vector"
+    android:hotSpotX="12dp"
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_vertical_double_arrow_vector.xml b/core/res/res/drawable/pointer_vertical_double_arrow_vector.xml
new file mode 100644
index 0000000..6ffcfef
--- /dev/null
+++ b/core/res/res/drawable/pointer_vertical_double_arrow_vector.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="M13.945 13.829V10.17c1.476-.131 2.363-1.75 1.606-3.069l-1.758-3.065c-.793-1.383-2.786-1.383-3.579 0L8.455 7.102c-.757 1.319.131 2.939 1.607 3.069v3.658c-1.477.13-2.364 1.75-1.607 3.069l1.758 3.065c.793 1.383 2.786 1.383 3.579 0l1.758-3.065c.758-1.319-.129-2.938-1.605-3.069m.739 2.572-1.758 3.065a1.063 1.063 0 0 1-1.845 0l-1.758-3.065a1.065 1.065 0 0 1 .922-1.596h.818v-5.61h-.818c-.818 0-1.33-.886-.922-1.596l1.758-3.065a1.063 1.063 0 0 1 1.845 0l1.758 3.065a1.065 1.065 0 0 1-.922 1.596h-.817v5.609h.817c.817.001 1.329.886.922 1.597" />
+    <path
+        android:fillColor="#000000"
+        android:pathData="M13.761 14.805h-.817v-5.61h.817c.818 0 1.33-.886.922-1.596l-1.758-3.065a1.063 1.063 0 0 0-1.845 0L9.323 7.599c-.407.71.104 1.596.922 1.596h.818v5.609h-.818c-.818 0-1.33.886-.922 1.596l1.758 3.065a1.063 1.063 0 0 0 1.845 0l1.758-3.065a1.065 1.065 0 0 0-.923-1.595" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_vertical_double_arrow_vector_icon.xml b/core/res/res/drawable/pointer_vertical_double_arrow_vector_icon.xml
new file mode 100644
index 0000000..64f3424
--- /dev/null
+++ b/core/res/res/drawable/pointer_vertical_double_arrow_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_vertical_double_arrow_vector"
+    android:hotSpotX="12dp"
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_vertical_text_vector.xml b/core/res/res/drawable/pointer_vertical_text_vector.xml
new file mode 100644
index 0000000..72f40cc
--- /dev/null
+++ b/core/res/res/drawable/pointer_vertical_text_vector.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M19 11H5a1 1 0 0 0 0 2h14a1 1 0 0 0 0-2" />
+    <path
+        android:fillColor="#FFFFFF"
+        android:pathData="M19 10H5c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2m0 3H5a1 1 0 0 1 0-2h14a1 1 0 0 1 0 2" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_vertical_text_vector_icon.xml b/core/res/res/drawable/pointer_vertical_text_vector_icon.xml
new file mode 100644
index 0000000..afb225a
--- /dev/null
+++ b/core/res/res/drawable/pointer_vertical_text_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_vertical_text_vector"
+    android:hotSpotX="12dp"
+    android:hotSpotY="12dp" />
diff --git a/core/res/res/drawable/pointer_zoom_in_vector.xml b/core/res/res/drawable/pointer_zoom_in_vector.xml
new file mode 100644
index 0000000..8921666
--- /dev/null
+++ b/core/res/res/drawable/pointer_zoom_in_vector.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <group>
+        <path android:fillColor="#FFFFFF" android:pathData="m20.445 17.298-3.591-3.613a7.5 7.5 0 0 0 1.243-4.138 7.547 7.547 0 1 0-7.546 7.546c1.239 0 2.402-.31 3.435-.84l3.733 3.756a1.922 1.922 0 1 0 2.726-2.711m-.713 2.009a.923.923 0 0 1-1.305-.004l-4.268-4.294a6.547 6.547 0 1 1 2.938-5.462 6.52 6.52 0 0 1-1.555 4.236l4.194 4.22a.92.92 0 0 1-.004 1.304" />
+        <path android:fillColor="#FFFFFF" android:pathData="M10.55 5a4.546 4.546 0 1 0 0 9.093 4.546 4.546 0 0 0 0-9.093m2.462 5h-2v2a.5.5 0 0 1-1 0v-2h-2a.5.5 0 0 1 0-1h2V7a.5.5 0 0 1 1 0v2h2a.5.5 0 0 1 0 1" />
+    </group>
+    <group>
+        <path android:fillColor="#000000" android:pathData="m19.736 18.003-4.194-4.22a6.547 6.547 0 1 0-1.382 1.226l4.268 4.294a.923.923 0 0 0 1.308-1.3m-9.186-3.91A4.546 4.546 0 1 1 10.549 5a4.546 4.546 0 0 1 .001 9.093" />
+        <path android:fillColor="#000000" android:pathData="M13.012 9h-2V7a.5.5 0 0 0-1 0v2h-2a.5.5 0 0 0 0 1h2v2a.5.5 0 0 0 1 0v-2h2a.5.5 0 0 0 0-1" />
+    </group>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_zoom_in_vector_icon.xml b/core/res/res/drawable/pointer_zoom_in_vector_icon.xml
new file mode 100644
index 0000000..fcc0c28
--- /dev/null
+++ b/core/res/res/drawable/pointer_zoom_in_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_zoom_in_vector"
+    android:hotSpotX="10.5dp"
+    android:hotSpotY="9.5dp" />
diff --git a/core/res/res/drawable/pointer_zoom_out_vector.xml b/core/res/res/drawable/pointer_zoom_out_vector.xml
new file mode 100644
index 0000000..815ce0e
--- /dev/null
+++ b/core/res/res/drawable/pointer_zoom_out_vector.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <group>
+        <path android:fillColor="#FFFFFF" android:pathData="m20.445 17.298-3.591-3.613a7.5 7.5 0 0 0 1.243-4.138 7.547 7.547 0 1 0-7.546 7.546c1.239 0 2.402-.31 3.435-.84l3.733 3.756a1.922 1.922 0 1 0 2.726-2.711m-.713 2.009a.923.923 0 0 1-1.305-.004l-4.268-4.294a6.547 6.547 0 1 1 2.938-5.462 6.52 6.52 0 0 1-1.555 4.236l4.194 4.22a.92.92 0 0 1-.004 1.304" />
+        <path android:fillColor="#FFFFFF" android:pathData="M10.55 5a4.546 4.546 0 1 0 0 9.093 4.546 4.546 0 0 0 0-9.093m2.462 5h-5a.5.5 0 0 1 0-1h5a.5.5 0 0 1 0 1" />
+    </group>
+    <group>
+        <path android:fillColor="#000000" android:pathData="m19.736 18.003-4.194-4.22a6.547 6.547 0 1 0-1.382 1.226l4.268 4.294a.923.923 0 0 0 1.308-1.3m-9.186-3.91A4.546 4.546 0 1 1 10.549 5a4.546 4.546 0 0 1 .001 9.093" />
+        <path android:fillColor="#000000" android:pathData="M13.012 9h-5a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1" />
+    </group>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/pointer_zoom_out_vector_icon.xml b/core/res/res/drawable/pointer_zoom_out_vector_icon.xml
new file mode 100644
index 0000000..37f4e79
--- /dev/null
+++ b/core/res/res/drawable/pointer_zoom_out_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_zoom_out_vector"
+    android:hotSpotX="10.5dp"
+    android:hotSpotY="9.5dp" />
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 d910940..4741012 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -3278,6 +3278,31 @@
              <p> By default, the behavior is configured by the same attribute in application.
         -->
         <attr name="enableOnBackInvokedCallback" format="boolean"/>
+
+        <!-- Specifies permissions necessary to launch this activity via
+             {@link android.content.Context#startActivity} when passing content URIs. The default
+             value is {@code none}, meaning no specific permissions are required. Setting this
+             attribute restricts activity invocation based on the invoker's permissions. If the
+             invoker doesn't have the required permissions, the activity start will be denied via a
+             {@link java.lang.SecurityException}.
+
+             <p> Note that the enforcement works for content URIs inside
+             {@link android.content.Intent#getData} and {@link android.content.Intent#getClipData}.
+             @FlaggedApi("android.security.content_uri_permission_apis") -->
+        <attr name="requireContentUriPermissionFromCaller" format="string">
+            <!-- Default, no specific permissions are required. -->
+            <enum name="none" value="0" />
+            <!-- Enforces the invoker to have read access to the passed content URIs. -->
+            <enum name="read" value="1" />
+            <!-- Enforces the invoker to have write access to the passed content URIs. -->
+            <enum name="write" value="2" />
+            <!-- Enforces the invoker to have either read or write access to the passed content
+                 URIs. -->
+            <enum name="readOrWrite" value="3" />
+            <!-- Enforces the invoker to have both read and write access to the passed content
+                 URIs. -->
+            <enum name="readAndWrite" value="4" />
+        </attr>
     </declare-styleable>
 
     <!-- The <code>activity-alias</code> tag declares a new
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 81a8908..f9cf28c 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -147,6 +147,10 @@
     <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"/>
+    <!-- @FlaggedApi("android.security.content_uri_permission_apis") -->
+    <public name="requireContentUriPermissionFromCaller" />
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 22d028c..adf8d9f 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1460,6 +1460,43 @@
     </style>
 
     <!-- @hide -->
+    <style name="VectorPointer">
+        <item name="pointerIconArrow">@drawable/pointer_arrow_vector_icon</item>
+        <item name="pointerIconSpotHover">@drawable/pointer_spot_hover_vector_icon</item>
+        <item name="pointerIconSpotTouch">@drawable/pointer_spot_touch_vector_icon</item>
+        <item name="pointerIconSpotAnchor">@drawable/pointer_spot_anchor_vector_icon</item>
+        <item name="pointerIconHand">@drawable/pointer_hand_vector_icon</item>
+        <item name="pointerIconContextMenu">@drawable/pointer_context_menu_vector_icon</item>
+        <item name="pointerIconHelp">@drawable/pointer_help_vector_icon</item>
+        <item name="pointerIconWait">@drawable/pointer_wait_icon</item>
+        <item name="pointerIconCell">@drawable/pointer_cell_vector_icon</item>
+        <item name="pointerIconCrosshair">@drawable/pointer_crosshair_vector_icon</item>
+        <item name="pointerIconText">@drawable/pointer_text_vector_icon</item>
+        <item name="pointerIconVerticalText">@drawable/pointer_vertical_text_vector_icon</item>
+        <item name="pointerIconAlias">@drawable/pointer_alias_vector_icon</item>
+        <item name="pointerIconCopy">@drawable/pointer_copy_vector_icon</item>
+        <item name="pointerIconAllScroll">@drawable/pointer_all_scroll_vector_icon</item>
+        <item name="pointerIconNodrop">@drawable/pointer_nodrop_vector_icon</item>
+        <item name="pointerIconHorizontalDoubleArrow">
+            @drawable/pointer_horizontal_double_arrow_vector_icon
+        </item>
+        <item name="pointerIconVerticalDoubleArrow">
+            @drawable/pointer_vertical_double_arrow_vector_icon
+        </item>
+        <item name="pointerIconTopRightDiagonalDoubleArrow">
+            @drawable/pointer_top_right_diagonal_double_arrow_vector_icon
+        </item>
+        <item name="pointerIconTopLeftDiagonalDoubleArrow">
+            @drawable/pointer_top_left_diagonal_double_arrow_vector_icon
+        </item>
+        <item name="pointerIconZoomIn">@drawable/pointer_zoom_in_vector_icon</item>
+        <item name="pointerIconZoomOut">@drawable/pointer_zoom_out_vector_icon</item>
+        <item name="pointerIconGrab">@drawable/pointer_grab_vector_icon</item>
+        <item name="pointerIconGrabbing">@drawable/pointer_grabbing_vector_icon</item>
+        <item name="pointerIconHandwriting">@drawable/pointer_handwriting_vector_icon</item>
+    </style>
+
+    <!-- @hide -->
     <style name="aerr_list_item" parent="Widget.Material.Light.Button.Borderless">
         <item name="minHeight">?attr/listPreferredItemHeightSmall</item>
         <item name="textAppearance">?attr/textAppearanceListItemSmall</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 58427b1..3df7570 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1681,6 +1681,7 @@
   <java-symbol type="style" name="Theme.DeviceDefault.VoiceInteractionSession" />
   <java-symbol type="style" name="Pointer" />
   <java-symbol type="style" name="LargePointer" />
+  <java-symbol type="style" name="VectorPointer" />
   <java-symbol type="style" name="TextAppearance.DeviceDefault.Notification.Title" />
   <java-symbol type="style" name="TextAppearance.DeviceDefault.Notification.Info" />
 
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/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/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 52e996c..2d117f7 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -829,6 +829,34 @@
     }
 
     /**
+     * A View should either vote a frame rate or a frame rate category instead of both.
+     */
+    @Test
+    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+    public void votePreferredFrameRate_voteFrameRateOnly() {
+        View view = new View(sContext);
+        float frameRate = 20;
+        attachViewToWindow(view);
+        sInstrumentation.waitForIdleSync();
+
+        ViewRootImpl viewRootImpl = view.getViewRootImpl();
+        sInstrumentation.runOnMainSync(() -> {
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+                    FRAME_RATE_CATEGORY_NO_PREFERENCE);
+
+            view.setRequestedFrameRate(frameRate);
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+                    FRAME_RATE_CATEGORY_NO_PREFERENCE);
+            assertEquals(viewRootImpl.getPreferredFrameRate(), frameRate, 0.1);
+
+            view.setRequestedFrameRate(view.REQUESTED_FRAME_RATE_CATEGORY_LOW);
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
+        });
+    }
+
+    /**
      * Test the logic of infrequent layer:
      * - NORMAL for infrequent update: FT2-FT1 > 100 && FT3-FT2 > 100.
      * - HIGH/NORMAL based on size for frequent update: (FT3-FT2) + (FT2 - FT1) < 100.
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/platform.xml b/data/etc/platform.xml
index 13d38d2..9d1e507 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -124,6 +124,10 @@
         <group gid="security_log_writer" />
     </permission>
 
+    <permission name="android.permission.MANAGE_VIRTUAL_MACHINE">
+        <group gid="virtualmachine" />
+    </permission>
+
     <!-- These are permissions that were mapped to gids but we need
          to keep them here until an upgrade from L to the current
          version is to be supported. These permissions are built-in
diff --git a/data/etc/preinstalled-packages-platform.xml b/data/etc/preinstalled-packages-platform.xml
index bf60944..7823277 100644
--- a/data/etc/preinstalled-packages-platform.xml
+++ b/data/etc/preinstalled-packages-platform.xml
@@ -108,6 +108,7 @@
         <install-in user-type="FULL" />
         <install-in user-type="PROFILE" />
         <do-not-install-in user-type="android.os.usertype.profile.CLONE" />
+        <do-not-install-in user-type="android.os.usertype.profile.PRIVATE" />
     </install-in-user-type>
 
     <!--  Settings (Settings app) -->
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/ktfmt_includes.txt b/ktfmt_includes.txt
index e4bf4c2..a926fcb 100644
--- a/ktfmt_includes.txt
+++ b/ktfmt_includes.txt
@@ -507,7 +507,7 @@
 -packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
--packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
+-packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
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/back/CrossActivityBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
index 160f922..55982dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
@@ -310,12 +310,16 @@
         float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top);
         float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width());
         float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height());
-        float alpha = mapRange(progress, mEnteringProgress, 1.0f);
-
+        float alpha = mapRange(progress, getPreCommitEnteringAlpha(), 1.0f);
         mEnteringRect.set(left, top, left + width, top + height);
         applyTransform(mEnteringTarget.leash, mEnteringRect, alpha);
     }
 
+    private float getPreCommitEnteringAlpha() {
+        return Math.max(smoothstep(ENTER_ALPHA_THRESHOLD, 0.7f, mEnteringProgress),
+                MIN_WINDOW_ALPHA);
+    }
+
     private float getEnteringProgress() {
         return mEnteringProgress * SCALE_FACTOR;
     }
@@ -325,9 +329,7 @@
         if (mEnteringTarget != null && mEnteringTarget.leash != null) {
             transformWithProgress(
                     mEnteringProgress,
-                    Math.max(
-                            smoothstep(ENTER_ALPHA_THRESHOLD, 0.7f, mEnteringProgress),
-                            MIN_WINDOW_ALPHA),  /* alpha */
+                    getPreCommitEnteringAlpha(),
                     mEnteringTarget.leash,
                     mEnteringRect,
                     -mWindowXShift,
@@ -336,6 +338,11 @@
         }
     }
 
+    private float getPreCommitLeavingAlpha() {
+        return Math.max(1 - smoothstep(0, ENTER_ALPHA_THRESHOLD, mLeavingProgress),
+                MIN_WINDOW_ALPHA);
+    }
+
     private float getLeavingProgress() {
         return mLeavingProgress * SCALE_FACTOR;
     }
@@ -345,9 +352,7 @@
         if (mClosingTarget != null && mClosingTarget.leash != null) {
             transformWithProgress(
                     mLeavingProgress,
-                    Math.max(
-                            1 - smoothstep(0, ENTER_ALPHA_THRESHOLD, mLeavingProgress),
-                            MIN_WINDOW_ALPHA),
+                    getPreCommitLeavingAlpha(),
                     mClosingTarget.leash,
                     mClosingRect,
                     0,
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..da530d7 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
@@ -53,8 +53,6 @@
 import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
 import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
 import com.android.wm.shell.common.bubbles.BubbleInfo;
-import com.android.wm.shell.taskview.TaskView;
-import com.android.wm.shell.taskview.TaskViewTaskController;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -403,13 +401,9 @@
      * Returns the existing {@link #mBubbleTaskView} if it's not {@code null}. Otherwise a new
      * instance of {@link BubbleTaskView} is created.
      */
-    public BubbleTaskView getOrCreateBubbleTaskView(Context context, BubbleController controller) {
+    public BubbleTaskView getOrCreateBubbleTaskView(BubbleTaskViewFactory taskViewFactory) {
         if (mBubbleTaskView == null) {
-            TaskViewTaskController taskViewTaskController = new TaskViewTaskController(context,
-                    controller.getTaskOrganizer(),
-                    controller.getTaskViewTransitions(), controller.getSyncTransactionQueue());
-            TaskView taskView = new TaskView(context, taskViewTaskController);
-            mBubbleTaskView = new BubbleTaskView(taskView, controller.getMainExecutor());
+            mBubbleTaskView = taskViewFactory.create();
         }
         return mBubbleTaskView;
     }
@@ -514,14 +508,18 @@
      *
      * @param callback the callback to notify one the bubble is ready to be displayed.
      * @param context the context for the bubble.
-     * @param controller the bubble controller.
+     * @param expandedViewManager the bubble expanded view manager.
+     * @param taskViewFactory the task view factory used to create the task view for the bubble.
+     * @param positioner the bubble positioner.
      * @param stackView the view the bubble is added to, iff showing as floating.
      * @param layerView the layer the bubble is added to, iff showing in the bubble bar.
-     * @param iconFactory the icon factory use to create images for the bubble.
+     * @param iconFactory the icon factory used to create images for the bubble.
      */
     void inflate(BubbleViewInfoTask.Callback callback,
             Context context,
-            BubbleController controller,
+            BubbleExpandedViewManager expandedViewManager,
+            BubbleTaskViewFactory taskViewFactory,
+            BubblePositioner positioner,
             @Nullable BubbleStackView stackView,
             @Nullable BubbleBarLayerView layerView,
             BubbleIconFactory iconFactory,
@@ -531,7 +529,9 @@
         }
         mInflationTask = new BubbleViewInfoTask(this,
                 context,
-                controller,
+                expandedViewManager,
+                taskViewFactory,
+                positioner,
                 stackView,
                 layerView,
                 iconFactory,
@@ -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 0aa8959..5c6f73f 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
@@ -113,6 +113,7 @@
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.taskview.TaskView;
+import com.android.wm.shell.taskview.TaskViewTaskController;
 import com.android.wm.shell.taskview.TaskViewTransitions;
 import com.android.wm.shell.transition.Transitions;
 
@@ -190,6 +191,8 @@
     private final ShellCommandHandler mShellCommandHandler;
     private final IWindowManager mWmService;
     private final BubbleProperties mBubbleProperties;
+    private final BubbleTaskViewFactory mBubbleTaskViewFactory;
+    private final BubbleExpandedViewManager mExpandedViewManager;
 
     // Used to post to main UI thread
     private final ShellExecutor mMainExecutor;
@@ -333,6 +336,16 @@
         mWmService = wmService;
         mBubbleProperties = bubbleProperties;
         shellInit.addInitCallback(this::onInit, this);
+        mBubbleTaskViewFactory = new BubbleTaskViewFactory() {
+            @Override
+            public BubbleTaskView create() {
+                TaskViewTaskController taskViewTaskController = new TaskViewTaskController(
+                        context, organizer, taskViewTransitions, syncQueue);
+                TaskView taskView = new TaskView(context, taskViewTaskController);
+                return new BubbleTaskView(taskView, mainExecutor);
+            }
+        };
+        mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(this);
     }
 
     private void registerOneHandedState(OneHandedController oneHanded) {
@@ -802,7 +815,13 @@
         try {
             mAddedToWindowManager = true;
             registerBroadcastReceiver();
-            mBubbleData.getOverflow().initialize(this, isShowingAsBubbleBar());
+            if (isShowingAsBubbleBar()) {
+                mBubbleData.getOverflow().initializeForBubbleBar(
+                        mExpandedViewManager, mBubblePositioner);
+            } else {
+                mBubbleData.getOverflow().initialize(
+                        mExpandedViewManager, mStackView, mBubblePositioner);
+            }
             // (TODO: b/273314541) some duplication in the inset listener
             if (isShowingAsBubbleBar()) {
                 mWindowManager.addView(mLayerView, mWmLayoutParams);
@@ -984,7 +1003,9 @@
         for (Bubble b : mBubbleData.getBubbles()) {
             b.inflate(null /* callback */,
                     mContext,
-                    this,
+                    mExpandedViewManager,
+                    mBubbleTaskViewFactory,
+                    mBubblePositioner,
                     mStackView,
                     mLayerView,
                     mBubbleIconFactory,
@@ -993,7 +1014,9 @@
         for (Bubble b : mBubbleData.getOverflowBubbles()) {
             b.inflate(null /* callback */,
                     mContext,
-                    this,
+                    mExpandedViewManager,
+                    mBubbleTaskViewFactory,
+                    mBubblePositioner,
                     mStackView,
                     mLayerView,
                     mBubbleIconFactory,
@@ -1377,7 +1400,9 @@
                 bubble.inflate(
                         (b) -> mBubbleData.overflowBubble(Bubbles.DISMISS_RELOAD_FROM_DISK, bubble),
                         mContext,
-                        this,
+                        mExpandedViewManager,
+                        mBubbleTaskViewFactory,
+                        mBubblePositioner,
                         mStackView,
                         mLayerView,
                         mBubbleIconFactory,
@@ -1431,7 +1456,9 @@
             Bubble bubble = mBubbleData.getBubbles().get(i);
             bubble.inflate(callback,
                     mContext,
-                    this,
+                    mExpandedViewManager,
+                    mBubbleTaskViewFactory,
+                    mBubblePositioner,
                     mStackView,
                     mLayerView,
                     mBubbleIconFactory,
@@ -1506,8 +1533,14 @@
         // Lazy init stack view when a bubble is created
         ensureBubbleViewsAndWindowCreated();
         bubble.setInflateSynchronously(mInflateSynchronously);
-        bubble.inflate(b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade),
-                mContext, this, mStackView,  mLayerView,
+        bubble.inflate(
+                b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade),
+                mContext,
+                mExpandedViewManager,
+                mBubbleTaskViewFactory,
+                mBubblePositioner,
+                mStackView,
+                mLayerView,
                 mBubbleIconFactory,
                 false /* skipInflation */);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 6d3f0c3..6c2f925 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -37,10 +37,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.launcher3.icons.BubbleIconFactory;
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.Bubbles.DismissReason;
-import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
 import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
 import com.android.wm.shell.common.bubbles.RemovedBubble;
 
@@ -180,7 +178,7 @@
      * This interface reports changes to the state and appearance of bubbles which should be applied
      * as necessary to the UI.
      */
-    interface Listener {
+    public interface Listener {
         /** Reports changes have have occurred as a result of the most recent operation. */
         void applyUpdate(Update update);
     }
@@ -419,8 +417,10 @@
 
     /**
      * When this method is called it is expected that all info in the bubble has completed loading.
-     * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleController, BubbleStackView,
-     * BubbleBarLayerView, BubbleIconFactory, boolean)
+     * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleExpandedViewManager,
+     * BubbleTaskViewFactory, BubblePositioner, BubbleStackView,
+     * com.android.wm.shell.bubbles.bar.BubbleBarLayerView,
+     * com.android.launcher3.icons.BubbleIconFactory, boolean)
      */
     void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
         mPendingBubbles.remove(bubble.getKey()); // No longer pending once we're here
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 088660e..df9ba63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -184,7 +184,7 @@
     private boolean mIsOverflow;
     private boolean mIsClipping;
 
-    private BubbleController mController;
+    private BubbleExpandedViewManager mManager;
     private BubbleStackView mStackView;
     private BubblePositioner mPositioner;
 
@@ -261,7 +261,7 @@
                     // the bubble again so we'll just remove it.
                     Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
                             + ", " + e.getMessage() + "; removing bubble");
-                    mController.removeBubble(getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT);
+                    mManager.removeBubble(getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT);
                 }
             });
             mInitialized = true;
@@ -281,7 +281,7 @@
 
             if (mBubble != null && mBubble.isAppBubble()) {
                 // Let the controller know sooner what the taskId is.
-                mController.setAppBubbleTaskId(mBubble.getKey(), mTaskId);
+                mManager.setAppBubbleTaskId(mBubble.getKey(), mTaskId);
             }
 
             // With the task org, the taskAppeared callback will only happen once the task has
@@ -301,7 +301,7 @@
             ProtoLog.d(WM_SHELL_BUBBLES, "onTaskRemovalStarted: taskId=%d bubble=%s",
                     taskId, getBubbleKey());
             if (mBubble != null) {
-                mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
+                mManager.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
             }
             if (mTaskView != null) {
                 // Release the surface
@@ -421,17 +421,20 @@
      * Initialize {@link BubbleController} and {@link BubbleStackView} here, this method must need
      * to be called after view inflate.
      */
-    void initialize(BubbleController controller, BubbleStackView stackView, boolean isOverflow,
+    void initialize(BubbleExpandedViewManager expandedViewManager,
+            BubbleStackView stackView,
+            BubblePositioner positioner,
+            boolean isOverflow,
             @Nullable BubbleTaskView bubbleTaskView) {
-        mController = controller;
+        mManager = expandedViewManager;
         mStackView = stackView;
         mIsOverflow = isOverflow;
-        mPositioner = mController.getPositioner();
+        mPositioner = positioner;
 
         if (mIsOverflow) {
             mOverflowView = (BubbleOverflowContainerView) LayoutInflater.from(getContext()).inflate(
                     R.layout.bubble_overflow_container, null /* root */);
-            mOverflowView.setBubbleController(mController);
+            mOverflowView.initialize(expandedViewManager, positioner);
             FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
             mExpandedViewContainer.addView(mOverflowView, lp);
             mExpandedViewContainer.setLayoutParams(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
new file mode 100644
index 0000000..b0d3cc4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.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.wm.shell.bubbles
+
+/** Manager interface for bubble expanded views. */
+interface BubbleExpandedViewManager {
+
+    val overflowBubbles: List<Bubble>
+    fun setOverflowListener(listener: BubbleData.Listener)
+    fun collapseStack()
+    fun updateWindowFlagsForBackpress(intercept: Boolean)
+    fun promoteBubbleFromOverflow(bubble: Bubble)
+    fun removeBubble(key: String, reason: Int)
+    fun dismissBubble(bubble: Bubble, reason: Int)
+    fun setAppBubbleTaskId(key: String, taskId: Int)
+    fun isStackExpanded(): Boolean
+    fun isShowingAsBubbleBar(): Boolean
+
+    companion object {
+        /**
+         * Convenience function for creating a [BubbleExpandedViewManager] that delegates to the
+         * given `controller`.
+         */
+        @JvmStatic
+        fun fromBubbleController(controller: BubbleController): BubbleExpandedViewManager {
+            return object : BubbleExpandedViewManager {
+
+                override val overflowBubbles: List<Bubble>
+                    get() = controller.overflowBubbles
+
+                override fun setOverflowListener(listener: BubbleData.Listener) {
+                    controller.setOverflowListener(listener)
+                }
+
+                override fun collapseStack() {
+                    controller.collapseStack()
+                }
+
+                override fun updateWindowFlagsForBackpress(intercept: Boolean) {
+                    controller.updateWindowFlagsForBackpress(intercept)
+                }
+
+                override fun promoteBubbleFromOverflow(bubble: Bubble) {
+                    controller.promoteBubbleFromOverflow(bubble)
+                }
+
+                override fun removeBubble(key: String, reason: Int) {
+                    controller.removeBubble(key, reason)
+                }
+
+                override fun dismissBubble(bubble: Bubble, reason: Int) {
+                    controller.dismissBubble(bubble, reason)
+                }
+
+                override fun setAppBubbleTaskId(key: String, taskId: Int) {
+                    controller.setAppBubbleTaskId(key, taskId)
+                }
+
+                override fun isStackExpanded(): Boolean = controller.isStackExpanded
+
+                override fun isShowingAsBubbleBar(): Boolean = controller.isShowingAsBubbleBar
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index e5d9ace..f32974e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -56,19 +56,32 @@
     }
 
     /** Call before use and again if cleanUpExpandedState was called. */
-    fun initialize(controller: BubbleController, forBubbleBar: Boolean) {
-        if (forBubbleBar) {
-            createBubbleBarExpandedView()
-                .initialize(controller, /* isOverflow= */ true, /* bubbleTaskView= */ null)
-        } else {
-            createExpandedView()
+    fun initialize(
+        expandedViewManager: BubbleExpandedViewManager,
+        stackView: BubbleStackView,
+        positioner: BubblePositioner
+    ) {
+        createExpandedView()
                 .initialize(
-                    controller,
-                    controller.stackView,
+                        expandedViewManager,
+                    stackView,
+                    positioner,
                     /* isOverflow= */ true,
                     /* bubbleTaskView= */ null
                 )
-        }
+    }
+
+    fun initializeForBubbleBar(
+        expandedViewManager: BubbleExpandedViewManager,
+        positioner: BubblePositioner
+    ) {
+        createBubbleBarExpandedView()
+            .initialize(
+                expandedViewManager,
+                positioner,
+                /* isOverflow= */ true,
+                /* bubbleTaskView= */ null
+            )
     }
 
     fun cleanUpExpandedState() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
index 70cdc82..b06de4f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
@@ -62,7 +62,8 @@
     private ImageView mEmptyStateImage;
     private int mHorizontalMargin;
     private int mVerticalMargin;
-    private BubbleController mController;
+    private BubbleExpandedViewManager mExpandedViewManager;
+    private BubblePositioner mPositioner;
     private BubbleOverflowAdapter mAdapter;
     private RecyclerView mRecyclerView;
     private List<Bubble> mOverflowBubbles = new ArrayList<>();
@@ -70,7 +71,7 @@
     private View.OnKeyListener mKeyListener = (view, i, keyEvent) -> {
         if (keyEvent.getAction() == KeyEvent.ACTION_UP
                 && keyEvent.getKeyCode() == KeyEvent.KEYCODE_BACK) {
-            mController.collapseStack();
+            mExpandedViewManager.collapseStack();
             return true;
         }
         return false;
@@ -126,8 +127,11 @@
         setFocusableInTouchMode(true);
     }
 
-    public void setBubbleController(BubbleController controller) {
-        mController = controller;
+    /** Initializes the view. Must be called after creation. */
+    public void initialize(BubbleExpandedViewManager expandedViewManager,
+            BubblePositioner positioner) {
+        mExpandedViewManager = expandedViewManager;
+        mPositioner = positioner;
     }
 
     public void show() {
@@ -149,9 +153,9 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        if (mController != null) {
+        if (mExpandedViewManager != null) {
             // For the overflow to get key events (e.g. back press) we need to adjust the flags
-            mController.updateWindowFlagsForBackpress(true);
+            mExpandedViewManager.updateWindowFlagsForBackpress(true);
         }
         setOnKeyListener(mKeyListener);
     }
@@ -159,8 +163,8 @@
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        if (mController != null) {
-            mController.updateWindowFlagsForBackpress(false);
+        if (mExpandedViewManager != null) {
+            mExpandedViewManager.updateWindowFlagsForBackpress(false);
         }
         setOnKeyListener(null);
     }
@@ -177,15 +181,15 @@
             mRecyclerView.addItemDecoration(new OverflowItemDecoration());
         }
         mAdapter = new BubbleOverflowAdapter(getContext(), mOverflowBubbles,
-                mController::promoteBubbleFromOverflow,
-                mController.getPositioner());
+                mExpandedViewManager::promoteBubbleFromOverflow,
+                mPositioner);
         mRecyclerView.setAdapter(mAdapter);
 
         mOverflowBubbles.clear();
-        mOverflowBubbles.addAll(mController.getOverflowBubbles());
+        mOverflowBubbles.addAll(mExpandedViewManager.getOverflowBubbles());
         mAdapter.notifyDataSetChanged();
 
-        mController.setOverflowListener(mDataListener);
+        mExpandedViewManager.setOverflowListener(mDataListener);
         updateEmptyStateVisibility();
         updateTheme();
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewFactory.kt
similarity index 67%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
copy to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewFactory.kt
index 128f58b..230626f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewFactory.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,8 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.animation
+package com.android.wm.shell.bubbles
 
-import com.android.systemui.kosmos.Kosmos
-
-val Kosmos.activityLaunchAnimator by Kosmos.Fixture { ActivityLaunchAnimator() }
+/** Factory for creating [BubbleTaskView]s. */
+fun interface BubbleTaskViewFactory {
+    /** Creates a new instance of [BubbleTaskView]. */
+    fun create(): BubbleTaskView
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index 530ec5a..21b70b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -62,7 +62,7 @@
     }
 
     private final Context mContext;
-    private final BubbleController mController;
+    private final BubbleExpandedViewManager mExpandedViewManager;
     private final BubbleTaskViewHelper.Listener mListener;
     private final View mParentView;
 
@@ -142,7 +142,8 @@
                     // the bubble again so we'll just remove it.
                     Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
                             + ", " + e.getMessage() + "; removing bubble");
-                    mController.removeBubble(getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT);
+                    mExpandedViewManager.removeBubble(
+                            getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT);
                 }
                 mInitialized = true;
             });
@@ -175,7 +176,7 @@
             ProtoLog.d(WM_SHELL_BUBBLES, "onTaskRemovalStarted: taskId=%d bubble=%s",
                     taskId, getBubbleKey());
             if (mBubble != null) {
-                mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
+                mExpandedViewManager.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
             }
             if (mTaskView != null) {
                 mTaskView.release();
@@ -186,19 +187,19 @@
 
         @Override
         public void onBackPressedOnTaskRoot(int taskId) {
-            if (mTaskId == taskId && mController.isStackExpanded()) {
+            if (mTaskId == taskId && mExpandedViewManager.isStackExpanded()) {
                 mListener.onBackPressed();
             }
         }
     };
 
     public BubbleTaskViewHelper(Context context,
-            BubbleController controller,
+            BubbleExpandedViewManager expandedViewManager,
             BubbleTaskViewHelper.Listener listener,
             BubbleTaskView bubbleTaskView,
             View parent) {
         mContext = context;
-        mController = controller;
+        mExpandedViewManager = expandedViewManager;
         mListener = listener;
         mParentView = parent;
         mTaskView = bubbleTaskView.getTaskView();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 5fc10a9..69119cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -70,7 +70,9 @@
 
     private Bubble mBubble;
     private WeakReference<Context> mContext;
-    private WeakReference<BubbleController> mController;
+    private WeakReference<BubbleExpandedViewManager> mExpandedViewManager;
+    private WeakReference<BubbleTaskViewFactory> mTaskViewFactory;
+    private WeakReference<BubblePositioner> mPositioner;
     private WeakReference<BubbleStackView> mStackView;
     private WeakReference<BubbleBarLayerView> mLayerView;
     private BubbleIconFactory mIconFactory;
@@ -84,7 +86,9 @@
      */
     BubbleViewInfoTask(Bubble b,
             Context context,
-            BubbleController controller,
+            BubbleExpandedViewManager expandedViewManager,
+            BubbleTaskViewFactory taskViewFactory,
+            BubblePositioner positioner,
             @Nullable BubbleStackView stackView,
             @Nullable BubbleBarLayerView layerView,
             BubbleIconFactory factory,
@@ -93,7 +97,9 @@
             Executor mainExecutor) {
         mBubble = b;
         mContext = new WeakReference<>(context);
-        mController = new WeakReference<>(controller);
+        mExpandedViewManager = new WeakReference<>(expandedViewManager);
+        mTaskViewFactory = new WeakReference<>(taskViewFactory);
+        mPositioner = new WeakReference<>(positioner);
         mStackView = new WeakReference<>(stackView);
         mLayerView = new WeakReference<>(layerView);
         mIconFactory = factory;
@@ -109,11 +115,13 @@
             return null;
         }
         if (mLayerView.get() != null) {
-            return BubbleViewInfo.populateForBubbleBar(mContext.get(), mController.get(),
-                    mLayerView.get(), mIconFactory, mBubble, mSkipInflation);
+            return BubbleViewInfo.populateForBubbleBar(mContext.get(), mExpandedViewManager.get(),
+                    mTaskViewFactory.get(), mPositioner.get(), mLayerView.get(), mIconFactory,
+                    mBubble, mSkipInflation);
         } else {
-            return BubbleViewInfo.populate(mContext.get(), mController.get(), mStackView.get(),
-                    mIconFactory, mBubble, mSkipInflation);
+            return BubbleViewInfo.populate(mContext.get(), mExpandedViewManager.get(),
+                    mTaskViewFactory.get(), mPositioner.get(), mStackView.get(), mIconFactory,
+                    mBubble, mSkipInflation);
         }
     }
 
@@ -135,7 +143,7 @@
     }
 
     private boolean verifyState() {
-        if (mController.get().isShowingAsBubbleBar()) {
+        if (mExpandedViewManager.get().isShowingAsBubbleBar()) {
             return mLayerView.get() != null;
         } else {
             return mStackView.get() != null;
@@ -167,18 +175,23 @@
         Bitmap badgeBitmap;
 
         @Nullable
-        public static BubbleViewInfo populateForBubbleBar(Context c, BubbleController controller,
-                BubbleBarLayerView layerView, BubbleIconFactory iconFactory, Bubble b,
+        public static BubbleViewInfo populateForBubbleBar(Context c,
+                BubbleExpandedViewManager expandedViewManager,
+                BubbleTaskViewFactory taskViewFactory,
+                BubblePositioner positioner,
+                BubbleBarLayerView layerView,
+                BubbleIconFactory iconFactory,
+                Bubble b,
                 boolean skipInflation) {
             BubbleViewInfo info = new BubbleViewInfo();
 
             if (!skipInflation && !b.isInflated()) {
-                BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(c, controller);
+                BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(taskViewFactory);
                 LayoutInflater inflater = LayoutInflater.from(c);
                 info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate(
                         R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */);
                 info.bubbleBarExpandedView.initialize(
-                        controller, false /* isOverflow */, bubbleTaskView);
+                        expandedViewManager, positioner, false /* isOverflow */, bubbleTaskView);
             }
 
             if (!populateCommonInfo(info, c, b, iconFactory)) {
@@ -191,8 +204,13 @@
 
         @VisibleForTesting
         @Nullable
-        public static BubbleViewInfo populate(Context c, BubbleController controller,
-                BubbleStackView stackView, BubbleIconFactory iconFactory, Bubble b,
+        public static BubbleViewInfo populate(Context c,
+                BubbleExpandedViewManager expandedViewManager,
+                BubbleTaskViewFactory taskViewFactory,
+                BubblePositioner positioner,
+                BubbleStackView stackView,
+                BubbleIconFactory iconFactory,
+                Bubble b,
                 boolean skipInflation) {
             BubbleViewInfo info = new BubbleViewInfo();
 
@@ -201,13 +219,14 @@
                 LayoutInflater inflater = LayoutInflater.from(c);
                 info.imageView = (BadgedImageView) inflater.inflate(
                         R.layout.bubble_view, stackView, false /* attachToRoot */);
-                info.imageView.initialize(controller.getPositioner());
+                info.imageView.initialize(positioner);
 
-                BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(c, controller);
+                BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(taskViewFactory);
                 info.expandedView = (BubbleExpandedView) inflater.inflate(
                         R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
                 info.expandedView.initialize(
-                        controller, stackView, false /* isOverflow */, bubbleTaskView);
+                        expandedViewManager, stackView, positioner, false /* isOverflow */,
+                        bubbleTaskView);
             }
 
             if (!populateCommonInfo(info, c, b, iconFactory)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 73a9cf4..ebb8e3e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -36,8 +36,9 @@
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.Bubble;
-import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.BubbleExpandedViewManager;
 import com.android.wm.shell.bubbles.BubbleOverflowContainerView;
+import com.android.wm.shell.bubbles.BubblePositioner;
 import com.android.wm.shell.bubbles.BubbleTaskView;
 import com.android.wm.shell.bubbles.BubbleTaskViewHelper;
 import com.android.wm.shell.bubbles.Bubbles;
@@ -45,11 +46,7 @@
 
 import java.util.function.Supplier;
 
-/**
- * Expanded view of a bubble when it's part of the bubble bar.
- *
- * {@link BubbleController#isShowingAsBubbleBar()}
- */
+/** Expanded view of a bubble when it's part of the bubble bar. */
 public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskViewHelper.Listener {
     /**
      * The expanded view listener notifying the {@link BubbleBarLayerView} about the internal
@@ -67,7 +64,7 @@
     private static final String TAG = BubbleBarExpandedView.class.getSimpleName();
     private static final int INVALID_TASK_ID = -1;
 
-    private BubbleController mController;
+    private BubbleExpandedViewManager mManager;
     private boolean mIsOverflow;
     private BubbleTaskViewHelper mBubbleTaskViewHelper;
     private BubbleBarMenuViewController mMenuViewController;
@@ -133,20 +130,22 @@
         mMenuViewController.hideMenu(false /* animated */);
     }
 
-    /** Set the BubbleController on the view, must be called before doing anything else. */
-    public void initialize(BubbleController controller, boolean isOverflow,
+    /** Initializes the view, must be called before doing anything else. */
+    public void initialize(BubbleExpandedViewManager expandedViewManager,
+            BubblePositioner positioner,
+            boolean isOverflow,
             @Nullable BubbleTaskView bubbleTaskView) {
-        mController = controller;
+        mManager = expandedViewManager;
         mIsOverflow = isOverflow;
 
         if (mIsOverflow) {
             mOverflowView = (BubbleOverflowContainerView) LayoutInflater.from(getContext()).inflate(
                     R.layout.bubble_overflow_container, null /* root */);
-            mOverflowView.setBubbleController(mController);
+            mOverflowView.initialize(expandedViewManager, positioner);
             addView(mOverflowView);
         } else {
             mTaskView = bubbleTaskView.getTaskView();
-            mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, mController,
+            mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, expandedViewManager,
                     /* listener= */ this, bubbleTaskView,
                     /* viewParent= */ this);
             if (mTaskView.getParent() != null) {
@@ -178,13 +177,13 @@
 
             @Override
             public void onOpenAppSettings(Bubble bubble) {
-                mController.collapseStack();
+                mManager.collapseStack();
                 mContext.startActivityAsUser(bubble.getSettingsIntent(mContext), bubble.getUser());
             }
 
             @Override
             public void onDismissBubble(Bubble bubble) {
-                mController.dismissBubble(bubble, Bubbles.DISMISS_USER_REMOVED);
+                mManager.dismissBubble(bubble, Bubbles.DISMISS_USER_REMOVED);
             }
         });
         mHandleView.setOnClickListener(view -> {
@@ -279,7 +278,7 @@
         if (mMenuViewController.isMenuVisible()) {
             mMenuViewController.hideMenu(/* animated = */ true);
         } else {
-            mController.collapseStack();
+            mManager.collapseStack();
         }
     }
 
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/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..5f9195a3 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/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/bubbles/BubbleOverflowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java
index f5b0174..094af96 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java
@@ -18,7 +18,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.testing.AndroidTestingRunner;
@@ -45,18 +44,22 @@
 
     private TestableBubblePositioner mPositioner;
     private BubbleOverflow mOverflow;
+    private BubbleExpandedViewManager mExpandedViewManager;
 
     @Mock
     private BubbleController mBubbleController;
+    @Mock
+    private BubbleStackView mBubbleStackView;
 
     @Before
-    public void setUp() throws Exception {
+    public void setUp() {
         MockitoAnnotations.initMocks(this);
 
+        mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(mBubbleController);
         mPositioner = new TestableBubblePositioner(mContext,
                 mContext.getSystemService(WindowManager.class));
         when(mBubbleController.getPositioner()).thenReturn(mPositioner);
-        when(mBubbleController.getStackView()).thenReturn(mock(BubbleStackView.class));
+        when(mBubbleController.getStackView()).thenReturn(mBubbleStackView);
 
         mOverflow = new BubbleOverflow(mContext, mPositioner);
     }
@@ -65,7 +68,7 @@
     public void test_initialize_forStack() {
         assertThat(mOverflow.getExpandedView()).isNull();
 
-        mOverflow.initialize(mBubbleController, /* forBubbleBar= */ false);
+        mOverflow.initialize(mExpandedViewManager, mBubbleStackView, mPositioner);
 
         assertThat(mOverflow.getExpandedView()).isNotNull();
         assertThat(mOverflow.getExpandedView().getBubbleKey()).isEqualTo(BubbleOverflow.KEY);
@@ -74,7 +77,7 @@
 
     @Test
     public void test_initialize_forBubbleBar() {
-        mOverflow.initialize(mBubbleController, /* forBubbleBar= */ true);
+        mOverflow.initializeForBubbleBar(mExpandedViewManager, mPositioner);
 
         assertThat(mOverflow.getBubbleBarExpandedView()).isNotNull();
         assertThat(mOverflow.getExpandedView()).isNull();
@@ -82,11 +85,10 @@
 
     @Test
     public void test_cleanUpExpandedState() {
-        mOverflow.initialize(mBubbleController, /* forBubbleBar= */ false);
+        mOverflow.initialize(mExpandedViewManager, mBubbleStackView, mPositioner);
         assertThat(mOverflow.getExpandedView()).isNotNull();
 
         mOverflow.cleanUpExpandedState();
         assertThat(mOverflow.getExpandedView()).isNull();
     }
-
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index 1668e37..ae39fbc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -44,6 +44,7 @@
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.taskview.TaskView
 import com.android.wm.shell.taskview.TaskViewTransitions
 import com.android.wm.shell.transition.Transitions
 import com.google.common.truth.Truth.assertThat
@@ -55,6 +56,7 @@
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
+import java.util.concurrent.Executor
 
 /** Tests for loading / inflating views & icons for a bubble. */
 @SmallTest
@@ -65,11 +67,16 @@
     private lateinit var metadataFlagListener: Bubbles.BubbleMetadataFlagListener
     private lateinit var iconFactory: BubbleIconFactory
     private lateinit var bubble: Bubble
-
     private lateinit var bubbleController: BubbleController
     private lateinit var mainExecutor: ShellExecutor
     private lateinit var bubbleStackView: BubbleStackView
     private lateinit var bubbleBarLayerView: BubbleBarLayerView
+    private lateinit var bubblePositioner: BubblePositioner
+    private lateinit var expandedViewManager: BubbleExpandedViewManager
+
+    private val bubbleTaskViewFactory = BubbleTaskViewFactory {
+        BubbleTaskView(mock<TaskView>(), mock<Executor>())
+    }
 
     @Before
     fun setup() {
@@ -88,7 +95,7 @@
         val shellInit = ShellInit(mainExecutor)
         val shellCommandHandler = ShellCommandHandler()
         val shellController = ShellController(context, shellInit, shellCommandHandler, mainExecutor)
-        val bubblePositioner = BubblePositioner(context, windowManager)
+        bubblePositioner = BubblePositioner(context, windowManager)
         val bubbleData =
             BubbleData(
                 context,
@@ -143,6 +150,7 @@
                 bubbleController,
                 mainExecutor
             )
+        expandedViewManager = BubbleExpandedViewManager.fromBubbleController(bubbleController)
         bubbleBarLayerView = BubbleBarLayerView(context, bubbleController, bubbleData)
     }
 
@@ -152,7 +160,9 @@
         val info =
             BubbleViewInfoTask.BubbleViewInfo.populate(
                 context,
-                bubbleController,
+                expandedViewManager,
+                bubbleTaskViewFactory,
+                bubblePositioner,
                 bubbleStackView,
                 iconFactory,
                 bubble,
@@ -178,7 +188,9 @@
         val info =
             BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(
                 context,
-                bubbleController,
+                expandedViewManager,
+                bubbleTaskViewFactory,
+                bubblePositioner,
                 bubbleBarLayerView,
                 iconFactory,
                 bubble,
@@ -212,7 +224,9 @@
         val info =
             BubbleViewInfoTask.BubbleViewInfo.populateForBubbleBar(
                 context,
-                bubbleController,
+                expandedViewManager,
+                bubbleTaskViewFactory,
+                bubblePositioner,
                 bubbleBarLayerView,
                 iconFactory,
                 bubble,
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/apex/android_paint.cpp b/libs/hwui/apex/android_paint.cpp
index cc79cba..5e73e76 100644
--- a/libs/hwui/apex/android_paint.cpp
+++ b/libs/hwui/apex/android_paint.cpp
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-#include "android/graphics/paint.h"
+#include <SkBlendMode.h>
+#include <SkImageFilter.h>
+#include <hwui/Paint.h>
 
 #include "TypeCast.h"
-
-#include <hwui/Paint.h>
-#include <SkBlendMode.h>
+#include "android/graphics/paint.h"
+#include "include/effects/SkImageFilters.h"
 
 using namespace android;
 
@@ -43,6 +44,22 @@
     }
 }
 
+static sk_sp<SkImageFilter> convertImageFilter(AImageFilter imageFilter) {
+    switch (imageFilter) {
+        case AIMAGE_FILTER_DROP_SHADOW_FOR_POINTER_ICON:
+            // Material Elevation Level 1 Drop Shadow.
+            sk_sp<SkImageFilter> key_shadow = SkImageFilters::DropShadow(
+                    0.0f, 1.0f, 2.0f, 2.0f, SkColorSetARGB(0x4D, 0x00, 0x00, 0x00), nullptr);
+            sk_sp<SkImageFilter> ambient_shadow = SkImageFilters::DropShadow(
+                    0.0f, 1.0f, 3.0f, 3.0f, SkColorSetARGB(0x26, 0x00, 0x00, 0x00), nullptr);
+            return SkImageFilters::Compose(ambient_shadow, key_shadow);
+    }
+}
+
 void APaint_setBlendMode(APaint* paint, ABlendMode blendMode) {
     TypeCast::toPaint(paint)->setBlendMode(convertBlendMode(blendMode));
 }
+
+void APaint_setImageFilter(APaint* paint, AImageFilter imageFilter) {
+    TypeCast::toPaint(paint)->setImageFilter(convertImageFilter(imageFilter));
+}
diff --git a/libs/hwui/apex/include/android/graphics/paint.h b/libs/hwui/apex/include/android/graphics/paint.h
index 058db8d..36b7575 100644
--- a/libs/hwui/apex/include/android/graphics/paint.h
+++ b/libs/hwui/apex/include/android/graphics/paint.h
@@ -26,6 +26,14 @@
  */
 typedef struct APaint APaint;
 
+/**
+ * Predefined Image filter type.
+ */
+enum AImageFilter {
+    /** Drop shadow image filter for PointerIcons. */
+    AIMAGE_FILTER_DROP_SHADOW_FOR_POINTER_ICON = 0,
+};
+
 /** Bitmap pixel format. */
 enum ABlendMode {
     /** replaces destination with zero: fully transparent */
@@ -42,6 +50,8 @@
 
 ANDROID_API void APaint_setBlendMode(APaint* paint, ABlendMode blendMode);
 
+ANDROID_API void APaint_setImageFilter(APaint* paint, AImageFilter imageFilter);
+
 __END_DECLS
 
 #ifdef	__cplusplus
@@ -54,6 +64,10 @@
 
         void setBlendMode(ABlendMode blendMode) { APaint_setBlendMode(mPaint, blendMode); }
 
+        void setImageFilter(AImageFilter imageFilter) {
+            APaint_setImageFilter(mPaint, imageFilter);
+        }
+
         const APaint& get() const { return *mPaint; }
 
     private:
diff --git a/libs/hwui/libhwui.map.txt b/libs/hwui/libhwui.map.txt
index fdb2373..d03ceb4 100644
--- a/libs/hwui/libhwui.map.txt
+++ b/libs/hwui/libhwui.map.txt
@@ -32,6 +32,7 @@
     APaint_createPaint;
     APaint_destroyPaint;
     APaint_setBlendMode;
+    APaint_setImageFilter;
     ARegionIterator_acquireIterator;
     ARegionIterator_releaseIterator;
     ARegionIterator_isComplex;
diff --git a/libs/input/SpriteIcon.cpp b/libs/input/SpriteIcon.cpp
index b7e51e2..59e36e4 100644
--- a/libs/input/SpriteIcon.cpp
+++ b/libs/input/SpriteIcon.cpp
@@ -34,6 +34,9 @@
 
     graphics::Paint paint;
     paint.setBlendMode(ABLEND_MODE_SRC);
+    if (drawNativeDropShadow) {
+        paint.setImageFilter(AIMAGE_FILTER_DROP_SHADOW_FOR_POINTER_ICON);
+    }
 
     graphics::Canvas canvas(outBuffer, (int32_t)surface->getBuffersDataSpace());
     canvas.drawBitmap(bitmap, 0, 0, &paint);
diff --git a/libs/input/SpriteIcon.h b/libs/input/SpriteIcon.h
index 5f085bb..9e6cc81 100644
--- a/libs/input/SpriteIcon.h
+++ b/libs/input/SpriteIcon.h
@@ -29,16 +29,22 @@
 struct SpriteIcon {
     inline SpriteIcon() : style(PointerIconStyle::TYPE_NULL), hotSpotX(0), hotSpotY(0) {}
     inline SpriteIcon(const graphics::Bitmap& bitmap, PointerIconStyle style, float hotSpotX,
-                      float hotSpotY)
-          : bitmap(bitmap), style(style), hotSpotX(hotSpotX), hotSpotY(hotSpotY) {}
+                      float hotSpotY, bool drawNativeDropShadow)
+          : bitmap(bitmap),
+            style(style),
+            hotSpotX(hotSpotX),
+            hotSpotY(hotSpotY),
+            drawNativeDropShadow(drawNativeDropShadow) {}
 
     graphics::Bitmap bitmap;
     PointerIconStyle style;
     float hotSpotX;
     float hotSpotY;
+    bool drawNativeDropShadow;
 
     inline SpriteIcon copy() const {
-        return SpriteIcon(bitmap.copy(ANDROID_BITMAP_FORMAT_RGBA_8888), style, hotSpotX, hotSpotY);
+        return SpriteIcon(bitmap.copy(ANDROID_BITMAP_FORMAT_RGBA_8888), style, hotSpotX, hotSpotY,
+                          drawNativeDropShadow);
     }
 
     inline void reset() {
@@ -46,6 +52,7 @@
         style = PointerIconStyle::TYPE_NULL;
         hotSpotX = 0;
         hotSpotY = 0;
+        drawNativeDropShadow = false;
     }
 
     inline bool isValid() const { return bitmap.isValid() && !bitmap.isEmpty(); }
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 e9ba779..8f3f82e 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -5513,6 +5513,26 @@
 
     /**
      * @hide
+     * @return All currently registered audio policy mixes.
+     */
+    @TestApi
+    @FlaggedApi(android.media.audiopolicy.Flags.FLAG_AUDIO_MIX_TEST_API)
+    @NonNull
+    public List<android.media.audiopolicy.AudioMix> getRegisteredPolicyMixes() {
+        if (!android.media.audiopolicy.Flags.audioMixTestApi()) {
+            return Collections.emptyList();
+        }
+
+        final IAudioService service = getService();
+        try {
+            return service.getRegisteredPolicyMixes();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
      * @return true if an AudioPolicy was previously registered
      */
     @TestApi
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index f73be35f..293c561 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1984,6 +1984,9 @@
     public static native int registerPolicyMixes(ArrayList<AudioMix> mixes, boolean register);
 
     /** @hide */
+    public static native int getRegisteredPolicyMixes(@NonNull List<AudioMix> devices);
+
+    /** @hide */
     public static native int updatePolicyMixes(
             AudioMix[] mixes,
             AudioMixingRule[] updatedMixingRules);
diff --git a/media/java/android/media/FadeManagerConfiguration.java b/media/java/android/media/FadeManagerConfiguration.java
index 40b0e3e..4f1a8ee 100644
--- a/media/java/android/media/FadeManagerConfiguration.java
+++ b/media/java/android/media/FadeManagerConfiguration.java
@@ -18,6 +18,7 @@
 
 import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
 
+import android.annotation.DurationMillisLong;
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -112,17 +113,11 @@
      */
     public static final int FADE_STATE_ENABLED_DEFAULT = 1;
 
-    /**
-     * Defines the enabled state with Automotive specific configurations
-     */
-    public static final int FADE_STATE_ENABLED_AUTO = 2;
-
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = false, prefix = "FADE_STATE", value = {
             FADE_STATE_DISABLED,
             FADE_STATE_ENABLED_DEFAULT,
-            FADE_STATE_ENABLED_AUTO,
     })
     public @interface FadeStateEnum {}
 
@@ -143,7 +138,14 @@
      * @see #getFadeOutDurationForAudioAttributes(AudioAttributes)
      * @see #getFadeInDurationForAudioAttributes(AudioAttributes)
      */
-    public static final long DURATION_NOT_SET = 0;
+    public static final @DurationMillisLong long DURATION_NOT_SET = 0;
+
+    /** Defines the default fade out duration */
+    private static final @DurationMillisLong long DEFAULT_FADE_OUT_DURATION_MS = 2_000;
+
+    /** Defines the default fade in duration */
+    private static final @DurationMillisLong long DEFAULT_FADE_IN_DURATION_MS = 1_000;
+
     /** Map of Usage to Fade volume shaper configs wrapper */
     private final SparseArray<FadeVolumeShaperConfigsWrapper> mUsageToFadeWrapperMap;
     /** Map of AudioAttributes to Fade volume shaper configs wrapper */
@@ -161,14 +163,15 @@
     /** fade state */
     private final @FadeStateEnum int mFadeState;
     /** fade out duration from builder - used for creating default fade out volume shaper */
-    private final long mFadeOutDurationMillis;
+    private final @DurationMillisLong long mFadeOutDurationMillis;
     /** fade in duration from builder - used for creating default fade in volume shaper */
-    private final long mFadeInDurationMillis;
+    private final @DurationMillisLong long mFadeInDurationMillis;
     /** delay after which the offending players are faded back in */
-    private final long mFadeInDelayForOffendersMillis;
+    private final @DurationMillisLong long mFadeInDelayForOffendersMillis;
 
-    private FadeManagerConfiguration(int fadeState, long fadeOutDurationMillis,
-            long fadeInDurationMillis, long offendersFadeInDelayMillis,
+    private FadeManagerConfiguration(int fadeState, @DurationMillisLong long fadeOutDurationMillis,
+            @DurationMillisLong long fadeInDurationMillis,
+            @DurationMillisLong long offendersFadeInDelayMillis,
             @NonNull SparseArray<FadeVolumeShaperConfigsWrapper> usageToFadeWrapperMap,
             @NonNull ArrayMap<AudioAttributes, FadeVolumeShaperConfigsWrapper> attrToFadeWrapperMap,
             @NonNull IntArray fadeableUsages, @NonNull IntArray unfadeableContentTypes,
@@ -196,8 +199,6 @@
 
     /**
      * Get the fade state
-     *
-     * @return one of the {@link FadeStateEnum} state
      */
     @FadeStateEnum
     public int getFadeState() {
@@ -207,7 +208,7 @@
     /**
      * Get the list of usages that can be faded
      *
-     * @return list of {@link android.media.AudioAttributes.AttributeUsage} that shall be faded
+     * @return list of {@link android.media.AudioAttributes usages} that shall be faded
      * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
      */
     @NonNull
@@ -217,10 +218,10 @@
     }
 
     /**
-     * Get the list of {@link android.media.AudioPlaybackConfiguration.PlayerType player types}
-     * that cannot be faded
+     * Get the list of {@link android.media.AudioPlaybackConfiguration player types} that can be
+     * faded
      *
-     * @return list of {@link android.media.AudioPlaybackConfiguration.PlayerType}
+     * @return list of {@link android.media.AudioPlaybackConfiguration player types}
      * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
      */
     @NonNull
@@ -230,10 +231,9 @@
     }
 
     /**
-     * Get the list of {@link android.media.AudioAttributes.AttributeContentType content types}
-     * that cannot be faded
+     * Get the list of {@link android.media.AudioAttributes content types} that can be faded
      *
-     * @return list of {@link android.media.AudioAttributes.AttributeContentType}
+     * @return list of {@link android.media.AudioAttributes content types}
      * @throws IllegalStateExceptionif if the fade state is set to {@link #FADE_STATE_DISABLED}
      */
     @NonNull
@@ -267,15 +267,15 @@
     }
 
     /**
-     * Get the duration used to fade out players with
-     * {@link android.media.AudioAttributes.AttributeUsage}
+     * Get the duration used to fade out players with {@link android.media.AudioAttributes usage}
      *
-     * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+     * @param usage the {@link android.media.AudioAttributes usage}
      * @return duration in milliseconds if set for the usage or {@link #DURATION_NOT_SET} otherwise
      * @throws IllegalArgumentException if the usage is invalid
      * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
      */
-    public long getFadeOutDurationForUsage(int usage) {
+    @DurationMillisLong
+    public long getFadeOutDurationForUsage(@AudioAttributes.AttributeUsage int usage) {
         ensureFadingIsEnabled();
         validateUsage(usage);
         return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper(
@@ -283,15 +283,15 @@
     }
 
     /**
-     * Get the duration used to fade in players with
-     * {@link android.media.AudioAttributes.AttributeUsage}
+     * Get the duration used to fade in players with {@link android.media.AudioAttributes usage}
      *
-     * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+     * @param usage the {@link android.media.AudioAttributes usage}
      * @return duration in milliseconds if set for the usage or {@link #DURATION_NOT_SET} otherwise
      * @throws IllegalArgumentException if the usage is invalid
      * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
      */
-    public long getFadeInDurationForUsage(int usage) {
+    @DurationMillisLong
+    public long getFadeInDurationForUsage(@AudioAttributes.AttributeUsage int usage) {
         ensureFadingIsEnabled();
         validateUsage(usage);
         return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper(
@@ -300,16 +300,17 @@
 
     /**
      * Get the {@link android.media.VolumeShaper.Configuration} used to fade out players with
-     * {@link android.media.AudioAttributes.AttributeUsage}
+     * {@link android.media.AudioAttributes usage}
      *
-     * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+     * @param usage the {@link android.media.AudioAttributes usage}
      * @return {@link android.media.VolumeShaper.Configuration} if set for the usage or
-     * {@code null} otherwise
+     *     {@code null} otherwise
      * @throws IllegalArgumentException if the usage is invalid
      * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
      */
     @Nullable
-    public VolumeShaper.Configuration getFadeOutVolumeShaperConfigForUsage(int usage) {
+    public VolumeShaper.Configuration getFadeOutVolumeShaperConfigForUsage(
+            @AudioAttributes.AttributeUsage int usage) {
         ensureFadingIsEnabled();
         validateUsage(usage);
         return getVolumeShaperConfigFromWrapper(mUsageToFadeWrapperMap.get(usage),
@@ -318,16 +319,17 @@
 
     /**
      * Get the {@link android.media.VolumeShaper.Configuration} used to fade in players with
-     * {@link android.media.AudioAttributes.AttributeUsage}
+     * {@link android.media.AudioAttributes usage}
      *
-     * @param usage the {@link android.media.AudioAttributes.AttributeUsage} of player
+     * @param usage the {@link android.media.AudioAttributes usage}
      * @return {@link android.media.VolumeShaper.Configuration} if set for the usage or
-     * {@code null} otherwise
+     *     {@code null} otherwise
      * @throws IllegalArgumentException if the usage is invalid
      * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
      */
     @Nullable
-    public VolumeShaper.Configuration getFadeInVolumeShaperConfigForUsage(int usage) {
+    public VolumeShaper.Configuration getFadeInVolumeShaperConfigForUsage(
+            @AudioAttributes.AttributeUsage int usage) {
         ensureFadingIsEnabled();
         validateUsage(usage);
         return getVolumeShaperConfigFromWrapper(mUsageToFadeWrapperMap.get(usage),
@@ -339,10 +341,11 @@
      *
      * @param audioAttributes {@link android.media.AudioAttributes}
      * @return duration in milliseconds if set for the audio attributes or
-     * {@link #DURATION_NOT_SET} otherwise
+     *     {@link #DURATION_NOT_SET} otherwise
      * @throws NullPointerException if the audio attributes is {@code null}
      * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
      */
+    @DurationMillisLong
     public long getFadeOutDurationForAudioAttributes(@NonNull AudioAttributes audioAttributes) {
         ensureFadingIsEnabled();
         return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper(
@@ -354,10 +357,11 @@
      *
      * @param audioAttributes {@link android.media.AudioAttributes}
      * @return duration in milliseconds if set for the audio attributes or
-     * {@link #DURATION_NOT_SET} otherwise
+     *     {@link #DURATION_NOT_SET} otherwise
      * @throws NullPointerException if the audio attributes is {@code null}
      * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
      */
+    @DurationMillisLong
     public long getFadeInDurationForAudioAttributes(@NonNull AudioAttributes audioAttributes) {
         ensureFadingIsEnabled();
         return getDurationForVolumeShaperConfig(getVolumeShaperConfigFromWrapper(
@@ -370,7 +374,7 @@
      *
      * @param audioAttributes {@link android.media.AudioAttributes}
      * @return {@link android.media.VolumeShaper.Configuration} if set for the audio attribute or
-     * {@code null} otherwise
+     *     {@code null} otherwise
      * @throws NullPointerException if the audio attributes is {@code null}
      * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
      */
@@ -389,7 +393,7 @@
      *
      * @param audioAttributes {@link android.media.AudioAttributes}
      * @return {@link android.media.VolumeShaper.Configuration} used for fading in if set for the
-     * audio attribute or {@code null} otherwise
+     *     audio attribute or {@code null} otherwise
      * @throws NullPointerException if the audio attributes is {@code null}
      * @throws IllegalStateException if the fade state is set to {@link #FADE_STATE_DISABLED}
      */
@@ -407,7 +411,7 @@
      * configurations are defined
      *
      * @return list of {@link android.media.AudioAttributes} with valid volume shaper configs or
-     * empty list if none set.
+     *     empty list if none set.
      */
     @NonNull
     public List<AudioAttributes> getAudioAttributesWithVolumeShaperConfigs() {
@@ -417,8 +421,14 @@
     /**
      * Get the delay after which the offending players are faded back in
      *
+     * Players are categorized as offending if they do not honor audio focus state changes. For
+     * example - when an app loses audio focus, it is expected that the app stops any active
+     * player in favor of the app(s) that gained audio focus. However, if the app do not stop the
+     * audio playback, such players are termed as offenders.
+     *
      * @return delay in milliseconds
      */
+    @DurationMillisLong
     public long getFadeInDelayForOffenders() {
         return mFadeInDelayForOffendersMillis;
     }
@@ -435,8 +445,9 @@
     /**
      * Query if the usage is fadeable
      *
-     * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
-     * @return {@code true} if usage is fadeable, {@code false} otherwise
+     * @param usage the {@link android.media.AudioAttributes usage}
+     * @return {@code true} if usage is fadeable, {@code false}  when the fade state is set to
+     *     {@link #FADE_STATE_DISABLED} or if the usage is not fadeable.
      */
     public boolean isUsageFadeable(@AudioAttributes.AttributeUsage int usage) {
         if (!isFadeEnabled()) {
@@ -448,9 +459,9 @@
     /**
      * Query if the content type is unfadeable
      *
-     * @param contentType the {@link android.media.AudioAttributes.AttributeContentType}
+     * @param contentType the {@link android.media.AudioAttributes content type}
      * @return {@code true} if content type is unfadeable or if fade state is set to
-     * {@link #FADE_STATE_DISABLED}, {@code false} otherwise
+     *     {@link #FADE_STATE_DISABLED}, {@code false} otherwise
      */
     public boolean isContentTypeUnfadeable(@AudioAttributes.AttributeContentType int contentType) {
         if (!isFadeEnabled()) {
@@ -462,11 +473,11 @@
     /**
      * Query if the player type is unfadeable
      *
-     * @param playerType the {@link android.media.AudioPlaybackConfiguration} player type
+     * @param playerType the {@link android.media.AudioPlaybackConfiguration player type}
      * @return {@code true} if player type is unfadeable or if fade state is set to
-     * {@link #FADE_STATE_DISABLED}, {@code false} otherwise
+     *     {@link #FADE_STATE_DISABLED}, {@code false} otherwise
      */
-    public boolean isPlayerTypeUnfadeable(int playerType) {
+    public boolean isPlayerTypeUnfadeable(@AudioPlaybackConfiguration.PlayerType int playerType) {
         if (!isFadeEnabled()) {
             return true;
         }
@@ -478,7 +489,7 @@
      *
      * @param audioAttributes the {@link android.media.AudioAttributes}
      * @return {@code true} if audio attributes is unfadeable or if fade state is set to
-     * {@link #FADE_STATE_DISABLED}, {@code false} otherwise
+     *     {@link #FADE_STATE_DISABLED}, {@code false} otherwise
      * @throws NullPointerException if the audio attributes is {@code null}
      */
     public boolean isAudioAttributesUnfadeable(@NonNull AudioAttributes audioAttributes) {
@@ -494,7 +505,7 @@
      *
      * @param uid the uid of application
      * @return {@code true} if uid is unfadeable or if fade state is set to
-     * {@link #FADE_STATE_DISABLED}, {@code false} otherwise
+     *     {@link #FADE_STATE_DISABLED}, {@code false} otherwise
      */
     public boolean isUidUnfadeable(int uid) {
         if (!isFadeEnabled()) {
@@ -503,6 +514,20 @@
         return mUnfadeableUids.contains(uid);
     }
 
+    /**
+     * Returns the default fade out duration (in milliseconds)
+     */
+    public static @DurationMillisLong long getDefaultFadeOutDurationMillis() {
+        return DEFAULT_FADE_OUT_DURATION_MS;
+    }
+
+    /**
+     * Returns the default fade in duration (in milliseconds)
+     */
+    public static @DurationMillisLong long getDefaultFadeInDurationMillis() {
+        return DEFAULT_FADE_IN_DURATION_MS;
+    }
+
     @Override
     public String toString() {
         return "FadeManagerConfiguration { fade state = " + fadeStateToString(mFadeState)
@@ -520,7 +545,7 @@
     /**
      * Convert fade state into a human-readable string
      *
-     * @param fadeState one of the fade state in {@link FadeStateEnum}
+     * @param fadeState one of {@link #FADE_STATE_DISABLED} or {@link #FADE_STATE_ENABLED_DEFAULT}
      * @return human-readable string
      * @hide
      */
@@ -531,8 +556,6 @@
                 return "FADE_STATE_DISABLED";
             case FADE_STATE_ENABLED_DEFAULT:
                 return "FADE_STATE_ENABLED_DEFAULT";
-            case FADE_STATE_ENABLED_AUTO:
-                return "FADE_STATE_ENABLED_AUTO";
             default:
                 return "unknown fade state: " + fadeState;
         }
@@ -712,9 +735,9 @@
      *
      * <p><b>Notes:</b>
      * <ul>
-     *     <li>When fade state is set to {@link #FADE_STATE_ENABLED_DEFAULT} or
-     *     {@link #FADE_STATE_ENABLED_AUTO}, the builder expects at least one valid usage to be
-     *     set/added. Failure to do so will result in an exception during {@link #build()}</li>
+     *     <li>When fade state is set to {@link #FADE_STATE_ENABLED_DEFAULT}, the builder expects at
+     *     least one valid usage to be set/added. Failure to do so will result in an exception
+     *     during {@link #build()}</li>
      *     <li>Every usage added to the fadeable list should have corresponding volume shaper
      *     configs defined. This can be achieved by setting either the duration or volume shaper
      *     config through {@link #setFadeOutDurationForUsage(int, long)} or
@@ -741,11 +764,6 @@
         private static final long IS_FADEABLE_USAGES_FIELD_SET = 1 << 1;
         private static final long IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET = 1 << 2;
 
-        /** duration of the fade out curve */
-        private static final long DEFAULT_FADE_OUT_DURATION_MS = 2_000;
-        /** duration of the fade in curve */
-        private static final long DEFAULT_FADE_IN_DURATION_MS = 1_000;
-
         /**
          * delay after which a faded out player will be faded back in. This will be heard by the
          * user only in the case of unmuting players that didn't respect audio focus and didn't
@@ -771,9 +789,10 @@
         });
 
         private int mFadeState = FADE_STATE_ENABLED_DEFAULT;
-        private long mFadeInDelayForOffendersMillis = DEFAULT_DELAY_FADE_IN_OFFENDERS_MS;
-        private long mFadeOutDurationMillis;
-        private long mFadeInDurationMillis;
+        private @DurationMillisLong long mFadeInDelayForOffendersMillis =
+                DEFAULT_DELAY_FADE_IN_OFFENDERS_MS;
+        private @DurationMillisLong long mFadeOutDurationMillis;
+        private @DurationMillisLong long mFadeInDurationMillis;
         private long mBuilderFieldsSet;
         private SparseArray<FadeVolumeShaperConfigsWrapper> mUsageToFadeWrapperMap =
                 new SparseArray<>();
@@ -787,7 +806,8 @@
         private List<AudioAttributes> mUnfadeableAudioAttributes = new ArrayList<>();
 
         /**
-         * Constructs a new Builder with default fade out and fade in durations
+         * Constructs a new Builder with {@link #DEFAULT_FADE_OUT_DURATION_MS} and
+         * {@link #DEFAULT_FADE_IN_DURATION_MS} durations.
          */
         public Builder() {
             mFadeOutDurationMillis = DEFAULT_FADE_OUT_DURATION_MS;
@@ -800,7 +820,8 @@
          * @param fadeOutDurationMillis duration in milliseconds used for fading out
          * @param fadeInDurationMills duration in milliseconds used for fading in
          */
-        public Builder(long fadeOutDurationMillis, long fadeInDurationMills) {
+        public Builder(@DurationMillisLong long fadeOutDurationMillis,
+                @DurationMillisLong long fadeInDurationMills) {
             mFadeOutDurationMillis = fadeOutDurationMillis;
             mFadeInDurationMillis = fadeInDurationMills;
         }
@@ -830,7 +851,8 @@
         /**
          * Set the overall fade state
          *
-         * @param state one of the {@link FadeStateEnum} states
+         * @param state one of the {@link #FADE_STATE_DISABLED} or
+         *     {@link #FADE_STATE_ENABLED_DEFAULT} states
          * @return the same Builder instance
          * @throws IllegalArgumentException if the fade state is invalid
          * @see #getFadeState()
@@ -844,21 +866,22 @@
 
         /**
          * Set the {@link android.media.VolumeShaper.Configuration} used to fade out players with
-         * {@link android.media.AudioAttributes.AttributeUsage}
+         * {@link android.media.AudioAttributes usage}
          * <p>
          * This method accepts {@code null} for volume shaper config to clear a previously set
          * configuration (example, if set through
          * {@link #Builder(android.media.FadeManagerConfiguration)})
          *
-         * @param usage the {@link android.media.AudioAttributes.AttributeUsage} of target player
+         * @param usage the {@link android.media.AudioAttributes usage} of target player
          * @param fadeOutVShaperConfig the {@link android.media.VolumeShaper.Configuration} used
-         *                             to fade out players with usage
+         *     to fade out players with usage
          * @return the same Builder instance
          * @throws IllegalArgumentException if the usage is invalid
          * @see #getFadeOutVolumeShaperConfigForUsage(int)
          */
         @NonNull
-        public Builder setFadeOutVolumeShaperConfigForUsage(int usage,
+        public Builder setFadeOutVolumeShaperConfigForUsage(
+                @AudioAttributes.AttributeUsage int usage,
                 @Nullable VolumeShaper.Configuration fadeOutVShaperConfig) {
             validateUsage(usage);
             getFadeVolShaperConfigWrapperForUsage(usage)
@@ -869,21 +892,22 @@
 
         /**
          * Set the {@link android.media.VolumeShaper.Configuration} used to fade in players with
-         * {@link android.media.AudioAttributes.AttributeUsage}
+         * {@link android.media.AudioAttributes usage}
          * <p>
          * This method accepts {@code null} for volume shaper config to clear a previously set
          * configuration (example, if set through
          * {@link #Builder(android.media.FadeManagerConfiguration)})
          *
-         * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+         * @param usage the {@link android.media.AudioAttributes usage}
          * @param fadeInVShaperConfig the {@link android.media.VolumeShaper.Configuration} used
-         *                            to fade in players with usage
+         *     to fade in players with usage
          * @return the same Builder instance
          * @throws IllegalArgumentException if the usage is invalid
          * @see #getFadeInVolumeShaperConfigForUsage(int)
          */
         @NonNull
-        public Builder setFadeInVolumeShaperConfigForUsage(int usage,
+        public Builder setFadeInVolumeShaperConfigForUsage(
+                @AudioAttributes.AttributeUsage int usage,
                 @Nullable VolumeShaper.Configuration fadeInVShaperConfig) {
             validateUsage(usage);
             getFadeVolShaperConfigWrapperForUsage(usage)
@@ -894,7 +918,7 @@
 
         /**
          * Set the duration used for fading out players with
-         * {@link android.media.AudioAttributes.AttributeUsage}
+         * {@link android.media.AudioAttributes usage}
          * <p>
          * A Volume shaper configuration is generated with the provided duration and default
          * volume curve definitions. This config is then used to fade out players with given usage.
@@ -904,17 +928,18 @@
          * {@link #DURATION_NOT_SET} and sets the corresponding fade out volume shaper config to
          * {@code null}
          *
-         * @param usage the {@link android.media.AudioAttributes.AttributeUsage} of target player
+         * @param usage the {@link android.media.AudioAttributes usage} of target player
          * @param fadeOutDurationMillis positive duration in milliseconds or
-         * {@link #DURATION_NOT_SET}
+         *     {@link #DURATION_NOT_SET}
          * @return the same Builder instance
          * @throws IllegalArgumentException if the fade out duration is non-positive with the
-         * exception of {@link #DURATION_NOT_SET}
+         *     exception of {@link #DURATION_NOT_SET}
          * @see #setFadeOutVolumeShaperConfigForUsage(int, VolumeShaper.Configuration)
          * @see #getFadeOutDurationForUsage(int)
          */
         @NonNull
-        public Builder setFadeOutDurationForUsage(int usage,  long fadeOutDurationMillis) {
+        public Builder setFadeOutDurationForUsage(@AudioAttributes.AttributeUsage int usage,
+                @DurationMillisLong long fadeOutDurationMillis) {
             validateUsage(usage);
             VolumeShaper.Configuration fadeOutVShaperConfig =
                     createVolShaperConfigForDuration(fadeOutDurationMillis, /* isFadeIn= */ false);
@@ -924,7 +949,7 @@
 
         /**
          * Set the duration used for fading in players with
-         * {@link android.media.AudioAttributes.AttributeUsage}
+         * {@link android.media.AudioAttributes usage}
          * <p>
          * A Volume shaper configuration is generated with the provided duration and default
          * volume curve definitions. This config is then used to fade in players with given usage.
@@ -934,17 +959,18 @@
          * {@link #DURATION_NOT_SET} and sets the corresponding fade in volume shaper config to
          * {@code null}
          *
-         * @param usage the {@link android.media.AudioAttributes.AttributeUsage} of target player
+         * @param usage the {@link android.media.AudioAttributes usage} of target player
          * @param fadeInDurationMillis positive duration in milliseconds or
-         * {@link #DURATION_NOT_SET}
+         *     {@link #DURATION_NOT_SET}
          * @return the same Builder instance
          * @throws IllegalArgumentException if the fade in duration is non-positive with the
-         * exception of {@link #DURATION_NOT_SET}
+         *     exception of {@link #DURATION_NOT_SET}
          * @see #setFadeInVolumeShaperConfigForUsage(int, VolumeShaper.Configuration)
          * @see #getFadeInDurationForUsage(int)
          */
         @NonNull
-        public Builder setFadeInDurationForUsage(int usage,  long fadeInDurationMillis) {
+        public Builder setFadeInDurationForUsage(@AudioAttributes.AttributeUsage int usage,
+                @DurationMillisLong long fadeInDurationMillis) {
             validateUsage(usage);
             VolumeShaper.Configuration fadeInVShaperConfig =
                     createVolShaperConfigForDuration(fadeInDurationMillis, /* isFadeIn= */ true);
@@ -962,9 +988,8 @@
          *
          * @param audioAttributes the {@link android.media.AudioAttributes}
          * @param fadeOutVShaperConfig the {@link android.media.VolumeShaper.Configuration} used to
-         *                             fade out players with audio attribute
+         *     fade out players with audio attribute
          * @return the same Builder instance
-         * @throws NullPointerException if the audio attributes is {@code null}
          * @see #getFadeOutVolumeShaperConfigForAudioAttributes(AudioAttributes)
          */
         @NonNull
@@ -988,7 +1013,7 @@
          *
          * @param audioAttributes the {@link android.media.AudioAttributes}
          * @param fadeInVShaperConfig the {@link android.media.VolumeShaper.Configuration} used to
-         *                            fade in players with audio attribute
+         *     fade in players with audio attribute
          * @return the same Builder instance
          * @throws NullPointerException if the audio attributes is {@code null}
          * @see #getFadeInVolumeShaperConfigForAudioAttributes(AudioAttributes)
@@ -1017,12 +1042,12 @@
          * {@code null}
          *
          * @param audioAttributes the {@link android.media.AudioAttributes} for which the fade out
-         * duration will be set/updated/reset
+         *     duration will be set/updated/reset
          * @param fadeOutDurationMillis positive duration in milliseconds or
-         * {@link #DURATION_NOT_SET}
+         *     {@link #DURATION_NOT_SET}
          * @return the same Builder instance
          * @throws IllegalArgumentException if the fade out duration is non-positive with the
-         * exception of {@link #DURATION_NOT_SET}
+         *     exception of {@link #DURATION_NOT_SET}
          * @see #getFadeOutDurationForAudioAttributes(AudioAttributes)
          * @see #setFadeOutVolumeShaperConfigForAudioAttributes(AudioAttributes,
          * VolumeShaper.Configuration)
@@ -1030,7 +1055,7 @@
         @NonNull
         public Builder setFadeOutDurationForAudioAttributes(
                 @NonNull AudioAttributes audioAttributes,
-                long fadeOutDurationMillis) {
+                @DurationMillisLong long fadeOutDurationMillis) {
             Objects.requireNonNull(audioAttributes, "Audio attribute cannot be null");
             VolumeShaper.Configuration fadeOutVShaperConfig =
                     createVolShaperConfigForDuration(fadeOutDurationMillis, /* isFadeIn= */ false);
@@ -1039,8 +1064,7 @@
         }
 
         /**
-         * Set the duration used for fading in players of type
-         * {@link android.media.AudioAttributes}.
+         * Set the duration used for fading in players of type {@link android.media.AudioAttributes}
          * <p>
          * A Volume shaper configuration is generated with the provided duration and default
          * volume curve definitions. This config is then used to fade in players with given usage.
@@ -1051,19 +1075,19 @@
          * {@code null}
          *
          * @param audioAttributes the {@link android.media.AudioAttributes} for which the fade in
-         * duration will be set/updated/reset
+         *     duration will be set/updated/reset
          * @param fadeInDurationMillis positive duration in milliseconds or
-         * {@link #DURATION_NOT_SET}
+         *     {@link #DURATION_NOT_SET}
          * @return the same Builder instance
          * @throws IllegalArgumentException if the fade in duration is non-positive with the
-         * exception of {@link #DURATION_NOT_SET}
+         *     exception of {@link #DURATION_NOT_SET}
          * @see #getFadeInDurationForAudioAttributes(AudioAttributes)
          * @see #setFadeInVolumeShaperConfigForAudioAttributes(AudioAttributes,
          * VolumeShaper.Configuration)
          */
         @NonNull
         public Builder setFadeInDurationForAudioAttributes(@NonNull AudioAttributes audioAttributes,
-                long fadeInDurationMillis) {
+                @DurationMillisLong long fadeInDurationMillis) {
             Objects.requireNonNull(audioAttributes, "Audio attribute cannot be null");
             VolumeShaper.Configuration fadeInVShaperConfig =
                     createVolShaperConfigForDuration(fadeInDurationMillis, /* isFadeIn= */ true);
@@ -1072,22 +1096,18 @@
         }
 
         /**
-         * Set the list of {@link android.media.AudioAttributes.AttributeUsage} that can be faded
+         * Set the list of {@link android.media.AudioAttributes usage} that can be faded
          *
          * <p>This is a positive list. Players with matching usage will be considered for fading.
          * Usages that are not part of this list will not be faded
          *
-         * <p>Passing an empty list as input clears the existing list. This can be used to
-         * reset the list when using a copy constructor
-         *
          * <p><b>Warning:</b> When fade state is set to enabled, the builder expects at least one
          * usage to be set/added. Failure to do so will result in an exception during
          * {@link #build()}
          *
-         * @param usages List of the {@link android.media.AudioAttributes.AttributeUsage}
+         * @param usages List of the {@link android.media.AudioAttributes usages}
          * @return the same Builder instance
          * @throws IllegalArgumentException if the usages are invalid
-         * @throws NullPointerException if the usage list is {@code null}
          * @see #getFadeableUsages()
          */
         @NonNull
@@ -1101,9 +1121,9 @@
         }
 
         /**
-         * Add the {@link android.media.AudioAttributes.AttributeUsage} to the fadeable list
+         * Add the {@link android.media.AudioAttributes usage} to the fadeable list
          *
-         * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+         * @param usage the {@link android.media.AudioAttributes usage}
          * @return the same Builder instance
          * @throws IllegalArgumentException if the usage is invalid
          * @see #getFadeableUsages()
@@ -1120,30 +1140,23 @@
         }
 
         /**
-         * Remove the {@link android.media.AudioAttributes.AttributeUsage} from the fadeable list
-         * <p>
-         * Players of this usage type will not be faded.
+         * Clears the fadeable {@link android.media.AudioAttributes usage} list
          *
-         * @param usage the {@link android.media.AudioAttributes.AttributeUsage}
+         * <p>This can be used to reset the list when using a copy constructor
+         *
          * @return the same Builder instance
-         * @throws IllegalArgumentException if the usage is invalid
          * @see #getFadeableUsages()
          * @see #setFadeableUsages(List)
          */
         @NonNull
-        public Builder clearFadeableUsage(@AudioAttributes.AttributeUsage int usage) {
-            validateUsage(usage);
+        public Builder clearFadeableUsages() {
             setFlag(IS_FADEABLE_USAGES_FIELD_SET);
-            int index = mFadeableUsages.indexOf(usage);
-            if (index != INVALID_INDEX) {
-                mFadeableUsages.remove(index);
-            }
+            mFadeableUsages.clear();
             return this;
         }
 
         /**
-         * Set the list of {@link android.media.AudioAttributes.AttributeContentType} that can not
-         * be faded
+         * Set the list of {@link android.media.AudioAttributes content type} that can not be faded
          *
          * <p>This is a negative list. Players with matching content type of this list will not be
          * faded. Content types that are not part of this list will be considered for fading.
@@ -1151,10 +1164,9 @@
          * <p>Passing an empty list as input clears the existing list. This can be used to
          * reset the list when using a copy constructor
          *
-         * @param contentTypes list of {@link android.media.AudioAttributes.AttributeContentType}
+         * @param contentTypes list of {@link android.media.AudioAttributes content types}
          * @return the same Builder instance
          * @throws IllegalArgumentException if the content types are invalid
-         * @throws NullPointerException if the content type list is {@code null}
          * @see #getUnfadeableContentTypes()
          */
         @NonNull
@@ -1168,9 +1180,9 @@
         }
 
         /**
-         * Add the {@link android.media.AudioAttributes.AttributeContentType} to unfadeable list
+         * Add the {@link android.media.AudioAttributes content type} to unfadeable list
          *
-         * @param contentType the {@link android.media.AudioAttributes.AttributeContentType}
+         * @param contentType the {@link android.media.AudioAttributes content type}
          * @return the same Builder instance
          * @throws IllegalArgumentException if the content type is invalid
          * @see #setUnfadeableContentTypes(List)
@@ -1188,24 +1200,18 @@
         }
 
         /**
-         * Remove the {@link android.media.AudioAttributes.AttributeContentType} from the
-         * unfadeable list
+         * Clears the unfadeable {@link android.media.AudioAttributes content type} list
          *
-         * @param contentType the {@link android.media.AudioAttributes.AttributeContentType}
+         * <p>This can be used to reset the list when using a copy constructor
+         *
          * @return the same Builder instance
-         * @throws IllegalArgumentException if the content type is invalid
          * @see #setUnfadeableContentTypes(List)
          * @see #getUnfadeableContentTypes()
          */
         @NonNull
-        public Builder clearUnfadeableContentType(
-                @AudioAttributes.AttributeContentType int contentType) {
-            validateContentType(contentType);
+        public Builder clearUnfadeableContentTypes() {
             setFlag(IS_UNFADEABLE_CONTENT_TYPE_FIELD_SET);
-            int index = mUnfadeableContentTypes.indexOf(contentType);
-            if (index != INVALID_INDEX) {
-                mUnfadeableContentTypes.remove(index);
-            }
+            mUnfadeableContentTypes.clear();
             return this;
         }
 
@@ -1213,14 +1219,10 @@
          * Set the uids that cannot be faded
          *
          * <p>This is a negative list. Players with matching uid of this list will not be faded.
-         * Uids that are not part of this list shall be considered for fading
-         *
-         * <p>Passing an empty list as input clears the existing list. This can be used to
-         * reset the list when using a copy constructor
+         * Uids that are not part of this list shall be considered for fading.
          *
          * @param uids list of uids
          * @return the same Builder instance
-         * @throws NullPointerException if the uid list is {@code null}
          * @see #getUnfadeableUids()
          */
         @NonNull
@@ -1248,19 +1250,17 @@
         }
 
         /**
-         * Remove the uid from unfadeable list
+         * Clears the unfadeable uid list
          *
-         * @param uid client uid
+         * <p>This can be used to reset the list when using a copy constructor.
+         *
          * @return the same Builder instance
          * @see #setUnfadeableUids(List)
          * @see #getUnfadeableUids()
          */
         @NonNull
-        public Builder clearUnfadeableUid(int uid) {
-            int index = mUnfadeableUids.indexOf(uid);
-            if (index != INVALID_INDEX) {
-                mUnfadeableUids.remove(index);
-            }
+        public Builder clearUnfadeableUids() {
+            mUnfadeableUids.clear();
             return this;
         }
 
@@ -1270,24 +1270,19 @@
          * <p>This is a negative list. Players with matching audio attributes of this list will not
          * be faded. Audio attributes that are not part of this list shall be considered for fading.
          *
-         * <p>Passing an empty list as input clears any existing list. This can be used to
-         * reset the list when using a copy constructor
-         *
          * <p><b>Note:</b> Be cautious when adding generic audio attributes into this list as it can
-         * negatively impact fadeability decision if such an audio attribute and corresponding
-         * usage fall into opposing lists.
+         * negatively impact fadeability decision (if such an audio attribute and corresponding
+         * usage fall into opposing lists).
          * For example:
          * <pre class=prettyprint>
          *    AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build() </pre>
          * is a generic audio attribute for {@link android.media.AudioAttributes.USAGE_MEDIA}.
-         * It is an undefined behavior to have an
-         * {@link android.media.AudioAttributes.AttributeUsage} in the fadeable usage list and the
-         * corresponding generic {@link android.media.AudioAttributes} in the unfadeable list. Such
-         * cases will result in an exception during {@link #build()}
+         * It is an undefined behavior to have an {@link android.media.AudioAttributes usage} in the
+         * fadeable usage list and the corresponding generic {@link android.media.AudioAttributes}
+         * in the unfadeable list. Such cases will result in an exception during {@link #build()}.
          *
          * @param attrs list of {@link android.media.AudioAttributes}
          * @return the same Builder instance
-         * @throws NullPointerException if the audio attributes list is {@code null}
          * @see #getUnfadeableAudioAttributes()
          */
         @NonNull
@@ -1303,7 +1298,6 @@
          *
          * @param audioAttributes the {@link android.media.AudioAttributes}
          * @return the same Builder instance
-         * @throws NullPointerException if the audio attributes is {@code null}
          * @see #setUnfadeableAudioAttributes(List)
          * @see #getUnfadeableAudioAttributes()
          */
@@ -1317,19 +1311,16 @@
         }
 
         /**
-         * Remove the {@link android.media.AudioAttributes} from the unfadeable list.
+         * Clears the unfadeable {@link android.media.AudioAttributes} list.
          *
-         * @param audioAttributes the {@link android.media.AudioAttributes}
+         * <p>This can be used to reset the list when using a copy constructor.
+         *
          * @return the same Builder instance
-         * @throws NullPointerException if the audio attributes is {@code null}
          * @see #getUnfadeableAudioAttributes()
          */
         @NonNull
-        public Builder clearUnfadeableAudioAttributes(@NonNull AudioAttributes audioAttributes) {
-            Objects.requireNonNull(audioAttributes, "Audio attributes cannot be null");
-            if (mUnfadeableAudioAttributes.contains(audioAttributes)) {
-                mUnfadeableAudioAttributes.remove(audioAttributes);
-            }
+        public Builder clearUnfadeableAudioAttributes() {
+            mUnfadeableAudioAttributes.clear();
             return this;
         }
 
@@ -1345,7 +1336,7 @@
          * @see #getFadeInDelayForOffenders()
          */
         @NonNull
-        public Builder setFadeInDelayForOffenders(long delayMillis) {
+        public Builder setFadeInDelayForOffenders(@DurationMillisLong long delayMillis) {
             Preconditions.checkArgument(delayMillis >= 0, "Delay cannot be negative");
             mFadeInDelayForOffendersMillis = delayMillis;
             return this;
@@ -1469,7 +1460,6 @@
             switch(state) {
                 case FADE_STATE_DISABLED:
                 case FADE_STATE_ENABLED_DEFAULT:
-                case FADE_STATE_ENABLED_AUTO:
                     break;
                 default:
                     throw new IllegalArgumentException("Unknown fade state: " + state);
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 8dfa6be..98bd3ca 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -365,6 +365,8 @@
 
     oneway void unregisterAudioPolicyAsync(in IAudioPolicyCallback pcb);
 
+    List<AudioMix> getRegisteredPolicyMixes();
+
     void unregisterAudioPolicy(in IAudioPolicyCallback pcb);
 
     int addMixForPolicy(in AudioPolicyConfig policyConfig, in IAudioPolicyCallback pcb);
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/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index b85decc..bbe461c 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -58,6 +58,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
@@ -597,6 +598,21 @@
         setRegistration(null);
     }
 
+    /**
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    @FlaggedApi(Flags.FLAG_AUDIO_MIX_TEST_API)
+    public List<AudioMix> getMixes() {
+        if (!Flags.audioMixTestApi()) {
+            return Collections.emptyList();
+        }
+        synchronized (mLock) {
+            return List.copyOf(mConfig.getMixes());
+        }
+    }
+
     public void setRegistration(String regId) {
         synchronized (mLock) {
             mRegistrationId = regId;
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/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index e8ef464..fa9afa8 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;
@@ -104,11 +103,9 @@
             RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED })
     private @interface ResultFlags { }
 
-    private final ArrayMap<IBinder, ConnectionRecord> mConnections = new ArrayMap<>();
-    private ConnectionRecord mCurConnection;
     private final Handler mHandler = new Handler();
-    private ServiceBinder mBinder;
-    MediaSession.Token mSession;
+
+    private final ServiceState mServiceState = new ServiceState();
 
     /**
      * All the info about a connection.
@@ -140,7 +137,7 @@
             service.mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    service.mConnections.remove(callbacks.asBinder());
+                    service.mServiceState.mConnections.remove(callbacks.asBinder());
                 }
             });
         }
@@ -239,17 +236,16 @@
                     @Override
                     public void run() {
                         final IBinder b = callbacks.asBinder();
-
                         // Clear out the old subscriptions. We are getting new ones.
-                        service.mConnections.remove(b);
+                        service.mServiceState.mConnections.remove(b);
 
                         // Temporarily sets a placeholder ConnectionRecord to make
                         // getCurrentBrowserInfo() work in onGetRoot().
-                        service.mCurConnection =
+                        service.mServiceState.mCurConnection =
                                 new ConnectionRecord(
                                         service, pkg, pid, uid, rootHints, callbacks, null);
                         BrowserRoot root = service.onGetRoot(pkg, uid, rootHints);
-                        service.mCurConnection = null;
+                        service.mServiceState.mCurConnection = null;
 
                         // If they didn't return something, don't allow this client.
                         if (root == null) {
@@ -266,16 +262,18 @@
                                 ConnectionRecord connection =
                                         new ConnectionRecord(
                                                 service, pkg, pid, uid, rootHints, callbacks, root);
-                                service.mConnections.put(b, connection);
+                                service.mServiceState.mConnections.put(b, connection);
                                 b.linkToDeath(connection, 0);
-                                if (service.mSession != null) {
-                                    callbacks.onConnect(connection.root.getRootId(),
-                                            service.mSession, connection.root.getExtras());
+                                if (service.mServiceState.mSession != null) {
+                                    callbacks.onConnect(
+                                            connection.root.getRootId(),
+                                            service.mServiceState.mSession,
+                                            connection.root.getExtras());
                                 }
                             } catch (RemoteException ex) {
                                 Log.w(TAG, "Calling onConnect() failed. Dropping client. "
                                         + "pkg=" + pkg);
-                                service.mConnections.remove(b);
+                                service.mServiceState.mConnections.remove(b);
                             }
                         }
                     }
@@ -295,7 +293,7 @@
                         final IBinder b = callbacks.asBinder();
 
                         // Clear out the old subscriptions. We are getting new ones.
-                        final ConnectionRecord old = service.mConnections.remove(b);
+                        final ConnectionRecord old = service.mServiceState.mConnections.remove(b);
                         if (old != null) {
                             // TODO
                             old.callbacks.asBinder().unlinkToDeath(old, 0);
@@ -323,7 +321,7 @@
                         final IBinder b = callbacks.asBinder();
 
                         // Get the record for the connection
-                        final ConnectionRecord connection = service.mConnections.get(b);
+                        ConnectionRecord connection = service.mServiceState.mConnections.get(b);
                         if (connection == null) {
                             Log.w(TAG, "addSubscription for callback that isn't registered id="
                                     + id);
@@ -354,7 +352,7 @@
                 public void run() {
                     final IBinder b = callbacks.asBinder();
 
-                    ConnectionRecord connection = service.mConnections.get(b);
+                    ConnectionRecord connection = service.mServiceState.mConnections.get(b);
                     if (connection == null) {
                         Log.w(TAG, "removeSubscription for callback that isn't registered id="
                                 + id);
@@ -380,7 +378,7 @@
                 @Override
                 public void run() {
                     final IBinder b = callbacks.asBinder();
-                    ConnectionRecord connection = service.mConnections.get(b);
+                    ConnectionRecord connection = service.mServiceState.mConnections.get(b);
                     if (connection == null) {
                         Log.w(TAG, "getMediaItem for callback that isn't registered id=" + mediaId);
                         return;
@@ -394,13 +392,13 @@
     @Override
     public void onCreate() {
         super.onCreate();
-        mBinder = new ServiceBinder(this);
+        mServiceState.mBinder = new ServiceBinder(this);
     }
 
     @Override
     public IBinder onBind(Intent intent) {
         if (SERVICE_INTERFACE.equals(intent.getAction())) {
-            return mBinder;
+            return mServiceState.mBinder;
         }
         return null;
     }
@@ -525,14 +523,14 @@
         if (token == null) {
             throw new IllegalArgumentException("Session token may not be null.");
         }
-        if (mSession != null) {
+        if (mServiceState.mSession != null) {
             throw new IllegalStateException("The session token has already been set.");
         }
-        mSession = token;
+        mServiceState.mSession = token;
         mHandler.post(new Runnable() {
             @Override
             public void run() {
-                Iterator<ConnectionRecord> iter = mConnections.values().iterator();
+                Iterator<ConnectionRecord> iter = mServiceState.mConnections.values().iterator();
                 while (iter.hasNext()) {
                     ConnectionRecord connection = iter.next();
                     try {
@@ -552,7 +550,7 @@
      * or if it has been destroyed.
      */
     public @Nullable MediaSession.Token getSessionToken() {
-        return mSession;
+        return mServiceState.mSession;
     }
 
     /**
@@ -568,11 +566,12 @@
      * @see MediaBrowserService.BrowserRoot#EXTRA_SUGGESTED
      */
     public final Bundle getBrowserRootHints() {
-        if (mCurConnection == null) {
+        ConnectionRecord curConnection = mServiceState.mCurConnection;
+        if (curConnection == null) {
             throw new IllegalStateException("This should be called inside of onGetRoot or"
                     + " onLoadChildren or onLoadItem methods");
         }
-        return mCurConnection.rootHints == null ? null : new Bundle(mCurConnection.rootHints);
+        return curConnection.rootHints == null ? null : new Bundle(curConnection.rootHints);
     }
 
     /**
@@ -583,11 +582,12 @@
      * @see MediaSessionManager#isTrustedForMediaControl(RemoteUserInfo)
      */
     public final RemoteUserInfo getCurrentBrowserInfo() {
-        if (mCurConnection == null) {
+        ConnectionRecord curConnection = mServiceState.mCurConnection;
+        if (curConnection == null) {
             throw new IllegalStateException("This should be called inside of onGetRoot or"
                     + " onLoadChildren or onLoadItem methods");
         }
-        return new RemoteUserInfo(mCurConnection.pkg, mCurConnection.pid, mCurConnection.uid);
+        return new RemoteUserInfo(curConnection.pkg, curConnection.pid, curConnection.uid);
     }
 
     /**
@@ -627,8 +627,8 @@
         mHandler.post(new Runnable() {
             @Override
             public void run() {
-                for (IBinder binder : mConnections.keySet()) {
-                    ConnectionRecord connection = mConnections.get(binder);
+                for (IBinder binder : mServiceState.mConnections.keySet()) {
+                    ConnectionRecord connection = mServiceState.mConnections.get(binder);
                     List<Pair<IBinder, Bundle>> callbackList =
                             connection.subscriptions.get(parentId);
                     if (callbackList != null) {
@@ -718,7 +718,7 @@
                 new Result<List<MediaBrowser.MediaItem>>(parentId) {
             @Override
             void onResultSent(List<MediaBrowser.MediaItem> list, @ResultFlags int flag) {
-                if (mConnections.get(connection.callbacks.asBinder()) != connection) {
+                if (mServiceState.mConnections.get(connection.callbacks.asBinder()) != connection) {
                     if (DBG) {
                         Log.d(TAG, "Not sending onLoadChildren result for connection that has"
                                 + " been disconnected. pkg=" + connection.pkg + " id=" + parentId);
@@ -728,7 +728,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;
@@ -748,13 +748,13 @@
             }
         };
 
-        mCurConnection = connection;
+        mServiceState.mCurConnection = connection;
         if (options == null) {
             onLoadChildren(parentId, result);
         } else {
             onLoadChildren(parentId, result, options);
         }
-        mCurConnection = null;
+        mServiceState.mCurConnection = null;
 
         if (!result.isDone()) {
             throw new IllegalStateException("onLoadChildren must call detach() or sendResult()"
@@ -762,34 +762,13 @@
         }
     }
 
-    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 =
                 new Result<MediaBrowser.MediaItem>(itemId) {
             @Override
             void onResultSent(MediaBrowser.MediaItem item, @ResultFlags int flag) {
-                if (mConnections.get(connection.callbacks.asBinder()) != connection) {
+                if (mServiceState.mConnections.get(connection.callbacks.asBinder()) != connection) {
                     if (DBG) {
                         Log.d(TAG, "Not sending onLoadItem result for connection that has"
                                 + " been disconnected. pkg=" + connection.pkg + " id=" + itemId);
@@ -806,9 +785,9 @@
             }
         };
 
-        mCurConnection = connection;
+        mServiceState.mCurConnection = connection;
         onLoadItem(itemId, result);
-        mCurConnection = null;
+        mServiceState.mCurConnection = null;
 
         if (!result.isDone()) {
             throw new IllegalStateException("onLoadItem must call detach() or sendResult()"
@@ -903,4 +882,22 @@
             return mExtras;
         }
     }
+
+    /**
+     * Holds all state associated with {@link #mSession}.
+     *
+     * <p>This class decouples the state associated with the session from the lifecycle of the
+     * service. This allows us to put the service in a valid state once the session is released
+     * (which is an irrecoverable invalid state). More details about this in b/185136506.
+     */
+    private static class ServiceState {
+
+        // Fields accessed from any caller thread.
+        @Nullable private MediaSession.Token mSession;
+        @Nullable private ServiceBinder mBinder;
+
+        // Fields accessed from mHandler only.
+        @NonNull private final ArrayMap<IBinder, ConnectionRecord> mConnections = new ArrayMap<>();
+        @Nullable private ConnectionRecord mCurConnection;
+    }
 }
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
index f105ae9..236b1fd 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
@@ -45,8 +45,10 @@
 @RunWith(AndroidJUnit4.class)
 @RequiresFlagsEnabled(FLAG_ENABLE_FADE_MANAGER_CONFIGURATION)
 public final class FadeManagerConfigurationUnitTest {
-    private static final long DEFAULT_FADE_OUT_DURATION_MS = 2_000;
-    private static final long DEFAULT_FADE_IN_DURATION_MS = 1_000;
+    private static final long DEFAULT_FADE_OUT_DURATION_MS =
+            FadeManagerConfiguration.getDefaultFadeOutDurationMillis();
+    private static final long DEFAULT_FADE_IN_DURATION_MS =
+            FadeManagerConfiguration.getDefaultFadeInDurationMillis();
     private static final long TEST_FADE_OUT_DURATION_MS = 1_500;
     private static final long TEST_FADE_IN_DURATION_MS = 750;
     private static final int TEST_INVALID_USAGE = -10;
@@ -259,16 +261,6 @@
     }
 
     @Test
-    public void testSetFadeState_toEnableAuto() {
-        final int fadeStateAuto = FadeManagerConfiguration.FADE_STATE_ENABLED_AUTO;
-        FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
-                .setFadeState(fadeStateAuto).build();
-
-        expect.withMessage("Fade state when enabled for audio").that(fmc.getFadeState())
-                .isEqualTo(fadeStateAuto);
-    }
-
-    @Test
     public void testSetFadeState_toInvalid_fails() {
         IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
                 new FadeManagerConfiguration.Builder()
@@ -310,13 +302,13 @@
     }
 
     @Test
-    public void testSetFadeVolShaperConfig_withNullVolumeShaper_getsNull() {
+    public void testSetFadeOutVolShaperConfig_withNullVolumeShaper_getsNull() {
         FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder(mFmc)
                 .setFadeOutVolumeShaperConfigForAudioAttributes(TEST_MEDIA_AUDIO_ATTRIBUTE,
                         /* VolumeShaper.Configuration= */ null)
                 .setFadeInVolumeShaperConfigForAudioAttributes(TEST_MEDIA_AUDIO_ATTRIBUTE,
                         /* VolumeShaper.Configuration= */ null)
-                .clearFadeableUsage(AudioAttributes.USAGE_MEDIA).build();
+                .clearFadeableUsages().addFadeableUsage(AudioAttributes.USAGE_MEDIA).build();
 
         expect.withMessage("Fade out volume shaper config set with null value")
                 .that(fmc.getFadeOutVolumeShaperConfigForAudioAttributes(
@@ -547,31 +539,13 @@
     }
 
     @Test
-    public void testClearFadeableUsage() {
-        final int usageToClear = AudioAttributes.USAGE_MEDIA;
-        List<Integer> updatedUsages = new ArrayList<>(mFmc.getFadeableUsages());
-        updatedUsages.remove((Integer) usageToClear);
+    public void testClearFadeableUsages() {
+        FadeManagerConfiguration updatedFmc = new FadeManagerConfiguration.Builder(mFmc)
+                .clearFadeableUsages().addFadeableUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
+                .build();
 
-        FadeManagerConfiguration updatedFmc = new FadeManagerConfiguration
-                .Builder(mFmc).clearFadeableUsage(usageToClear).build();
-
-        expect.withMessage("Clear fadeable usage").that(updatedFmc.getFadeableUsages())
-                .containsExactlyElementsIn(updatedUsages);
-    }
-
-    @Test
-    public void testClearFadeableUsage_withInvalidUsage_fails() {
-        FadeManagerConfiguration.Builder fmcBuilder = new FadeManagerConfiguration.Builder(mFmc);
-
-        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
-                fmcBuilder.clearFadeableUsage(TEST_INVALID_USAGE)
-        );
-
-        FadeManagerConfiguration fmc = fmcBuilder.build();
-        expect.withMessage("Clear invalid usage").that(thrown).hasMessageThat()
-                .contains("Invalid usage");
-        expect.withMessage("Fadeable usages").that(fmc.getFadeableUsages())
-                .containsExactlyElementsIn(mFmc.getFadeableUsages());
+        expect.withMessage("Clear fadeable usages").that(updatedFmc.getFadeableUsages())
+                .containsExactlyElementsIn(List.of(AudioAttributes.USAGE_VOICE_COMMUNICATION));
     }
 
     @Test
@@ -673,7 +647,7 @@
     }
 
     @Test
-    public void testClearUnfadeableContentType() {
+    public void testClearUnfadeableContentTypes() {
         List<Integer> unfadeableContentTypes = new ArrayList<>(Arrays.asList(
                 AudioAttributes.CONTENT_TYPE_MOVIE,
                 AudioAttributes.CONTENT_TYPE_SONIFICATION
@@ -682,23 +656,10 @@
 
         FadeManagerConfiguration updatedFmc = new FadeManagerConfiguration.Builder()
                 .setUnfadeableContentTypes(unfadeableContentTypes)
-                .clearUnfadeableContentType(contentTypeToClear).build();
+                .clearUnfadeableContentTypes().build();
 
-        unfadeableContentTypes.remove((Integer) contentTypeToClear);
         expect.withMessage("Unfadeable content types").that(updatedFmc.getUnfadeableContentTypes())
-                .containsExactlyElementsIn(unfadeableContentTypes);
-    }
-
-    @Test
-    public void testClearUnfadeableContentType_withInvalidContentType_fails() {
-        FadeManagerConfiguration.Builder fmcBuilder = new FadeManagerConfiguration.Builder(mFmc);
-
-        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () ->
-                fmcBuilder.clearUnfadeableContentType(TEST_INVALID_CONTENT_TYPE).build()
-        );
-
-        expect.withMessage("Invalid content type exception").that(thrown).hasMessageThat()
-                .contains("Invalid content type");
+                .isEmpty();
     }
 
     @Test
@@ -735,7 +696,7 @@
     }
 
     @Test
-    public void testClearUnfadebaleUid() {
+    public void testClearUnfadebaleUids() {
         final List<Integer> unfadeableUids = List.of(
                 TEST_UID_1,
                 TEST_UID_2
@@ -744,10 +705,9 @@
                 .setUnfadeableUids(unfadeableUids).build();
 
         FadeManagerConfiguration updatedFmc = new FadeManagerConfiguration.Builder(fmc)
-                .clearUnfadeableUid(TEST_UID_1).build();
+                .clearUnfadeableUids().build();
 
-        expect.withMessage("Unfadeable uids").that(updatedFmc.getUnfadeableUids())
-                .isEqualTo(List.of(TEST_UID_2));
+        expect.withMessage("Unfadeable uids").that(updatedFmc.getUnfadeableUids()).isEmpty();
     }
 
     @Test
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/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
index 9a2cf61..e7d1072 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
@@ -40,7 +40,7 @@
             Log.d(TAG, "Received UI cancel request, shouldShowCancellationUi: $this")
         }
         if (showCancel) {
-            val appLabel = packageManager.appLabel(cancelUiRequest.appPackageName)
+            val appLabel = packageManager.appLabel(cancelUiRequest.packageName)
             if (appLabel == null) {
                 Log.d(TAG, "Received UI cancel request with an invalid package name.")
                 null
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 0ccb07a..3097387 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,6 +106,11 @@
             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
@@ -113,7 +119,7 @@
 
         val cancellationRequest = getCancelUiRequest(intent)
         val cancelUiRequestState = cancellationRequest?.let {
-            CancelUiRequestState(getAppLabel(context.getPackageManager(), it.appPackageName))
+            CancelUiRequestState(getAppLabel(context.getPackageManager(), it.packageName))
         }
 
         initialUiState = when (requestInfo?.type) {
@@ -200,7 +206,7 @@
     }
 
     fun onCancel(cancelCode: Int) {
-        sendCancellationCode(cancelCode, requestInfo?.token, resultReceiver)
+        sendCancellationCode(cancelCode, requestInfo?.token, resultReceiver, finalResponseReceiver)
     }
 
     fun onOptionSelected(
@@ -219,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
@@ -286,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..4771237 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -135,7 +135,7 @@
         Log.d(
             Constants.LOG_TAG, "Received UI cancellation intent. Should show cancellation" +
             " ui = $shouldShowCancellationUi")
-        val appDisplayName = getAppLabel(packageManager, cancelUiRequest.appPackageName)
+        val appDisplayName = getAppLabel(packageManager, cancelUiRequest.packageName)
         if (!shouldShowCancellationUi) {
             this.finish()
         }
@@ -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/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 6c5a984..f4da1e6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -72,7 +72,7 @@
 
     init {
         uiMetrics.logNormal(LifecycleEvent.CREDMAN_ACTIVITY_INIT,
-            credManRepo.requestInfo?.appPackageName)
+            credManRepo.requestInfo?.packageName)
     }
 
     /**************************************************************************/
@@ -107,7 +107,7 @@
         if (this.credManRepo.requestInfo?.token != credManRepo.requestInfo?.token) {
             this.uiMetrics.resetInstanceId()
             this.uiMetrics.logNormal(LifecycleEvent.CREDMAN_ACTIVITY_NEW_REQUEST,
-                credManRepo.requestInfo?.appPackageName)
+                credManRepo.requestInfo?.packageName)
         }
     }
 
@@ -189,7 +189,7 @@
     private fun onInternalError() {
         Log.w(Constants.LOG_TAG, "UI closed due to illegal internal state")
         this.uiMetrics.logNormal(LifecycleEvent.CREDMAN_ACTIVITY_INTERNAL_ERROR,
-            credManRepo.requestInfo?.appPackageName)
+            credManRepo.requestInfo?.packageName)
         credManRepo.onParsingFailureCancel()
         uiState = uiState.copy(dialogState = DialogState.COMPLETE)
     }
@@ -399,6 +399,6 @@
 
     @Composable
     fun logUiEvent(uiEventEnum: UiEventEnum) {
-        this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo?.appPackageName)
+        this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo?.packageName)
     }
 }
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 64595e2..997c45e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -195,7 +195,7 @@
                 }
             return com.android.credentialmanager.getflow.RequestDisplayInfo(
                 appName = originName?.ifEmpty { null }
-                    ?: getAppLabel(context.packageManager, requestInfo.appPackageName)
+                    ?: getAppLabel(context.packageManager, requestInfo.packageName)
                     ?: return null,
                 preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials,
                 preferIdentityDocUi = getCredentialRequest.data.getBoolean(
@@ -269,7 +269,7 @@
                 return null
             }
             val appLabel = originName?.ifEmpty { null }
-                ?: getAppLabel(context.packageManager, requestInfo.appPackageName)
+                ?: getAppLabel(context.packageManager, requestInfo.packageName)
                 ?: return null
             val createCredentialRequest = requestInfo.createCredentialRequest ?: return null
             val createCredentialRequestJetpack = CreateCredentialRequest.createFrom(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 2628f09..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
@@ -131,30 +129,7 @@
         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
-                }
-
+                Log.i(TAG, "getCandidateCredentials onResult")
                 val fillResponse = convertToFillResponse(result, request,
                     responseClientState)
                 if (fillResponse != null) {
@@ -181,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> {
@@ -275,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(
@@ -387,7 +312,7 @@
                                             presentationBuilder.build())
                                             .build())
                             .setAuthentication(pendingIntent.intentSender)
-                            .setAuthenticationExtras(fillInIntent.extras)
+                            .setCredentialFillInIntent(fillInIntent)
                             .build())
             datasetAdded = true
             i++
@@ -407,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) {
@@ -437,7 +362,8 @@
             fillResponseBuilder: FillResponse.Builder
     ) {
         val presentationBuilder = Presentations.Builder()
-                .setMenuPresentation(RemoteViewsFactory.createMoreSignInOptionsPresentation(this))
+                .setMenuPresentation(
+                        RemoteViewsFactory.createMoreSignInOptionsPresentation(this))
 
         fillResponseBuilder.addDataset(
                 Dataset.Builder()
@@ -477,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
@@ -489,7 +414,7 @@
                                         presentationBuilder.build())
                                         .build())
                         .setAuthentication(bottomSheetPendingIntent.intentSender)
-                        .setAuthenticationExtras(extraBundle)
+                        .setCredentialFillInIntent(extrasIntent)
                         .build()
         )
     }
@@ -640,7 +565,6 @@
             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!!) {
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/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 @@
 edgarwang@google.com
 evanlaird@google.com
 juliacr@google.com
-yantingyang@google.com
 ykhung@google.com
 
 # Exempt resource files (because they are in a flat directory and too hard to manage via OWNERS)
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
index f44b161..aed985e 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
@@ -14,10 +14,10 @@
   See the License for the specific language governing permissions and
   limitations under the License.
   -->
-<resources>
+<resources xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
     <style name="TextAppearance.PreferenceTitle.SettingsLib"
            parent="@android:style/TextAppearance.Material.Subhead">
-        <item name="android:textColor">@color/settingslib_text_color_primary</item>
+        <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
         <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
         <item name="android:textSize">20sp</item>
     </style>
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 b34c310..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/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt b/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt
new file mode 100644
index 0000000..2a4658b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.settingslib.media.data.repository
+
+import android.media.AudioDeviceAttributes
+import android.media.Spatializer
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
+
+interface SpatializerRepository {
+
+    /**
+     * Returns true when Spatial audio feature is supported for the [audioDeviceAttributes] and
+     * false the otherwise.
+     */
+    suspend fun isAvailableForDevice(audioDeviceAttributes: AudioDeviceAttributes): Boolean
+
+    /** Returns a list [AudioDeviceAttributes] that are compatible with spatial audio. */
+    suspend fun getCompatibleDevices(): Collection<AudioDeviceAttributes>
+
+    /** Adds a [audioDeviceAttributes] to [getCompatibleDevices] list. */
+    suspend fun addCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes)
+
+    /** Removes a [audioDeviceAttributes] to [getCompatibleDevices] list. */
+    suspend fun removeCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes)
+}
+
+class SpatializerRepositoryImpl(
+    private val spatializer: Spatializer,
+    private val backgroundContext: CoroutineContext,
+) : SpatializerRepository {
+
+    override suspend fun isAvailableForDevice(
+        audioDeviceAttributes: AudioDeviceAttributes
+    ): Boolean {
+        return withContext(backgroundContext) {
+            spatializer.isAvailableForDevice(audioDeviceAttributes)
+        }
+    }
+
+    override suspend fun getCompatibleDevices(): Collection<AudioDeviceAttributes> =
+        withContext(backgroundContext) { spatializer.compatibleAudioDevices }
+
+    override suspend fun addCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes) {
+        withContext(backgroundContext) {
+            spatializer.addCompatibleAudioDevice(audioDeviceAttributes)
+        }
+    }
+
+    override suspend fun removeCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes) {
+        withContext(backgroundContext) {
+            spatializer.removeCompatibleAudioDevice(audioDeviceAttributes)
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt
new file mode 100644
index 0000000..c3cc340
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.settingslib.media.domain.interactor
+
+import android.media.AudioDeviceAttributes
+import com.android.settingslib.media.data.repository.SpatializerRepository
+
+class SpatializerInteractor(private val repository: SpatializerRepository) {
+
+    suspend fun isAvailable(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
+        repository.isAvailableForDevice(audioDeviceAttributes)
+
+    /** Checks if spatial audio is enabled for the [audioDeviceAttributes]. */
+    suspend fun isEnabled(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
+        repository.getCompatibleDevices().contains(audioDeviceAttributes)
+
+    /** Enblaes or disables spatial audio for [audioDeviceAttributes]. */
+    suspend fun setEnabled(audioDeviceAttributes: AudioDeviceAttributes, isEnabled: Boolean) {
+        if (isEnabled) {
+            repository.addCompatibleDevice(audioDeviceAttributes)
+        } else {
+            repository.removeCompatibleDevice(audioDeviceAttributes)
+        }
+    }
+}
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/media/domain/interactor/FakeSpatializerRepository.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/FakeSpatializerRepository.kt
new file mode 100644
index 0000000..3f52f24
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/FakeSpatializerRepository.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS 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.media.domain.interactor
+
+import android.media.AudioDeviceAttributes
+import com.android.settingslib.media.data.repository.SpatializerRepository
+
+class FakeSpatializerRepository : SpatializerRepository {
+
+    private val availabilityByDevice: MutableMap<AudioDeviceAttributes, Boolean> = mutableMapOf()
+    private val compatibleDevices: MutableList<AudioDeviceAttributes> = mutableListOf()
+
+    override suspend fun isAvailableForDevice(
+        audioDeviceAttributes: AudioDeviceAttributes
+    ): Boolean = availabilityByDevice.getOrDefault(audioDeviceAttributes, false)
+
+    override suspend fun getCompatibleDevices(): Collection<AudioDeviceAttributes> =
+        compatibleDevices
+
+    override suspend fun addCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes) {
+        compatibleDevices.add(audioDeviceAttributes)
+    }
+
+    override suspend fun removeCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes) {
+        compatibleDevices.remove(audioDeviceAttributes)
+    }
+
+    fun setIsAvailable(audioDeviceAttributes: AudioDeviceAttributes, isAvailable: Boolean) {
+        availabilityByDevice[audioDeviceAttributes] = isAvailable
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/SpatializerInteractorTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/SpatializerInteractorTest.kt
new file mode 100644
index 0000000..a44baeb
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/SpatializerInteractorTest.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.settingslib.media.domain.interactor
+
+import android.media.AudioDeviceAttributes
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SpatializerInteractorTest {
+
+    private val testScope = TestScope()
+    private val underTest = SpatializerInteractor(FakeSpatializerRepository())
+
+    @Test
+    fun setEnabledFalse_isEnabled_false() {
+        testScope.runTest {
+            underTest.setEnabled(deviceAttributes, false)
+
+            assertThat(underTest.isEnabled(deviceAttributes)).isFalse()
+        }
+    }
+
+    @Test
+    fun setEnabledTrue_isEnabled_true() {
+        testScope.runTest {
+            underTest.setEnabled(deviceAttributes, true)
+
+            assertThat(underTest.isEnabled(deviceAttributes)).isTrue()
+        }
+    }
+
+    private companion object {
+        val deviceAttributes = AudioDeviceAttributes(0, 0, "test_device")
+    }
+}
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/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/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index e99fcc9..84ef6e5 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -914,6 +914,9 @@
     <!-- Permission required for Cts test ScreenRecordingCallbackTests -->
     <uses-permission android:name="android.permission.DETECT_SCREEN_RECORDING" />
 
+    <!-- Permissions required for CTS test - GrammaticalInflectionManagerTest -->
+    <uses-permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER" />
+
     <application
         android:label="@string/app_label"
         android:theme="@android:style/Theme.DeviceDefault.DayNight"
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/RingtonePickerApplication.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java
deleted file mode 100644
index 48fd4fe..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java
+++ /dev/null
@@ -1,28 +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.Application;
-
-import dagger.hilt.android.HiltAndroidApp;
-
-/**
- * The main application class for the project.
- */
-@HiltAndroidApp(Application.class)
-public class RingtonePickerApplication extends Hilt_RingtonePickerApplication {
-}
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/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index f877d7a..12e8f57 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -262,6 +262,9 @@
 
     <uses-permission android:name="android.permission.MODIFY_THEME_OVERLAY" />
 
+    <!-- Activity Manager -->
+    <uses-permission android:name="android.permission.SET_THEME_OVERLAY_CONTROLLER_READY" />
+
     <!-- accessibility -->
     <uses-permission android:name="android.permission.MODIFY_ACCESSIBILITY_DATA" />
     <uses-permission android:name="android.permission.MANAGE_ACCESSIBILITY" />
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
similarity index 78%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
rename to packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index 8194055..1b99e19 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -45,20 +45,20 @@
 import com.android.internal.policy.ScreenDecorationsUtils
 import kotlin.math.roundToInt
 
-private const val TAG = "ActivityLaunchAnimator"
+private const val TAG = "ActivityTransitionAnimator"
 
 /**
  * A class that allows activities to be started in a seamless way from a view that is transforming
  * nicely into the starting window.
  */
-class ActivityLaunchAnimator(
+class ActivityTransitionAnimator(
     /** 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,
@@ -97,10 +97,11 @@
             )
 
         // TODO(b/288507023): Remove this flag.
-        @JvmField val DEBUG_LAUNCH_ANIMATION = Build.IS_DEBUGGABLE
+        @JvmField val DEBUG_TRANSITION_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
@@ -112,13 +113,13 @@
         private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f)
 
         /** The time we wait before timing out the remote animation after starting the intent. */
-        private const val LAUNCH_TIMEOUT = 1_000L
+        private const val TRANSITION_TIMEOUT = 1_000L
 
         /**
          * The time we wait before we Log.wtf because the remote animation was neither started or
          * cancelled by WM.
          */
-        private const val LONG_LAUNCH_TIMEOUT = 5_000L
+        private const val LONG_TRANSITION_TIMEOUT = 5_000L
     }
 
     /**
@@ -133,20 +134,20 @@
     /** Top-level listener that can be used to notify all registered [listeners]. */
     private val lifecycleListener =
         object : Listener {
-            override fun onLaunchAnimationStart() {
-                listeners.forEach { it.onLaunchAnimationStart() }
+            override fun onTransitionAnimationStart() {
+                listeners.forEach { it.onTransitionAnimationStart() }
             }
 
-            override fun onLaunchAnimationEnd() {
-                listeners.forEach { it.onLaunchAnimationEnd() }
+            override fun onTransitionAnimationEnd() {
+                listeners.forEach { it.onTransitionAnimationEnd() }
             }
 
-            override fun onLaunchAnimationProgress(linearProgress: Float) {
-                listeners.forEach { it.onLaunchAnimationProgress(linearProgress) }
+            override fun onTransitionAnimationProgress(linearProgress: Float) {
+                listeners.forEach { it.onTransitionAnimationProgress(linearProgress) }
             }
 
-            override fun onLaunchAnimationCancelled() {
-                listeners.forEach { it.onLaunchAnimationCancelled() }
+            override fun onTransitionAnimationCancelled() {
+                listeners.forEach { it.onTransitionAnimationCancelled() }
             }
         }
 
@@ -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
@@ -187,7 +188,7 @@
         val callback =
             this.callback
                 ?: throw IllegalStateException(
-                    "ActivityLaunchAnimator.callback must be set before using this animator"
+                    "ActivityTransitionAnimator.callback must be set before using this animator"
                 )
         val runner = createRunner(controller)
         val runnerDelegate = runner.delegate!!
@@ -255,11 +256,11 @@
 
     private fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) {
         if (Looper.myLooper() != Looper.getMainLooper()) {
-            this.launchContainer.context.mainExecutor.execute {
+            this.transitionContainer.context.mainExecutor.execute {
                 callOnIntentStartedOnMainThread(willAnimate)
             }
         } else {
-            if (DEBUG_LAUNCH_ANIMATION) {
+            if (DEBUG_TRANSITION_ANIMATION) {
                 Log.d(
                     TAG,
                     "Calling controller.onIntentStarted(willAnimate=$willAnimate) " +
@@ -292,7 +293,7 @@
         }
     }
 
-    /** Add a [Listener] that can listen to launch animations. */
+    /** Add a [Listener] that can listen to transition animations. */
     fun addListener(listener: Listener) {
         listeners.add(listener)
     }
@@ -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 {
@@ -339,24 +340,24 @@
     }
 
     interface Listener {
-        /** Called when an activity launch animation started. */
-        fun onLaunchAnimationStart() {}
+        /** Called when an activity transition animation started. */
+        fun onTransitionAnimationStart() {}
 
         /**
-         * Called when an activity launch animation is finished. This will be called if and only if
-         * [onLaunchAnimationStart] was called earlier.
+         * Called when an activity transition animation is finished. This will be called if and only
+         * if [onTransitionAnimationStart] was called earlier.
          */
-        fun onLaunchAnimationEnd() {}
+        fun onTransitionAnimationEnd() {}
 
         /**
-         * 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.
          */
-        fun onLaunchAnimationCancelled() {}
+        fun onTransitionAnimationCancelled() {}
 
-        /** Called when an activity launch animation made progress. */
-        fun onLaunchAnimationProgress(linearProgress: Float) {}
+        /** Called when an activity transition animation made progress. */
+        fun onTransitionAnimationProgress(linearProgress: Float) {}
     }
 
     /**
@@ -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.
@@ -382,9 +383,10 @@
                 // issues.
                 if (view !is LaunchableView) {
                     throw IllegalArgumentException(
-                        "An ActivityLaunchAnimator.Controller was created from a View that does " +
-                            "not implement LaunchableView. This can lead to subtle bugs where the" +
-                            " visibility of the View we are launching from is not what we expected."
+                        "An ActivityTransitionAnimator.Controller was created from a View that " +
+                            "does not implement LaunchableView. This can lead to subtle bugs " +
+                            "where the visibility of the View we are launching from is not what " +
+                            "we expected."
                     )
                 }
 
@@ -397,7 +399,7 @@
                     return null
                 }
 
-                return GhostedViewLaunchAnimatorController(view, cujType)
+                return GhostedViewTransitionAnimatorController(view, cujType)
             }
         }
 
@@ -410,11 +412,11 @@
             get() = false
 
         /**
-         * Whether the expandable controller by this [Controller] is below the launching window that
-         * is going to be animated.
+         * Whether the expandable controller by this [Controller] is below the window that is going
+         * to be animated.
          *
-         * This should be `false` when launching an app from the shade or status bar, given that
-         * they are drawn above all apps. This is usually `true` when using this launcher in a
+         * This should be `false` when animating an app from or to the shade or status bar, given
+         * that they are drawn above all apps. This is usually `true` when using this animator in a
          * normal app or a launcher, that are drawn below the animating activity/window.
          */
         val isBelowAnimatingWindow: Boolean
@@ -427,14 +429,15 @@
         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.
+         * If this transition animation affected the occlusion state of the keyguard, WM will
+         * provide us with [newKeyguardOccludedState] so that we can set the occluded state
+         * appropriately.
          */
-        fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {}
+        fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {}
     }
 
     /**
@@ -448,24 +451,24 @@
     ) : Listener {
         var cancelled = false
 
-        override fun onLaunchAnimationStart() {
-            delegate?.onLaunchAnimationStart()
+        override fun onTransitionAnimationStart() {
+            delegate?.onTransitionAnimationStart()
         }
 
-        override fun onLaunchAnimationProgress(linearProgress: Float) {
-            delegate?.onLaunchAnimationProgress(linearProgress)
+        override fun onTransitionAnimationProgress(linearProgress: Float) {
+            delegate?.onTransitionAnimationProgress(linearProgress)
         }
 
-        override fun onLaunchAnimationEnd() {
-            delegate?.onLaunchAnimationEnd()
+        override fun onTransitionAnimationEnd() {
+            delegate?.onTransitionAnimationEnd()
             if (!cancelled) {
                 onAnimationComplete.invoke()
             }
         }
 
-        override fun onLaunchAnimationCancelled() {
+        override fun onTransitionAnimationCancelled() {
             cancelled = true
-            delegate?.onLaunchAnimationCancelled()
+            delegate?.onTransitionAnimationCancelled()
             onAnimationComplete.invoke()
         }
     }
@@ -474,12 +477,12 @@
     inner class Runner(
         controller: Controller,
         callback: Callback,
-        /** The animator to use to animate the window launch. */
-        launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR,
+        /** The animator to use to animate the window transition. */
+        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 +495,7 @@
                     controller,
                     callback,
                     DelegatingAnimationCompletionListener(listener, this::dispose),
-                    launchAnimator,
+                    transitionAnimator,
                     disableWmTimeout
                 )
         }
@@ -542,8 +545,8 @@
         private val callback: Callback,
         /** 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,
+        /** The animator to use to animate the window transition. */
+        private val transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
 
         /**
          * Whether we should disable the WindowManager timeout. This should be set to true in tests
@@ -552,10 +555,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,11 +573,11 @@
         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
-         * cancelled within [LAUNCH_TIMEOUT] milliseconds after the intent was started.
+         * A timeout to cancel the transition animation if the remote animation is not started or
+         * cancelled within [TRANSITION_TIMEOUT] milliseconds after the intent was started.
          *
          * Note that this is important to keep this a Runnable (and not a Kotlin lambda), otherwise
          * it will be automatically converted when posted and we wouldn't be able to remove it after
@@ -584,21 +587,22 @@
 
         /**
          * A long timeout to Log.wtf (signaling a bug in WM) when the remote animation wasn't
-         * started or cancelled within [LONG_LAUNCH_TIMEOUT] milliseconds after the intent was
+         * started or cancelled within [LONG_TRANSITION_TIMEOUT] milliseconds after the intent was
          * started.
          */
         private var onLongTimeout = Runnable {
             Log.wtf(
                 TAG,
-                "The remote animation was neither cancelled or started within $LONG_LAUNCH_TIMEOUT"
+                "The remote animation was neither cancelled or started within " +
+                    "$LONG_TRANSITION_TIMEOUT"
             )
         }
 
         @UiThread
         internal fun postTimeouts() {
             if (timeoutHandler != null) {
-                timeoutHandler.postDelayed(onTimeout, LAUNCH_TIMEOUT)
-                timeoutHandler.postDelayed(onLongTimeout, LONG_LAUNCH_TIMEOUT)
+                timeoutHandler.postDelayed(onTimeout, TRANSITION_TIMEOUT)
+                timeoutHandler.postDelayed(onLongTimeout, LONG_TRANSITION_TIMEOUT)
             }
         }
 
@@ -660,7 +664,7 @@
             nonApps: Array<out RemoteAnimationTarget>?,
             iCallback: IRemoteAnimationFinishedCallback?
         ) {
-            if (LaunchAnimator.DEBUG) {
+            if (TransitionAnimator.DEBUG) {
                 Log.d(TAG, "Remote animation started")
             }
 
@@ -669,14 +673,14 @@
                 Log.i(TAG, "Aborting the animation as no window is opening")
                 iCallback?.invoke()
 
-                if (DEBUG_LAUNCH_ANIMATION) {
+                if (DEBUG_TRANSITION_ANIMATION) {
                     Log.d(
                         TAG,
-                        "Calling controller.onLaunchAnimationCancelled() [no window opening]"
+                        "Calling controller.onTransitionAnimationCancelled() [no window opening]"
                     )
                 }
-                controller.onLaunchAnimationCancelled()
-                listener?.onLaunchAnimationCancelled()
+                controller.onTransitionAnimationCancelled()
+                listener?.onTransitionAnimationCancelled()
                 return
             }
 
@@ -687,7 +691,7 @@
 
             val windowBounds = window.screenSpaceBounds
             val endState =
-                LaunchAnimator.State(
+                TransitionAnimator.State(
                     top = windowBounds.top,
                     bottom = windowBounds.bottom,
                     left = windowBounds.left,
@@ -699,7 +703,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,35 +722,37 @@
             val delegate = this.controller
             val controller =
                 object : Controller by delegate {
-                    override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
-                        listener?.onLaunchAnimationStart()
+                    override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
+                        listener?.onTransitionAnimationStart()
 
-                        if (DEBUG_LAUNCH_ANIMATION) {
+                        if (DEBUG_TRANSITION_ANIMATION) {
                             Log.d(
                                 TAG,
-                                "Calling controller.onLaunchAnimationStart(isExpandingFullyAbove=" +
-                                    "$isExpandingFullyAbove) [controller=$delegate]"
+                                "Calling controller.onTransitionAnimationStart(" +
+                                    "isExpandingFullyAbove=$isExpandingFullyAbove) " +
+                                    "[controller=$delegate]"
                             )
                         }
-                        delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+                        delegate.onTransitionAnimationStart(isExpandingFullyAbove)
                     }
 
-                    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
-                        listener?.onLaunchAnimationEnd()
+                    override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+                        listener?.onTransitionAnimationEnd()
                         iCallback?.invoke()
 
-                        if (DEBUG_LAUNCH_ANIMATION) {
+                        if (DEBUG_TRANSITION_ANIMATION) {
                             Log.d(
                                 TAG,
-                                "Calling controller.onLaunchAnimationEnd(isExpandingFullyAbove=" +
-                                    "$isExpandingFullyAbove) [controller=$delegate]"
+                                "Calling controller.onTransitionAnimationEnd(" +
+                                    "isExpandingFullyAbove=$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
                     ) {
@@ -757,13 +763,13 @@
                         }
                         navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
 
-                        listener?.onLaunchAnimationProgress(linearProgress)
-                        delegate.onLaunchAnimationProgress(state, progress, linearProgress)
+                        listener?.onTransitionAnimationProgress(linearProgress)
+                        delegate.onTransitionAnimationProgress(state, progress, linearProgress)
                     }
                 }
 
             animation =
-                launchAnimator.startAnimation(
+                transitionAnimator.startAnimation(
                     controller,
                     endState,
                     windowBackgroundColor,
@@ -774,7 +780,7 @@
 
         private fun applyStateToWindow(
             window: RemoteAnimationTarget,
-            state: LaunchAnimator.State,
+            state: TransitionAnimator.State,
             linearProgress: Float,
         ) {
             if (transactionApplierView.viewRootImpl == null || !window.leash.isValid) {
@@ -825,7 +831,7 @@
             val alpha =
                 if (controller.isBelowAnimatingWindow) {
                     val windowProgress =
-                        LaunchAnimator.getProgress(
+                        TransitionAnimator.getProgress(
                             TIMINGS,
                             linearProgress,
                             TIMINGS.contentAfterFadeInDelay,
@@ -857,7 +863,7 @@
 
         private fun applyStateToNavigationBar(
             navigationBar: RemoteAnimationTarget,
-            state: LaunchAnimator.State,
+            state: TransitionAnimator.State,
             linearProgress: Float
         ) {
             if (transactionApplierView.viewRootImpl == null || !navigationBar.leash.isValid) {
@@ -868,7 +874,7 @@
             }
 
             val fadeInProgress =
-                LaunchAnimator.getProgress(
+                TransitionAnimator.getProgress(
                     TIMINGS,
                     linearProgress,
                     ANIMATION_DELAY_NAV_FADE_IN,
@@ -890,7 +896,7 @@
                     .withVisibility(true)
             } else {
                 val fadeOutProgress =
-                    LaunchAnimator.getProgress(
+                    TransitionAnimator.getProgress(
                         TIMINGS,
                         linearProgress,
                         0,
@@ -903,7 +909,7 @@
         }
 
         private fun onAnimationTimedOut() {
-            // The remote animation was cancelled by WM, so we already cancelled the launch
+            // The remote animation was cancelled by WM, so we already cancelled the transition
             // animation.
             if (cancelled) {
                 return
@@ -912,18 +918,21 @@
             Log.w(TAG, "Remote animation timed out")
             timedOut = true
 
-            if (DEBUG_LAUNCH_ANIMATION) {
-                Log.d(TAG, "Calling controller.onLaunchAnimationCancelled() [animation timed out]")
+            if (DEBUG_TRANSITION_ANIMATION) {
+                Log.d(
+                    TAG,
+                    "Calling controller.onTransitionAnimationCancelled() [animation timed out]"
+                )
             }
-            controller.onLaunchAnimationCancelled()
-            listener?.onLaunchAnimationCancelled()
+            controller.onTransitionAnimationCancelled()
+            listener?.onTransitionAnimationCancelled()
         }
 
         @UiThread
         override fun onAnimationCancelled() {
             removeTimeouts()
 
-            // The short timeout happened, so we already cancelled the launch animation.
+            // The short timeout happened, so we already cancelled the transition animation.
             if (timedOut) {
                 return
             }
@@ -933,14 +942,15 @@
 
             animation?.cancel()
 
-            if (DEBUG_LAUNCH_ANIMATION) {
+            if (DEBUG_TRANSITION_ANIMATION) {
                 Log.d(
                     TAG,
-                    "Calling controller.onLaunchAnimationCancelled() [remote animation cancelled]",
+                    "Calling controller.onTransitionAnimationCancelled() [remote animation " +
+                        "cancelled]",
                 )
             }
-            controller.onLaunchAnimationCancelled()
-            listener?.onLaunchAnimationCancelled()
+            controller.onTransitionAnimationCancelled()
+            listener?.onTransitionAnimationCancelled()
         }
 
         private fun IRemoteAnimationFinishedCallback.invoke() {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateTransitionAnimatorController.kt
similarity index 74%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt
rename to packages/SystemUI/animation/src/com/android/systemui/animation/DelegateTransitionAnimatorController.kt
index b879ba0..e246562 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateTransitionAnimatorController.kt
@@ -17,10 +17,10 @@
 package com.android.systemui.animation
 
 /**
- * A base class to easily create an implementation of [ActivityLaunchAnimator.Controller] which
+ * A base class to easily create an implementation of [ActivityTransitionAnimator.Controller] which
  * delegates most of its call to [delegate]. This is mostly useful for Java code which can't easily
  * create such a delegated class.
  */
-open class DelegateLaunchAnimatorController(
-    protected val delegate: ActivityLaunchAnimator.Controller
-) : ActivityLaunchAnimator.Controller by delegate
+open class DelegateTransitionAnimatorController(
+    protected val delegate: ActivityTransitionAnimator.Controller
+) : ActivityTransitionAnimator.Controller by delegate
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..a3b3a0a 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -58,17 +58,18 @@
     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 {
-        private val TIMINGS = ActivityLaunchAnimator.TIMINGS
+        private val TIMINGS = ActivityTransitionAnimator.TIMINGS
 
         // We use the same interpolator for X and Y axis to make sure the dialog does not move out
         // of the screen bounds during the animation.
         private val INTERPOLATORS =
-            ActivityLaunchAnimator.INTERPOLATORS.copy(
-                positionXInterpolator = ActivityLaunchAnimator.INTERPOLATORS.positionInterpolator
+            ActivityTransitionAnimator.INTERPOLATORS.copy(
+                positionXInterpolator =
+                    ActivityTransitionAnimator.INTERPOLATORS.positionInterpolator
             )
     }
 
@@ -108,21 +109,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 +271,7 @@
 
         val animatedDialog =
             AnimatedDialog(
-                launchAnimator = launchAnimator,
+                transitionAnimator = transitionAnimator,
                 callback = callback,
                 interactionJankMonitor = interactionJankMonitor,
                 controller = controller,
@@ -319,9 +320,9 @@
     }
 
     /**
-     * Create an [ActivityLaunchAnimator.Controller] that can be used to launch an activity from the
-     * dialog that contains [View]. Note that the dialog must have been shown using this animator,
-     * otherwise this method will return null.
+     * Create an [ActivityTransitionAnimator.Controller] that can be used to launch an activity from
+     * the dialog that contains [View]. Note that the dialog must have been shown using this
+     * animator, otherwise this method will return null.
      *
      * The returned controller will take care of dismissing the dialog at the right time after the
      * activity started, when the dialog to app animation is done (or when it is cancelled). If this
@@ -333,7 +334,7 @@
     fun createActivityLaunchController(
         view: View,
         cujType: Int? = null,
-    ): ActivityLaunchAnimator.Controller? {
+    ): ActivityTransitionAnimator.Controller? {
         val animatedDialog =
             openedDialogs.firstOrNull {
                 it.dialog.window?.decorView?.viewRootImpl == view.viewRootImpl
@@ -343,7 +344,7 @@
     }
 
     /**
-     * Create an [ActivityLaunchAnimator.Controller] that can be used to launch an activity from
+     * Create an [ActivityTransitionAnimator.Controller] that can be used to launch an activity from
      * [dialog]. Note that the dialog must have been shown using this animator, otherwise this
      * method will return null.
      *
@@ -357,7 +358,7 @@
     fun createActivityLaunchController(
         dialog: Dialog,
         cujType: Int? = null,
-    ): ActivityLaunchAnimator.Controller? {
+    ): ActivityTransitionAnimator.Controller? {
         val animatedDialog = openedDialogs.firstOrNull { it.dialog == dialog } ?: return null
         return createActivityLaunchController(animatedDialog, cujType)
     }
@@ -365,7 +366,7 @@
     private fun createActivityLaunchController(
         animatedDialog: AnimatedDialog,
         cujType: Int? = null
-    ): ActivityLaunchAnimator.Controller? {
+    ): ActivityTransitionAnimator.Controller? {
         // At this point, we know that the intent of the caller is to dismiss the dialog to show
         // an app, so we disable the exit animation into the source because we will never want to
         // run it anyways.
@@ -384,12 +385,12 @@
 
         val dialogContentWithBackground = animatedDialog.dialogContentWithBackground ?: return null
         val controller =
-            ActivityLaunchAnimator.Controller.fromView(dialogContentWithBackground, cujType)
+            ActivityTransitionAnimator.Controller.fromView(dialogContentWithBackground, cujType)
                 ?: return null
 
         // Wrap the controller into one that will instantly dismiss the dialog when the animation is
         // done or dismiss it normally (fading it out) if the animation is cancelled.
-        return object : ActivityLaunchAnimator.Controller by controller {
+        return object : ActivityTransitionAnimator.Controller by controller {
             override val isDialogLaunch = true
 
             override fun onIntentStarted(willAnimate: Boolean) {
@@ -400,14 +401,14 @@
                 }
             }
 
-            override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) {
-                controller.onLaunchAnimationCancelled()
+            override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+                controller.onTransitionAnimationCancelled()
                 enableDialogDismiss()
                 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 +421,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 +493,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,
 
@@ -632,7 +633,7 @@
 
         val background = dialogContentWithBackground.background
         originalDialogBackgroundColor =
-            GhostedViewLaunchAnimatorController.findGradientDrawable(background)
+            GhostedViewTransitionAnimatorController.findGradientDrawable(background)
                 ?.color
                 ?.defaultColor
                 ?: Color.BLACK
@@ -892,44 +893,44 @@
         // Create 2 controllers to animate both the dialog and the source.
         val startController =
             if (isLaunching) {
-                controller.createLaunchController()
+                controller.createTransitionController()
             } else {
-                GhostedViewLaunchAnimatorController(dialogContentWithBackground!!)
+                GhostedViewTransitionAnimatorController(dialogContentWithBackground!!)
             }
         val endController =
             if (isLaunching) {
-                GhostedViewLaunchAnimatorController(dialogContentWithBackground!!)
+                GhostedViewTransitionAnimatorController(dialogContentWithBackground!!)
             } 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 +944,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
@@ -967,13 +968,13 @@
                     // Therefore we update the end state to the new position/size. Usually the
                     // dialog dimension or position will change in the early frames, so changing the
                     // end state shouldn't really be noticeable.
-                    if (endController is GhostedViewLaunchAnimatorController) {
+                    if (endController is GhostedViewTransitionAnimatorController) {
                         endController.fillGhostedViewState(endState)
                     }
                 }
             }
 
-        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/Expandable.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
index c49a487..2ba5948 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
@@ -21,14 +21,14 @@
 /** A piece of UI that can be expanded into a Dialog or an Activity. */
 interface Expandable {
     /**
-     * Create an [ActivityLaunchAnimator.Controller] that can be used to expand this [Expandable]
-     * into an Activity, or return `null` if this [Expandable] should not be animated (e.g. if it is
-     * currently not attached or visible).
+     * Create an [ActivityTransitionAnimator.Controller] that can be used to expand this
+     * [Expandable] into an Activity, or return `null` if this [Expandable] should not be animated
+     * (e.g. if it is currently not attached or visible).
      *
      * @param cujType the CUJ type from the [com.android.internal.jank.InteractionJankMonitor]
      *   associated to the launch that will use this controller.
      */
-    fun activityLaunchController(cujType: Int? = null): ActivityLaunchAnimator.Controller?
+    fun activityLaunchController(cujType: Int? = null): ActivityTransitionAnimator.Controller?
 
     /**
      * Create a [DialogLaunchAnimator.Controller] that can be used to expand this [Expandable] into
@@ -49,8 +49,8 @@
             return object : Expandable {
                 override fun activityLaunchController(
                     cujType: Int?,
-                ): ActivityLaunchAnimator.Controller? {
-                    return ActivityLaunchAnimator.Controller.fromView(view, cujType)
+                ): ActivityTransitionAnimator.Controller? {
+                    return ActivityTransitionAnimator.Controller.fromView(view, cujType)
                 }
 
                 override fun dialogLaunchController(
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
similarity index 89%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
rename to packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
index 055252b..e4dc9be 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
@@ -39,21 +39,21 @@
 import kotlin.math.min
 import kotlin.math.roundToInt
 
-private const val TAG = "GhostedViewLaunchAnimatorController"
+private const val TAG = "GhostedViewTransitionAnimatorController"
 
 /**
- * A base implementation of [ActivityLaunchAnimator.Controller] which creates a [ghost][GhostView]
- * of [ghostedView] as well as an expandable background view, which are drawn and animated instead
- * of the ghosted view.
+ * A base implementation of [ActivityTransitionAnimator.Controller] which creates a
+ * [ghost][GhostView] of [ghostedView] as well as an expandable background view, which are drawn and
+ * animated instead of the ghosted view.
  *
  * Important: [ghostedView] must be attached to a [ViewGroup] when calling this function and during
  * the animation. It must also implement [LaunchableView], otherwise an exception will be thrown
  * during this controller instantiation.
  *
- * Note: Avoid instantiating this directly and call [ActivityLaunchAnimator.Controller.fromView]
+ * Note: Avoid instantiating this directly and call [ActivityTransitionAnimator.Controller.fromView]
  * whenever possible instead.
  */
-open class GhostedViewLaunchAnimatorController
+open class GhostedViewTransitionAnimatorController
 @JvmOverloads
 constructor(
     /** The view that will be ghosted and from which the background will be extracted. */
@@ -63,14 +63,14 @@
     private val cujType: Int? = null,
     private var interactionJankMonitor: InteractionJankMonitor =
         InteractionJankMonitor.getInstance(),
-) : ActivityLaunchAnimator.Controller {
+) : ActivityTransitionAnimator.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..e07f945 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 {
-        val delegate = GhostedViewLaunchAnimatorController(source)
-        return object : LaunchAnimator.Controller by delegate {
-            override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+    override fun createTransitionController(): TransitionAnimator.Controller {
+        val delegate = GhostedViewTransitionAnimatorController(source)
+        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,8 +94,8 @@
         }
     }
 
-    override fun createExitController(): LaunchAnimator.Controller {
-        return GhostedViewLaunchAnimatorController(source)
+    override fun createExitController(): TransitionAnimator.Controller {
+        return GhostedViewTransitionAnimatorController(source)
     }
 
     override fun shouldAnimateExit(): Boolean {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
index 2cd587f..59354c8 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
@@ -15,8 +15,8 @@
  */
 package com.android.systemui.surfaceeffects.turbulencenoise
 
-import android.graphics.BlendMode
 import android.graphics.Color
+import java.util.Random
 
 /** Turbulence noise animation configuration. */
 data class TurbulenceNoiseAnimationConfig(
@@ -26,6 +26,11 @@
     /** Multiplier for the noise luma matte. Increase this for brighter effects. */
     val luminosityMultiplier: Float = DEFAULT_LUMINOSITY_MULTIPLIER,
 
+    /** Initial noise offsets. */
+    val noiseOffsetX: Float = random.nextFloat(),
+    val noiseOffsetY: Float = random.nextFloat(),
+    val noiseOffsetZ: Float = random.nextFloat(),
+
     /**
      * Noise move speed variables.
      *
@@ -45,18 +50,15 @@
     val noiseMoveSpeedZ: Float = DEFAULT_NOISE_SPEED_Z,
 
     /** Color of the effect. */
-    var color: Int = DEFAULT_COLOR,
+    val color: Int = DEFAULT_COLOR,
     /** Background color of the effect. */
     val backgroundColor: Int = DEFAULT_BACKGROUND_COLOR,
-    val opacity: Int = DEFAULT_OPACITY,
     val width: Float = 0f,
     val height: Float = 0f,
     val maxDuration: Float = DEFAULT_MAX_DURATION_IN_MILLIS,
     val easeInDuration: Float = DEFAULT_EASING_DURATION_IN_MILLIS,
     val easeOutDuration: Float = DEFAULT_EASING_DURATION_IN_MILLIS,
     val pixelDensity: Float = 1f,
-    val blendMode: BlendMode = DEFAULT_BLEND_MODE,
-    val onAnimationEnd: Runnable? = null,
     /**
      * Variants in noise. Higher number means more contrast; lower number means less contrast but
      * make the noise dimmed. You may want to increase the [lumaMatteBlendFactor] to compensate.
@@ -68,7 +70,9 @@
      * want to use this if you have made the noise softer using [lumaMatteBlendFactor]. Expected
      * range [0, 1].
      */
-    val lumaMatteOverallBrightness: Float = DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS
+    val lumaMatteOverallBrightness: Float = DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS,
+    /** Whether to flip the luma mask. */
+    val shouldInverseNoiseLuminosity: Boolean = false
 ) {
     companion object {
         const val DEFAULT_MAX_DURATION_IN_MILLIS = 30_000f // Max 30 sec
@@ -76,11 +80,10 @@
         const val DEFAULT_LUMINOSITY_MULTIPLIER = 1f
         const val DEFAULT_NOISE_GRID_COUNT = 1.2f
         const val DEFAULT_NOISE_SPEED_Z = 0.3f
-        const val DEFAULT_OPACITY = 150 // full opacity is 255.
         const val DEFAULT_COLOR = Color.WHITE
         const val DEFAULT_LUMA_MATTE_BLEND_FACTOR = 1f
         const val DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS = 0f
         const val DEFAULT_BACKGROUND_COLOR = Color.BLACK
-        val DEFAULT_BLEND_MODE = BlendMode.SRC_OVER
+        private val random = Random()
     }
 }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
index b8f4b27..535c2d3 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
@@ -69,12 +69,15 @@
      *
      * <p>It plays ease-in, main, and ease-out animations in sequence.
      */
-    fun play(config: TurbulenceNoiseAnimationConfig) {
+    fun play(
+        baseType: TurbulenceNoiseShader.Companion.Type,
+        config: TurbulenceNoiseAnimationConfig
+    ) {
         if (state != AnimationState.NOT_PLAYING) {
             return // Ignore if any of the animation is playing.
         }
 
-        turbulenceNoiseView.applyConfig(config)
+        turbulenceNoiseView.initShader(baseType, config)
         playEaseInAnimation()
     }
 
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
index d3c57c9..30108ac 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
@@ -22,10 +22,10 @@
 /**
  * Shader that renders turbulence simplex noise, by default no octave.
  *
- * @param useFractal whether to use fractal noise (4 octaves).
+ * @param baseType the base [Type] of the shader.
  */
-class TurbulenceNoiseShader(useFractal: Boolean = false) :
-    RuntimeShader(if (useFractal) FRACTAL_NOISE_SHADER else SIMPLEX_NOISE_SHADER) {
+class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) :
+    RuntimeShader(getShader(baseType)) {
     // language=AGSL
     companion object {
         private const val UNIFORMS =
@@ -86,11 +86,34 @@
                 return vec4(color * in_opacity, in_opacity);
             }
         """
-
         private const val SIMPLEX_NOISE_SHADER =
             ShaderUtilLibrary.SHADER_LIB + UNIFORMS + SIMPLEX_SHADER
         private const val FRACTAL_NOISE_SHADER =
             ShaderUtilLibrary.SHADER_LIB + UNIFORMS + FRACTAL_SHADER
+        // TODO (b/282007590): Add NOISE_WITH_SPARKLE
+
+        enum class Type {
+            SIMPLEX_NOISE,
+            SIMPLEX_NOISE_FRACTAL,
+        }
+
+        fun getShader(type: Type): String {
+            return when (type) {
+                Type.SIMPLEX_NOISE -> SIMPLEX_NOISE_SHADER
+                Type.SIMPLEX_NOISE_FRACTAL -> FRACTAL_NOISE_SHADER
+            }
+        }
+    }
+
+    /** Convenient way for updating multiple uniform values via config object. */
+    fun applyConfig(config: TurbulenceNoiseAnimationConfig) {
+        setGridCount(config.gridCount)
+        setPixelDensity(config.pixelDensity)
+        setColor(config.color)
+        setBackgroundColor(config.backgroundColor)
+        setSize(config.width, config.height)
+        setLumaMatteFactors(config.lumaMatteBlendFactor, config.lumaMatteOverallBrightness)
+        setInverseNoiseLuminosity(config.shouldInverseNoiseLuminosity)
     }
 
     /** Sets the number of grid for generating noise. */
@@ -107,18 +130,19 @@
         setFloatUniform("in_pixelDensity", pixelDensity)
     }
 
-    /** Sets the noise color of the effect. */
+    /** Sets the noise color of the effect. Alpha is ignored. */
     fun setColor(color: Int) {
         setColorUniform("in_color", color)
     }
 
-    /** Sets the background color of the effect. */
+    /** Sets the background color of the effect. Alpha is ignored. */
     fun setBackgroundColor(color: Int) {
         setColorUniform("in_backgroundColor", color)
     }
 
     /**
-     * Sets the opacity to achieve fade in/ out of the animation.
+     * Sets the opacity of the effect. Not intended to set by the client as it is used for
+     * ease-in/out animations.
      *
      * Expected value range is [1, 0].
      */
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
index 43d6504..c59bc10 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
@@ -19,12 +19,12 @@
 import android.animation.AnimatorListenerAdapter
 import android.animation.ValueAnimator
 import android.content.Context
+import android.graphics.BlendMode
 import android.graphics.Canvas
 import android.graphics.Paint
 import android.util.AttributeSet
 import android.view.View
 import androidx.annotation.VisibleForTesting
-import androidx.core.graphics.ColorUtils
 
 /**
  * View that renders turbulence noise effect.
@@ -44,8 +44,8 @@
         private const val MS_TO_SEC = 0.001f
     }
 
-    private val turbulenceNoiseShader = TurbulenceNoiseShader()
-    private val paint = Paint().apply { this.shader = turbulenceNoiseShader }
+    private val paint = Paint()
+    @VisibleForTesting var turbulenceNoiseShader: TurbulenceNoiseShader? = null
     @VisibleForTesting var noiseConfig: TurbulenceNoiseAnimationConfig? = null
     @VisibleForTesting var currentAnimator: ValueAnimator? = null
 
@@ -61,9 +61,7 @@
 
     /** Updates the color during the animation. No-op if there's no animation playing. */
     internal fun updateColor(color: Int) {
-        noiseConfig?.let {
-            turbulenceNoiseShader.setColor(ColorUtils.setAlphaComponent(color, it.opacity))
-        }
+        turbulenceNoiseShader?.setColor(color)
     }
 
     /** Plays the turbulence noise with no easing. */
@@ -73,24 +71,25 @@
             return
         }
         val config = noiseConfig!!
+        val shader = turbulenceNoiseShader!!
 
         val animator = ValueAnimator.ofFloat(0f, 1f)
         animator.duration = config.maxDuration.toLong()
 
         // Animation should start from the initial position to avoid abrupt transition.
-        val initialX = turbulenceNoiseShader.noiseOffsetX
-        val initialY = turbulenceNoiseShader.noiseOffsetY
-        val initialZ = turbulenceNoiseShader.noiseOffsetZ
+        val initialX = shader.noiseOffsetX
+        val initialY = shader.noiseOffsetY
+        val initialZ = shader.noiseOffsetZ
 
         animator.addUpdateListener { updateListener ->
             val timeInSec = updateListener.currentPlayTime * MS_TO_SEC
-            turbulenceNoiseShader.setNoiseMove(
+            shader.setNoiseMove(
                 initialX + timeInSec * config.noiseMoveSpeedX,
                 initialY + timeInSec * config.noiseMoveSpeedY,
                 initialZ + timeInSec * config.noiseMoveSpeedZ
             )
 
-            turbulenceNoiseShader.setOpacity(config.luminosityMultiplier)
+            shader.setOpacity(config.luminosityMultiplier)
 
             invalidate()
         }
@@ -115,27 +114,28 @@
             return
         }
         val config = noiseConfig!!
+        val shader = turbulenceNoiseShader!!
 
         val animator = ValueAnimator.ofFloat(0f, 1f)
         animator.duration = config.easeInDuration.toLong()
 
         // Animation should start from the initial position to avoid abrupt transition.
-        val initialX = turbulenceNoiseShader.noiseOffsetX
-        val initialY = turbulenceNoiseShader.noiseOffsetY
-        val initialZ = turbulenceNoiseShader.noiseOffsetZ
+        val initialX = shader.noiseOffsetX
+        val initialY = shader.noiseOffsetY
+        val initialZ = shader.noiseOffsetZ
 
         animator.addUpdateListener { updateListener ->
             val timeInSec = updateListener.currentPlayTime * MS_TO_SEC
             val progress = updateListener.animatedValue as Float
 
-            turbulenceNoiseShader.setNoiseMove(
+            shader.setNoiseMove(
                 offsetX + initialX + timeInSec * config.noiseMoveSpeedX,
                 offsetY + initialY + timeInSec * config.noiseMoveSpeedY,
                 initialZ + timeInSec * config.noiseMoveSpeedZ
             )
 
             // TODO: Replace it with a better curve.
-            turbulenceNoiseShader.setOpacity(progress * config.luminosityMultiplier)
+            shader.setOpacity(progress * config.luminosityMultiplier)
 
             invalidate()
         }
@@ -160,27 +160,28 @@
             return
         }
         val config = noiseConfig!!
+        val shader = turbulenceNoiseShader!!
 
         val animator = ValueAnimator.ofFloat(0f, 1f)
         animator.duration = config.easeOutDuration.toLong()
 
         // Animation should start from the initial position to avoid abrupt transition.
-        val initialX = turbulenceNoiseShader.noiseOffsetX
-        val initialY = turbulenceNoiseShader.noiseOffsetY
-        val initialZ = turbulenceNoiseShader.noiseOffsetZ
+        val initialX = shader.noiseOffsetX
+        val initialY = shader.noiseOffsetY
+        val initialZ = shader.noiseOffsetZ
 
         animator.addUpdateListener { updateListener ->
             val timeInSec = updateListener.currentPlayTime * MS_TO_SEC
             val progress = updateListener.animatedValue as Float
 
-            turbulenceNoiseShader.setNoiseMove(
+            shader.setNoiseMove(
                 initialX + timeInSec * config.noiseMoveSpeedX,
                 initialY + timeInSec * config.noiseMoveSpeedY,
                 initialZ + timeInSec * config.noiseMoveSpeedZ
             )
 
             // TODO: Replace it with a better curve.
-            turbulenceNoiseShader.setOpacity((1f - progress) * config.luminosityMultiplier)
+            shader.setOpacity((1f - progress) * config.luminosityMultiplier)
 
             invalidate()
         }
@@ -211,18 +212,22 @@
 
     /** Applies shader uniforms. Must be called before playing animation. */
     @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
-    fun applyConfig(config: TurbulenceNoiseAnimationConfig) {
+    fun initShader(
+        baseType: TurbulenceNoiseShader.Companion.Type,
+        config: TurbulenceNoiseAnimationConfig
+    ) {
         noiseConfig = config
-        with(turbulenceNoiseShader) {
-            setGridCount(config.gridCount)
-            setColor(config.color)
-            setBackgroundColor(config.backgroundColor)
-            setSize(config.width, config.height)
-            setPixelDensity(config.pixelDensity)
-            setInverseNoiseLuminosity(inverse = false)
-            setLumaMatteFactors(config.lumaMatteBlendFactor, config.lumaMatteOverallBrightness)
+        if (turbulenceNoiseShader == null || turbulenceNoiseShader?.baseType != baseType) {
+            turbulenceNoiseShader = TurbulenceNoiseShader(baseType)
+
+            paint.shader = turbulenceNoiseShader!!
         }
-        paint.blendMode = config.blendMode
+        turbulenceNoiseShader!!.applyConfig(config)
+    }
+
+    /** Sets the blend mode of the View. */
+    fun setBlendMode(blendMode: BlendMode) {
+        paint.blendMode = blendMode
     }
 
     internal fun clearConfig() {
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..84e5725 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
@@ -40,11 +40,11 @@
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.LayoutDirection
 import com.android.internal.jank.InteractionJankMonitor
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
 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?>,
@@ -135,7 +135,7 @@
         object : Expandable {
             override fun activityLaunchController(
                 cujType: Int?,
-            ): ActivityLaunchAnimator.Controller? {
+            ): ActivityTransitionAnimator.Controller? {
                 if (!isComposed.value) {
                     return null
                 }
@@ -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.
+                // ActivityTransitionAnimator.
                 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(),
@@ -256,19 +256,20 @@
         }
     }
 
-    /** 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)
+    /** Create an [ActivityTransitionAnimator.Controller] that can be used to animate activities. */
+    private fun activityController(cujType: Int?): ActivityTransitionAnimator.Controller {
+        val delegate = transitionController()
+        return object :
+            ActivityTransitionAnimator.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/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index ff53ff2..378a1e4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -34,6 +34,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
@@ -43,23 +44,21 @@
 @Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
-    private val viewModel: LockscreenSceneViewModel,
+    viewModel: LockscreenSceneViewModel,
     private val lockscreenContent: Lazy<LockscreenContent>,
 ) : ComposableScene {
     override val key = SceneKey.Lockscreen
 
     override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> =
-        viewModel.upDestinationSceneKey
-            .map { pageKey ->
-                destinationScenes(up = pageKey, left = viewModel.leftDestinationSceneKey)
-            }
+        combine(viewModel.upDestinationSceneKey, viewModel.leftDestinationSceneKey, ::Pair)
+            .map { (upKey, leftKey) -> destinationScenes(up = upKey, left = leftKey) }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.Eagerly,
                 initialValue =
                     destinationScenes(
                         up = viewModel.upDestinationSceneKey.value,
-                        left = viewModel.leftDestinationSceneKey,
+                        left = viewModel.leftDestinationSceneKey.value,
                     )
             )
 
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 d70f82f..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
@@ -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/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/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index b3d2bc9..c8fbad4 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
@@ -46,7 +46,7 @@
     val draggable: DraggableHandler = SceneDraggableHandler(this)
 
     private var _swipeTransition: SwipeTransition? = null
-    internal var swipeTransition: SwipeTransition
+    private var swipeTransition: SwipeTransition
         get() = _swipeTransition ?: error("SwipeTransition needs to be initialized")
         set(value) {
             _swipeTransition = value
@@ -92,10 +92,6 @@
     /** The [Swipes] associated to the current gesture. */
     private var swipes: Swipes? = null
 
-    /** The [UserActionResult] associated to up and down swipes. */
-    private var upOrLeftResult: UserActionResult? = null
-    private var downOrRightResult: UserActionResult? = null
-
     /**
      * Whether we should immediately intercept a gesture.
      *
@@ -128,7 +124,7 @@
             // This [transition] was already driving the animation: simply take over it.
             // Stop animating and start from where the current offset.
             swipeTransition.cancelOffsetAnimation()
-            updateSwipesResults(swipeTransition._fromScene)
+            swipes!!.updateSwipesResults(swipeTransition._fromScene)
             return
         }
 
@@ -144,16 +140,24 @@
         }
 
         val fromScene = layoutImpl.scene(transitionState.currentScene)
-        updateSwipes(fromScene, startedPosition, pointersDown)
+        val newSwipes = computeSwipes(fromScene, startedPosition, pointersDown)
+        swipes = newSwipes
+        val result = newSwipes.findUserActionResult(fromScene, overSlop, true)
 
-        val result =
-            findUserActionResult(fromScene, directionOffset = overSlop, updateSwipesResults = true)
-                ?: return
-        updateTransition(SwipeTransition(fromScene, result), force = true)
-    }
+        // As we were unable to locate a valid target scene, the initial SwipeTransition cannot be
+        // defined.
+        if (result == null) return
 
-    private fun updateSwipes(fromScene: Scene, startedPosition: Offset?, pointersDown: Int) {
-        this.swipes = computeSwipes(fromScene, startedPosition, pointersDown)
+        val newSwipeTransition =
+            SwipeTransition(
+                fromScene = fromScene,
+                result = result,
+                swipes = newSwipes,
+                layoutImpl = layoutImpl,
+                orientation = orientation
+            )
+
+        updateTransition(newSwipeTransition, force = true)
     }
 
     private fun computeSwipes(
@@ -210,13 +214,6 @@
         }
     }
 
-    private fun Scene.getAbsoluteDistance(distance: UserActionDistance?): Float {
-        val targetSize = this.targetSize
-        return with(distance ?: DefaultSwipeDistance) {
-            layoutImpl.density.absoluteDistance(targetSize, orientation)
-        }
-    }
-
     internal fun onDrag(delta: Float) {
         if (delta == 0f || !isDrivingTransition) return
         swipeTransition.dragOffset += delta
@@ -226,15 +223,17 @@
 
         val isNewFromScene = fromScene.key != swipeTransition.fromScene
         val result =
-            findUserActionResult(
-                fromScene,
-                swipeTransition.dragOffset,
-                updateSwipesResults = isNewFromScene,
+            swipes!!.findUserActionResult(
+                fromScene = fromScene,
+                directionOffset = swipeTransition.dragOffset,
+                updateSwipesResults = isNewFromScene
             )
-                ?: run {
-                    onDragStopped(delta, true)
-                    return
-                }
+
+        if (result == null) {
+            onDragStopped(velocity = delta, canChangeScene = true)
+            return
+        }
+
         swipeTransition.dragOffset += acceleratedOffset
 
         if (
@@ -242,25 +241,20 @@
                 result.toScene != swipeTransition.toScene ||
                 result.transitionKey != swipeTransition.key
         ) {
-            updateTransition(
-                SwipeTransition(fromScene, result).apply {
-                    this.dragOffset = swipeTransition.dragOffset
-                }
-            )
+            val newSwipeTransition =
+                SwipeTransition(
+                        fromScene = fromScene,
+                        result = result,
+                        swipes = swipes!!,
+                        layoutImpl = layoutImpl,
+                        orientation = orientation
+                    )
+                    .apply { dragOffset = swipeTransition.dragOffset }
+
+            updateTransition(newSwipeTransition)
         }
     }
 
-    private fun updateSwipesResults(fromScene: Scene) {
-        val (upOrLeftResult, downOrRightResult) =
-            computeSwipesResults(
-                fromScene,
-                this.swipes ?: error("updateSwipes() should be called before updateSwipesResults()")
-            )
-
-        this.upOrLeftResult = upOrLeftResult
-        this.downOrRightResult = downOrRightResult
-    }
-
     private fun computeSwipesResults(
         fromScene: Scene,
         swipes: Swipes
@@ -295,74 +289,20 @@
 
         // If the swipe was not committed, don't do anything.
         if (swipeTransition._currentScene != toScene) {
-            return Pair(fromScene, 0f)
+            return fromScene to 0f
         }
 
         // If the offset is past the distance then let's change fromScene so that the user can swipe
         // to the next screen or go back to the previous one.
         val offset = swipeTransition.dragOffset
-        return if (offset <= -absoluteDistance && upOrLeftResult?.toScene == toScene.key) {
-            Pair(toScene, absoluteDistance)
-        } else if (offset >= absoluteDistance && downOrRightResult?.toScene == toScene.key) {
-            Pair(toScene, -absoluteDistance)
+        return if (offset <= -absoluteDistance && swipes!!.upOrLeftResult?.toScene == toScene.key) {
+            toScene to absoluteDistance
+        } else if (
+            offset >= absoluteDistance && swipes!!.downOrRightResult?.toScene == toScene.key
+        ) {
+            toScene to -absoluteDistance
         } else {
-            Pair(fromScene, 0f)
-        }
-    }
-
-    /**
-     * Returns the [UserActionResult] from [fromScene] in the direction of [directionOffset].
-     *
-     * @param fromScene the scene from which we look for the target
-     * @param directionOffset signed float that indicates the direction. Positive is down or right
-     *   negative is up or left.
-     * @param updateSwipesResults whether the target scenes should be updated to the current values
-     *   held in the Scenes map. Usually we don't want to update them while doing a drag, because
-     *   this could change the target scene (jump cutting) to a different scene, when some system
-     *   state changed the targets the background. However, an update is needed any time we
-     *   calculate the targets for a new fromScene.
-     * @return null when there are no targets in either direction. If one direction is null and you
-     *   drag into the null direction this function will return the opposite direction, assuming
-     *   that the users intention is to start the drag into the other direction eventually. If
-     *   [directionOffset] is 0f and both direction are available, it will default to
-     *   [upOrLeftResult].
-     */
-    private fun findUserActionResult(
-        fromScene: Scene,
-        directionOffset: Float,
-        updateSwipesResults: Boolean,
-    ): UserActionResult? {
-        if (updateSwipesResults) updateSwipesResults(fromScene)
-
-        return when {
-            upOrLeftResult == null && downOrRightResult == null -> null
-            (directionOffset < 0f && upOrLeftResult != null) || downOrRightResult == null ->
-                upOrLeftResult
-            else -> downOrRightResult
-        }
-    }
-
-    /**
-     * A strict version of [findUserActionResult] that will return null when there is no Scene in
-     * [directionOffset] direction
-     */
-    private fun findUserActionResultStrict(directionOffset: Float): UserActionResult? {
-        return when {
-            directionOffset > 0f -> upOrLeftResult
-            directionOffset < 0f -> downOrRightResult
-            else -> null
-        }
-    }
-
-    private fun computeAbsoluteDistance(
-        fromScene: Scene,
-        result: UserActionResult,
-    ): Float {
-        return if (result == upOrLeftResult) {
-            -fromScene.getAbsoluteDistance(result.distance)
-        } else {
-            check(result == downOrRightResult)
-            fromScene.getAbsoluteDistance(result.distance)
+            fromScene to 0f
         }
     }
 
@@ -430,19 +370,24 @@
 
             if (startFromIdlePosition) {
                 // If there is a target scene, we start the overscroll animation.
-                val result =
-                    findUserActionResultStrict(velocity)
-                        ?: run {
-                            // We will not animate
-                            layoutState.finishTransition(swipeTransition, idleScene = fromScene.key)
-                            return
-                        }
+                val result = swipes!!.findUserActionResultStrict(velocity)
+                if (result == null) {
+                    // We will not animate
+                    layoutState.finishTransition(swipeTransition, idleScene = fromScene.key)
+                    return
+                }
 
-                updateTransition(
-                    SwipeTransition(fromScene, result).apply {
-                        _currentScene = swipeTransition._currentScene
-                    }
-                )
+                val newSwipeTransition =
+                    SwipeTransition(
+                            fromScene = fromScene,
+                            result = result,
+                            swipes = swipes!!,
+                            layoutImpl = layoutImpl,
+                            orientation = orientation
+                        )
+                        .apply { _currentScene = swipeTransition._currentScene }
+
+                updateTransition(newSwipeTransition)
                 animateTo(targetScene = fromScene, targetOffset = 0f)
             } else {
                 // We were between two scenes: animate to the initial scene.
@@ -486,134 +431,220 @@
         }
     }
 
-    private fun SwipeTransition(fromScene: Scene, result: UserActionResult): SwipeTransition {
-        return SwipeTransition(
-            result.transitionKey,
-            fromScene,
-            layoutImpl.scene(result.toScene),
-            computeAbsoluteDistance(fromScene, result),
-        )
-    }
-
-    internal class SwipeTransition(
-        val key: TransitionKey?,
-        val _fromScene: Scene,
-        val _toScene: Scene,
-        /**
-         * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is
-         * above or to the left of [toScene].
-         */
-        val distance: Float,
-    ) : TransitionState.Transition(_fromScene.key, _toScene.key) {
-        var _currentScene by mutableStateOf(_fromScene)
-        override val currentScene: SceneKey
-            get() = _currentScene.key
-
-        override val progress: Float
-            get() {
-                val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
-                return offset / distance
-            }
-
-        override val isInitiatedByUserInput = true
-
-        /** The current offset caused by the drag gesture. */
-        var dragOffset by mutableFloatStateOf(0f)
-
-        /**
-         * Whether the offset is animated (the user lifted their finger) or if it is driven by
-         * gesture.
-         */
-        var isAnimatingOffset by mutableStateOf(false)
-
-        // If we are not animating offset, it means the offset is being driven by the user's finger.
-        override val isUserInputOngoing: Boolean
-            get() = !isAnimatingOffset
-
-        /** The animatable used to animate the offset once the user lifted its finger. */
-        val offsetAnimatable = Animatable(0f, OffsetVisibilityThreshold)
-
-        /** Job to check that there is at most one offset animation in progress. */
-        private var offsetAnimationJob: Job? = null
-
-        /** The spec to use when animating this transition to either [fromScene] or [toScene]. */
-        lateinit var swipeSpec: SpringSpec<Float>
-
-        /** Ends any previous [offsetAnimationJob] and runs the new [job]. */
-        private fun startOffsetAnimation(job: () -> Job) {
-            cancelOffsetAnimation()
-            offsetAnimationJob = job()
-        }
-
-        /** Cancel any ongoing offset animation. */
-        // TODO(b/317063114) This should be a suspended function to avoid multiple jobs running at
-        // the same time.
-        fun cancelOffsetAnimation() {
-            offsetAnimationJob?.cancel()
-            finishOffsetAnimation()
-        }
-
-        fun finishOffsetAnimation() {
-            if (isAnimatingOffset) {
-                isAnimatingOffset = false
-                dragOffset = offsetAnimatable.value
-            }
-        }
-
-        fun animateOffset(
-            // TODO(b/317063114) The CoroutineScope should be removed.
-            coroutineScope: CoroutineScope,
-            initialVelocity: Float,
-            targetOffset: Float,
-            onAnimationCompleted: () -> Unit,
-        ) {
-            startOffsetAnimation {
-                coroutineScope.launch {
-                    animateOffset(targetOffset, initialVelocity)
-                    onAnimationCompleted()
-                }
-            }
-        }
-
-        private suspend fun animateOffset(targetOffset: Float, initialVelocity: Float) {
-            if (!isAnimatingOffset) {
-                offsetAnimatable.snapTo(dragOffset)
-            }
-            isAnimatingOffset = true
-
-            offsetAnimatable.animateTo(
-                targetValue = targetOffset,
-                animationSpec = swipeSpec,
-                initialVelocity = initialVelocity,
-            )
-
-            finishOffsetAnimation()
-        }
-    }
-
     companion object {
         private const val TAG = "SceneGestureHandler"
     }
+}
 
-    private object DefaultSwipeDistance : UserActionDistance {
-        override fun Density.absoluteDistance(
-            fromSceneSize: IntSize,
-            orientation: Orientation,
-        ): Float {
-            return when (orientation) {
-                Orientation.Horizontal -> fromSceneSize.width
-                Orientation.Vertical -> fromSceneSize.height
-            }.toFloat()
+private fun SwipeTransition(
+    fromScene: Scene,
+    result: UserActionResult,
+    swipes: Swipes,
+    layoutImpl: SceneTransitionLayoutImpl,
+    orientation: Orientation,
+): SwipeTransition {
+    val upOrLeftResult = swipes.upOrLeftResult
+    val downOrRightResult = swipes.downOrRightResult
+    val userActionDistance = result.distance ?: DefaultSwipeDistance
+    val absoluteDistance =
+        with(userActionDistance) {
+            layoutImpl.density.absoluteDistance(fromScene.targetSize, orientation)
+        }
+
+    return SwipeTransition(
+        key = result.transitionKey,
+        _fromScene = fromScene,
+        _toScene = layoutImpl.scene(result.toScene),
+        distance =
+            when (result) {
+                upOrLeftResult -> -absoluteDistance
+                downOrRightResult -> absoluteDistance
+                else -> error("Unknown result $result ($upOrLeftResult $downOrRightResult)")
+            },
+    )
+}
+
+private class SwipeTransition(
+    val key: TransitionKey?,
+    val _fromScene: Scene,
+    val _toScene: Scene,
+    /**
+     * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is above
+     * or to the left of [toScene]
+     */
+    val distance: Float,
+) : TransitionState.Transition(_fromScene.key, _toScene.key) {
+    var _currentScene by mutableStateOf(_fromScene)
+    override val currentScene: SceneKey
+        get() = _currentScene.key
+
+    override val progress: Float
+        get() {
+            val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
+            return offset / distance
+        }
+
+    override val isInitiatedByUserInput = true
+
+    /** The current offset caused by the drag gesture. */
+    var dragOffset by mutableFloatStateOf(0f)
+
+    /**
+     * Whether the offset is animated (the user lifted their finger) or if it is driven by gesture.
+     */
+    var isAnimatingOffset by mutableStateOf(false)
+
+    // If we are not animating offset, it means the offset is being driven by the user's finger.
+    override val isUserInputOngoing: Boolean
+        get() = !isAnimatingOffset
+
+    /** The animatable used to animate the offset once the user lifted its finger. */
+    val offsetAnimatable = Animatable(0f, OffsetVisibilityThreshold)
+
+    /** Job to check that there is at most one offset animation in progress. */
+    private var offsetAnimationJob: Job? = null
+
+    /** The spec to use when animating this transition to either [fromScene] or [toScene]. */
+    lateinit var swipeSpec: SpringSpec<Float>
+
+    /** Ends any previous [offsetAnimationJob] and runs the new [job]. */
+    private fun startOffsetAnimation(job: () -> Job) {
+        cancelOffsetAnimation()
+        offsetAnimationJob = job()
+    }
+
+    /** Cancel any ongoing offset animation. */
+    // TODO(b/317063114) This should be a suspended function to avoid multiple jobs running at
+    // the same time.
+    fun cancelOffsetAnimation() {
+        offsetAnimationJob?.cancel()
+        finishOffsetAnimation()
+    }
+
+    fun finishOffsetAnimation() {
+        if (isAnimatingOffset) {
+            isAnimatingOffset = false
+            dragOffset = offsetAnimatable.value
         }
     }
 
-    /** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */
-    private class Swipes(
-        val upOrLeft: Swipe?,
-        val downOrRight: Swipe?,
-        val upOrLeftNoSource: Swipe?,
-        val downOrRightNoSource: Swipe?,
-    )
+    fun animateOffset(
+        // TODO(b/317063114) The CoroutineScope should be removed.
+        coroutineScope: CoroutineScope,
+        initialVelocity: Float,
+        targetOffset: Float,
+        onAnimationCompleted: () -> Unit,
+    ) {
+        startOffsetAnimation {
+            coroutineScope.launch {
+                animateOffset(targetOffset, initialVelocity)
+                onAnimationCompleted()
+            }
+        }
+    }
+
+    private suspend fun animateOffset(targetOffset: Float, initialVelocity: Float) {
+        if (!isAnimatingOffset) {
+            offsetAnimatable.snapTo(dragOffset)
+        }
+        isAnimatingOffset = true
+
+        offsetAnimatable.animateTo(
+            targetValue = targetOffset,
+            animationSpec = swipeSpec,
+            initialVelocity = initialVelocity,
+        )
+
+        finishOffsetAnimation()
+    }
+}
+
+private object DefaultSwipeDistance : UserActionDistance {
+    override fun Density.absoluteDistance(
+        fromSceneSize: IntSize,
+        orientation: Orientation,
+    ): Float {
+        return when (orientation) {
+            Orientation.Horizontal -> fromSceneSize.width
+            Orientation.Vertical -> fromSceneSize.height
+        }.toFloat()
+    }
+}
+
+/** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */
+private class Swipes(
+    val upOrLeft: Swipe?,
+    val downOrRight: Swipe?,
+    val upOrLeftNoSource: Swipe?,
+    val downOrRightNoSource: Swipe?,
+) {
+    /** The [UserActionResult] associated to up and down swipes. */
+    var upOrLeftResult: UserActionResult? = null
+    var downOrRightResult: UserActionResult? = null
+
+    fun computeSwipesResults(fromScene: Scene): Pair<UserActionResult?, UserActionResult?> {
+        val userActions = fromScene.userActions
+        fun result(swipe: Swipe?): UserActionResult? {
+            return userActions[swipe ?: return null]
+        }
+
+        val upOrLeftResult = result(upOrLeft) ?: result(upOrLeftNoSource)
+        val downOrRightResult = result(downOrRight) ?: result(downOrRightNoSource)
+        return upOrLeftResult to downOrRightResult
+    }
+
+    fun updateSwipesResults(fromScene: Scene) {
+        val (upOrLeftResult, downOrRightResult) = computeSwipesResults(fromScene)
+
+        this.upOrLeftResult = upOrLeftResult
+        this.downOrRightResult = downOrRightResult
+    }
+
+    /**
+     * Returns the [UserActionResult] from [fromScene] in the direction of [directionOffset].
+     *
+     * @param fromScene the scene from which we look for the target
+     * @param directionOffset signed float that indicates the direction. Positive is down or right
+     *   negative is up or left.
+     * @param updateSwipesResults whether the target scenes should be updated to the current values
+     *   held in the Scenes map. Usually we don't want to update them while doing a drag, because
+     *   this could change the target scene (jump cutting) to a different scene, when some system
+     *   state changed the targets the background. However, an update is needed any time we
+     *   calculate the targets for a new fromScene.
+     * @return null when there are no targets in either direction. If one direction is null and you
+     *   drag into the null direction this function will return the opposite direction, assuming
+     *   that the users intention is to start the drag into the other direction eventually. If
+     *   [directionOffset] is 0f and both direction are available, it will default to
+     *   [upOrLeftResult].
+     */
+    fun findUserActionResult(
+        fromScene: Scene,
+        directionOffset: Float,
+        updateSwipesResults: Boolean,
+    ): UserActionResult? {
+        if (updateSwipesResults) {
+            updateSwipesResults(fromScene)
+        }
+
+        return when {
+            upOrLeftResult == null && downOrRightResult == null -> null
+            (directionOffset < 0f && upOrLeftResult != null) || downOrRightResult == null ->
+                upOrLeftResult
+            else -> downOrRightResult
+        }
+    }
+
+    /**
+     * A strict version of [findUserActionResult] that will return null when there is no Scene in
+     * [directionOffset] direction
+     */
+    fun findUserActionResultStrict(directionOffset: Float): UserActionResult? {
+        return when {
+            directionOffset > 0f -> upOrLeftResult
+            directionOffset < 0f -> downOrRightResult
+            else -> null
+        }
+    }
 }
 
 private class SceneDraggableHandler(
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/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..c91d298 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,
@@ -131,6 +127,9 @@
         val progress: Float
             get() = (transitionState as Transition).progress
 
+        val isUserInputOngoing: Boolean
+            get() = (transitionState as Transition).isUserInputOngoing
+
         fun advanceUntilIdle() {
             testScope.testScheduler.advanceUntilIdle()
         }
@@ -507,7 +506,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 +523,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))
 
@@ -542,12 +541,11 @@
         onDragStopped(velocity = velocityThreshold)
 
         assertTransition(currentScene = SceneC)
-        assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
-        assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isTrue()
+        assertThat(isUserInputOngoing).isFalse()
 
         // Start a new gesture while the offset is animating
         onDragStartedImmediately()
-        assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse()
+        assertThat(isUserInputOngoing).isTrue()
     }
 
     @Test
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/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index f7743e2..259f349 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -37,7 +37,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.biometrics.ui.viewmodel.DefaultUdfpsTouchOverlayViewModel
@@ -106,7 +106,7 @@
     @Mock private lateinit var udfpsController: UdfpsController
     @Mock private lateinit var udfpsView: UdfpsView
     @Mock private lateinit var mUdfpsKeyguardViewLegacy: UdfpsKeyguardViewLegacy
-    @Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
+    @Mock private lateinit var mActivityTransitionAnimator: ActivityTransitionAnimator
     @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
     @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
     @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
@@ -167,7 +167,7 @@
                 reason,
                 controllerCallback,
                 onTouch,
-                activityLaunchAnimator,
+                mActivityTransitionAnimator,
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
                 isDebuggable,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 90c3c14..529403a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -75,7 +75,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
 import com.android.systemui.biometrics.udfps.InteractionEvent;
@@ -203,7 +203,7 @@
     @Mock
     private SystemUIDialogManager mSystemUIDialogManager;
     @Mock
-    private ActivityLaunchAnimator mActivityLaunchAnimator;
+    private ActivityTransitionAnimator mActivityTransitionAnimator;
     @Mock
     private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     @Mock
@@ -331,7 +331,7 @@
                 mUnlockedScreenOffAnimationController,
                 mSystemUIDialogManager,
                 mLatencyTracker,
-                mActivityLaunchAnimator,
+                mActivityTransitionAnimator,
                 mBiometricExecutor,
                 mPrimaryBouncerInteractor,
                 mShadeInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
index 7d9c2f9..324534f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
@@ -26,7 +26,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
@@ -70,7 +70,7 @@
     protected @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
     protected @Mock SystemUIDialogManager mDialogManager;
     protected @Mock UdfpsController mUdfpsController;
-    protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
+    protected @Mock ActivityTransitionAnimator mActivityTransitionAnimator;
     protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     protected @Mock ShadeInteractor mShadeInteractor;
     protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor;
@@ -148,7 +148,7 @@
                 mUnlockedScreenOffAnimationController,
                 mDialogManager,
                 mUdfpsController,
-                mActivityLaunchAnimator,
+                mActivityTransitionAnimator,
                 mPrimaryBouncerInteractor,
                 mAlternateBouncerInteractor,
                 mUdfpsKeyguardAccessibilityDelegate,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
index bd9ca30..b4e2eab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
@@ -16,26 +16,18 @@
 
 package com.android.systemui.communal.data.repository
 
-import android.content.pm.UserInfo
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.Flags
-import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.data.repository.sceneContainerRepository
 import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.testKosmos
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runTest
@@ -48,37 +40,20 @@
 class CommunalRepositoryImplTest : SysuiTestCase() {
     private lateinit var underTest: CommunalRepositoryImpl
 
-    private lateinit var secureSettings: FakeSettings
-    private lateinit var userRepository: FakeUserRepository
-
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val sceneContainerRepository = kosmos.sceneContainerRepository
 
     @Before
     fun setUp() {
-        secureSettings = FakeSettings()
-        userRepository = kosmos.fakeUserRepository
-
-        val listOfUserInfo = listOf(MAIN_USER_INFO)
-        userRepository.setUserInfos(listOfUserInfo)
-
-        kosmos.fakeFeatureFlagsClassic.apply { set(Flags.COMMUNAL_SERVICE_ENABLED, true) }
-        mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
-
         underTest = createRepositoryImpl(false)
     }
 
     private fun createRepositoryImpl(sceneContainerEnabled: Boolean): CommunalRepositoryImpl {
         return CommunalRepositoryImpl(
             testScope.backgroundScope,
-            testScope.backgroundScope,
-            kosmos.testDispatcher,
-            kosmos.fakeFeatureFlagsClassic,
             kosmos.fakeSceneContainerFlags.apply { enabled = sceneContainerEnabled },
             sceneContainerRepository,
-            kosmos.fakeUserRepository,
-            secureSettings,
         )
     }
 
@@ -159,29 +134,4 @@
             assertThat(transitionState)
                 .isEqualTo(ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT))
         }
-
-    @Test
-    fun communalEnabledState_false_whenGlanceableHubSettingFalse() =
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(MAIN_USER_INFO)
-            secureSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 0, MAIN_USER_INFO.id)
-
-            val communalEnabled by collectLastValue(underTest.communalEnabledState)
-            assertThat(communalEnabled).isFalse()
-        }
-
-    @Test
-    fun communalEnabledState_true_whenGlanceableHubSettingTrue() =
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(MAIN_USER_INFO)
-            secureSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 1, MAIN_USER_INFO.id)
-
-            val communalEnabled by collectLastValue(underTest.communalEnabledState)
-            assertThat(communalEnabled).isTrue()
-        }
-
-    companion object {
-        private const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled"
-        private val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
-    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
new file mode 100644
index 0000000..0aca16d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.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.communal.data.repository
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
+import android.app.admin.devicePolicyManager
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.communal.data.model.DisabledReason
+import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl.Companion.GLANCEABLE_HUB_ENABLED
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.fakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalSettingsRepositoryImplTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private lateinit var underTest: CommunalSettingsRepository
+
+    @Before
+    fun setUp() {
+        kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
+        setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_FEATURES_NONE)
+        setKeyguardFeaturesDisabled(SECONDARY_USER, KEYGUARD_DISABLE_FEATURES_NONE)
+        underTest = kosmos.communalSettingsRepository
+    }
+
+    @EnableFlags(FLAG_COMMUNAL_HUB)
+    @Test
+    fun secondaryUserIsInvalid() =
+        testScope.runTest {
+            val enabledState by collectLastValue(underTest.getEnabledState(SECONDARY_USER))
+
+            assertThat(enabledState?.enabled).isFalse()
+            assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_INVALID_USER)
+        }
+
+    @EnableFlags(FLAG_COMMUNAL_HUB)
+    @Test
+    fun classicFlagIsDisabled() =
+        testScope.runTest {
+            kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
+            val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
+            assertThat(enabledState?.enabled).isFalse()
+            assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_FLAG)
+        }
+
+    @DisableFlags(FLAG_COMMUNAL_HUB)
+    @Test
+    fun communalHubFlagIsDisabled() =
+        testScope.runTest {
+            val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
+            assertThat(enabledState?.enabled).isFalse()
+            assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_FLAG)
+        }
+
+    @EnableFlags(FLAG_COMMUNAL_HUB)
+    @Test
+    fun hubIsDisabledByUser() =
+        testScope.runTest {
+            kosmos.fakeSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 0, PRIMARY_USER.id)
+            val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
+            assertThat(enabledState?.enabled).isFalse()
+            assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_USER_SETTING)
+
+            kosmos.fakeSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 1, SECONDARY_USER.id)
+            assertThat(enabledState?.enabled).isFalse()
+
+            kosmos.fakeSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 1, PRIMARY_USER.id)
+            assertThat(enabledState?.enabled).isTrue()
+        }
+
+    @EnableFlags(FLAG_COMMUNAL_HUB)
+    @Test
+    fun hubIsDisabledByDevicePolicy() =
+        testScope.runTest {
+            val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
+            assertThat(enabledState?.enabled).isTrue()
+
+            setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_WIDGETS_ALL)
+            assertThat(enabledState?.enabled).isFalse()
+            assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_DEVICE_POLICY)
+        }
+
+    @EnableFlags(FLAG_COMMUNAL_HUB)
+    @Test
+    fun hubIsDisabledByUserAndDevicePolicy() =
+        testScope.runTest {
+            val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
+            assertThat(enabledState?.enabled).isTrue()
+
+            kosmos.fakeSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 0, PRIMARY_USER.id)
+            setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_WIDGETS_ALL)
+
+            assertThat(enabledState?.enabled).isFalse()
+            assertThat(enabledState)
+                .containsExactly(
+                    DisabledReason.DISABLED_REASON_DEVICE_POLICY,
+                    DisabledReason.DISABLED_REASON_USER_SETTING,
+                )
+        }
+
+    private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
+        whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
+            .thenReturn(disabledFlags)
+        kosmos.broadcastDispatcher.sendIntentToMatchingReceiversOnly(
+            context,
+            Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+        )
+    }
+
+    private companion object {
+        val PRIMARY_USER =
+            UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN)
+        val SECONDARY_USER = UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 475179d..a54a00d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -52,6 +52,7 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.eq
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -198,23 +199,27 @@
         }
 
     @Test
-    fun deleteWidgetFromDb() =
+    fun deleteWidget_deletefromDbTrue_alsoDeleteFromHost() =
         testScope.runTest {
             val id = 1
-            underTest.deleteWidgetFromDb(id)
+            whenever(communalWidgetDao.deleteWidgetById(eq(id))).thenReturn(true)
+            underTest.deleteWidget(id)
             runCurrent()
 
             verify(communalWidgetDao).deleteWidgetById(id)
+            verify(appWidgetHost).deleteAppWidgetId(id)
         }
 
     @Test
-    fun deleteWidgetFromHost() =
+    fun deleteWidget_deletefromDbFalse_doesNotDeleteFromHost() =
         testScope.runTest {
             val id = 1
-            underTest.deleteWidgetFromHost(id)
+            whenever(communalWidgetDao.deleteWidgetById(eq(id))).thenReturn(false)
+            underTest.deleteWidget(id)
             runCurrent()
 
-            verify(appWidgetHost).deleteAppWidgetId(id)
+            verify(communalWidgetDao).deleteWidgetById(id)
+            verify(appWidgetHost, never()).deleteAppWidgetId(id)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
index 6a3fc2a..824733b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
@@ -19,6 +19,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.FakeCommunalRepository
 import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
@@ -59,7 +60,7 @@
         widgetRepository = kosmos.fakeCommunalWidgetRepository
         keyguardRepository = kosmos.fakeKeyguardRepository
 
-        communalRepository.setIsCommunalEnabled(false)
+        mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB)
 
         underTest = kosmos.communalInteractor
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index c5485c5..3ac19e4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -23,6 +23,7 @@
 import android.widget.RemoteViews
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
 import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
@@ -41,6 +42,8 @@
 import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
 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.fakeKeyguardRepository
 import com.android.systemui.kosmos.testScope
@@ -109,12 +112,19 @@
         whenever(secondaryUser.isMain).thenReturn(false)
         userRepository.setUserInfos(listOf(mainUser, secondaryUser))
 
+        kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
+        mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
+
         underTest = kosmos.communalInteractor
     }
 
     @Test
     fun communalEnabled_true() =
-        testScope.runTest { assertThat(underTest.isCommunalEnabled).isTrue() }
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(mainUser)
+            runCurrent()
+            assertThat(underTest.isCommunalEnabled).isTrue()
+        }
 
     @Test
     fun isCommunalAvailable_storageUnlockedAndMainUser_true() =
@@ -125,7 +135,6 @@
             keyguardRepository.setIsEncryptedOrLockdown(false)
             userRepository.setSelectedUserInfo(mainUser)
             keyguardRepository.setKeyguardShowing(true)
-            communalRepository.setCommunalEnabledState(true)
 
             assertThat(isAvailable).isTrue()
         }
@@ -139,7 +148,6 @@
             keyguardRepository.setIsEncryptedOrLockdown(true)
             userRepository.setSelectedUserInfo(mainUser)
             keyguardRepository.setKeyguardShowing(true)
-            communalRepository.setCommunalEnabledState(true)
 
             assertThat(isAvailable).isFalse()
         }
@@ -153,7 +161,6 @@
             keyguardRepository.setIsEncryptedOrLockdown(false)
             userRepository.setSelectedUserInfo(secondaryUser)
             keyguardRepository.setKeyguardShowing(true)
-            communalRepository.setCommunalEnabledState(true)
 
             assertThat(isAvailable).isFalse()
         }
@@ -167,7 +174,6 @@
             keyguardRepository.setIsEncryptedOrLockdown(false)
             userRepository.setSelectedUserInfo(mainUser)
             keyguardRepository.setDreaming(true)
-            communalRepository.setCommunalEnabledState(true)
 
             assertThat(isAvailable).isTrue()
         }
@@ -175,13 +181,14 @@
     @Test
     fun isCommunalAvailable_communalDisabled_false() =
         testScope.runTest {
+            mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB)
+
             val isAvailable by collectLastValue(underTest.isCommunalAvailable)
             assertThat(isAvailable).isFalse()
 
             keyguardRepository.setIsEncryptedOrLockdown(false)
             userRepository.setSelectedUserInfo(mainUser)
             keyguardRepository.setKeyguardShowing(true)
-            communalRepository.setCommunalEnabledState(false)
 
             assertThat(isAvailable).isFalse()
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
index 6c87e0f..ceb7fac 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
@@ -22,12 +22,15 @@
 import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_STARTED
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.FakeCommunalRepository
 import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
 import com.android.systemui.communal.data.repository.fakeCommunalRepository
 import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
 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.fakeKeyguardRepository
 import com.android.systemui.kosmos.testScope
@@ -35,11 +38,13 @@
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class CommunalTutorialInteractorTest : SysuiTestCase() {
@@ -62,6 +67,8 @@
         userRepository = kosmos.fakeUserRepository
 
         userRepository.setUserInfos(listOf(MAIN_USER_INFO))
+        kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
+        mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
 
         underTest = kosmos.communalTutorialInteractor
     }
@@ -127,6 +134,7 @@
         testScope.runTest {
             val tutorialSettingState by
                 collectLastValue(communalTutorialRepository.tutorialSettingState)
+            userRepository.setSelectedUserInfo(MAIN_USER_INFO)
             communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
 
             communalRepository.setIsCommunalHubShowing(true)
@@ -139,6 +147,7 @@
         testScope.runTest {
             val tutorialSettingState by
                 collectLastValue(communalTutorialRepository.tutorialSettingState)
+            userRepository.setSelectedUserInfo(MAIN_USER_INFO)
             communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
 
             communalRepository.setIsCommunalHubShowing(true)
@@ -151,6 +160,7 @@
         testScope.runTest {
             val tutorialSettingState by
                 collectLastValue(communalTutorialRepository.tutorialSettingState)
+            userRepository.setSelectedUserInfo(MAIN_USER_INFO)
             communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
 
             communalRepository.setIsCommunalHubShowing(true)
@@ -163,6 +173,7 @@
         testScope.runTest {
             val tutorialSettingState by
                 collectLastValue(communalTutorialRepository.tutorialSettingState)
+            userRepository.setSelectedUserInfo(MAIN_USER_INFO)
             communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
 
             communalRepository.setIsCommunalHubShowing(false)
@@ -175,6 +186,7 @@
         testScope.runTest {
             val tutorialSettingState by
                 collectLastValue(communalTutorialRepository.tutorialSettingState)
+            userRepository.setSelectedUserInfo(MAIN_USER_INFO)
             communalRepository.setIsCommunalHubShowing(true)
             communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
 
@@ -188,6 +200,7 @@
         testScope.runTest {
             val tutorialSettingState by
                 collectLastValue(communalTutorialRepository.tutorialSettingState)
+            userRepository.setSelectedUserInfo(MAIN_USER_INFO)
             communalRepository.setIsCommunalHubShowing(true)
             communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
 
@@ -198,14 +211,11 @@
 
     private suspend fun setCommunalAvailable(available: Boolean) {
         if (available) {
-            communalRepository.setIsCommunalEnabled(true)
-            communalRepository.setCommunalEnabledState(true)
             keyguardRepository.setIsEncryptedOrLockdown(false)
             userRepository.setSelectedUserInfo(MAIN_USER_INFO)
             keyguardRepository.setKeyguardShowing(true)
         } else {
-            communalRepository.setIsCommunalEnabled(false)
-            communalRepository.setCommunalEnabledState(false)
+            keyguardRepository.setIsEncryptedOrLockdown(true)
         }
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 73d3091..f70b6a5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -22,12 +22,12 @@
 import android.widget.RemoteViews
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
 import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
 import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
 import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
-import com.android.systemui.communal.data.repository.fakeCommunalRepository
 import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
 import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
 import com.android.systemui.communal.domain.interactor.communalInteractor
@@ -37,6 +37,8 @@
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
+import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.kosmos.testScope
@@ -92,7 +94,8 @@
         mediaRepository = kosmos.fakeCommunalMediaRepository
         userRepository = kosmos.fakeUserRepository
 
-        kosmos.fakeCommunalRepository.setCommunalEnabledState(true)
+        kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
+        mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
 
         underTest =
             CommunalViewModel(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
index 032d76f..8488843 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
@@ -19,12 +19,15 @@
 import android.content.pm.UserInfo
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl.Companion.GLANCEABLE_HUB_ENABLED
 import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 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.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testDispatcher
@@ -33,6 +36,7 @@
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableSharedFlow
@@ -62,6 +66,8 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
+        kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
+        mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
 
         appWidgetIdToRemove = MutableSharedFlow()
         whenever(appWidgetHost.appWidgetIdToRemove).thenReturn(appWidgetIdToRemove)
@@ -169,7 +175,8 @@
             fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
             fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
             fakeKeyguardRepository.setKeyguardShowing(true)
-            fakeCommunalRepository.setCommunalEnabledState(available)
+            val settingsValue = if (available) 1 else 0
+            fakeSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, settingsValue, MAIN_USER_INFO.id)
         }
 
     private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
index 2a9bc4a..8dcf903 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
@@ -18,15 +18,20 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.view.GestureDetector;
 import android.view.MotionEvent;
 
+import androidx.lifecycle.Lifecycle;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 
@@ -39,28 +44,60 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class CommunalTouchHandlerTest extends SysuiTestCase {
+    private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+
     @Mock
     CentralSurfaces mCentralSurfaces;
     @Mock
     DreamTouchHandler.TouchSession mTouchSession;
     CommunalTouchHandler mTouchHandler;
+    @Mock
+    Lifecycle mLifecycle;
 
     private static final int INITIATION_WIDTH = 20;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        AtomicReference reference = new AtomicReference<>(null);
+        when(mLifecycle.getInternalScopeRef()).thenReturn(reference);
+        when(mLifecycle.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
         mTouchHandler = new CommunalTouchHandler(
                 Optional.of(mCentralSurfaces),
-                INITIATION_WIDTH);
+                INITIATION_WIDTH,
+                mKosmos.getCommunalInteractor(),
+                mLifecycle
+                );
+    }
+
+    @Test
+    public void communalTouchHandler_disabledByDefault() {
+        assertThat(mTouchHandler.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void communalTouchHandler_disabled_whenCommunalUnavailable() {
+        mTouchHandler.mIsCommunalAvailableCallback.accept(false);
+        assertThat(mTouchHandler.isEnabled()).isFalse();
+
+        mTouchHandler.onSessionStart(mTouchSession);
+        verify(mTouchSession, never()).registerGestureListener(any());
+    }
+
+    @Test
+    public void communalTouchHandler_enabled_whenCommunalAvailable() {
+        mTouchHandler.mIsCommunalAvailableCallback.accept(true);
+        assertThat(mTouchHandler.isEnabled()).isTrue();
     }
 
     @Test
     public void testEventPropagation() {
+        mTouchHandler.mIsCommunalAvailableCallback.accept(true);
         final MotionEvent motionEvent = Mockito.mock(MotionEvent.class);
 
         final ArgumentCaptor<InputChannelCompat.InputEventListener>
@@ -75,6 +112,7 @@
 
     @Test
     public void testTouchPilferingOnScroll() {
+        mTouchHandler.mIsCommunalAvailableCallback.accept(true);
         final MotionEvent motionEvent1 = Mockito.mock(MotionEvent.class);
         final MotionEvent motionEvent2 = Mockito.mock(MotionEvent.class);
 
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/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index a613ad8..0768340 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -23,14 +23,14 @@
 import android.service.quickaccesswallet.WalletCard
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -76,26 +76,28 @@
     }
 
     @Test
-    fun affordance_keyguardShowing_hasWalletCard_visibleModel() = testScope.runTest {
-        setUpState()
+    fun affordance_keyguardShowing_hasWalletCard_visibleModel() =
+        testScope.runTest {
+            setUpState()
 
-        val latest by collectLastValue(underTest.lockScreenState)
+            val latest by collectLastValue(underTest.lockScreenState)
 
-        val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
-        assertThat(visibleModel.icon)
-            .isEqualTo(
-                Icon.Loaded(
-                    drawable = ICON,
-                    contentDescription =
-                        ContentDescription.Resource(
-                            res = R.string.accessibility_wallet_button,
-                        ),
+            val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
+            assertThat(visibleModel.icon)
+                .isEqualTo(
+                    Icon.Loaded(
+                        drawable = ICON,
+                        contentDescription =
+                            ContentDescription.Resource(
+                                res = R.string.accessibility_wallet_button,
+                            ),
+                    )
                 )
-            )
-    }
+        }
 
     @Test
-    fun affordance_keyguardShowing_hasNonPaymentCard_modelIsNone() = testScope.runTest {
+    fun affordance_keyguardShowing_hasNonPaymentCard_modelIsNone() =
+        testScope.runTest {
             setUpState(cardType = WalletCard.CARD_TYPE_NON_PAYMENT)
 
             val latest by collectLastValue(underTest.lockScreenState)
@@ -104,54 +106,58 @@
         }
 
     @Test
-    fun affordance_keyguardShowing_hasPaymentCard_visibleModel() = testScope.runTest {
-        setUpState(cardType = WalletCard.CARD_TYPE_PAYMENT)
+    fun affordance_keyguardShowing_hasPaymentCard_visibleModel() =
+        testScope.runTest {
+            setUpState(cardType = WalletCard.CARD_TYPE_PAYMENT)
 
-        val latest by collectLastValue(underTest.lockScreenState)
+            val latest by collectLastValue(underTest.lockScreenState)
 
-        val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
-        assertThat(visibleModel.icon)
-            .isEqualTo(
-                Icon.Loaded(
-                    drawable = ICON,
-                    contentDescription =
-                        ContentDescription.Resource(
-                            res = R.string.accessibility_wallet_button,
-                        ),
+            val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
+            assertThat(visibleModel.icon)
+                .isEqualTo(
+                    Icon.Loaded(
+                        drawable = ICON,
+                        contentDescription =
+                            ContentDescription.Resource(
+                                res = R.string.accessibility_wallet_button,
+                            ),
+                    )
                 )
-            )
-    }
+        }
 
     @Test
-    fun affordance_walletFeatureNotEnabled_modelIsNone() = testScope.runTest {
-        setUpState(isWalletFeatureAvailable = false)
+    fun affordance_walletFeatureNotEnabled_modelIsNone() =
+        testScope.runTest {
+            setUpState(isWalletFeatureAvailable = false)
 
-        val latest by collectLastValue(underTest.lockScreenState)
+            val latest by collectLastValue(underTest.lockScreenState)
 
-        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
-    }
+            assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+        }
 
     @Test
-    fun affordance_queryNotSuccessful_modelIsNone() = testScope.runTest {
-        setUpState(isWalletQuerySuccessful = false)
+    fun affordance_queryNotSuccessful_modelIsNone() =
+        testScope.runTest {
+            setUpState(isWalletQuerySuccessful = false)
 
-        val latest by collectLastValue(underTest.lockScreenState)
+            val latest by collectLastValue(underTest.lockScreenState)
 
-        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
-    }
+            assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+        }
 
     @Test
-    fun affordance_noSelectedCard_modelIsNone() = testScope.runTest {
-        setUpState(hasSelectedCard = false)
+    fun affordance_noSelectedCard_modelIsNone() =
+        testScope.runTest {
+            setUpState(hasSelectedCard = false)
 
-        val latest by collectLastValue(underTest.lockScreenState)
+            val latest by collectLastValue(underTest.lockScreenState)
 
-        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
-    }
+            assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+        }
 
     @Test
     fun onQuickAffordanceTriggered() {
-        val animationController: ActivityLaunchAnimator.Controller = mock()
+        val animationController: ActivityTransitionAnimator.Controller = mock()
         val expandable: Expandable = mock {
             whenever(this.activityLaunchController()).thenReturn(animationController)
         }
@@ -167,42 +173,46 @@
     }
 
     @Test
-    fun getPickerScreenState_default() = testScope.runTest {
-        setUpState()
+    fun getPickerScreenState_default() =
+        testScope.runTest {
+            setUpState()
 
-        assertThat(underTest.getPickerScreenState())
-            .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default())
-    }
+            assertThat(underTest.getPickerScreenState())
+                .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default())
+        }
 
     @Test
-    fun getPickerScreenState_unavailable() = testScope.runTest {
-        setUpState(
-            isWalletServiceAvailable = false,
-        )
+    fun getPickerScreenState_unavailable() =
+        testScope.runTest {
+            setUpState(
+                isWalletServiceAvailable = false,
+            )
 
-        assertThat(underTest.getPickerScreenState())
-            .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
-    }
+            assertThat(underTest.getPickerScreenState())
+                .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+        }
 
     @Test
-    fun getPickerScreenState_disabledWhenTheFeatureIsNotEnabled() = testScope.runTest {
-        setUpState(
-            isWalletFeatureAvailable = false,
-        )
+    fun getPickerScreenState_disabledWhenTheFeatureIsNotEnabled() =
+        testScope.runTest {
+            setUpState(
+                isWalletFeatureAvailable = false,
+            )
 
-        assertThat(underTest.getPickerScreenState())
-            .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
-    }
+            assertThat(underTest.getPickerScreenState())
+                .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
+        }
 
     @Test
-    fun getPickerScreenState_disabledWhenThereIsNoCard() = testScope.runTest {
-        setUpState(
-            hasSelectedCard = false,
-        )
+    fun getPickerScreenState_disabledWhenThereIsNoCard() =
+        testScope.runTest {
+            setUpState(
+                hasSelectedCard = false,
+            )
 
-        assertThat(underTest.getPickerScreenState())
-            .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
-    }
+            assertThat(underTest.getPickerScreenState())
+                .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
+        }
 
     private fun setUpState(
         isWalletFeatureAvailable: Boolean = true,
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 2de013b..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
@@ -116,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/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 4595fbf..7261723 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -18,22 +18,28 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.content.pm.UserInfo
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.communal.data.repository.fakeCommunalRepository
-import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
+import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.kosmos.testScope
 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 com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
 import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -49,9 +55,7 @@
     private val testScope = kosmos.testScope
     private val sceneInteractor by lazy { kosmos.sceneInteractor }
 
-    private val underTest by lazy {
-        createLockscreenSceneViewModel()
-    }
+    private val underTest by lazy { createLockscreenSceneViewModel() }
 
     @Test
     fun upTransitionSceneKey_canSwipeToUnlock_gone() =
@@ -80,29 +84,37 @@
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
         }
 
+    @EnableFlags(FLAG_COMMUNAL_HUB)
     @Test
     fun leftTransitionSceneKey_communalIsEnabled_communal() =
         testScope.runTest {
-            kosmos.fakeCommunalRepository.setIsCommunalEnabled(true)
-            val underTest = createLockscreenSceneViewModel()
-
-            assertThat(underTest.leftDestinationSceneKey).isEqualTo(SceneKey.Communal)
+            with(kosmos.fakeUserRepository) {
+                setUserInfos(listOf(PRIMARY_USER))
+                setSelectedUserInfo(PRIMARY_USER)
+            }
+            kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
+            val leftDestinationSceneKey by collectLastValue(underTest.leftDestinationSceneKey)
+            assertThat(leftDestinationSceneKey).isEqualTo(SceneKey.Communal)
         }
 
+    @DisableFlags(FLAG_COMMUNAL_HUB)
     @Test
     fun leftTransitionSceneKey_communalIsDisabled_null() =
         testScope.runTest {
-            kosmos.fakeCommunalRepository.setIsCommunalEnabled(false)
-            val underTest = createLockscreenSceneViewModel()
-
-            assertThat(underTest.leftDestinationSceneKey).isNull()
+            with(kosmos.fakeUserRepository) {
+                setUserInfos(listOf(PRIMARY_USER))
+                setSelectedUserInfo(PRIMARY_USER)
+            }
+            kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false)
+            val leftDestinationSceneKey by collectLastValue(underTest.leftDestinationSceneKey)
+            assertThat(leftDestinationSceneKey).isNull()
         }
 
     private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
         return LockscreenSceneViewModel(
             applicationScope = testScope.backgroundScope,
             deviceEntryInteractor = kosmos.deviceEntryInteractor,
-            communalInteractor = kosmos.communalInteractor,
+            communalSettingsInteractor = kosmos.communalSettingsInteractor,
             longPress =
                 KeyguardLongPressViewModel(
                     interactor = mock(),
@@ -110,4 +122,9 @@
             notifications = kosmos.notificationsPlaceholderViewModel,
         )
     }
+
+    private companion object {
+        val PRIMARY_USER =
+            UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN)
+    }
 }
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 9d3f0d6..006f429 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -40,7 +40,7 @@
 import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel
 import com.android.systemui.classifier.domain.interactor.falsingInteractor
 import com.android.systemui.classifier.falsingCollector
-import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
@@ -130,7 +130,7 @@
     private val sceneInteractor by lazy { kosmos.sceneInteractor }
     private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
     private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
-    private val communalInteractor by lazy { kosmos.communalInteractor }
+    private val communalSettingsInteractor by lazy { kosmos.communalSettingsInteractor }
 
     private val transitionState by lazy {
         MutableStateFlow<ObservableTransitionState>(
@@ -155,7 +155,7 @@
         LockscreenSceneViewModel(
             applicationScope = testScope.backgroundScope,
             deviceEntryInteractor = deviceEntryInteractor,
-            communalInteractor = communalInteractor,
+            communalSettingsInteractor = communalSettingsInteractor,
             longPress =
                 KeyguardLongPressViewModel(
                     interactor = mock(),
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/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
new file mode 100644
index 0000000..bec8cfe
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
@@ -0,0 +1,237 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ActiveNotificationsInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val activeNotificationListRepository = kosmos.activeNotificationListRepository
+
+    private val underTest = kosmos.activeNotificationsInteractor
+
+    @Test
+    fun testAllNotificationsCount() =
+        testScope.runTest {
+            val count by collectLastValue(underTest.allNotificationsCount)
+
+            activeNotificationListRepository.setActiveNotifs(5)
+            runCurrent()
+
+            assertThat(count).isEqualTo(5)
+            assertThat(underTest.allNotificationsCountValue).isEqualTo(5)
+        }
+
+    @Test
+    fun areAnyNotificationsPresent_isTrue() =
+        testScope.runTest {
+            val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent)
+
+            activeNotificationListRepository.setActiveNotifs(2)
+            runCurrent()
+
+            assertThat(areAnyNotificationsPresent).isTrue()
+            assertThat(underTest.areAnyNotificationsPresentValue).isTrue()
+        }
+
+    @Test
+    fun areAnyNotificationsPresent_isFalse() =
+        testScope.runTest {
+            val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent)
+
+            activeNotificationListRepository.setActiveNotifs(0)
+            runCurrent()
+
+            assertThat(areAnyNotificationsPresent).isFalse()
+            assertThat(underTest.areAnyNotificationsPresentValue).isFalse()
+        }
+
+    @Test
+    fun testActiveNotificationRanks_sizeMatches() {
+        testScope.runTest {
+            val activeNotificationRanks by collectLastValue(underTest.activeNotificationRanks)
+
+            activeNotificationListRepository.setActiveNotifs(5)
+            runCurrent()
+
+            assertThat(activeNotificationRanks!!.size).isEqualTo(5)
+        }
+    }
+
+    @Test
+    fun clearableNotifications_whenHasClearableAlertingNotifs() =
+        testScope.runTest {
+            val hasClearable by collectLastValue(underTest.hasClearableNotifications)
+
+            activeNotificationListRepository.notifStats.value =
+                NotifStats(
+                    numActiveNotifs = 2,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = true,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = false,
+                )
+            runCurrent()
+
+            assertThat(hasClearable).isTrue()
+        }
+
+    @Test
+    fun hasClearableNotifications_whenHasClearableSilentNotifs() =
+        testScope.runTest {
+            val hasClearable by collectLastValue(underTest.hasClearableNotifications)
+
+            activeNotificationListRepository.notifStats.value =
+                NotifStats(
+                    numActiveNotifs = 2,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = false,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = true,
+                )
+            runCurrent()
+
+            assertThat(hasClearable).isTrue()
+        }
+
+    @Test
+    fun testHasClearableNotifications_whenHasNoNotifs() =
+        testScope.runTest {
+            val hasClearable by collectLastValue(underTest.hasClearableNotifications)
+
+            activeNotificationListRepository.notifStats.value =
+                NotifStats(
+                    numActiveNotifs = 0,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = false,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = false,
+                )
+            runCurrent()
+
+            assertThat(hasClearable).isFalse()
+        }
+
+    @Test
+    fun hasClearableAlertingNotifications_whenHasClearableSilentNotifs() =
+        testScope.runTest {
+            val hasClearable by collectLastValue(underTest.hasClearableAlertingNotifications)
+
+            activeNotificationListRepository.notifStats.value =
+                NotifStats(
+                    numActiveNotifs = 2,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = false,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = true,
+                )
+            runCurrent()
+
+            assertThat(hasClearable).isFalse()
+        }
+
+    @Test
+    fun hasClearableAlertingNotifications_whenHasNoClearableNotifs() =
+        testScope.runTest {
+            val hasClearable by collectLastValue(underTest.hasClearableAlertingNotifications)
+
+            activeNotificationListRepository.notifStats.value =
+                NotifStats(
+                    numActiveNotifs = 2,
+                    hasNonClearableAlertingNotifs = true,
+                    hasClearableAlertingNotifs = false,
+                    hasNonClearableSilentNotifs = true,
+                    hasClearableSilentNotifs = false,
+                )
+            runCurrent()
+
+            assertThat(hasClearable).isFalse()
+        }
+
+    @Test
+    fun hasClearableAlertingNotifications_whenHasAlertingNotifs() =
+        testScope.runTest {
+            val hasClearable by collectLastValue(underTest.hasClearableAlertingNotifications)
+
+            activeNotificationListRepository.notifStats.value =
+                NotifStats(
+                    numActiveNotifs = 2,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = true,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = false,
+                )
+            runCurrent()
+
+            assertThat(hasClearable).isTrue()
+        }
+
+    @Test
+    fun hasNonClearableSilentNotifications_whenHasNonClearableSilentNotifs() =
+        testScope.runTest {
+            val hasNonClearable by collectLastValue(underTest.hasNonClearableSilentNotifications)
+
+            activeNotificationListRepository.notifStats.value =
+                NotifStats(
+                    numActiveNotifs = 2,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = false,
+                    hasNonClearableSilentNotifs = true,
+                    hasClearableSilentNotifs = false,
+                )
+            runCurrent()
+
+            assertThat(hasNonClearable).isTrue()
+        }
+
+    @Test
+    fun testHasNonClearableSilentNotifications_whenHasClearableSilentNotifs() =
+        testScope.runTest {
+            val hasNonClearable by collectLastValue(underTest.hasNonClearableSilentNotifications)
+
+            activeNotificationListRepository.notifStats.value =
+                NotifStats(
+                    numActiveNotifs = 2,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = false,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = true,
+                )
+            runCurrent()
+
+            assertThat(hasNonClearable).isFalse()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index cc4ebd4..c01f1c7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -27,7 +27,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.ActivityIntentHelper
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.animation.LaunchableView
 import com.android.systemui.assist.AssistManager
 import com.android.systemui.keyguard.KeyguardViewMediator
@@ -78,7 +78,7 @@
     @Mock private lateinit var shadeController: ShadeController
     @Mock private lateinit var shadeViewController: ShadeViewController
     @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
-    @Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
+    @Mock private lateinit var mActivityTransitionAnimator: ActivityTransitionAnimator
     @Mock private lateinit var lockScreenUserManager: NotificationLockscreenUserManager
     @Mock private lateinit var statusBarWindowController: StatusBarWindowController
     @Mock private lateinit var notifShadeWindowController: NotificationShadeWindowController
@@ -109,7 +109,7 @@
                 shadeAnimationInteractor,
                 Lazy { statusBarKeyguardViewManager },
                 Lazy { notifShadeWindowController },
-                activityLaunchAnimator,
+                mActivityTransitionAnimator,
                 context,
                 DISPLAY_ID,
                 lockScreenUserManager,
@@ -149,7 +149,7 @@
                 override fun setShouldBlockVisibilityChanges(block: Boolean) {}
             }
         parent.addView(view)
-        val controller = ActivityLaunchAnimator.Controller.fromView(view)
+        val controller = ActivityTransitionAnimator.Controller.fromView(view)
         whenever(pendingIntent.isActivity).thenReturn(true)
         whenever(keyguardStateController.isShowing).thenReturn(true)
         whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
@@ -163,7 +163,7 @@
         )
         mainExecutor.runAllReady()
 
-        verify(activityLaunchAnimator)
+        verify(mActivityTransitionAnimator)
             .startPendingIntentWithAnimation(
                 nullable(),
                 eq(true),
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
index 6434209..1126ec3 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -20,7 +20,7 @@
 import android.os.UserHandle;
 import android.view.View;
 
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.plugins.annotations.ProvidesInterface;
 
 /**
@@ -54,7 +54,7 @@
      */
     void startPendingIntentDismissingKeyguard(PendingIntent intent,
             Runnable intentSentUiThreadCallback,
-            @Nullable ActivityLaunchAnimator.Controller animationController);
+            @Nullable ActivityTransitionAnimator.Controller animationController);
 
     /**
      * Similar to {@link #startPendingIntentDismissingKeyguard}, except that it supports launching
@@ -64,7 +64,7 @@
      */
     void startPendingIntentMaybeDismissingKeyguard(PendingIntent intent,
             @Nullable Runnable intentSentUiThreadCallback,
-            @Nullable ActivityLaunchAnimator.Controller animationController);
+            @Nullable ActivityTransitionAnimator.Controller animationController);
 
     /**
      * The intent flag can be specified in startActivity().
@@ -72,26 +72,26 @@
     void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade, int flags);
     void startActivity(Intent intent, boolean dismissShade);
     default void startActivity(Intent intent, boolean dismissShade,
-            @Nullable ActivityLaunchAnimator.Controller animationController) {
+            @Nullable ActivityTransitionAnimator.Controller animationController) {
         startActivity(intent, dismissShade, animationController,
                 false /* showOverLockscreenWhenLocked */);
     }
 
     void startActivity(Intent intent, boolean dismissShade,
-            @Nullable ActivityLaunchAnimator.Controller animationController,
+            @Nullable ActivityTransitionAnimator.Controller animationController,
             boolean showOverLockscreenWhenLocked);
     void startActivity(Intent intent, boolean dismissShade,
-            @Nullable ActivityLaunchAnimator.Controller animationController,
+            @Nullable ActivityTransitionAnimator.Controller animationController,
             boolean showOverLockscreenWhenLocked, UserHandle userHandle);
     void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade);
     void startActivity(Intent intent, boolean dismissShade, Callback callback);
     void postStartActivityDismissingKeyguard(Intent intent, int delay);
     void postStartActivityDismissingKeyguard(Intent intent, int delay,
-            @Nullable ActivityLaunchAnimator.Controller animationController);
+            @Nullable ActivityTransitionAnimator.Controller animationController);
 
     /** Posts a start activity intent that dismisses keyguard. */
     void postStartActivityDismissingKeyguard(Intent intent, int delay,
-            @Nullable ActivityLaunchAnimator.Controller animationController,
+            @Nullable ActivityTransitionAnimator.Controller animationController,
             @Nullable String customMessage);
     void postStartActivityDismissingKeyguard(PendingIntent intent);
 
@@ -100,7 +100,7 @@
      * animation controller that should be used for the activity launch animation.
      */
     void postStartActivityDismissingKeyguard(PendingIntent intent,
-            @Nullable ActivityLaunchAnimator.Controller animationController);
+            @Nullable ActivityTransitionAnimator.Controller animationController);
 
     void postQSRunnableDismissingKeyguard(Runnable runnable);
 
@@ -123,7 +123,7 @@
             boolean disallowEnterPictureInPictureWhileLaunching,
             Callback callback,
             int flags,
-            @Nullable ActivityLaunchAnimator.Controller animationController,
+            @Nullable ActivityTransitionAnimator.Controller animationController,
             UserHandle userHandle);
 
     /** Execute a runnable after dismissing keyguard. */
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/strings.xml b/packages/SystemUI/res/values/strings.xml
index 1b71256..b30e4a2 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>
 
@@ -1923,7 +1932,7 @@
     <!-- User visible title for the keyboard shortcut that locks screen. [CHAR LIMIT=70] -->
     <string name="group_system_lock_screen">Lock screen</string>
     <!-- User visible title for the keyboard shortcut that pulls up Notes app for quick memo. [CHAR LIMIT=70] -->
-    <string name="group_system_quick_memo">Open notes</string>
+    <string name="group_system_quick_memo">Take a note</string>
 
     <!-- User visible title for the system multitasking keyboard shortcuts list. [CHAR LIMIT=70] -->
     <string name="keyboard_shortcut_group_system_multitasking">System multitasking</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 1a10c7a..458a21c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -38,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;
@@ -212,7 +211,6 @@
         private final FeatureFlags mFeatureFlags;
         private final SelectedUserInteractor mSelectedUserInteractor;
         private final UiEventLogger mUiEventLogger;
-        private final KeyboardRepository mKeyboardRepository;
         private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
 
         @Inject
@@ -228,7 +226,6 @@
                 KeyguardViewController keyguardViewController,
                 FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor,
                 UiEventLogger uiEventLogger,
-                KeyboardRepository keyboardRepository,
                 KeyguardKeyboardInteractor keyguardKeyboardInteractor) {
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
             mLockPatternUtils = lockPatternUtils;
@@ -246,7 +243,6 @@
             mFeatureFlags = featureFlags;
             mSelectedUserInteractor = selectedUserInteractor;
             mUiEventLogger = uiEventLogger;
-            mKeyboardRepository = keyboardRepository;
             mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
         }
 
@@ -277,7 +273,7 @@
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mLiftToActivateListener, emergencyButtonController, mFalsingCollector,
                         mDevicePostureController, mFeatureFlags, mSelectedUserInteractor,
-                        mUiEventLogger, mKeyboardRepository
+                        mUiEventLogger, mKeyguardKeyboardInteractor
                 );
             } else if (keyguardInputView instanceof KeyguardSimPinView) {
                 return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
@@ -285,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/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..cb1c4b3 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/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/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/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 66fe4b3..716209d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -66,7 +66,7 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dumpable;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.biometrics.dagger.BiometricsBackground;
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
@@ -162,7 +162,7 @@
             mUnlockedScreenOffAnimationController;
     @NonNull private final LatencyTracker mLatencyTracker;
     @VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
-    @NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
+    @NonNull private final ActivityTransitionAnimator mActivityTransitionAnimator;
     @NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     @NonNull private final ShadeInteractor mShadeInteractor;
     @Nullable private final TouchProcessor mTouchProcessor;
@@ -287,7 +287,7 @@
                             event,
                             fromUdfpsView
                         ),
-                        mActivityLaunchAnimator,
+                            mActivityTransitionAnimator,
                         mPrimaryBouncerInteractor,
                         mAlternateBouncerInteractor,
                         mUdfpsKeyguardAccessibilityDelegate,
@@ -663,7 +663,7 @@
             @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
             @NonNull SystemUIDialogManager dialogManager,
             @NonNull LatencyTracker latencyTracker,
-            @NonNull ActivityLaunchAnimator activityLaunchAnimator,
+            @NonNull ActivityTransitionAnimator activityTransitionAnimator,
             @NonNull @BiometricsBackground Executor biometricsExecutor,
             @NonNull PrimaryBouncerInteractor primaryBouncerInteractor,
             @NonNull ShadeInteractor shadeInteractor,
@@ -706,7 +706,7 @@
         mSystemClock = systemClock;
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
         mLatencyTracker = latencyTracker;
-        mActivityLaunchAnimator = activityLaunchAnimator;
+        mActivityTransitionAnimator = activityTransitionAnimator;
         mSensorProps = new FingerprintSensorPropertiesInternal(
                 -1 /* sensorId */,
                 SensorProperties.STRENGTH_CONVENIENCE,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 4ea5f4c..a209eae 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -44,7 +44,7 @@
 import androidx.annotation.LayoutRes
 import androidx.annotation.VisibleForTesting
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.biometrics.ui.binder.UdfpsTouchOverlayBinder
@@ -100,7 +100,7 @@
     @RequestReason val requestReason: Int,
     private val controllerCallback: IUdfpsOverlayControllerCallback,
     private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
-    private val activityLaunchAnimator: ActivityLaunchAnimator,
+    private val activityTransitionAnimator: ActivityTransitionAnimator,
     private val primaryBouncerInteractor: PrimaryBouncerInteractor,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
     private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
@@ -304,7 +304,7 @@
                     unlockedScreenOffAnimationController,
                     dialogManager,
                     controller,
-                    activityLaunchAnimator,
+                    activityTransitionAnimator,
                     primaryBouncerInteractor,
                     alternateBouncerInteractor,
                     udfpsKeyguardAccessibilityDelegate,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 7020d05..018d92e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -25,7 +25,7 @@
 import com.android.app.animation.Interpolators
 import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
@@ -69,7 +69,7 @@
     private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
     systemUIDialogManager: SystemUIDialogManager,
     private val udfpsController: UdfpsController,
-    private val activityLaunchAnimator: ActivityLaunchAnimator,
+    private val activityTransitionAnimator: ActivityTransitionAnimator,
     private val primaryBouncerInteractor: PrimaryBouncerInteractor,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
     private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
@@ -137,20 +137,20 @@
             }
         }
 
-    private val activityLaunchAnimatorListener: ActivityLaunchAnimator.Listener =
-        object : ActivityLaunchAnimator.Listener {
-            override fun onLaunchAnimationStart() {
+    private val mActivityTransitionAnimatorListener: ActivityTransitionAnimator.Listener =
+        object : ActivityTransitionAnimator.Listener {
+            override fun onTransitionAnimationStart() {
                 isLaunchingActivity = true
                 activityLaunchProgress = 0f
                 updateAlpha()
             }
 
-            override fun onLaunchAnimationEnd() {
+            override fun onTransitionAnimationEnd() {
                 isLaunchingActivity = false
                 updateAlpha()
             }
 
-            override fun onLaunchAnimationProgress(linearProgress: Float) {
+            override fun onTransitionAnimationProgress(linearProgress: Float) {
                 activityLaunchProgress = linearProgress
                 updateAlpha()
             }
@@ -370,7 +370,7 @@
         updatePauseAuth()
         keyguardViewManager.setOccludingAppBiometricUI(occludingAppBiometricUI)
         lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = this
-        activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
+        activityTransitionAnimator.addListener(mActivityTransitionAnimatorListener)
         view.startIconAsyncInflate {
             val animationViewInternal: View =
                 view.requireViewById(R.id.udfps_animation_view_internal)
@@ -389,7 +389,7 @@
         if (lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy === this) {
             lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = null
         }
-        activityLaunchAnimator.removeListener(activityLaunchAnimatorListener)
+        activityTransitionAnimator.removeListener(mActivityTransitionAnimatorListener)
         keyguardViewManager.removeCallback(statusBarKeyguardViewManagerCallback)
     }
 
@@ -536,7 +536,7 @@
                 val udfpsActivityLaunchAlphaMultiplier =
                     1f -
                         (activityLaunchProgress *
-                                (ActivityLaunchAnimator.TIMINGS.totalDuration / 83))
+                                (ActivityTransitionAnimator.TIMINGS.totalDuration / 83))
                             .coerceIn(0f, 1f)
                 alpha = (alpha * udfpsActivityLaunchAlphaMultiplier).toInt()
             }
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/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index dc07c1b..0bad33b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.communal.data.repository.CommunalMediaRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalRepositoryModule
+import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
@@ -36,6 +37,7 @@
             CommunalWidgetRepositoryModule::class,
             CommunalDatabaseModule::class,
             CommunalPrefsRepositoryModule::class,
+            CommunalSettingsRepositoryModule::class,
         ]
 )
 interface CommunalModule {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalEnabledState.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalEnabledState.kt
new file mode 100644
index 0000000..83a5bdb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalEnabledState.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.model
+
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+import java.util.EnumSet
+
+/** Reasons that communal is disabled, primarily for logging. */
+enum class DisabledReason(val loggingString: String) {
+    /** Communal should be disabled due to invalid current user */
+    DISABLED_REASON_INVALID_USER("invalidUser"),
+    /** Communal should be disabled due to the flag being off */
+    DISABLED_REASON_FLAG("flag"),
+    /** Communal should be disabled because the user has turned off the setting */
+    DISABLED_REASON_USER_SETTING("userSetting"),
+    /** Communal is disabled by the device policy app */
+    DISABLED_REASON_DEVICE_POLICY("devicePolicy"),
+}
+
+/**
+ * Model representing the reasons communal hub should be disabled. Allows logging reasons separately
+ * for debugging.
+ */
+@JvmInline
+value class CommunalEnabledState(
+    private val disabledReasons: EnumSet<DisabledReason> =
+        EnumSet.noneOf(DisabledReason::class.java)
+) : Diffable<CommunalEnabledState>, Set<DisabledReason> by disabledReasons {
+
+    /** Creates [CommunalEnabledState] with a single reason for being disabled */
+    constructor(reason: DisabledReason) : this(EnumSet.of(reason))
+
+    /** Checks if there are any reasons communal should be disabled. If none, returns true. */
+    val enabled: Boolean
+        get() = isEmpty()
+
+    override fun logDiffs(prevVal: CommunalEnabledState, row: TableRowLogger) {
+        for (reason in DisabledReason.entries) {
+            val newVal = contains(reason)
+            if (newVal != prevVal.contains(reason)) {
+                row.logChange(
+                    columnName = reason.loggingString,
+                    value = newVal,
+                )
+            }
+        }
+    }
+
+    override fun logFull(row: TableRowLogger) {
+        for (reason in DisabledReason.entries) {
+            row.logChange(columnName = reason.loggingString, value = contains(reason))
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
index addd880..4a06585f5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
@@ -16,22 +16,14 @@
 
 package com.android.systemui.communal.data.repository
 
-import com.android.systemui.Flags.communalHub
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
 import com.android.systemui.scene.data.repository.SceneContainerRepository
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.user.data.repository.UserRepository
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
@@ -39,26 +31,13 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.withContext
 
 /** Encapsulates the state of communal mode. */
 interface CommunalRepository {
-    /** Whether communal features are enabled. */
-    val isCommunalEnabled: Boolean
-
-    /**
-     * A {@link StateFlow} that tracks whether communal hub is enabled (it can be disabled in
-     * settings).
-     */
-    val communalEnabledState: StateFlow<Boolean>
-
     /** Whether the communal hub is showing. */
     val isCommunalHubShowing: Flow<Boolean>
 
@@ -87,37 +66,11 @@
 class CommunalRepositoryImpl
 @Inject
 constructor(
-    @Application private val applicationScope: CoroutineScope,
     @Background backgroundScope: CoroutineScope,
-    @Background private val backgroundDispatcher: CoroutineDispatcher,
-    private val featureFlagsClassic: FeatureFlagsClassic,
     sceneContainerFlags: SceneContainerFlags,
     sceneContainerRepository: SceneContainerRepository,
-    userRepository: UserRepository,
-    private val secureSettings: SecureSettings
 ) : CommunalRepository {
 
-    private val communalEnabledSettingState: Flow<Boolean> =
-        userRepository.selectedUserInfo
-            .flatMapLatest { userInfo -> observeSettings(userInfo.id) }
-            .shareIn(scope = applicationScope, started = SharingStarted.WhileSubscribed())
-
-    override val communalEnabledState: StateFlow<Boolean> =
-        if (featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()) {
-            communalEnabledSettingState
-                .filterNotNull()
-                .stateIn(
-                    scope = applicationScope,
-                    started = SharingStarted.Eagerly,
-                    initialValue = true
-                )
-        } else {
-            MutableStateFlow(false)
-        }
-
-    override val isCommunalEnabled: Boolean
-        get() = communalEnabledState.value
-
     private val _desiredScene: MutableStateFlow<CommunalSceneKey> =
         MutableStateFlow(CommunalSceneKey.DEFAULT)
     override val desiredScene: StateFlow<CommunalSceneKey> = _desiredScene.asStateFlow()
@@ -153,26 +106,4 @@
         } else {
             desiredScene.map { sceneKey -> sceneKey == CommunalSceneKey.Communal }
         }
-
-    private fun observeSettings(userId: Int): Flow<Boolean> =
-        secureSettings
-            .observerFlow(
-                userId = userId,
-                names =
-                    arrayOf(
-                        GLANCEABLE_HUB_ENABLED,
-                    )
-            )
-            // Force an update
-            .onStart { emit(Unit) }
-            .map { readFromSettings(userId) }
-
-    private suspend fun readFromSettings(userId: Int): Boolean =
-        withContext(backgroundDispatcher) {
-            secureSettings.getIntForUser(GLANCEABLE_HUB_ENABLED, 1, userId) == 1
-        }
-
-    companion object {
-        private const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled"
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
new file mode 100644
index 0000000..201b049
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.repository
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
+import android.content.IntentFilter
+import android.content.pm.UserInfo
+import com.android.systemui.Flags.communalHub
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.communal.data.model.CommunalEnabledState
+import com.android.systemui.communal.data.model.DisabledReason
+import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_DEVICE_POLICY
+import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_FLAG
+import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_INVALID_USER
+import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_USER_SETTING
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.util.kotlin.emitOnStart
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import java.util.EnumSet
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+interface CommunalSettingsRepository {
+    /** A [CommunalEnabledState] for the specified user. */
+    fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState>
+}
+
+@SysUISingleton
+class CommunalSettingsRepositoryImpl
+@Inject
+constructor(
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    private val featureFlagsClassic: FeatureFlagsClassic,
+    private val secureSettings: SecureSettings,
+    private val broadcastDispatcher: BroadcastDispatcher,
+    private val devicePolicyManager: DevicePolicyManager,
+) : CommunalSettingsRepository {
+
+    private val flagEnabled: Boolean by lazy {
+        featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()
+    }
+
+    override fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState> {
+        if (!user.isMain) {
+            return flowOf(CommunalEnabledState(DISABLED_REASON_INVALID_USER))
+        }
+        if (!flagEnabled) {
+            return flowOf(CommunalEnabledState(DISABLED_REASON_FLAG))
+        }
+        return combine(
+                getEnabledByUser(user).mapToReason(DISABLED_REASON_USER_SETTING),
+                getAllowedByDevicePolicy(user).mapToReason(DISABLED_REASON_DEVICE_POLICY),
+            ) { reasons ->
+                reasons.filterNotNull()
+            }
+            .map { reasons ->
+                if (reasons.isEmpty()) {
+                    EnumSet.noneOf(DisabledReason::class.java)
+                } else {
+                    EnumSet.copyOf(reasons)
+                }
+            }
+            .map { reasons -> CommunalEnabledState(reasons) }
+            .flowOn(bgDispatcher)
+    }
+
+    private fun getEnabledByUser(user: UserInfo): Flow<Boolean> =
+        secureSettings
+            .observerFlow(userId = user.id, names = arrayOf(GLANCEABLE_HUB_ENABLED))
+            // Force an update
+            .onStart { emit(Unit) }
+            .map {
+                secureSettings.getIntForUser(
+                    GLANCEABLE_HUB_ENABLED,
+                    ENABLED_SETTING_DEFAULT,
+                    user.id,
+                ) == 1
+            }
+
+    private fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> =
+        broadcastDispatcher
+            .broadcastFlow(
+                filter =
+                    IntentFilter(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+                user = user.userHandle
+            )
+            .emitOnStart()
+            .map { devicePolicyManager.areKeyguardWidgetsAllowed(user.id) }
+
+    companion object {
+        const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled"
+        private const val ENABLED_SETTING_DEFAULT = 1
+    }
+}
+
+private fun DevicePolicyManager.areKeyguardWidgetsAllowed(userId: Int): Boolean =
+    (getKeyguardDisabledFeatures(null, userId) and KEYGUARD_DISABLE_WIDGETS_ALL) == 0
+
+private fun Flow<Boolean>.mapToReason(reason: DisabledReason) = map { enabled ->
+    if (enabled) null else reason
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryModule.kt
similarity index 63%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryModule.kt
index 128f58b..a931d3f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryModule.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,8 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.animation
+package com.android.systemui.communal.data.repository
 
-import com.android.systemui.kosmos.Kosmos
+import dagger.Binds
+import dagger.Module
 
-val Kosmos.activityLaunchAnimator by Kosmos.Fixture { ActivityLaunchAnimator() }
+@Module
+interface CommunalSettingsRepositoryModule {
+    @Binds
+    fun communalSettingsRepository(impl: CommunalSettingsRepositoryImpl): CommunalSettingsRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index 2ac9d05..e4c9195 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -54,11 +54,12 @@
         configurator: WidgetConfigurator? = null
     ) {}
 
-    /** Delete a widget by id from the database. */
-    fun deleteWidgetFromDb(widgetId: Int) {}
-
-    /** Delete a widget by id from app widget host. */
-    fun deleteWidgetFromHost(widgetId: Int) {}
+    /**
+     * Delete a widget by id from the database and app widget host.
+     *
+     * @param widgetId id of the widget to remove.
+     */
+    fun deleteWidget(widgetId: Int) {}
 
     /**
      * Update the order of widgets in the database.
@@ -146,23 +147,15 @@
         }
     }
 
-    override fun deleteWidgetFromDb(widgetId: Int) {
+    override fun deleteWidget(widgetId: Int) {
         bgScope.launch {
             if (communalWidgetDao.deleteWidgetById(widgetId)) {
-                logger.i("Deleted widget with id $widgetId from DB .")
-            } else {
-                logger.w("Widget with id $widgetId cannot be deleted from DB.")
+                appWidgetHost.deleteAppWidgetId(widgetId)
+                logger.i("Deleted widget with id $widgetId.")
             }
         }
     }
 
-    override fun deleteWidgetFromHost(widgetId: Int) {
-        bgScope.launch {
-            appWidgetHost.deleteAppWidgetId(widgetId)
-            logger.i("Deleted widget with id $widgetId.")
-        }
-    }
-
     override fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
         bgScope.launch {
             communalWidgetDao.updateWidgetOrder(widgetIdToPriorityMap)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 950ac3c..b4f4099 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -42,7 +42,6 @@
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.smartspace.data.repository.SmartspaceRepository
-import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.kotlin.BooleanFlowOperators.and
 import com.android.systemui.util.kotlin.BooleanFlowOperators.not
 import com.android.systemui.util.kotlin.BooleanFlowOperators.or
@@ -74,8 +73,8 @@
     private val communalPrefsRepository: CommunalPrefsRepository,
     mediaRepository: CommunalMediaRepository,
     smartspaceRepository: SmartspaceRepository,
-    userRepository: UserRepository,
     keyguardInteractor: KeyguardInteractor,
+    private val communalSettingsInteractor: CommunalSettingsInteractor,
     private val appWidgetHost: CommunalAppWidgetHost,
     private val editWidgetsActivityStarter: EditWidgetsActivityStarter,
     @CommunalLog logBuffer: LogBuffer,
@@ -90,13 +89,12 @@
 
     /** Whether communal features are enabled. */
     val isCommunalEnabled: Boolean
-        get() = communalRepository.isCommunalEnabled
+        get() = communalSettingsInteractor.isCommunalEnabled.value
 
     /** Whether communal features are enabled and available. */
     val isCommunalAvailable: Flow<Boolean> =
         and(
-                communalRepository.communalEnabledState,
-                userRepository.selectedUserInfo.map { it.isMain },
+                communalSettingsInteractor.isCommunalEnabled,
                 not(keyguardInteractor.isEncryptedOrLockdown),
                 or(keyguardInteractor.isKeyguardVisible, keyguardInteractor.isDreaming)
             )
@@ -238,14 +236,11 @@
     ) = widgetRepository.addWidget(componentName, priority, configurator)
 
     /**
-     * Delete a widget by id from the database. [CommunalAppWidgetHostStartable] invokes this
-     * function to manage the deletion from the database for uninstalled or user-deleted widgets,
-     * following the removal of a widget from the host.
+     * Delete a widget by id. Called when user deletes a widget from the hub or a widget is
+     * uninstalled from App widget host.
      */
-    fun deleteWidgetFromDb(id: Int) = widgetRepository.deleteWidgetFromDb(id)
+    fun deleteWidget(id: Int) = widgetRepository.deleteWidget(id)
 
-    /** Delete a widget by id from AppWidgetHost. Called when user deletes a widget from the hub */
-    fun deleteWidgetFromHost(id: Int) = widgetRepository.deleteWidgetFromHost(id)
     /**
      * Reorder the widgets.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
new file mode 100644
index 0000000..0b096ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.domain.interactor
+
+import com.android.systemui.communal.data.model.CommunalEnabledState
+import com.android.systemui.communal.data.repository.CommunalSettingsRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class CommunalSettingsInteractor
+@Inject
+constructor(
+    @Background private val bgScope: CoroutineScope,
+    private val repository: CommunalSettingsRepository,
+    userInteractor: SelectedUserInteractor,
+    @CommunalTableLog tableLogBuffer: TableLogBuffer,
+) {
+    /** Whether or not communal is enabled for the currently selected user. */
+    val isCommunalEnabled: StateFlow<Boolean> =
+        userInteractor.selectedUserInfo
+            .flatMapLatest { user -> repository.getEnabledState(user) }
+            .logDiffsForTable(
+                tableLogBuffer = tableLogBuffer,
+                columnPrefix = "disabledReason",
+                initialValue = CommunalEnabledState()
+            )
+            .map { model -> model.enabled }
+            // Start this eagerly since the value is accessed synchronously in many places.
+            .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
index 1404ee2..25dfc02 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
@@ -28,17 +28,18 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transformWhile
 import kotlinx.coroutines.launch
 
 /** Encapsulates business-logic related to communal tutorial state. */
@@ -51,6 +52,7 @@
     private val communalTutorialRepository: CommunalTutorialRepository,
     keyguardInteractor: KeyguardInteractor,
     private val communalRepository: CommunalRepository,
+    private val communalSettingsInteractor: CommunalSettingsInteractor,
     communalInteractor: CommunalInteractor,
     @CommunalTableLog tableLogBuffer: TableLogBuffer,
 ) {
@@ -110,20 +112,24 @@
         return null
     }
 
-    private var job: Job? = null
     private fun listenForTransitionToUpdateTutorialState() {
-        if (!communalRepository.isCommunalEnabled) {
-            return
-        }
-        job =
-            scope.launch {
-                tutorialStateToUpdate.collect {
-                    communalTutorialRepository.setTutorialState(it)
-                    if (it == Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) {
-                        job?.cancel()
+        scope.launch {
+            communalSettingsInteractor.isCommunalEnabled
+                .flatMapLatest { enabled ->
+                    if (!enabled) {
+                        emptyFlow()
+                    } else {
+                        tutorialStateToUpdate
                     }
                 }
-            }
+                .transformWhile { tutorialState ->
+                    emit(tutorialState)
+                    tutorialState != Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
+                }
+                .collect { tutorialState ->
+                    communalTutorialRepository.setTutorialState(tutorialState)
+                }
+        }
     }
 
     init {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 69d5581..0b355cc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -62,7 +62,7 @@
     override val reorderingWidgets: StateFlow<Boolean>
         get() = _reorderingWidgets
 
-    override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidgetFromHost(id)
+    override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
 
     override fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) =
         communalInteractor.updateWidgetOrder(widgetIdToPriorityMap)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
index 6fd0fbe..4ddd768 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
@@ -50,7 +50,7 @@
             .launchIn(bgScope)
 
         appWidgetHost.appWidgetIdToRemove
-            .onEach { appWidgetId -> communalInteractor.deleteWidgetFromDb(appWidgetId) }
+            .onEach { appWidgetId -> communalInteractor.deleteWidget(id = appWidgetId) }
             .launchIn(bgScope)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
index afa7fa9..4c1e77b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
@@ -19,7 +19,7 @@
 import android.app.PendingIntent
 import android.view.View
 import android.widget.RemoteViews
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.common.ui.view.getNearestParent
 import com.android.systemui.plugins.ActivityStarter
 import javax.inject.Inject
@@ -42,7 +42,7 @@
 
     private fun startActivity(view: View, pendingIntent: PendingIntent): Boolean {
         val hostView = view.getNearestParent<CommunalAppWidgetHostView>()
-        val animationController = hostView?.let(ActivityLaunchAnimator.Controller::fromView)
+        val animationController = hostView?.let(ActivityTransitionAnimator.Controller::fromView)
 
         activityStarter.startPendingIntentMaybeDismissingKeyguard(
             pendingIntent,
diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
index 9d4ed20..afa2375 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
@@ -35,7 +35,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.settingslib.Utils;
 import com.android.systemui.CoreStartable;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.complication.dagger.DreamHomeControlsComplicationComponent;
 import com.android.systemui.controls.ControlsServiceInfo;
 import com.android.systemui.controls.dagger.ControlsComponent;
@@ -275,8 +275,9 @@
                     .putExtra(ControlsUiController.EXTRA_ANIMATE, true)
                     .putExtra(ControlsUiController.EXIT_TO_DREAM, true);
 
-            final ActivityLaunchAnimator.Controller controller =
-                    v != null ? ActivityLaunchAnimator.Controller.fromView(v, null /* cujType */)
+            final ActivityTransitionAnimator.Controller controller =
+                    v != null
+                            ? ActivityTransitionAnimator.Controller.fromView(v, null /* cujType */)
                             : null;
             if (mControlsComponent.getVisibility() == AVAILABLE) {
                 // Controls can be made visible.
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/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index 05279fc..e5c705f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -17,15 +17,21 @@
 package com.android.systemui.dreams.touch;
 
 import static com.android.systemui.dreams.touch.dagger.ShadeModule.COMMUNAL_GESTURE_INITIATION_WIDTH;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.view.GestureDetector;
 import android.view.MotionEvent;
 
+import androidx.annotation.VisibleForTesting;
+import androidx.lifecycle.Lifecycle;
+
+import com.android.systemui.communal.domain.interactor.CommunalInteractor;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 
 import java.util.Optional;
+import java.util.function.Consumer;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -34,17 +40,49 @@
 public class CommunalTouchHandler implements DreamTouchHandler {
     private final int mInitiationWidth;
     private final Optional<CentralSurfaces> mCentralSurfaces;
+    private final Lifecycle mLifecycle;
+    private final CommunalInteractor mCommunalInteractor;
+    private Boolean mIsEnabled = false;
+
+    @VisibleForTesting
+    final Consumer<Boolean> mIsCommunalAvailableCallback =
+            isAvailable -> {
+                setIsEnabled(isAvailable);
+            };
 
     @Inject
     public CommunalTouchHandler(
             Optional<CentralSurfaces> centralSurfaces,
-            @Named(COMMUNAL_GESTURE_INITIATION_WIDTH) int initiationWidth) {
+            @Named(COMMUNAL_GESTURE_INITIATION_WIDTH) int initiationWidth,
+            CommunalInteractor communalInteractor,
+            Lifecycle lifecycle) {
         mInitiationWidth = initiationWidth;
         mCentralSurfaces = centralSurfaces;
+        mLifecycle = lifecycle;
+        mCommunalInteractor = communalInteractor;
+
+        collectFlow(
+                mLifecycle,
+                mCommunalInteractor.isCommunalAvailable(),
+                mIsCommunalAvailableCallback
+        );
+    }
+
+    @Override
+    public Boolean isEnabled() {
+        return mIsEnabled;
+    }
+
+    @Override
+    public void setIsEnabled(Boolean enabled) {
+        mIsEnabled = enabled;
     }
 
     @Override
     public void onSessionStart(TouchSession session) {
+        if (!mIsEnabled) {
+            return;
+        }
         mCentralSurfaces.ifPresent(surfaces -> handleSessionStart(surfaces, session));
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
index aca621b..55a9c0c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
@@ -292,6 +292,9 @@
                         new HashMap<>();
 
                 for (DreamTouchHandler handler : mHandlers) {
+                            if (!handler.isEnabled()) {
+                                continue;
+                            }
                     final Rect maxBounds = mDisplayHelper.getMaxBounds(ev.getDisplayId(),
                             TYPE_APPLICATION_OVERLAY);
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java
index b37010c..72ad45d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java
@@ -86,6 +86,20 @@
     }
 
     /**
+     * Returns whether the handler is enabled to handle touch on dream.
+     * @return isEnabled state. By default it's true.
+     */
+    default Boolean isEnabled() {
+        return true;
+    }
+
+    /**
+     * Sets whether to enable the handler to handle touch on dream.
+     * @param enabled new value to be set whether to enable the handler.
+     */
+    default void setIsEnabled(Boolean enabled){}
+
+    /**
      * Returns the region the touch handler is interested in. By default, no region is specified,
      * indicating the entire screen should be considered.
      * @param region A {@link Region} that is passed in to the target entry touch region.
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/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 8f08efa..0ee924d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -130,8 +130,8 @@
 import com.android.systemui.DejankUtils;
 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.ActivityTransitionAnimator;
+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;
@@ -957,16 +957,17 @@
      * Animation launch controller for activities that occlude the keyguard.
      */
     @VisibleForTesting
-    final ActivityLaunchAnimator.Controller mOccludeAnimationController =
-            new ActivityLaunchAnimator.Controller() {
+    final ActivityTransitionAnimator.Controller mOccludeAnimationController =
+            new ActivityTransitionAnimator.Controller() {
                 @Override
-                public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {
+                public void onTransitionAnimationStart(boolean isExpandingFullyAbove) {
                     mOccludeAnimationPlaying = true;
                     mScrimControllerLazy.get().setOccludeAnimationPlaying(true);
                 }
 
                 @Override
-                public void onLaunchAnimationCancelled(@Nullable Boolean newKeyguardOccludedState) {
+                public void onTransitionAnimationCancelled(
+                        @Nullable Boolean newKeyguardOccludedState) {
                     Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: "
                             + mOccluded);
                     mOccludeAnimationPlaying = false;
@@ -977,7 +978,7 @@
                 }
 
                 @Override
-                public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
+                public void onTransitionAnimationEnd(boolean launchIsFullScreen) {
                     if (launchIsFullScreen) {
                         mShadeController.get().instantCollapseShade();
                     }
@@ -994,23 +995,23 @@
 
                 @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.");
+                            + "ActivityTransitionAnimator, which should never happen.");
                 }
 
                 @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;
@@ -1018,7 +1019,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 */,
@@ -1030,7 +1031,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,
@@ -1167,8 +1168,8 @@
 
     /**
      * Animation controller for activities that unocclude the keyguard. This does not use the
-     * ActivityLaunchAnimator since we're just translating down, rather than emerging from a view
-     * or the power button.
+     * ActivityTransitionAnimator since we're just translating down, rather than emerging from a
+     * view or the power button.
      */
     private final IRemoteAnimationRunner mUnoccludeAnimationRunner =
             new IRemoteAnimationRunner.Stub() {
@@ -1347,7 +1348,7 @@
     private boolean mWallpaperSupportsAmbientMode;
     private final KeyguardTransitions mKeyguardTransitions;
 
-    private final Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
+    private final Lazy<ActivityTransitionAnimator> mActivityTransitionAnimator;
     private final Lazy<ScrimController> mScrimControllerLazy;
     private final IActivityTaskManager mActivityTaskManagerService;
 
@@ -1394,7 +1395,7 @@
             WallpaperRepository wallpaperRepository,
             Lazy<ShadeController> shadeControllerLazy,
             Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
-            Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
+            Lazy<ActivityTransitionAnimator> activityTransitionAnimator,
             Lazy<ScrimController> scrimControllerLazy,
             IActivityTaskManager activityTaskManagerService,
             FeatureFlags featureFlags,
@@ -1459,7 +1460,7 @@
         mJavaAdapter = javaAdapter;
         mWallpaperRepository = wallpaperRepository;
 
-        mActivityLaunchAnimator = activityLaunchAnimator;
+        mActivityTransitionAnimator = activityTransitionAnimator;
         mScrimControllerLazy = scrimControllerLazy;
         mActivityTaskManagerService = activityTaskManagerService;
 
@@ -3812,15 +3813,15 @@
 
     /**
      * Implementation of RemoteAnimationRunner that creates a new
-     * {@link ActivityLaunchAnimator.Runner} whenever onAnimationStart is called, delegating the
+     * {@link ActivityTransitionAnimator.Runner} whenever onAnimationStart is called, delegating the
      * remote animation methods to that runner.
      */
     private class ActivityLaunchRemoteAnimationRunner extends IRemoteAnimationRunner.Stub {
 
-        private final ActivityLaunchAnimator.Controller mActivityLaunchController;
-        @Nullable private ActivityLaunchAnimator.Runner mRunner;
+        private final ActivityTransitionAnimator.Controller mActivityLaunchController;
+        @Nullable private ActivityTransitionAnimator.Runner mRunner;
 
-        ActivityLaunchRemoteAnimationRunner(ActivityLaunchAnimator.Controller controller) {
+        ActivityLaunchRemoteAnimationRunner(ActivityTransitionAnimator.Controller controller) {
             mActivityLaunchController = controller;
         }
 
@@ -3837,7 +3838,7 @@
                 RemoteAnimationTarget[] nonApps,
                 IRemoteAnimationFinishedCallback finishedCallback)
                 throws RemoteException {
-            mRunner = mActivityLaunchAnimator.get().createRunner(mActivityLaunchController);
+            mRunner = mActivityTransitionAnimator.get().createRunner(mActivityLaunchController);
             mRunner.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback);
         }
     }
@@ -3850,7 +3851,7 @@
             extends ActivityLaunchRemoteAnimationRunner {
 
         OccludeActivityLaunchRemoteAnimationRunner(
-                ActivityLaunchAnimator.Controller controller) {
+                ActivityTransitionAnimator.Controller controller) {
             super(controller);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 70da3e7..0b227fa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -35,7 +35,7 @@
 import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
 import com.android.keyguard.mediator.ScreenOnCoordinator;
 import com.android.systemui.CoreStartable;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.classifier.FalsingModule;
@@ -150,7 +150,7 @@
             WallpaperRepository wallpaperRepository,
             Lazy<ShadeController> shadeController,
             Lazy<NotificationShadeWindowController> notificationShadeWindowController,
-            Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
+            Lazy<ActivityTransitionAnimator> activityTransitionAnimator,
             Lazy<ScrimController> scrimControllerLazy,
             IActivityTaskManager activityTaskManagerService,
             FeatureFlags featureFlags,
@@ -196,7 +196,7 @@
                 wallpaperRepository,
                 shadeController,
                 notificationShadeWindowController,
-                activityLaunchAnimator,
+                activityTransitionAnimator,
                 scrimControllerLazy,
                 activityTaskManagerService,
                 featureFlags,
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/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 5606d43..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
@@ -98,6 +98,8 @@
                         val modeOnCanceled =
                             if (lastStartedStep.from == KeyguardState.LOCKSCREEN) {
                                 TransitionModeOnCanceled.REVERSE
+                            } else if (lastStartedStep.from == KeyguardState.GONE) {
+                                TransitionModeOnCanceled.RESET
                             } else {
                                 TransitionModeOnCanceled.LAST_VALUE
                             }
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/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index 96e83b0..3630b40 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -32,7 +32,7 @@
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.animation.Interpolators
 import com.android.settingslib.Utils
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.animation.Expandable
 import com.android.systemui.animation.view.LaunchableLinearLayout
 import com.android.systemui.common.shared.model.Icon
@@ -534,7 +534,7 @@
         activityStarter.postStartActivityDismissingKeyguard(
             WallpaperPickerIntentUtils.getIntent(view.context, LAUNCH_SOURCE_KEYGUARD),
             /* delay= */ 0,
-            /* animationController= */ ActivityLaunchAnimator.Controller.fromView(view),
+            /* animationController= */ ActivityTransitionAnimator.Controller.fromView(view),
             /* customMessage= */ view.context.getString(R.string.keyguard_unlock_to_customize_ls)
         )
     }
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/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
index f67cb68..b1adef4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
@@ -22,7 +22,7 @@
 import androidx.core.view.isVisible
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.common.ui.binder.TextViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
@@ -115,7 +115,7 @@
         activityStarter.postStartActivityDismissingKeyguard(
             WallpaperPickerIntentUtils.getIntent(view.context, LAUNCH_SOURCE_KEYGUARD),
             /* delay= */ 0,
-            /* animationController= */ ActivityLaunchAnimator.Controller.fromView(view),
+            /* animationController= */ ActivityTransitionAnimator.Controller.fromView(view),
             /* customMessage= */ view.context.getString(R.string.keyguard_unlock_to_customize_ls)
         )
     }
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 828e033..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
@@ -31,6 +31,10 @@
 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
@@ -58,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,
 ) {
@@ -83,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()
@@ -115,6 +121,10 @@
         }
     }
 
+    private fun isInTransition(state: TransitionState): Boolean {
+        return state == STARTED || state == RUNNING
+    }
+
     private fun burnIn(
         params: BurnInParameters,
     ): Flow<BurnInModel> {
@@ -185,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/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index 6763e0a..f981fd5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.ComposeLockscreen
 import com.android.systemui.keyguard.shared.model.SettingsClockSize
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.res.R
@@ -100,12 +101,23 @@
             initialValue = false
         )
 
-    // Needs to use a non application context to get display cutout.
-    fun getSmallClockTopMargin(context: Context) =
+    /** Calculates the top margin for the small clock. */
+    fun getSmallClockTopMargin(context: Context): Int {
+        var topMargin: Int
+        val statusBarHeight = Utils.getStatusBarHeaderHeightKeyguard(context)
+
         if (splitShadeStateController.shouldUseSplitNotificationShade(context.resources)) {
-            context.resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)
+            topMargin =
+                context.resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)
+            if (ComposeLockscreen.isEnabled) {
+                topMargin -= statusBarHeight
+            }
         } else {
-            context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
-                Utils.getStatusBarHeaderHeightKeyguard(context)
+            topMargin = context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin)
+            if (!ComposeLockscreen.isEnabled) {
+                topMargin += statusBarHeight
+            }
         }
+        return topMargin
+    }
 }
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 f8a12bd..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
@@ -78,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
 
@@ -138,6 +147,7 @@
     /** Is the notification icon container visible? */
     val isNotifIconContainerVisible: Flow<AnimatedValue<Boolean>> =
         combine(
+                goneToAodTransitionRunning,
                 keyguardTransitionInteractor.finishedKeyguardState.map {
                     KeyguardState.lockscreenVisibleInState(it)
                 },
@@ -145,6 +155,7 @@
                 areNotifsFullyHiddenAnimated(),
                 isPulseExpandingAnimated(),
             ) {
+                goneToAodTransitionRunning: Boolean,
                 onKeyguard: Boolean,
                 isBypassEnabled: Boolean,
                 notifsFullyHidden: AnimatedValue<Boolean>,
@@ -154,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/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 2b28a71..fa18557 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
@@ -36,7 +36,7 @@
 constructor(
     @Application applicationScope: CoroutineScope,
     deviceEntryInteractor: DeviceEntryInteractor,
-    communalInteractor: CommunalInteractor,
+    communalSettingsInteractor: CommunalSettingsInteractor,
     val longPress: KeyguardLongPressViewModel,
     val notifications: NotificationsPlaceholderViewModel,
 ) {
@@ -55,10 +55,12 @@
     }
 
     /** The key of the scene we should switch to when swiping left. */
-    val leftDestinationSceneKey: SceneKey? =
-        if (communalInteractor.isCommunalEnabled) {
-            SceneKey.Communal
-        } else {
-            null
-        }
+    val leftDestinationSceneKey: StateFlow<SceneKey?> =
+        communalSettingsInteractor.isCommunalEnabled
+            .map { available -> if (available) SceneKey.Communal else null }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = null,
+            )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/OWNERS b/packages/SystemUI/src/com/android/systemui/media/OWNERS
index b2d00df..69ea57b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/media/OWNERS
@@ -1,5 +1 @@
 per-file MediaProjectionPermissionActivity.java = michaelwr@google.com
-
-# Haptics team also works on Ringtone
-per-file NotificationPlayer.java = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file RingtonePlayer.java = file:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index 7a48836..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/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 5720cc7..aa92814 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -81,8 +81,8 @@
 import com.android.internal.widget.CachingIconView;
 import com.android.settingslib.widget.AdaptiveIcon;
 import com.android.systemui.ActivityIntentHelper;
-import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.animation.GhostedViewLaunchAnimatorController;
+import com.android.systemui.animation.ActivityTransitionAnimator;
+import com.android.systemui.animation.GhostedViewTransitionAnimatorController;
 import com.android.systemui.bluetooth.BroadcastDialogController;
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -118,6 +118,8 @@
 import com.android.systemui.surfaceeffects.ripple.RippleShader;
 import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig;
 import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController;
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader.Companion.Type;
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView;
 import com.android.systemui.util.ColorUtilKt;
 import com.android.systemui.util.animation.TransitionLayout;
 import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -132,6 +134,7 @@
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Random;
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
@@ -251,6 +254,8 @@
     private boolean mWasPlaying = false;
     private boolean mButtonClicked = false;
 
+    private final Random mRandom = new Random();
+
     /**
      * Initialize a new control panel
      *
@@ -448,7 +453,10 @@
 
         MultiRippleView multiRippleView = vh.getMultiRippleView();
         mMultiRippleController = new MultiRippleController(multiRippleView);
-        mTurbulenceNoiseController = new TurbulenceNoiseController(vh.getTurbulenceNoiseView());
+
+        TurbulenceNoiseView turbulenceNoiseView = vh.getTurbulenceNoiseView();
+        turbulenceNoiseView.setBlendMode(BlendMode.SCREEN);
+        mTurbulenceNoiseController = new TurbulenceNoiseController(turbulenceNoiseView);
 
         mColorSchemeTransition = new ColorSchemeTransition(
                 mContext, mMediaViewHolder, mMultiRippleController, mTurbulenceNoiseController);
@@ -587,6 +595,7 @@
             }
             // Color will be correctly updated in ColorSchemeTransition.
             mTurbulenceNoiseController.play(
+                    Type.SIMPLEX_NOISE,
                     mTurbulenceNoiseAnimationConfig
             );
             mMainExecutor.executeDelayed(
@@ -1227,22 +1236,23 @@
         return new TurbulenceNoiseAnimationConfig(
                 /* gridCount= */ 2.14f,
                 TurbulenceNoiseAnimationConfig.DEFAULT_LUMINOSITY_MULTIPLIER,
+                /* noiseOffsetX= */ mRandom.nextFloat(),
+                /* noiseOffsetY= */ mRandom.nextFloat(),
+                /* noiseOffsetZ= */ mRandom.nextFloat(),
                 /* noiseMoveSpeedX= */ 0.42f,
                 /* noiseMoveSpeedY= */ 0f,
                 TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z,
                 /* color= */ mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
                 /* backgroundColor= */ Color.BLACK,
-                /* opacity= */ 51,
                 /* width= */ mMediaViewHolder.getTurbulenceNoiseView().getWidth(),
                 /* height= */ mMediaViewHolder.getTurbulenceNoiseView().getHeight(),
                 TurbulenceNoiseAnimationConfig.DEFAULT_MAX_DURATION_IN_MILLIS,
                 /* easeInDuration= */ 1350f,
                 /* easeOutDuration= */ 1350f,
                 getContext().getResources().getDisplayMetrics().density,
-                BlendMode.SCREEN,
-                /* onAnimationEnd= */ null,
                 /* lumaMatteBlendFactor= */ 0.26f,
-                /* lumaMatteOverallBrightness= */ 0.09f
+                /* lumaMatteOverallBrightness= */ 0.09f,
+                /* shouldInverseNoiseLuminosity= */ false
         );
     }
     private void clearButton(final ImageButton button) {
@@ -1309,7 +1319,7 @@
     }
 
     @Nullable
-    private ActivityLaunchAnimator.Controller buildLaunchAnimatorController(
+    private ActivityTransitionAnimator.Controller buildLaunchAnimatorController(
             TransitionLayout player) {
         if (!(player.getParent() instanceof ViewGroup)) {
             // TODO(b/192194319): Throw instead of just logging.
@@ -1320,7 +1330,7 @@
 
         // TODO(b/174236650): Make sure that the carousel indicator also fades out.
         // TODO(b/174236650): Instrument the animation to measure jank.
-        return new GhostedViewLaunchAnimatorController(player,
+        return new GhostedViewTransitionAnimatorController(player,
                 InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER) {
             @Override
             protected float getCurrentTopCornerRadius() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index 523414c..35e0271 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -1155,7 +1155,7 @@
                 onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
                 // TODO(b/311234666): revisit logic once interactions between the hub and
                 //  shade/keyguard state are finalized
-                isCommunalShowing && communalInteractor.isCommunalEnabled -> LOCATION_COMMUNAL_HUB
+                isCommunalShowing -> LOCATION_COMMUNAL_HUB
                 onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN
                 else -> LOCATION_QQS
             }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 375a0ce..687f268 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -78,7 +78,7 @@
 import com.android.settingslib.media.LocalMediaManager;
 import com.android.settingslib.media.MediaDevice;
 import com.android.settingslib.utils.ThreadUtils;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.flags.FeatureFlags;
@@ -400,7 +400,7 @@
     void tryToLaunchInAppRoutingIntent(String routeId, View view) {
         ComponentName componentName = mLocalMediaManager.getLinkedItemComponentName();
         if (componentName != null) {
-            ActivityLaunchAnimator.Controller controller =
+            ActivityTransitionAnimator.Controller controller =
                     mDialogLaunchAnimator.createActivityLaunchController(view);
             Intent launchIntent = new Intent(ACTION_TRANSFER_MEDIA);
             launchIntent.setComponent(componentName);
@@ -412,7 +412,7 @@
     }
 
     void tryToLaunchMediaApplication(View view) {
-        ActivityLaunchAnimator.Controller controller =
+        ActivityTransitionAnimator.Controller controller =
                 mDialogLaunchAnimator.createActivityLaunchController(view);
         Intent launchIntent = getAppLaunchIntent();
         if (launchIntent != null) {
@@ -881,7 +881,7 @@
     }
 
     void launchBluetoothPairing(View view) {
-        ActivityLaunchAnimator.Controller controller =
+        ActivityTransitionAnimator.Controller controller =
                 mDialogLaunchAnimator.createActivityLaunchController(view);
 
         if (controller == null || (mKeyGuardManager != null
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/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/HeaderPrivacyIconsController.kt b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
index 0941a20..3fd9803 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt
@@ -11,7 +11,7 @@
 import androidx.annotation.WorkerThread
 import com.android.internal.R
 import com.android.internal.logging.UiEventLogger
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.appops.AppOpsController
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.flags.FeatureFlags
@@ -175,7 +175,7 @@
             startSafetyCenter.flags = Intent.FLAG_ACTIVITY_NEW_TASK
             uiExecutor.execute {
                 activityStarter.startActivity(startSafetyCenter, true,
-                    ActivityLaunchAnimator.Controller.fromView(privacyChip))
+                    ActivityTransitionAnimator.Controller.fromView(privacyChip))
             }
         }
     }
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/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index bd13d06..b53c245 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -51,7 +51,7 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -65,14 +65,14 @@
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.DisplayTracker;
 
-import java.util.Objects;
-import java.util.concurrent.atomic.AtomicBoolean;
-
 import dagger.Lazy;
 import dagger.assisted.Assisted;
 import dagger.assisted.AssistedFactory;
 import dagger.assisted.AssistedInject;
 
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+
 public class CustomTile extends QSTileImpl<State> implements TileChangeListener,
         CustomTileInterface {
     public static final String PREFIX = "custom(";
@@ -540,9 +540,9 @@
         } else {
             Log.i(TAG, "The activity is starting");
 
-            ActivityLaunchAnimator.Controller controller =
+            ActivityTransitionAnimator.Controller controller =
                     mViewClicked == null ? null :
-                    ActivityLaunchAnimator.Controller.fromView(
+                    ActivityTransitionAnimator.Controller.fromView(
                             mViewClicked,
                             InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
                     );
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 529d684..35cac4b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -57,7 +57,7 @@
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedLockUtilsInternal;
 import com.android.systemui.Dumpable;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QSTile;
@@ -70,7 +70,6 @@
 import com.android.systemui.qs.logging.QSLogger;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 
 /**
  * Base quick-settings tile, extend this to create a new tile.
@@ -412,8 +411,8 @@
      * @param view The view from which the opening window will be animated.
      */
     protected void handleLongClick(@Nullable View view) {
-        ActivityLaunchAnimator.Controller animationController =
-                view != null ? ActivityLaunchAnimator.Controller.fromView(view,
+        ActivityTransitionAnimator.Controller animationController =
+                view != null ? ActivityTransitionAnimator.Controller.fromView(view,
                         InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE) : null;
         mActivityStarter.postStartActivityDismissingKeyguard(getLongClickIntent(), 0,
                 animationController);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
index d98141f..688f3ca 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
@@ -14,7 +14,7 @@
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.MetricsLogger
 import com.android.systemui.res.R
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
@@ -75,7 +75,7 @@
 
     override fun handleClick(view: View?) {
         val animationController = view?.let {
-            ActivityLaunchAnimator.Controller.fromView(
+            ActivityTransitionAnimator.Controller.fromView(
                     it, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE)
         }
         val pendingIntent = lastAlarmInfo?.showIntent
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index a698208..690b711 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -38,7 +38,7 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.animation.DialogCuj;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -222,7 +222,7 @@
                     mContext,
                     ROUTE_TYPE_REMOTE_DISPLAY,
                     v -> {
-                        ActivityLaunchAnimator.Controller controller =
+                        ActivityTransitionAnimator.Controller controller =
                                 mDialogLaunchAnimator.createActivityLaunchController(v);
 
                         if (controller == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
index 91b2d97..bb175e2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -26,7 +26,7 @@
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.MetricsLogger
 import com.android.systemui.res.R
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE
@@ -112,7 +112,7 @@
             putExtra(ControlsUiController.EXTRA_ANIMATE, true)
         }
         val animationController = view?.let {
-            ActivityLaunchAnimator.Controller.fromView(
+            ActivityTransitionAnimator.Controller.fromView(
                     it, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE)
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
index f70e27d..de9a08e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
@@ -27,8 +27,7 @@
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -40,6 +39,7 @@
 import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
 
 import javax.inject.Inject;
 
@@ -108,8 +108,8 @@
             return;
         }
 
-        ActivityLaunchAnimator.Controller animationController =
-                view == null ? null : ActivityLaunchAnimator.Controller.fromView(view,
+        ActivityTransitionAnimator.Controller animationController =
+                view == null ? null : ActivityTransitionAnimator.Controller.fromView(view,
                         InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE);
         mActivityStarter.startActivity(intent, true /* dismissShade */,
                 animationController, true /* showOverLockscreenWhenLocked */);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index 3b8fb26..1b73225 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -43,7 +43,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
@@ -132,8 +132,8 @@
 
     @Override
     protected void handleClick(@Nullable View view) {
-        ActivityLaunchAnimator.Controller animationController =
-                view == null ? null : ActivityLaunchAnimator.Controller.fromView(view,
+        ActivityTransitionAnimator.Controller animationController =
+                view == null ? null : ActivityTransitionAnimator.Controller.fromView(view,
                         InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE);
 
         mUiHandler.post(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
index fe10eaa..7192f58 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
@@ -22,7 +22,7 @@
 import android.os.UserHandle
 import android.view.View
 import com.android.internal.jank.InteractionJankMonitor
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.ActivityStarter
 import javax.inject.Inject
@@ -53,9 +53,9 @@
 ) : QSTileIntentUserInputHandler {
 
     override fun handle(view: View?, intent: Intent) {
-        val animationController: ActivityLaunchAnimator.Controller? =
+        val animationController: ActivityTransitionAnimator.Controller? =
             view?.let {
-                ActivityLaunchAnimator.Controller.fromView(
+                ActivityTransitionAnimator.Controller.fromView(
                     it,
                     InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
                 )
@@ -70,9 +70,9 @@
         requestLaunchingDefaultActivity: Boolean
     ) {
         if (pendingIntent.isActivity) {
-            val animationController: ActivityLaunchAnimator.Controller? =
+            val animationController: ActivityTransitionAnimator.Controller? =
                 view?.let {
-                    ActivityLaunchAnimator.Controller.fromView(
+                    ActivityTransitionAnimator.Controller.fromView(
                         it,
                         InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
                     )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 211b4594..41de65c1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -75,7 +75,7 @@
 import com.android.settingslib.net.SignalStrengthUtil;
 import com.android.settingslib.wifi.WifiUtils;
 import com.android.settingslib.wifi.dpp.WifiDppIntentHelper;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -748,7 +748,7 @@
     }
 
     private void startActivity(Intent intent, View view) {
-        ActivityLaunchAnimator.Controller controller =
+        ActivityTransitionAnimator.Controller controller =
                 mDialogLaunchAnimator.createActivityLaunchController(view);
 
         if (controller == null && mCallback != null) {
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/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 e9af295..861a2ed 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -31,8 +31,8 @@
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.systemui.Gefingerpoken;
 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;
@@ -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);
                 }
             }
         }
@@ -239,11 +226,7 @@
             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) {
@@ -258,11 +241,7 @@
             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/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/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 1c7cc00..df845f5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.util.kotlin.collectFlow
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
 
 /**
  * Controller that's responsible for the glanceable hub container view and its touch handling.
@@ -105,13 +106,9 @@
      */
     private var shadeShowing = false
 
-    /** Returns true if the glanceable hub is enabled and the container view can be created. */
-    fun isEnabled(): Boolean {
-        return communalInteractor.isCommunalEnabled && isComposeAvailable()
-    }
-
-    /** Returns a {@link StateFlow} that tracks whether communal hub is available. */
-    fun communalAvailable(): Flow<Boolean> = communalInteractor.isCommunalAvailable
+    /** Returns a flow that tracks whether communal hub is available. */
+    fun communalAvailable(): Flow<Boolean> =
+        if (isComposeAvailable()) communalInteractor.isCommunalAvailable else flowOf(false)
 
     /**
      * Creates the container view containing the glanceable hub UI.
@@ -127,9 +124,6 @@
     /** Override for testing. */
     @VisibleForTesting
     internal fun initView(containerView: View): View {
-        if (!isEnabled()) {
-            throw RuntimeException("Glanceable hub is not enabled")
-        }
         if (communalContainerView != null) {
             throw RuntimeException("Communal view has already been initialized")
         }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 133fa12..2968490 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -112,8 +112,8 @@
 import com.android.systemui.DejankUtils;
 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.ActivityTransitionAnimator;
+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;
@@ -136,6 +136,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver;
+import com.android.systemui.keyguard.shared.ComposeLockscreen;
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -259,7 +260,7 @@
     /** The parallax amount of the quick settings translation when dragging down the panel. */
     public static final float QS_PARALLAX_AMOUNT = 0.175f;
     private static final long ANIMATION_DELAY_ICON_FADE_IN =
-            ActivityLaunchAnimator.TIMINGS.getTotalDuration()
+            ActivityTransitionAnimator.TIMINGS.getTotalDuration()
                     - CollapsedStatusBarFragment.FADE_IN_DURATION
                     - CollapsedStatusBarFragment.FADE_IN_DELAY - 48;
     private static final int NO_FIXED_DURATION = -1;
@@ -1348,7 +1349,7 @@
         }
         updateClockAppearance();
         mQsController.updateQsState();
-        if (!KeyguardShadeMigrationNssl.isEnabled()) {
+        if (!KeyguardShadeMigrationNssl.isEnabled() && !FooterViewRefactor.isEnabled()) {
             mNotificationStackScrollLayoutController.updateFooter();
         }
     }
@@ -2476,6 +2477,12 @@
         if (!isKeyguardShowing()) {
             return 0;
         }
+
+        if (ComposeLockscreen.isEnabled()) {
+            return (int) mKeyguardInteractor.getNotificationContainerBounds()
+                    .getValue().getTop();
+        }
+
         if (!mKeyguardBypassController.getBypassEnabled()) {
             if (migrateClocksToBlueprint() && !mSplitShadeEnabled) {
                 return (int) mKeyguardInteractor.getNotificationContainerBounds()
@@ -3231,7 +3238,7 @@
 
     @Override
     public void applyLaunchAnimationProgress(float linearProgress) {
-        boolean hideIcons = LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS,
+        boolean hideIcons = TransitionAnimator.getProgress(ActivityTransitionAnimator.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/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index ef820f3..aa2d606 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -34,7 +34,7 @@
 import com.android.keyguard.AuthKeyguardMessageArea;
 import com.android.keyguard.LockIconViewController;
 import com.android.systemui.Dumpable;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.bouncer.ui.binder.BouncerViewBinder;
@@ -679,7 +679,7 @@
     void setExpandAnimationRunning(boolean running) {
         if (mExpandAnimationRunning != running) {
             // TODO(b/288507023): Remove this log.
-            if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
+            if (ActivityTransitionAnimator.DEBUG_TRANSITION_ANIMATION) {
                 Log.d(TAG, "Setting mExpandAnimationRunning=" + running);
             }
             if (running) {
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/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index b64e0b7..91340be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -26,7 +26,7 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.CoreStartable;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.animation.AnimationFeatureFlags;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
@@ -190,8 +190,8 @@
     /** */
     @Provides
     @SysUISingleton
-    static ActivityLaunchAnimator provideActivityLaunchAnimator() {
-        return new ActivityLaunchAnimator();
+    static ActivityTransitionAnimator provideActivityTransitionAnimator() {
+        return new ActivityTransitionAnimator();
     }
 
     /** */
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..4af8cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
@@ -2,9 +2,9 @@
 
 import android.util.MathUtils
 import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
 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,11 @@
         }
 
     fun getProgress(delay: Long, duration: Long): Float {
-        return LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS, linearProgress, delay,
-            duration)
+        return TransitionAnimator.getProgress(
+            ActivityTransitionAnimator.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/NotificationTransitionAnimatorController.kt
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt
index 5983fc1..eb0870a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt
@@ -19,8 +19,8 @@
 import android.util.Log
 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.ActivityTransitionAnimator
+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
@@ -31,7 +31,7 @@
 
 private const val TAG = "NotificationLaunchAnimatorController"
 
-/** A provider of [NotificationLaunchAnimatorController]. */
+/** A provider of [NotificationTransitionAnimatorController]. */
 class NotificationLaunchAnimatorControllerProvider(
     private val notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
     private val notificationListContainer: NotificationListContainer,
@@ -42,8 +42,8 @@
     fun getAnimatorController(
         notification: ExpandableNotificationRow,
         onFinishAnimationCallback: Runnable? = null
-    ): NotificationLaunchAnimatorController {
-        return NotificationLaunchAnimatorController(
+    ): NotificationTransitionAnimatorController {
+        return NotificationTransitionAnimatorController(
             notificationLaunchAnimationInteractor,
             notificationListContainer,
             headsUpManager,
@@ -55,18 +55,18 @@
 }
 
 /**
- * An [ActivityLaunchAnimator.Controller] that animates an [ExpandableNotificationRow]. An instance
- * of this class can be passed to [ActivityLaunchAnimator.startIntentWithAnimation] to animate a
- * notification expanding into an opening window.
+ * An [ActivityTransitionAnimator.Controller] that animates an [ExpandableNotificationRow]. An
+ * instance of this class can be passed to [ActivityTransitionAnimator.startIntentWithAnimation] to
+ * animate a notification expanding into an opening window.
  */
-class NotificationLaunchAnimatorController(
+class NotificationTransitionAnimatorController(
     private val notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
     private val notificationListContainer: NotificationListContainer,
     private val headsUpManager: HeadsUpManager,
     private val notification: ExpandableNotificationRow,
     private val jankMonitor: InteractionJankMonitor,
     private val onFinishAnimationCallback: Runnable?
-) : ActivityLaunchAnimator.Controller {
+) : ActivityTransitionAnimator.Controller {
 
     companion object {
         const val ANIMATION_DURATION_TOP_ROUNDING = 100L
@@ -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
@@ -140,7 +140,7 @@
     }
 
     override fun onIntentStarted(willAnimate: Boolean) {
-        if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
+        if (ActivityTransitionAnimator.DEBUG_TRANSITION_ANIMATION) {
             Log.d(TAG, "onIntentStarted(willAnimate=$willAnimate)")
         }
         notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(willAnimate)
@@ -173,8 +173,8 @@
         headsUpManager.removeNotification(row.entry.key, true /* releaseImmediately */, animate)
     }
 
-    override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) {
-        if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
+    override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+        if (ActivityTransitionAnimator.DEBUG_TRANSITION_ANIMATION) {
             Log.d(TAG, "onLaunchAnimationCancelled()")
         }
 
@@ -186,15 +186,15 @@
         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) {
-        if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
+    override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+        if (ActivityTransitionAnimator.DEBUG_TRANSITION_ANIMATION) {
             Log.d(TAG, "onLaunchAnimationEnd()")
         }
         jankMonitor.end(InteractionJankMonitor.CUJ_NOTIFICATION_APP_START)
@@ -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/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index 6e2beb4..c8ca63d 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
@@ -55,10 +55,9 @@
             val notifStats = calculateNotifStats(entries)
             if (FooterViewRefactor.isEnabled) {
                 activeNotificationsInteractor.setNotifStats(notifStats)
+            } else {
+                controller.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.
-            controller.setNotifStats(notifStats)
             if (NotificationIconContainerRefactor.isEnabled || FooterViewRefactor.isEnabled) {
                 renderListInteractor.setRenderedList(entries)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 92b0c04..2a1ec3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -169,11 +169,12 @@
     /** Provides notification launch animator. */
     @Provides
     @SysUISingleton
-    static NotificationLaunchAnimatorControllerProvider provideNotifLaunchAnimControllerProvider(
-            NotificationLaunchAnimationInteractor notificationLaunchAnimationInteractor,
-            NotificationListContainer notificationListContainer,
-            HeadsUpManager headsUpManager,
-            InteractionJankMonitor jankMonitor) {
+    static NotificationLaunchAnimatorControllerProvider
+            provideNotificationTransitionAnimatorControllerProvider(
+                    NotificationLaunchAnimationInteractor notificationLaunchAnimationInteractor,
+                    NotificationListContainer notificationListContainer,
+                    HeadsUpManager headsUpManager,
+                    InteractionJankMonitor jankMonitor) {
         return new NotificationLaunchAnimatorControllerProvider(
                 notificationLaunchAnimationInteractor,
                 notificationListContainer,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index b22e9fd..0dbc8c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -102,6 +102,18 @@
             .distinctUntilChanged()
             .flowOn(backgroundDispatcher)
 
+    val hasClearableAlertingNotifications: Flow<Boolean> =
+        repository.notifStats
+            .map { it.hasClearableAlertingNotifs }
+            .distinctUntilChanged()
+            .flowOn(backgroundDispatcher)
+
+    val hasNonClearableSilentNotifications: Flow<Boolean> =
+        repository.notifStats
+            .map { it.hasNonClearableSilentNotifs }
+            .distinctUntilChanged()
+            .flowOn(backgroundDispatcher)
+
     fun setNotifStats(notifStats: NotifStats) {
         repository.notifStats.value = notifStats
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt
index 22ce4f1..27f2810 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.notification.domain.interactor
 
 import android.util.Log
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
 import javax.inject.Inject
@@ -33,14 +33,14 @@
      * Emits true if an animation that expands a notification object into an opening window is
      * running and false otherwise.
      *
-     * See [com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController].
+     * See [com.android.systemui.statusbar.notification.NotificationTransitionAnimatorController].
      */
     val isLaunchAnimationRunning: StateFlow<Boolean>
         get() = repository.isLaunchAnimationRunning
 
     /** Sets whether the notification expansion launch animation is currently running. */
     fun setIsLaunchAnimationRunning(running: Boolean) {
-        if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
+        if (ActivityTransitionAnimator.DEBUG_TRANSITION_ANIMATION) {
             Log.d(TAG, "setIsLaunchAnimationRunning(running=$running)")
         }
         repository.isLaunchAnimationRunning.value = running
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/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index aca8b64..d7fe36f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -223,7 +223,8 @@
             // ranking.lockscreenVisibilityOverride contains possibly out of date DPC and Setting
             // info, and NotificationLockscreenUserManagerImpl is already listening for updates
             // to those
-            entry.ranking.channel.lockscreenVisibility == VISIBILITY_SECRET
+            entry.ranking.channel != null && entry.ranking.channel.lockscreenVisibility ==
+                    VISIBILITY_SECRET
         } else {
             entry.ranking.lockscreenVisibilityOverride == VISIBILITY_SECRET
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 5eeb066..5686c08 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -87,7 +87,7 @@
 import com.android.systemui.statusbar.notification.FeedbackIcon;
 import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
 import com.android.systemui.statusbar.notification.NotificationFadeAware;
-import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController;
+import com.android.systemui.statusbar.notification.NotificationTransitionAnimatorController;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -2343,7 +2343,8 @@
             // top. Otherwise, we just take the top directly.
             float expandProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
                     params.getProgress(0,
-                            NotificationLaunchAnimatorController.ANIMATION_DURATION_TOP_ROUNDING));
+                            NotificationTransitionAnimatorController
+                                    .ANIMATION_DURATION_TOP_ROUNDING));
             int startTop = params.getStartNotificationTop();
             top = (int) Math.min(MathUtils.lerp(startTop, params.getTop(), expandProgress),
                     startTop);
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..aa9d3b2 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
@@ -101,7 +101,7 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.FakeShadowView;
 import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
-import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController;
+import com.android.systemui.statusbar.notification.NotificationTransitionAnimatorController;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
@@ -233,7 +233,7 @@
     private final ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
     private final ArrayList<View> mSwipedOutViews = new ArrayList<>();
     private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
-    private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
+    private final StackStateAnimator mStateAnimator;
     private boolean mAnimationsEnabled;
     private boolean mChangePositionInProgress;
     private boolean mChildTransferInProgress;
@@ -670,6 +670,7 @@
         mExpandHelper.setScrollAdapter(mScrollAdapter);
 
         mStackScrollAlgorithm = createStackScrollAlgorithm(context);
+        mStateAnimator = new StackStateAnimator(context, this);
         mShouldDrawNotificationBackground =
                 res.getBoolean(R.bool.config_drawNotificationBackground);
         setOutlineProvider(mOutlineProvider);
@@ -752,6 +753,7 @@
     }
 
     public void setIsRemoteInputActive(boolean isActive) {
+        FooterViewRefactor.assertInLegacyMode();
         mIsRemoteInputActive = isActive;
         updateFooter();
     }
@@ -764,6 +766,7 @@
 
     @VisibleForTesting
     public void updateFooter() {
+        FooterViewRefactor.assertInLegacyMode();
         if (mFooterView == null || mController == null) {
             return;
         }
@@ -776,10 +779,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()
@@ -1079,6 +1084,7 @@
         }
         mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
         mStackScrollAlgorithm.initView(context);
+        mStateAnimator.initView(context);
         mAmbientState.reload(context);
         mPaddingBetweenElements = Math.max(1,
                 res.getDimensionPixelSize(R.dimen.notification_divider_height));
@@ -1825,8 +1831,8 @@
     }
 
     private ExpandableView getChildAtPosition(float touchX, float touchY) {
-        return getChildAtPosition(
-                touchX, touchY, true /* requireMinHeight */, true /* ignoreDecors */);
+        return getChildAtPosition(touchX, touchY, true /* requireMinHeight */,
+                true /* ignoreDecors */, true /* ignoreWidth */);
     }
 
     /**
@@ -1836,10 +1842,11 @@
      * @param touchY           the y coordinate
      * @param requireMinHeight Whether a minimum height is required for a child to be returned.
      * @param ignoreDecors     Whether decors can be returned
+     * @param ignoreWidth      Whether we should ignore the width of the child
      * @return the child at the given location.
      */
-    ExpandableView getChildAtPosition(float touchX, float touchY,
-                                      boolean requireMinHeight, boolean ignoreDecors) {
+    ExpandableView getChildAtPosition(float touchX, float touchY, boolean requireMinHeight,
+            boolean ignoreDecors, boolean ignoreWidth) {
         // find the view under the pointer, accounting for GONE views
         final int count = getChildCount();
         for (int childIdx = 0; childIdx < count; childIdx++) {
@@ -1855,8 +1862,8 @@
 
             // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
             // camera affordance).
-            int left = 0;
-            int right = getWidth();
+            int left = ignoreWidth ? 0 : slidingChild.getLeft();
+            int right = ignoreWidth ? getWidth() : slidingChild.getRight();
 
             if ((bottom - top >= mMinInteractionHeight || !requireMinHeight)
                     && touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
@@ -3579,8 +3586,19 @@
     public boolean onTouchEvent(MotionEvent ev) {
         if (mTouchHandler != null) {
             boolean touchHandled = mTouchHandler.onTouchEvent(ev);
-            if (SceneContainerFlag.isEnabled() || touchHandled) {
-                return touchHandled;
+            if (SceneContainerFlag.isEnabled()) {
+                if (getChildAtPosition(
+                        mInitialTouchX, mInitialTouchY, true, true, false) == null) {
+                    // If scene container is enabled, any touch that we are handling that is not on
+                    // a child view should be handled by scene container instead.
+                    return false;
+                } else {
+                    // If scene container is enabled, any touch that we are handling that is not on
+                    // a child view should be handled by scene container instead.
+                    return touchHandled;
+                }
+            } else if (touchHandled) {
+                return true;
             }
         }
 
@@ -4021,7 +4039,8 @@
                 final int y = (int) ev.getY();
                 mScrolledToTopOnFirstDown = mScrollAdapter.isScrolledToTop();
                 final ExpandableView childAtTouchPos = getChildAtPosition(
-                        ev.getX(), y, false /* requireMinHeight */, false /* ignoreDecors */);
+                        ev.getX(), y, false /* requireMinHeight */,
+                        false /* ignoreDecors */, true /* ignoreWidth */);
                 if (childAtTouchPos == null) {
                     setIsBeingDragged(false);
                     recycleVelocityTracker();
@@ -4359,6 +4378,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 +4748,6 @@
                 footerView.setClearAllButtonVisible(false /* visible */, true /* animate */);
             });
         }
-        if (FooterViewRefactor.isEnabled()) {
-            updateFooter();
-        }
     }
 
     public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
@@ -4790,16 +4812,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 +5091,7 @@
         if (mOwnScrollY > 0) {
             setOwnScrollY((int) MathUtils.lerp(mOwnScrollY, 0, mQsExpansionFraction));
         }
-        if (footerAffected) {
+        if (!FooterViewRefactor.isEnabled() && footerAffected) {
             updateFooter();
         }
     }
@@ -5081,6 +5102,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 +5201,7 @@
     }
 
     void setUpcomingStatusBarState(int upcomingStatusBarState) {
+        FooterViewRefactor.assertInLegacyMode();
         mUpcomingStatusBarState = upcomingStatusBarState;
         if (mUpcomingStatusBarState != mStatusBarState) {
             updateFooter();
@@ -5193,7 +5219,9 @@
 
         setDimmed(onKeyguard, fromShadeLocked);
         setExpandingEnabled(!onKeyguard);
-        updateFooter();
+        if (!FooterViewRefactor.isEnabled()) {
+            updateFooter();
+        }
         requestChildrenUpdate();
         onUpdateRowStates();
         updateVisibility();
@@ -5270,8 +5298,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 +5321,7 @@
     }
 
     private void dumpFooterViewVisibility(IndentingPrintWriter pw) {
+        FooterViewRefactor.assertInLegacyMode();
         final boolean showDismissView = shouldShowDismissView();
 
         pw.println("showFooterView: " + shouldShowFooterView(showDismissView));
@@ -5358,23 +5390,8 @@
                 && (!hasClipBounds || mTmpRect.height() > 0);
     }
 
-    private boolean shouldHideParent(View view, @SelectedRows int selection) {
-        final boolean silentSectionWillBeGone =
-                !mController.hasNotifications(ROWS_GENTLE, false /* clearable */);
-
-        // The only SectionHeaderView we have is the silent section header.
-        if (view instanceof SectionHeaderView && silentSectionWillBeGone) {
-            return true;
-        }
-        if (view instanceof ExpandableNotificationRow row) {
-            if (isVisible(row) && includeChildInClearAll(row, selection)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private boolean isChildrenVisible(ExpandableNotificationRow parent) {
+    /** Whether the group is expanded to show the child notifications, and they are visible. */
+    private boolean areChildrenVisible(ExpandableNotificationRow parent) {
         List<ExpandableNotificationRow> children = parent.getAttachedChildren();
         return isVisible(parent)
                 && children != null
@@ -5382,18 +5399,27 @@
     }
 
     // Similar to #getRowsToDismissInBackend, but filters for visible views.
-    private ArrayList<View> getVisibleViewsToAnimateAway(@SelectedRows int selection) {
+    private ArrayList<View> getVisibleViewsToAnimateAway(@SelectedRows int selection,
+            boolean hideSilentSection) {
         final int viewCount = getChildCount();
         final ArrayList<View> viewsToHide = new ArrayList<>(viewCount);
 
         for (int i = 0; i < viewCount; i++) {
             final View view = getChildAt(i);
 
-            if (shouldHideParent(view, selection)) {
-                viewsToHide.add(view);
+            if (view instanceof SectionHeaderView) {
+                // The only SectionHeaderView we have is the silent section header.
+                if (hideSilentSection) {
+                    viewsToHide.add(view);
+                }
             }
+
             if (view instanceof ExpandableNotificationRow parent) {
-                if (isChildrenVisible(parent)) {
+                if (isVisible(parent) && includeChildInClearAll(parent, selection)) {
+                    viewsToHide.add(parent);
+                }
+
+                if (areChildrenVisible(parent)) {
                     for (ExpandableNotificationRow child : parent.getAttachedChildren()) {
                         if (isVisible(child) && includeChildInClearAll(child, selection)) {
                             viewsToHide.add(child);
@@ -5431,17 +5457,33 @@
     }
 
     /** Clear all clearable notifications when the user requests it. */
-    public void clearAllNotifications() {
-        clearNotifications(ROWS_ALL, /* closeShade = */ true);
+    public void clearAllNotifications(boolean hideSilentSection) {
+        clearNotifications(ROWS_ALL, /* closeShade = */ true, hideSilentSection);
+    }
+
+    /** Clear all clearable silent notifications when the user requests it. */
+    public void clearSilentNotifications(boolean closeShade,
+            boolean hideSilentSection) {
+        clearNotifications(ROWS_GENTLE, closeShade, hideSilentSection);
+    }
+
+    /** Legacy version of clearNotifications below. Uses the old data source for notif stats. */
+    void clearNotifications(@SelectedRows int selection, boolean closeShade) {
+        FooterViewRefactor.assertInLegacyMode();
+        final boolean hideSilentSection = !mController.hasNotifications(
+                ROWS_GENTLE, false /* clearable */);
+        clearNotifications(selection, closeShade, hideSilentSection);
     }
 
     /**
      * Collects a list of visible rows, and animates them away in a staggered fashion as if they
      * were dismissed. Notifications are dismissed in the backend via onClearAllAnimationsEnd.
      */
-    void clearNotifications(@SelectedRows int selection, boolean closeShade) {
+    void clearNotifications(@SelectedRows int selection, boolean closeShade,
+            boolean hideSilentSection) {
         // Animate-swipe all dismissable notifications, then animate the shade closed
-        final ArrayList<View> viewsToAnimateAway = getVisibleViewsToAnimateAway(selection);
+        final ArrayList<View> viewsToAnimateAway = getVisibleViewsToAnimateAway(selection,
+                hideSilentSection);
         final ArrayList<ExpandableNotificationRow> rowsToDismissInBackend =
                 getRowsToDismissInBackend(selection);
         if (mClearAllListener != null) {
@@ -5886,7 +5928,7 @@
                 mRoundedRectClippingBottom);
         float expandProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
                 mLaunchAnimationParams.getProgress(0,
-                        NotificationLaunchAnimatorController.ANIMATION_DURATION_TOP_ROUNDING));
+                        NotificationTransitionAnimatorController.ANIMATION_DURATION_TOP_ROUNDING));
         int top = (int) Math.min(MathUtils.lerp(mRoundedRectClippingTop,
                         mLaunchAnimationParams.getTop() - absoluteCoords[1], expandProgress),
                 mRoundedRectClippingTop);
@@ -5988,6 +6030,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..47daf49 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();
+            }
         }
     };
 
@@ -590,7 +596,8 @@
                             ev.getX(),
                             ev.getY(),
                             true /* requireMinHeight */,
-                            false /* ignoreDecors */);
+                            false /* ignoreDecors */,
+                            true /* ignoreWidth */);
                     if (child instanceof ExpandableNotificationRow row) {
                         ExpandableNotificationRow parent = row.getNotificationParent();
                         if (parent != null && parent.areChildrenExpanded()
@@ -810,14 +817,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 +878,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 +902,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
@@ -906,7 +917,9 @@
             mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
         }
         mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
-        mSilentHeaderController.setOnClearSectionClickListener(v -> clearSilentNotifications());
+        if (!FooterViewRefactor.isEnabled()) {
+            mSilentHeaderController.setOnClearSectionClickListener(v -> clearSilentNotifications());
+        }
 
         mGroupExpansionManager.registerGroupExpansionChangeListener(
                 (changedRow, expanded) -> mView.onGroupExpandChanged(changedRow, expanded));
@@ -1106,8 +1119,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 +1154,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 +1526,12 @@
      * 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
+        FooterViewRefactor.assertInLegacyMode();
         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
+        FooterViewRefactor.assertInLegacyMode();
         boolean hasAlertingMatchingClearable = isClearable
                 ? mNotifStats.getHasClearableAlertingNotifs()
                 : mNotifStats.getHasNonClearableAlertingNotifs();
@@ -1558,7 +1573,9 @@
                     boolean remoteInputActive) {
                 mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
                 entry.notifyHeightChanged(true /* needsAnimation */);
-                updateFooter();
+                if (!FooterViewRefactor.isEnabled()) {
+                    updateFooter();
+                }
             }
 
             public void lockScrollTo(NotificationEntry entry) {
@@ -1573,6 +1590,7 @@
     }
 
     public void updateFooter() {
+        FooterViewRefactor.assertInLegacyMode();
         Trace.beginSection("NSSLC.updateFooter");
         mView.updateFooter();
         Trace.endSection();
@@ -1664,6 +1682,7 @@
     }
 
     public void clearSilentNotifications() {
+        FooterViewRefactor.assertInLegacyMode();
         // Leave the shade open if there will be other notifs left over to clear
         final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY);
         mView.clearNotifications(ROWS_GENTLE, closeShade);
@@ -2134,19 +2153,15 @@
     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.
+            FooterViewRefactor.assertInLegacyMode();
             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/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index b38d619..ab62ed6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -23,6 +23,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.content.Context;
 import android.util.Property;
 import android.view.View;
 
@@ -66,9 +67,8 @@
     public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
     private static final int MAX_STAGGER_COUNT = 5;
 
-    private final int mGoToFullShadeAppearingTranslation;
-    @VisibleForTesting
-    float mHeadsUpAppearStartAboveScreen;
+    @VisibleForTesting int mGoToFullShadeAppearingTranslation;
+    @VisibleForTesting float mHeadsUpAppearStartAboveScreen;
     private final ExpandableViewState mTmpState = new ExpandableViewState();
     private final AnimationProperties mAnimationProperties;
     public NotificationStackScrollLayout mHostLayout;
@@ -92,14 +92,9 @@
     private NotificationShelf mShelf;
     private StackStateLogger mLogger;
 
-    public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
+    public StackStateAnimator(Context context, NotificationStackScrollLayout hostLayout) {
         mHostLayout = hostLayout;
-        // TODO(b/317061579) reload on configuration changes
-        mGoToFullShadeAppearingTranslation =
-                hostLayout.getContext().getResources().getDimensionPixelSize(
-                        R.dimen.go_to_full_shade_appearing_translation);
-        mHeadsUpAppearStartAboveScreen = hostLayout.getContext().getResources()
-                .getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen);
+        initView(context);
         mAnimationProperties = new AnimationProperties() {
             @Override
             public AnimationFilter getAnimationFilter() {
@@ -118,6 +113,21 @@
         };
     }
 
+    /**
+     * Needs to be called on configuration changes, to update cached resource values.
+     */
+    public void initView(Context context) {
+        updateResources(context);
+    }
+
+    private void updateResources(Context context) {
+        mGoToFullShadeAppearingTranslation =
+                context.getResources().getDimensionPixelSize(
+                        R.dimen.go_to_full_shade_appearing_translation);
+        mHeadsUpAppearStartAboveScreen = context.getResources()
+                .getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen);
+    }
+
     protected void setLogger(StackStateLogger logger) {
         mLogger = logger;
     }
@@ -460,15 +470,8 @@
                 mHeadsUpAppearChildren.add(changingView);
 
                 mTmpState.copyFrom(changingView.getViewState());
-                if (event.headsUpFromBottom) {
-                    // start from the bottom of the screen
-                    mTmpState.setYTranslation(
-                            mHeadsUpAppearHeightBottom + mHeadsUpAppearStartAboveScreen);
-                } else {
-                    // start from the top of the screen
-                    mTmpState.setYTranslation(
-                            -mStackTopMargin - mHeadsUpAppearStartAboveScreen);
-                }
+                // translate the HUN in from the top, or the bottom of the screen
+                mTmpState.setYTranslation(getHeadsUpYTranslationStart(event.headsUpFromBottom));
                 // set the height and the initial position
                 mTmpState.applyToView(changingView);
                 mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
@@ -512,12 +515,20 @@
                     || event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) {
                 mHeadsUpDisappearChildren.add(changingView);
                 Runnable endRunnable = null;
+                mTmpState.copyFrom(changingView.getViewState());
                 if (changingView.getParent() == null) {
                     // This notification was actually removed, so we need to add it
                     // transiently
                     mHostLayout.addTransientView(changingView, 0);
                     changingView.setTransientContainer(mHostLayout);
-                    mTmpState.initFrom(changingView);
+                    if (NotificationsImprovedHunAnimation.isEnabled()) {
+                        // StackScrollAlgorithm cannot find this view because it has been removed
+                        // from the NSSL. To correctly translate the view to the top or bottom of
+                        // the screen (where it animated from), we need to update its translation.
+                        mTmpState.setYTranslation(
+                                getHeadsUpYTranslationStart(event.headsUpFromBottom)
+                        );
+                    }
                     endRunnable = changingView::removeFromTransientContainer;
                 }
 
@@ -565,16 +576,19 @@
                             changingView.setInRemovalAnimation(true);
                         };
                     }
-                    if (NotificationsImprovedHunAnimation.isEnabled()) {
-                        mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
-                                Interpolators.FAST_OUT_SLOW_IN_REVERSE);
-                    }
                     long removeAnimationDelay = changingView.performRemoveAnimation(
                             ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
                             0, 0.0f, true /* isHeadsUpAppear */,
                             startAnimation, postAnimation,
                             getGlobalAnimationFinishedListener());
                     mAnimationProperties.delay += removeAnimationDelay;
+                    if (NotificationsImprovedHunAnimation.isEnabled()) {
+                        mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR;
+                        mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y,
+                                Interpolators.FAST_OUT_SLOW_IN_REVERSE);
+                        mAnimationProperties.getAnimationFilter().animateY = true;
+                        mTmpState.animateTo(changingView, mAnimationProperties);
+                    }
                 } else if (endRunnable != null) {
                     endRunnable.run();
                 }
@@ -585,6 +599,15 @@
         return needsCustomAnimation;
     }
 
+    private float getHeadsUpYTranslationStart(boolean headsUpFromBottom) {
+        if (headsUpFromBottom) {
+            // start from the bottom of the screen
+            return mHeadsUpAppearHeightBottom + mHeadsUpAppearStartAboveScreen;
+        }
+        // start from the top of the screen
+        return -mStackTopMargin - mHeadsUpAppearStartAboveScreen;
+    }
+
     public void animateOverScrollToAmount(float targetAmount, final boolean onTop,
             final boolean isRubberbanded) {
         final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
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..6b30393 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
@@ -30,9 +29,12 @@
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.NotificationShelf
 import com.android.systemui.statusbar.notification.NotificationActivityStarter
+import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
+import com.android.systemui.statusbar.notification.dagger.SilentHeader
 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 +45,22 @@
 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.DisposableHandle
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
 /** Binds a [NotificationStackScrollLayout] to its [view model][NotificationListViewModel]. */
@@ -65,6 +77,7 @@
     private val nicBinder: NotificationIconContainerShelfViewBinder,
     // Using a provider to avoid a circular dependency.
     private val notificationActivityStarter: Provider<NotificationActivityStarter>,
+    @SilentHeader private val silentHeaderController: SectionHeaderController,
     private val viewModel: NotificationListViewModel,
 ) {
 
@@ -83,9 +96,14 @@
                 bindHideList(viewController, viewModel, hiderTracker)
 
                 if (FooterViewRefactor.isEnabled) {
-                    launch { bindFooter(view) }
+                    val hasNonClearableSilentNotifications: StateFlow<Boolean> =
+                        viewModel.hasNonClearableSilentNotifications.stateIn(this)
+                    launch { reinflateAndBindFooter(view, hasNonClearableSilentNotifications) }
                     launch { bindEmptyShade(view) }
                     launch {
+                        bindSilentHeaderClickListener(view, hasNonClearableSilentNotifications)
+                    }
+                    launch {
                         viewModel.isImportantForAccessibility.collect { isImportantForAccessibility
                             ->
                             view.setImportantForAccessibilityYesNo(isImportantForAccessibility)
@@ -108,42 +126,75 @@
         )
     }
 
-    private suspend fun bindFooter(parentView: NotificationStackScrollLayout) {
+    private suspend fun reinflateAndBindFooter(
+        parentView: NotificationStackScrollLayout,
+        hasNonClearableSilentNotifications: StateFlow<Boolean>
+    ) {
         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(
+            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,
-                            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,
+                            hasNonClearableSilentNotifications
                         )
-                    parentView.setFooterView(footerView)
-                    return@reinflateAndBindLatest disposableHandle
+                    }
                 }
+        }
+    }
+
+    /**
+     * Binds the footer (including its visibility) and dispose of the [DisposableHandle] when done.
+     */
+    private suspend fun bindFooter(
+        footerView: FooterView,
+        footerViewModel: FooterViewModel,
+        parentView: NotificationStackScrollLayout,
+        hasNonClearableSilentNotifications: StateFlow<Boolean>
+    ): Unit = coroutineScope {
+        val disposableHandle =
+            FooterViewBinder.bindWhileAttached(
+                footerView,
+                footerViewModel,
+                clearAllNotifications = {
+                    metricsLogger.action(MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES)
+                    clearAllNotifications(
+                        parentView,
+                        // Hide the silent section header (if present) if there will be
+                        // no remaining silent notifications upon clearing.
+                        hideSilentSection = !hasNonClearableSilentNotifications.value,
+                    )
+                },
+                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) {
@@ -162,6 +213,45 @@
             }
     }
 
+    private suspend fun bindSilentHeaderClickListener(
+        parentView: NotificationStackScrollLayout,
+        hasNonClearableSilentNotifications: StateFlow<Boolean>,
+    ): Unit = coroutineScope {
+        val hasClearableAlertingNotifications: StateFlow<Boolean> =
+            viewModel.hasClearableAlertingNotifications.stateIn(this)
+        silentHeaderController.setOnClearSectionClickListener {
+            clearSilentNotifications(
+                view = parentView,
+                // Leave the shade open if there will be other notifs left over to clear.
+                closeShade = !hasClearableAlertingNotifications.value,
+                // Hide the silent section header itself, if there will be no remaining silent
+                // notifications upon clearing.
+                hideSilentSection = !hasNonClearableSilentNotifications.value,
+            )
+        }
+        try {
+            awaitCancellation()
+        } finally {
+            silentHeaderController.setOnClearSectionClickListener {}
+        }
+    }
+
+    private fun clearAllNotifications(
+        view: NotificationStackScrollLayout,
+        hideSilentSection: Boolean,
+    ) {
+        metricsLogger.action(MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES)
+        view.clearAllNotifications(hideSilentSection)
+    }
+
+    private fun clearSilentNotifications(
+        view: NotificationStackScrollLayout,
+        closeShade: Boolean,
+        hideSilentSection: Boolean
+    ) {
+        view.clearSilentNotifications(closeShade, hideSilentSection)
+    }
+
     private suspend fun bindLogger(view: NotificationStackScrollLayout) {
         if (NotificationsLiveDataStoreRefactor.isEnabled) {
             viewModel.logger.getOrNull()?.let { viewModel ->
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..6321820 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,30 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+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 +51,12 @@
     val footer: Optional<FooterViewModel>,
     val logger: Optional<NotificationLoggerViewModel>,
     activeNotificationsInteractor: ActiveNotificationsInteractor,
-    keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    keyguardInteractor: KeyguardInteractor,
+    powerInteractor: PowerInteractor,
+    remoteInputInteractor: RemoteInputInteractor,
     seenNotificationsInteractor: SeenNotificationsInteractor,
     shadeInteractor: ShadeInteractor,
+    userSetupInteractor: UserSetupInteractor,
     zenModeInteractor: ZenModeInteractor,
 ) {
     /**
@@ -59,11 +71,9 @@
         } else {
             combine(
                     activeNotificationsInteractor.areAnyNotificationsPresent,
-                    keyguardTransitionInteractor.isFinishedInStateWhere {
-                        KeyguardState.lockscreenVisibleInState(it)
-                    }
-                ) { hasNotifications, isOnKeyguard ->
-                    hasNotifications || !isOnKeyguard
+                    isShowingOnLockscreen,
+                ) { hasNotifications, isShowingOnLockscreen ->
+                    hasNotifications || !isShowingOnLockscreen
                 }
                 .distinctUntilChanged()
         }
@@ -76,22 +86,109 @@
             combine(
                     activeNotificationsInteractor.areAnyNotificationsPresent,
                     shadeInteractor.isQsFullscreen,
-                    keyguardTransitionInteractor.isInTransitionToState(KeyguardState.AOD).onStart {
-                        emit(false)
-                    },
-                    keyguardTransitionInteractor
-                        .isFinishedInState(KeyguardState.PRIMARY_BOUNCER)
-                        .onStart { emit(false) }
-                ) { hasNotifications, isQsFullScreen, transitioningToAOD, isBouncerShowing ->
-                    !hasNotifications &&
-                        !isQsFullScreen &&
-                        // Hide empty shade view when in transition to AOD.
-                        // That avoids "No Notifications" blinking when transitioning to AOD.
-                        // For more details, see b/228790482.
-                        !transitioningToAOD &&
-                        // Don't show any notification content if the bouncer is showing. See
-                        // b/267060171.
-                        !isBouncerShowing
+                    isShowingOnLockscreen,
+                ) { hasNotifications, isQsFullScreen, isShowingOnLockscreen ->
+                    when {
+                        hasNotifications -> false
+                        isQsFullScreen -> false
+                        // Do not show the empty shade if the lockscreen is visible (including AOD
+                        // b/228790482 and bouncer b/267060171), except if the shade is opened on
+                        // top.
+                        isShowingOnLockscreen -> false
+                        else -> true
+                    }
+                }
+                .distinctUntilChanged()
+        }
+    }
+
+    val shouldShowFooterView: Flow<AnimatedValue<Boolean>> by lazy {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+            flowOf(AnimatedValue.NotAnimating(false))
+        } else {
+            combine(
+                    activeNotificationsInteractor.areAnyNotificationsPresent,
+                    userSetupInteractor.isUserSetUp,
+                    isShowingOnLockscreen,
+                    shadeInteractor.qsExpansion,
+                    shadeInteractor.isQsFullscreen,
+                    remoteInputInteractor.isRemoteInputActive,
+                    shadeInteractor.shadeExpansion.map { it == 0f }
+                ) {
+                    hasNotifications,
+                    isUserSetUp,
+                    isShowingOnLockscreen,
+                    qsExpansion,
+                    qsFullScreen,
+                    isRemoteInputActive,
+                    isShadeClosed ->
+                    // A pair of (visible, canAnimate)
+                    when {
+                        !hasNotifications -> Pair(false, true)
+                        // Hide the footer until the user setup is complete, to prevent access
+                        // to settings (b/193149550).
+                        !isUserSetUp -> Pair(false, true)
+                        // Do not show the footer if the lockscreen is visible (incl. AOD),
+                        // except if the shade is opened on top. See also b/219680200.
+                        // Do not animate, as that makes the footer appear briefly when
+                        // transitioning between the shade and keyguard.
+                        isShowingOnLockscreen -> Pair(false, 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 -> Pair(false, true)
+                        // Hide the footer if remote input is active (i.e. user is replying to a
+                        // notification). See b/75984847.
+                        isRemoteInputActive -> Pair(false, true)
+                        // Never show the footer if the shade is collapsed (e.g. when HUNing).
+                        isShadeClosed -> Pair(false, false)
+                        else -> Pair(true, true)
+                    }
+                }
+                .distinctUntilChanged(
+                    // Equivalent unless visibility changes
+                    areEquivalent = { a: Pair<Boolean, Boolean>, b: Pair<Boolean, Boolean> ->
+                        a.first == b.first
+                    }
+                )
+                // 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, canAnimate), (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 && canAnimate
+                    AnimatableEvent(visible, shouldAnimate)
+                }
+                .toAnimatedValueFlow()
+        }
+    }
+
+    private val isShowingOnLockscreen: Flow<Boolean> by lazy {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+            flowOf(false)
+        } else {
+            combine(
+                    // Non-notification UI elements of the notification list should not be visible
+                    // on the lockscreen (incl. AOD and bouncer), except if the shade is opened on
+                    // top. See b/219680200 for the footer and b/228790482, b/267060171 for the
+                    // empty shade.
+                    // TODO(b/323187006): There's a plan to eventually get rid of StatusBarState
+                    //  entirely, so this will have to be replaced at some point.
+                    keyguardInteractor.statusBarState.map { it == StatusBarState.KEYGUARD },
+                    // The StatusBarState is unfortunately not updated quickly enough when the power
+                    // button is pressed, so this is necessary in addition to the KEYGUARD check to
+                    // cover the transition to AOD while going to sleep (b/190227875).
+                    powerInteractor.isAsleep,
+                ) { (isOnKeyguard, isAsleep) ->
+                    isOnKeyguard || isAsleep
                 }
                 .distinctUntilChanged()
         }
@@ -114,4 +211,20 @@
             seenNotificationsInteractor.hasFilteredOutSeenNotifications
         }
     }
+
+    val hasClearableAlertingNotifications: Flow<Boolean> by lazy {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+            flowOf(false)
+        } else {
+            activeNotificationsInteractor.hasClearableAlertingNotifications
+        }
+    }
+
+    val hasNonClearableSilentNotifications: Flow<Boolean> by lazy {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+            flowOf(false)
+        } else {
+            activeNotificationsInteractor.hasNonClearableSilentNotifications
+        }
+    }
 }
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/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 8a56da3..270b94b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -30,9 +30,9 @@
 import android.view.WindowManager
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.ActivityIntentHelper
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.ActivityLaunchAnimator.PendingIntentStarter
-import com.android.systemui.animation.DelegateLaunchAnimatorController
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator.PendingIntentStarter
+import com.android.systemui.animation.DelegateTransitionAnimatorController
 import com.android.systemui.assist.AssistManager
 import com.android.systemui.camera.CameraIntents.Companion.isInsecureCameraIntent
 import com.android.systemui.dagger.SysUISingleton
@@ -75,7 +75,7 @@
     private val shadeAnimationInteractor: ShadeAnimationInteractor,
     private val statusBarKeyguardViewManagerLazy: Lazy<StatusBarKeyguardViewManager>,
     private val notifShadeWindowControllerLazy: Lazy<NotificationShadeWindowController>,
-    private val activityLaunchAnimator: ActivityLaunchAnimator,
+    private val activityTransitionAnimator: ActivityTransitionAnimator,
     private val context: Context,
     @DisplayId private val displayId: Int,
     private val lockScreenUserManager: NotificationLockscreenUserManager,
@@ -127,7 +127,7 @@
     override fun startPendingIntentDismissingKeyguard(
         intent: PendingIntent,
         intentSentUiThreadCallback: Runnable?,
-        animationController: ActivityLaunchAnimator.Controller?,
+        animationController: ActivityTransitionAnimator.Controller?,
     ) {
         activityStarterInternal.startPendingIntentDismissingKeyguard(
             intent = intent,
@@ -139,7 +139,7 @@
     override fun startPendingIntentMaybeDismissingKeyguard(
         intent: PendingIntent,
         intentSentUiThreadCallback: Runnable?,
-        animationController: ActivityLaunchAnimator.Controller?
+        animationController: ActivityTransitionAnimator.Controller?
     ) {
         activityStarterInternal.startPendingIntentDismissingKeyguard(
             intent = intent,
@@ -209,7 +209,7 @@
     override fun startActivity(
         intent: Intent,
         dismissShade: Boolean,
-        animationController: ActivityLaunchAnimator.Controller?,
+        animationController: ActivityTransitionAnimator.Controller?,
         showOverLockscreenWhenLocked: Boolean,
     ) {
         activityStarterInternal.startActivity(
@@ -222,7 +222,7 @@
     override fun startActivity(
         intent: Intent,
         dismissShade: Boolean,
-        animationController: ActivityLaunchAnimator.Controller?,
+        animationController: ActivityTransitionAnimator.Controller?,
         showOverLockscreenWhenLocked: Boolean,
         userHandle: UserHandle?,
     ) {
@@ -245,7 +245,7 @@
 
     override fun postStartActivityDismissingKeyguard(
         intent: PendingIntent,
-        animationController: ActivityLaunchAnimator.Controller?
+        animationController: ActivityTransitionAnimator.Controller?
     ) {
         postOnUiThread {
             activityStarterInternal.startPendingIntentDismissingKeyguard(
@@ -268,7 +268,7 @@
     override fun postStartActivityDismissingKeyguard(
         intent: Intent,
         delay: Int,
-        animationController: ActivityLaunchAnimator.Controller?,
+        animationController: ActivityTransitionAnimator.Controller?,
     ) {
         postOnUiThread(delay) {
             activityStarterInternal.startActivityDismissingKeyguard(
@@ -283,7 +283,7 @@
     override fun postStartActivityDismissingKeyguard(
         intent: Intent,
         delay: Int,
-        animationController: ActivityLaunchAnimator.Controller?,
+        animationController: ActivityTransitionAnimator.Controller?,
         customMessage: String?,
     ) {
         postOnUiThread(delay) {
@@ -342,7 +342,7 @@
         disallowEnterPictureInPictureWhileLaunching: Boolean,
         callback: ActivityStarter.Callback?,
         flags: Int,
-        animationController: ActivityLaunchAnimator.Controller?,
+        animationController: ActivityTransitionAnimator.Controller?,
         userHandle: UserHandle?,
     ) {
         activityStarterInternal.startActivityDismissingKeyguard(
@@ -430,7 +430,7 @@
             disallowEnterPictureInPictureWhileLaunching: Boolean = false,
             callback: ActivityStarter.Callback? = null,
             flags: Int = 0,
-            animationController: ActivityLaunchAnimator.Controller? = null,
+            animationController: ActivityTransitionAnimator.Controller? = null,
             userHandle: UserHandle? = null,
             customMessage: String? = null,
         ) {
@@ -464,7 +464,7 @@
                 intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
                 intent.addFlags(flags)
                 val result = intArrayOf(ActivityManager.START_CANCELED)
-                activityLaunchAnimator.startIntentWithAnimation(
+                activityTransitionAnimator.startIntentWithAnimation(
                     animController,
                     animate,
                     intent.getPackage()
@@ -552,7 +552,7 @@
             intent: PendingIntent,
             intentSentUiThreadCallback: Runnable? = null,
             associatedView: View? = null,
-            animationController: ActivityLaunchAnimator.Controller? = null,
+            animationController: ActivityTransitionAnimator.Controller? = null,
             showOverLockscreen: Boolean = false,
         ) {
             val animationController =
@@ -602,7 +602,7 @@
             val collapse = !animate
             val runnable = Runnable {
                 try {
-                    activityLaunchAnimator.startPendingIntentWithAnimation(
+                    activityTransitionAnimator.startPendingIntentWithAnimation(
                         controller,
                         animate,
                         intent.creatorPackage,
@@ -670,7 +670,7 @@
         fun startActivity(
             intent: Intent,
             dismissShade: Boolean = false,
-            animationController: ActivityLaunchAnimator.Controller? = null,
+            animationController: ActivityTransitionAnimator.Controller? = null,
             showOverLockscreenWhenLocked: Boolean = false,
             userHandle: UserHandle? = null,
         ) {
@@ -698,7 +698,7 @@
                         showOverLockscreenWhenLocked
                     ) == true
 
-            var controller: ActivityLaunchAnimator.Controller? = null
+            var controller: ActivityTransitionAnimator.Controller? = null
             if (animate) {
                 // Wrap the animation controller to dismiss the shade and set
                 // mIsLaunchingActivityOverLockscreen during the animation.
@@ -721,7 +721,7 @@
                 centralSurfaces?.awakenDreams()
             }
 
-            activityLaunchAnimator.startIntentWithAnimation(
+            activityTransitionAnimator.startIntentWithAnimation(
                 controller,
                 animate,
                 intent.getPackage(),
@@ -815,7 +815,7 @@
         }
 
         /**
-         * Return a [ActivityLaunchAnimator.Controller] wrapping `animationController` so that:
+         * Return a [ActivityTransitionAnimator.Controller] wrapping `animationController` so that:
          * - if it launches in the notification shade window and `dismissShade` is true, then the
          *   shade will be instantly dismissed at the end of the animation.
          * - if it launches in status bar window, it will make the status bar window match the
@@ -830,15 +830,15 @@
          * @param isLaunchForActivity whether the launch is for an activity.
          */
         private fun wrapAnimationControllerForShadeOrStatusBar(
-            animationController: ActivityLaunchAnimator.Controller?,
+            animationController: ActivityTransitionAnimator.Controller?,
             dismissShade: Boolean,
             isLaunchForActivity: Boolean,
-        ): ActivityLaunchAnimator.Controller? {
+        ): ActivityTransitionAnimator.Controller? {
             if (animationController == null) {
                 return null
             }
-            val rootView = animationController.launchContainer.rootView
-            val controllerFromStatusBar: Optional<ActivityLaunchAnimator.Controller> =
+            val rootView = animationController.transitionContainer.rootView
+            val controllerFromStatusBar: Optional<ActivityTransitionAnimator.Controller> =
                 statusBarWindowController.wrapAnimationControllerIfInStatusBar(
                     rootView,
                     animationController
@@ -851,7 +851,7 @@
                 // If the view is not in the status bar, then we are animating a view in the shade.
                 // We have to make sure that we collapse it when the animation ends or is cancelled.
                 if (dismissShade) {
-                    return StatusBarLaunchAnimatorController(
+                    return StatusBarTransitionAnimatorController(
                         animationController,
                         shadeViewControllerLazy.get(),
                         shadeAnimationInteractor,
@@ -870,10 +870,10 @@
          * lockscreen, the correct flags are set for it to be occluded.
          */
         private fun wrapAnimationControllerForLockscreen(
-            animationController: ActivityLaunchAnimator.Controller?
-        ): ActivityLaunchAnimator.Controller? {
+            animationController: ActivityTransitionAnimator.Controller?
+        ): ActivityTransitionAnimator.Controller? {
             return animationController?.let {
-                object : DelegateLaunchAnimatorController(it) {
+                object : DelegateTransitionAnimatorController(it) {
                     override fun onIntentStarted(willAnimate: Boolean) {
                         delegate.onIntentStarted(willAnimate)
                         if (willAnimate) {
@@ -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,17 +902,19 @@
                         }
                     }
 
-                    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?) {
+                    override fun onTransitionAnimationCancelled(
+                        newKeyguardOccludedState: Boolean?
+                    ) {
                         if (newKeyguardOccludedState != null) {
                             keyguardViewMediatorLazy
                                 .get()
@@ -925,7 +927,7 @@
                         // collapse the shade (or at least run the // post collapse
                         // runnables) later on.
                         centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
-                        delegate.onLaunchAnimationCancelled(newKeyguardOccludedState)
+                        delegate.onTransitionAnimationCancelled(newKeyguardOccludedState)
                     }
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 4019436..9052409 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -38,7 +38,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.AuthKeyguardMessageArea;
 import com.android.systemui.Dumpable;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.display.data.repository.DisplayMetricsRepository;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
@@ -334,6 +334,6 @@
     /**
      * Gets an animation controller from a notification row.
      */
-    ActivityLaunchAnimator.Controller getAnimatorControllerFromNotification(
+    ActivityTransitionAnimator.Controller getAnimatorControllerFromNotification(
             ExpandableNotificationRow associatedView);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
index 60dfaa7..8af7ee8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -20,7 +20,7 @@
 import android.view.MotionEvent
 import androidx.lifecycle.LifecycleRegistry
 import com.android.keyguard.AuthKeyguardMessageArea
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.navigationbar.NavigationBarView
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
 import com.android.systemui.qs.QSPanelController
@@ -99,5 +99,5 @@
     override fun setIsLaunchingActivityOverLockscreen(isLaunchingActivityOverLockscreen: Boolean) {}
     override fun getAnimatorControllerFromNotification(
         associatedView: ExpandableNotificationRow?,
-    ): ActivityLaunchAnimator.Controller? = null
+    ): ActivityTransitionAnimator.Controller? = null
 }
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 3e7089c..51830c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -115,7 +115,7 @@
 import com.android.systemui.InitController;
 import com.android.systemui.Prefs;
 import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.back.domain.interactor.BackActionInteractor;
 import com.android.systemui.biometrics.AuthRippleController;
@@ -576,7 +576,7 @@
     private boolean mNoAnimationOnNextBarModeChange;
     private final SysuiStatusBarStateController mStatusBarStateController;
 
-    private final ActivityLaunchAnimator mActivityLaunchAnimator;
+    private final ActivityTransitionAnimator mActivityTransitionAnimator;
     private final NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
     private final Lazy<NotificationPresenter> mPresenterLazy;
     private final Lazy<NotificationActivityStarter> mNotificationActivityStarterLazy;
@@ -655,7 +655,7 @@
             // Lazys due to b/298099682.
             Lazy<NotificationPresenter> notificationPresenterLazy,
             Lazy<NotificationActivityStarter> notificationActivityStarterLazy,
-            NotificationLaunchAnimatorControllerProvider notifLaunchAnimatorControllerProvider,
+            NotificationLaunchAnimatorControllerProvider notifTransitionAnimatorControllerProvider,
             DozeParameters dozeParameters,
             ScrimController scrimController,
             Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
@@ -694,7 +694,7 @@
             @Main MessageRouter messageRouter,
             WallpaperManager wallpaperManager,
             Optional<StartingSurface> startingSurfaceOptional,
-            ActivityLaunchAnimator activityLaunchAnimator,
+            ActivityTransitionAnimator activityTransitionAnimator,
             DeviceStateManager deviceStateManager,
             WiredChargingRippleController wiredChargingRippleController,
             IDreamManager dreamManager,
@@ -761,7 +761,7 @@
         mNotifListContainer = mStackScrollerController.getNotificationListContainer();
         mPresenterLazy = notificationPresenterLazy;
         mNotificationActivityStarterLazy = notificationActivityStarterLazy;
-        mNotificationAnimationProvider = notifLaunchAnimatorControllerProvider;
+        mNotificationAnimationProvider = notifTransitionAnimatorControllerProvider;
         mDozeServiceHost = dozeServiceHost;
         mPowerManager = powerManager;
         mDozeParameters = dozeParameters;
@@ -816,7 +816,7 @@
         shadeExpansionListener.onPanelExpansionChanged(currentState);
 
         mActivityIntentHelper = new ActivityIntentHelper(mContext);
-        mActivityLaunchAnimator = activityLaunchAnimator;
+        mActivityTransitionAnimator = activityTransitionAnimator;
 
         // TODO(b/190746471): Find a better home for this.
         DateTimeView.setReceiverHandler(timeTickHandler);
@@ -1424,8 +1424,8 @@
 
     private void setUpPresenter() {
         // Set up the initial notification state.
-        mActivityLaunchAnimator.setCallback(mActivityLaunchAnimatorCallback);
-        mActivityLaunchAnimator.addListener(mActivityLaunchAnimatorListener);
+        mActivityTransitionAnimator.setCallback(mActivityTransitionAnimatorCallback);
+        mActivityTransitionAnimator.addListener(mActivityTransitionAnimatorListener);
         mRemoteInputManager.addControllerCallback(mNotificationShadeWindowController);
         mStackScrollerController.setNotificationActivityStarter(
                 mNotificationActivityStarterLazy.get());
@@ -3176,8 +3176,8 @@
                 }
             };
 
-    private final ActivityLaunchAnimator.Callback mActivityLaunchAnimatorCallback =
-            new ActivityLaunchAnimator.Callback() {
+    private final ActivityTransitionAnimator.Callback mActivityTransitionAnimatorCallback =
+            new ActivityTransitionAnimator.Callback() {
                 @Override
                 public boolean isOnKeyguard() {
                     return mKeyguardStateController.isShowing();
@@ -3205,15 +3205,15 @@
                 }
             };
 
-    private final ActivityLaunchAnimator.Listener mActivityLaunchAnimatorListener =
-            new ActivityLaunchAnimator.Listener() {
+    private final ActivityTransitionAnimator.Listener mActivityTransitionAnimatorListener =
+            new ActivityTransitionAnimator.Listener() {
                 @Override
-                public void onLaunchAnimationStart() {
+                public void onTransitionAnimationStart() {
                     mKeyguardViewMediator.setBlursDisabledForAppLaunch(true);
                 }
 
                 @Override
-                public void onLaunchAnimationEnd() {
+                public void onTransitionAnimationEnd() {
                     mKeyguardViewMediator.setBlursDisabledForAppLaunch(false);
                 }
             };
@@ -3267,7 +3267,7 @@
     }
 
     @Override
-    public ActivityLaunchAnimator.Controller getAnimatorControllerFromNotification(
+    public ActivityTransitionAnimator.Controller getAnimatorControllerFromNotification(
             ExpandableNotificationRow associatedView) {
         return mNotificationAnimationProvider.getAnimatorController(associatedView);
     }
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 3ad60d9..7f7eb04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -482,20 +482,19 @@
 
         if (KeyguardWmStateRefactor.isEnabled()) {
             // Show the keyguard views whenever we've told WM that the lockscreen is visible.
-            mShadeViewController.postToView(() ->
-                    collectFlow(
-                        getViewRootImpl().getView(),
-                        combineFlows(
-                                mWmLockscreenVisibilityInteractor.get().getLockscreenVisibility(),
-                                mSurfaceBehindInteractor.get().isAnimatingSurface(),
-                                (lockscreenVis, animatingSurface) ->
-                                        // TODO(b/322546110): Waiting until we're not animating the
-                                        // surface is a workaround to avoid jank. We should actually
-                                        // fix the source of the jank, and then hide the keyguard
-                                        // view without waiting for the animation to end.
-                                        lockscreenVis || animatingSurface
-                        ),
-                        this::consumeShowStatusBarKeyguardView));
+            collectFlow(
+                    getViewRootImpl().getView(),
+                    combineFlows(
+                            mWmLockscreenVisibilityInteractor.get().getLockscreenVisibility(),
+                            mSurfaceBehindInteractor.get().isAnimatingSurface(),
+                            (lockscreenVis, animatingSurface) ->
+                                    // TODO(b/322546110): Waiting until we're not animating the
+                                    // surface is a workaround to avoid jank. We should actually
+                                    // fix the source of the jank, and then hide the keyguard
+                                    // view without waiting for the animation to end.
+                                    lockscreenVis || animatingSurface
+                    ),
+                    this::consumeShowStatusBarKeyguardView);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 4ee061d..4fd33ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -52,7 +52,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.ActivityIntentHelper;
 import com.android.systemui.EventLogTags;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.DisplayId;
@@ -125,7 +125,7 @@
     private final NotificationPresenter mPresenter;
     private final ShadeViewController mShadeViewController;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
-    private final ActivityLaunchAnimator mActivityLaunchAnimator;
+    private final ActivityTransitionAnimator mActivityTransitionAnimator;
     private final NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
     private final PowerInteractor mPowerInteractor;
     private final UserTracker mUserTracker;
@@ -161,7 +161,7 @@
             NotificationPresenter presenter,
             ShadeViewController shadeViewController,
             NotificationShadeWindowController notificationShadeWindowController,
-            ActivityLaunchAnimator activityLaunchAnimator,
+            ActivityTransitionAnimator activityTransitionAnimator,
             ShadeAnimationInteractor shadeAnimationInteractor,
             NotificationLaunchAnimatorControllerProvider notificationAnimationProvider,
             LaunchFullScreenIntentProvider launchFullScreenIntentProvider,
@@ -194,7 +194,7 @@
         mOnUserInteractionCallback = onUserInteractionCallback;
         mPresenter = presenter;
         mShadeViewController = shadeViewController;
-        mActivityLaunchAnimator = activityLaunchAnimator;
+        mActivityTransitionAnimator = activityTransitionAnimator;
         mNotificationAnimationProvider = notificationAnimationProvider;
         mPowerInteractor = powerInteractor;
         mUserTracker = userTracker;
@@ -440,15 +440,15 @@
             boolean isActivityIntent) {
         mLogger.logStartNotificationIntent(entry);
         try {
-            ActivityLaunchAnimator.Controller animationController =
-                    new StatusBarLaunchAnimatorController(
+            ActivityTransitionAnimator.Controller animationController =
+                    new StatusBarTransitionAnimatorController(
                             mNotificationAnimationProvider.getAnimatorController(row, null),
                             mShadeViewController,
                             mShadeAnimationInteractor,
                             mShadeController,
                             mNotificationShadeWindowController,
                             isActivityIntent);
-            mActivityLaunchAnimator.startPendingIntentWithAnimation(
+            mActivityTransitionAnimator.startPendingIntentWithAnimation(
                     animationController,
                     animate,
                     intent.getCreatorPackage(),
@@ -482,8 +482,8 @@
             @Override
             public boolean onDismiss() {
                 AsyncTask.execute(() -> {
-                    ActivityLaunchAnimator.Controller animationController =
-                            new StatusBarLaunchAnimatorController(
+                    ActivityTransitionAnimator.Controller animationController =
+                            new StatusBarTransitionAnimatorController(
                                     mNotificationAnimationProvider.getAnimatorController(row),
                                     mShadeViewController,
                                     mShadeAnimationInteractor,
@@ -491,7 +491,7 @@
                                     mNotificationShadeWindowController,
                                     true /* isActivityIntent */);
 
-                    mActivityLaunchAnimator.startIntentWithAnimation(
+                    mActivityTransitionAnimator.startIntentWithAnimation(
                             animationController, animate, intent.getPackage(),
                             (adapter) -> TaskStackBuilder.create(mContext)
                                     .addNextIntentWithParentStack(intent)
@@ -528,13 +528,13 @@
                         tsb.addNextIntent(intent);
                     }
 
-                    ActivityLaunchAnimator.Controller viewController =
-                            ActivityLaunchAnimator.Controller.fromView(view,
+                    ActivityTransitionAnimator.Controller viewController =
+                            ActivityTransitionAnimator.Controller.fromView(view,
                                     InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON
                             );
-                    ActivityLaunchAnimator.Controller animationController =
+                    ActivityTransitionAnimator.Controller animationController =
                             viewController == null ? null
-                                : new StatusBarLaunchAnimatorController(
+                                : new StatusBarTransitionAnimatorController(
                                         viewController,
                                         mShadeViewController,
                                         mShadeAnimationInteractor,
@@ -542,8 +542,8 @@
                                         mNotificationShadeWindowController,
                                         true /* isActivityIntent */);
 
-                    mActivityLaunchAnimator.startIntentWithAnimation(animationController, animate,
-                            intent.getPackage(),
+                    mActivityTransitionAnimator.startIntentWithAnimation(
+                            animationController, animate, intent.getPackage(),
                             (adapter) -> tsb.startActivities(
                                     getActivityOptions(mDisplayId, adapter),
                                     mUserTracker.getUserHandle()));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt
similarity index 62%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt
index 8ca5bfc..7e907d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTransitionAnimatorController.kt
@@ -1,25 +1,25 @@
 package com.android.systemui.statusbar.phone
 
 import android.view.View
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.LaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
+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
 import com.android.systemui.statusbar.NotificationShadeWindowController
 
 /**
- * A [ActivityLaunchAnimator.Controller] that takes care of collapsing the status bar at the right
- * time.
+ * A [ActivityTransitionAnimator.Controller] that takes care of collapsing the status bar at the
+ * right time.
  */
-class StatusBarLaunchAnimatorController(
-    private val delegate: ActivityLaunchAnimator.Controller,
+class StatusBarTransitionAnimatorController(
+    private val delegate: ActivityTransitionAnimator.Controller,
     private val shadeViewController: ShadeViewController,
     private val shadeAnimationInteractor: ShadeAnimationInteractor,
     private val shadeController: ShadeController,
     private val notificationShadeWindowController: NotificationShadeWindowController,
     private val isLaunchForActivity: Boolean = true
-) : ActivityLaunchAnimator.Controller by delegate {
+) : ActivityTransitionAnimator.Controller by delegate {
     // Always sync the opening window with the shade, given that we draw a hole punch in the shade
     // of the same size and position as the opening app to make it visible.
     override val openingWindowSyncView: View?
@@ -34,33 +34,34 @@
         }
     }
 
-    override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
-        delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+    override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
+        delegate.onTransitionAnimationStart(isExpandingFullyAbove)
         shadeAnimationInteractor.setIsLaunchingActivity(true)
         if (!isExpandingFullyAbove) {
             shadeViewController.collapseWithDuration(
-                ActivityLaunchAnimator.TIMINGS.totalDuration.toInt())
+                ActivityTransitionAnimator.TIMINGS.totalDuration.toInt()
+            )
         }
     }
 
-    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)
     }
 
-    override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) {
-        delegate.onLaunchAnimationCancelled()
+    override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+        delegate.onTransitionAnimationCancelled()
         shadeAnimationInteractor.setIsLaunchingActivity(false)
         shadeController.onLaunchAnimationCancelled(isLaunchForActivity)
     }
-}
\ No newline at end of file
+}
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/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index 0bdd1a5..a20468f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -30,7 +30,7 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.Dumpable
 import com.android.systemui.res.R
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
@@ -233,7 +233,7 @@
                 logger.logChipClicked()
                 activityStarter.postStartActivityDismissingKeyguard(
                     intent,
-                    ActivityLaunchAnimator.Controller.fromView(
+                    ActivityTransitionAnimator.Controller.fromView(
                         backgroundView,
                         InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP)
                 )
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..21f1a3d 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.animation.ActivityTransitionAnimator;
+import com.android.systemui.animation.DelegateTransitionAnimatorController;
 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;
@@ -188,23 +188,23 @@
      *   updated animation controller that handles status-bar-related animation details. Returns an
      *   empty optional if the animation is *not* on a view in the status bar.
      */
-    public Optional<ActivityLaunchAnimator.Controller> wrapAnimationControllerIfInStatusBar(
-            View rootView, ActivityLaunchAnimator.Controller animationController) {
+    public Optional<ActivityTransitionAnimator.Controller> wrapAnimationControllerIfInStatusBar(
+            View rootView, ActivityTransitionAnimator.Controller animationController) {
         if (rootView != mStatusBarWindowView) {
             return Optional.empty();
         }
 
-        animationController.setLaunchContainer(mLaunchAnimationContainer);
-        return Optional.of(new DelegateLaunchAnimatorController(animationController) {
+        animationController.setTransitionContainer(mLaunchAnimationContainer);
+        return Optional.of(new DelegateTransitionAnimatorController(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/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index 3376e23..147e158 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -15,6 +15,8 @@
  */
 package com.android.systemui.theme;
 
+import static com.android.systemui.shared.Flags.enableHomeDelay;
+
 import android.annotation.AnyThread;
 import android.content.om.FabricatedOverlay;
 import android.content.om.OverlayIdentifier;
@@ -32,6 +34,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 
 import com.google.android.collect.Lists;
@@ -142,6 +145,7 @@
     private final Map<String, String> mCategoryToTargetPackage = new ArrayMap<>();
     private final OverlayManager mOverlayManager;
     private final Executor mBgExecutor;
+    private final Executor mMainExecutor;
     private final String mLauncherPackage;
     private final String mThemePickerPackage;
 
@@ -150,9 +154,11 @@
             @Background Executor bgExecutor,
             @Named(ThemeModule.LAUNCHER_PACKAGE) String launcherPackage,
             @Named(ThemeModule.THEME_PICKER_PACKAGE) String themePickerPackage,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            @Main Executor mainExecutor) {
         mOverlayManager = overlayManager;
         mBgExecutor = bgExecutor;
+        mMainExecutor = mainExecutor;
         mLauncherPackage = launcherPackage;
         mThemePickerPackage = themePickerPackage;
         mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet(
@@ -184,12 +190,21 @@
     /**
      * Apply the set of overlay packages to the set of {@code UserHandle}s provided. Overlays that
      * affect sysui will also be applied to the system user.
+     *
+     * @param categoryToPackage Overlay packages to be applied
+     * @param pendingCreation Overlays yet to be created
+     * @param currentUser Current User ID
+     * @param managedProfiles Profiles get overlays
+     * @param onComplete Callback for when resources are ready. Runs in the main thread.
      */
     public void applyCurrentUserOverlays(
             Map<String, OverlayIdentifier> categoryToPackage,
             FabricatedOverlay[] pendingCreation,
             int currentUser,
-            Set<UserHandle> managedProfiles) {
+            Set<UserHandle> managedProfiles,
+            Runnable onComplete
+    ) {
+
         mBgExecutor.execute(() -> {
 
             // Disable all overlays that have not been specified in the user setting.
@@ -236,6 +251,10 @@
 
             try {
                 mOverlayManager.commit(transaction.build());
+                if (enableHomeDelay() && onComplete != null) {
+                    Log.d(TAG, "Executing onComplete runnable");
+                    mMainExecutor.execute(onComplete);
+                }
             } catch (SecurityException | IllegalStateException e) {
                 Log.e(TAG, "setEnabled failed", e);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 2b9ad50..585ab72 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -20,6 +20,7 @@
 
 import static com.android.systemui.Flags.themeOverlayControllerWakefulnessDeprecation;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
+import static com.android.systemui.shared.Flags.enableHomeDelay;
 import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_HOME;
 import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_LOCK;
 import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_PRESET;
@@ -31,6 +32,7 @@
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_SOURCE;
 import static com.android.systemui.theme.ThemeOverlayApplier.TIMESTAMP_FIELD;
 
+import android.app.ActivityManager;
 import android.app.UiModeManager;
 import android.app.WallpaperColors;
 import android.app.WallpaperManager;
@@ -140,6 +142,7 @@
     // Current wallpaper colors associated to a user.
     private final SparseArray<WallpaperColors> mCurrentColors = new SparseArray<>();
     private final WallpaperManager mWallpaperManager;
+    private final ActivityManager mActivityManager;
     @VisibleForTesting
     protected ColorScheme mColorScheme;
     // If fabricated overlays were already created for the current theme.
@@ -414,7 +417,8 @@
             WakefulnessLifecycle wakefulnessLifecycle,
             JavaAdapter javaAdapter,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
-            UiModeManager uiModeManager) {
+            UiModeManager uiModeManager,
+            ActivityManager activityManager) {
         mContext = context;
         mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET);
         mIsFidelityEnabled = featureFlags.isEnabled(Flags.COLOR_FIDELITY);
@@ -433,6 +437,7 @@
         mJavaAdapter = javaAdapter;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
         mUiModeManager = uiModeManager;
+        mActivityManager = activityManager;
         dumpManager.registerDumpable(TAG, this);
     }
 
@@ -806,8 +811,16 @@
             }
         }
 
+        final Runnable onCompleteCallback = !enableHomeDelay()
+                ? () -> {}
+                : () -> {
+                    Log.d(TAG, "ThemeHomeDelay: ThemeOverlayController ready");
+                    mActivityManager.setThemeOverlayReady(true);
+                };
+
         if (colorSchemeIsApplied(managedProfiles)) {
             Log.d(TAG, "Skipping overlay creation. Theme was already: " + mColorScheme);
+            onCompleteCallback.run();
             return;
         }
 
@@ -816,15 +829,19 @@
                     .map(key -> key + " -> " + categoryToPackage.get(key)).collect(
                             Collectors.joining(", ")));
         }
+
+        FabricatedOverlay[] fOverlays = null;
+
         if (mNeedsOverlayCreation) {
             mNeedsOverlayCreation = false;
-            mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[]{
+            fOverlays = new FabricatedOverlay[]{
                     mSecondaryOverlay, mNeutralOverlay, mDynamicOverlay
-            }, currentUser, managedProfiles);
-        } else {
-            mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, currentUser,
-                    managedProfiles);
+            };
         }
+
+        mThemeManager.applyCurrentUserOverlays(categoryToPackage, fOverlays, currentUser,
+                managedProfiles, onCompleteCallback);
+
     }
 
     private Style fetchThemeStyleFromSetting() {
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 = this@UnfoldLightRevealOverlayAnimation.scrimLogger
-                }
-
-        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/SelectedUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
index 0fb4b43..38b381a 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
@@ -1,6 +1,7 @@
 package com.android.systemui.user.domain.interactor
 
 import android.annotation.UserIdInt
+import android.content.pm.UserInfo
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.Flags.refactorGetCurrentUser
 import com.android.systemui.dagger.SysUISingleton
@@ -16,6 +17,9 @@
     /** Flow providing the ID of the currently selected user. */
     val selectedUser = repository.selectedUserInfo.map { it.id }.distinctUntilChanged()
 
+    /** Flow providing the [UserInfo] of the currently selected user. */
+    val selectedUserInfo = repository.selectedUserInfo
+
     /**
      * Returns the ID of the currently-selected user.
      *
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/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 0d0a646..46ce5f2 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -18,6 +18,7 @@
 
 import android.view.View
 import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.coroutineScope
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -76,6 +77,23 @@
     }
 }
 
+/**
+ * Collect information for the given [flow], calling [consumer] for each emitted event. Defaults to
+ * [LifeCycle.State.CREATED] which is mapped over from the equivalent definition for collecting the
+ * flow on a view.
+ */
+@JvmOverloads
+fun <T> collectFlow(
+    lifecycle: Lifecycle,
+    flow: Flow<T>,
+    consumer: Consumer<T>,
+    state: Lifecycle.State = Lifecycle.State.CREATED,
+) {
+    lifecycle.coroutineScope.launch {
+        lifecycle.repeatOnLifecycle(state) { flow.collect { consumer.accept(it) } }
+    }
+}
+
 fun <A, B, R> combineFlows(flow1: Flow<A>, flow2: Flow<B>, bifunction: (A, B) -> R): Flow<R> {
     return combine(flow1, flow2, bifunction)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/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..1af5c46 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -18,9 +18,14 @@
 
 import android.content.Context
 import android.media.AudioManager
+import com.android.settingslib.media.data.repository.SpatializerRepository
+import com.android.settingslib.media.data.repository.SpatializerRepositoryImpl
+import com.android.settingslib.media.domain.interactor.SpatializerInteractor
 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,16 +40,33 @@
     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 =
             AudioModeInteractor(repository)
+
+        @Provides
+        fun provdieSpatializerRepository(
+            audioManager: AudioManager,
+            @Background backgroundContext: CoroutineContext,
+        ): SpatializerRepository =
+            SpatializerRepositoryImpl(audioManager.spatializer, backgroundContext)
+
+        @Provides
+        fun provideSpatializerInetractor(repository: SpatializerRepository): SpatializerInteractor =
+            SpatializerInteractor(repository)
     }
 }
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/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
index e0228d9..1d9b90a 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
@@ -34,7 +34,7 @@
 import android.service.quickaccesswallet.QuickAccessWalletClientImpl;
 import android.util.Log;
 
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -236,12 +236,12 @@
      * that too is null, then fall back to {@link WalletActivity}.
      *
      * @param activityStarter an {@link ActivityStarter} to launch the Intent or PendingIntent.
-     * @param animationController an {@link ActivityLaunchAnimator.Controller} to provide a
+     * @param animationController an {@link ActivityTransitionAnimator.Controller} to provide a
      *                            smooth animation for the activity launch.
      * @param hasCard whether the service returns any cards.
      */
     public void startQuickAccessUiIntent(ActivityStarter activityStarter,
-            ActivityLaunchAnimator.Controller animationController,
+            ActivityTransitionAnimator.Controller animationController,
             boolean hasCard) {
         mQuickAccessWalletClient.getWalletPendingIntent(mExecutor,
                 walletPendingIntent -> {
@@ -271,7 +271,7 @@
     private void startQuickAccessViaIntent(Intent intent,
             boolean hasCard,
             ActivityStarter activityStarter,
-            ActivityLaunchAnimator.Controller animationController) {
+            ActivityTransitionAnimator.Controller animationController) {
         if (hasCard) {
             activityStarter.startActivity(intent, true /* dismissShade */,
                     animationController, true /* showOverLockscreenWhenLocked */);
@@ -285,7 +285,7 @@
 
     private void startQuickAccessViaPendingIntent(PendingIntent pendingIntent,
             ActivityStarter activityStarter,
-            ActivityLaunchAnimator.Controller animationController) {
+            ActivityTransitionAnimator.Controller animationController) {
         activityStarter.postStartActivityDismissingKeyguard(
                 pendingIntent,
                 animationController);
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..4a2554e 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/ActivityTransitionAnimatorTest.kt
similarity index 72%
rename from packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
index 8faf715..75a49d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
@@ -44,33 +44,37 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper
-class ActivityLaunchAnimatorTest : SysuiTestCase() {
-    private val launchContainer = LinearLayout(mContext)
-    private val testLaunchAnimator = fakeLaunchAnimator()
-    @Mock lateinit var callback: ActivityLaunchAnimator.Callback
-    @Mock lateinit var listener: ActivityLaunchAnimator.Listener
-    @Spy private val controller = TestLaunchAnimatorController(launchContainer)
+class ActivityTransitionAnimatorTest : SysuiTestCase() {
+    private val transitionContainer = LinearLayout(mContext)
+    private val testTransitionAnimator = fakeTransitionAnimator()
+    @Mock lateinit var callback: ActivityTransitionAnimator.Callback
+    @Mock lateinit var listener: ActivityTransitionAnimator.Listener
+    @Spy private val controller = TestTransitionAnimatorController(transitionContainer)
     @Mock lateinit var iCallback: IRemoteAnimationFinishedCallback
 
-    private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
+    private lateinit var activityTransitionAnimator: ActivityTransitionAnimator
     @get:Rule val rule = MockitoJUnit.rule()
 
     @Before
     fun setup() {
-        activityLaunchAnimator =
-            ActivityLaunchAnimator(testLaunchAnimator, testLaunchAnimator, disableWmTimeout = true)
-        activityLaunchAnimator.callback = callback
-        activityLaunchAnimator.addListener(listener)
+        activityTransitionAnimator =
+            ActivityTransitionAnimator(
+                testTransitionAnimator,
+                testTransitionAnimator,
+                disableWmTimeout = true
+            )
+        activityTransitionAnimator.callback = callback
+        activityTransitionAnimator.addListener(listener)
     }
 
     @After
     fun tearDown() {
-        activityLaunchAnimator.removeListener(listener)
+        activityTransitionAnimator.removeListener(listener)
     }
 
     private fun startIntentWithAnimation(
-        animator: ActivityLaunchAnimator = this.activityLaunchAnimator,
-        controller: ActivityLaunchAnimator.Controller? = this.controller,
+        animator: ActivityTransitionAnimator = this.activityTransitionAnimator,
+        controller: ActivityTransitionAnimator.Controller? = this.controller,
         animate: Boolean = true,
         intentStarter: (RemoteAnimationAdapter?) -> Int
     ) {
@@ -134,7 +138,7 @@
         val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
         var animationAdapter: RemoteAnimationAdapter? = null
 
-        startIntentWithAnimation(activityLaunchAnimator) { adapter ->
+        startIntentWithAnimation(activityTransitionAnimator) { adapter ->
             animationAdapter = adapter
             ActivityManager.START_DELIVERED_TO_TOP
         }
@@ -159,50 +163,50 @@
 
     @Test
     fun doesNotStartIfAnimationIsCancelled() {
-        val runner = activityLaunchAnimator.createRunner(controller)
+        val runner = activityTransitionAnimator.createRunner(controller)
         runner.onAnimationCancelled()
         runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback)
 
         waitForIdleSync()
-        verify(controller).onLaunchAnimationCancelled()
-        verify(controller, never()).onLaunchAnimationStart(anyBoolean())
-        verify(listener).onLaunchAnimationCancelled()
-        verify(listener, never()).onLaunchAnimationStart()
+        verify(controller).onTransitionAnimationCancelled()
+        verify(controller, never()).onTransitionAnimationStart(anyBoolean())
+        verify(listener).onTransitionAnimationCancelled()
+        verify(listener, never()).onTransitionAnimationStart()
         assertNull(runner.delegate)
     }
 
     @Test
     fun cancelsIfNoOpeningWindowIsFound() {
-        val runner = activityLaunchAnimator.createRunner(controller)
+        val runner = activityTransitionAnimator.createRunner(controller)
         runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback)
 
         waitForIdleSync()
-        verify(controller).onLaunchAnimationCancelled()
-        verify(controller, never()).onLaunchAnimationStart(anyBoolean())
-        verify(listener).onLaunchAnimationCancelled()
-        verify(listener, never()).onLaunchAnimationStart()
+        verify(controller).onTransitionAnimationCancelled()
+        verify(controller, never()).onTransitionAnimationStart(anyBoolean())
+        verify(listener).onTransitionAnimationCancelled()
+        verify(listener, never()).onTransitionAnimationStart()
         assertNull(runner.delegate)
     }
 
     @Test
     fun startsAnimationIfWindowIsOpening() {
-        val runner = activityLaunchAnimator.createRunner(controller)
+        val runner = activityTransitionAnimator.createRunner(controller)
         runner.onAnimationStart(0, arrayOf(fakeWindow()), emptyArray(), emptyArray(), iCallback)
         waitForIdleSync()
-        verify(listener).onLaunchAnimationStart()
-        verify(controller).onLaunchAnimationStart(anyBoolean())
+        verify(listener).onTransitionAnimationStart()
+        verify(controller).onTransitionAnimationStart(anyBoolean())
     }
 
     @Test
     fun creatingControllerFromNormalViewThrows() {
         assertThrows(IllegalArgumentException::class.java) {
-            ActivityLaunchAnimator.Controller.fromView(FrameLayout(mContext))
+            ActivityTransitionAnimator.Controller.fromView(FrameLayout(mContext))
         }
     }
 
     @Test
     fun disposeRunner_delegateDereferenced() {
-        val runner = activityLaunchAnimator.createRunner(controller)
+        val runner = activityTransitionAnimator.createRunner(controller)
         assertNotNull(runner.delegate)
         runner.dispose()
         waitForIdleSync()
@@ -237,13 +241,13 @@
 }
 
 /**
- * A simple implementation of [ActivityLaunchAnimator.Controller] which throws if it is called
+ * A simple implementation of [ActivityTransitionAnimator.Controller] which throws if it is called
  * outside of the main thread.
  */
-private class TestLaunchAnimatorController(override var launchContainer: ViewGroup) :
-    ActivityLaunchAnimator.Controller {
+private class TestTransitionAnimatorController(override var transitionContainer: ViewGroup) :
+    ActivityTransitionAnimator.Controller {
     override fun createAnimatorState() =
-        LaunchAnimator.State(
+        TransitionAnimator.State(
             top = 100,
             bottom = 200,
             left = 300,
@@ -262,23 +266,23 @@
         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()
     }
 
-    override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+    override fun onTransitionAnimationCancelled(newKeyguardOccludedState: 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/GhostedViewTransitionAnimatorControllerTest.kt
similarity index 70%
rename from packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
index d1ac0e8..b31fe21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt
@@ -29,22 +29,22 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
-class GhostedViewLaunchAnimatorControllerTest : SysuiTestCase() {
+class GhostedViewTransitionAnimatorControllerTest : 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))
+        val controller = GhostedViewTransitionAnimatorController(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
     fun creatingControllerFromNormalViewThrows() {
         assertThrows(IllegalArgumentException::class.java) {
-            GhostedViewLaunchAnimatorController(FrameLayout(mContext))
+            GhostedViewTransitionAnimatorController(FrameLayout(mContext))
         }
     }
-}
\ No newline at end of file
+}
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/dreams/touch/DreamOverlayTouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
index 21397d97..1c6f251 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
@@ -153,7 +153,7 @@
 
     @Test
     public void testReportedDisplayBounds() {
-        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+        final DreamTouchHandler touchHandler = createTouchHandler();
         final Environment environment = new Environment(Stream.of(touchHandler)
                 .collect(Collectors.toCollection(HashSet::new)));
 
@@ -175,7 +175,7 @@
 
     @Test
     public void testEntryTouchZone() {
-        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+        final DreamTouchHandler touchHandler = createTouchHandler();
         final Rect touchArea = new Rect(4, 4, 8 , 8);
 
         doAnswer(invocation -> {
@@ -203,10 +203,10 @@
 
     @Test
     public void testSessionCount() {
-        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+        final DreamTouchHandler touchHandler = createTouchHandler();
         final Rect touchArea = new Rect(4, 4, 8 , 8);
 
-        final DreamTouchHandler unzonedTouchHandler = Mockito.mock(DreamTouchHandler.class);
+        final DreamTouchHandler unzonedTouchHandler = createTouchHandler();
         doAnswer(invocation -> {
             final Region region = (Region) invocation.getArguments()[1];
             region.set(touchArea);
@@ -248,9 +248,28 @@
         }
     }
 
+
+    @Test
+    public void testNoActiveSessionWhenHandlerDisabled() {
+        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+        // disable the handler
+        when(touchHandler.isEnabled()).thenReturn(false);
+
+        final Environment environment = new Environment(Stream.of(touchHandler)
+                .collect(Collectors.toCollection(HashSet::new)));
+        final MotionEvent initialEvent = Mockito.mock(MotionEvent.class);
+        when(initialEvent.getX()).thenReturn(5.0f);
+        when(initialEvent.getY()).thenReturn(5.0f);
+        environment.publishInputEvent(initialEvent);
+
+        // Make sure there is no active session.
+        verify(touchHandler, never()).onSessionStart(any());
+        verify(touchHandler, never()).getTouchInitiationRegion(any(), any());
+    }
+
     @Test
     public void testInputEventPropagation() {
-        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+        final DreamTouchHandler touchHandler = createTouchHandler();
 
         final Environment environment = new Environment(Stream.of(touchHandler)
                 .collect(Collectors.toCollection(HashSet::new)));
@@ -270,7 +289,7 @@
 
     @Test
     public void testInputEventPropagationAfterRemoval() {
-        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+        final DreamTouchHandler touchHandler = createTouchHandler();
 
         final Environment environment = new Environment(Stream.of(touchHandler)
                 .collect(Collectors.toCollection(HashSet::new)));
@@ -294,7 +313,7 @@
 
     @Test
     public void testInputGesturePropagation() {
-        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+        final DreamTouchHandler touchHandler = createTouchHandler();
 
         final Environment environment = new Environment(Stream.of(touchHandler)
                 .collect(Collectors.toCollection(HashSet::new)));
@@ -313,7 +332,7 @@
 
     @Test
     public void testGestureConsumption() {
-        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+        final DreamTouchHandler touchHandler = createTouchHandler();
 
         final Environment environment = new Environment(Stream.of(touchHandler)
                 .collect(Collectors.toCollection(HashSet::new)));
@@ -336,8 +355,9 @@
 
     @Test
     public void testBroadcast() {
-        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
-        final DreamTouchHandler touchHandler2 = Mockito.mock(DreamTouchHandler.class);
+        final DreamTouchHandler touchHandler = createTouchHandler();
+        final DreamTouchHandler touchHandler2 = createTouchHandler();
+        when(touchHandler2.isEnabled()).thenReturn(true);
 
         final Environment environment = new Environment(Stream.of(touchHandler, touchHandler2)
                 .collect(Collectors.toCollection(HashSet::new)));
@@ -361,7 +381,7 @@
 
     @Test
     public void testPush() throws InterruptedException, ExecutionException {
-        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+        final DreamTouchHandler touchHandler = createTouchHandler();
 
         final Environment environment = new Environment(Stream.of(touchHandler)
                 .collect(Collectors.toCollection(HashSet::new)));
@@ -404,7 +424,8 @@
 
     @Test
     public void testPop() {
-        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+        final DreamTouchHandler touchHandler = createTouchHandler();
+
         final DreamTouchHandler.TouchSession.Callback callback =
                 Mockito.mock(DreamTouchHandler.TouchSession.Callback.class);
 
@@ -424,7 +445,7 @@
 
     @Test
     public void testPauseWithNoActiveSessions() {
-        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+        final DreamTouchHandler touchHandler = createTouchHandler();
 
         final Environment environment = new Environment(Stream.of(touchHandler)
                 .collect(Collectors.toCollection(HashSet::new)));
@@ -438,7 +459,7 @@
 
     @Test
     public void testDeferredPauseWithActiveSessions() {
-        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+        final DreamTouchHandler touchHandler = createTouchHandler();
 
         final Environment environment = new Environment(Stream.of(touchHandler)
                 .collect(Collectors.toCollection(HashSet::new)));
@@ -476,7 +497,7 @@
 
     @Test
     public void testDestroyWithActiveSessions() {
-        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+        final DreamTouchHandler touchHandler = createTouchHandler();
 
         final Environment environment = new Environment(Stream.of(touchHandler)
                 .collect(Collectors.toCollection(HashSet::new)));
@@ -509,9 +530,8 @@
 
     @Test
     public void testPilfering() {
-        final DreamTouchHandler touchHandler1 = Mockito.mock(DreamTouchHandler.class);
-        final DreamTouchHandler touchHandler2 = Mockito.mock(DreamTouchHandler.class);
-
+        final DreamTouchHandler touchHandler1 = createTouchHandler();
+        final DreamTouchHandler touchHandler2 = createTouchHandler();
         final Environment environment = new Environment(Stream.of(touchHandler1, touchHandler2)
                 .collect(Collectors.toCollection(HashSet::new)));
 
@@ -543,7 +563,8 @@
 
     @Test
     public void testOnRemovedCallbackOnStopMonitoring() {
-        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+        final DreamTouchHandler touchHandler = createTouchHandler();
+
         final DreamTouchHandler.TouchSession.Callback callback =
                 Mockito.mock(DreamTouchHandler.TouchSession.Callback.class);
 
@@ -607,4 +628,11 @@
             DreamTouchHandler handler) {
         return registerInputEventListener(captureSession(handler));
     }
+
+    private DreamTouchHandler createTouchHandler() {
+        final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+        // enable the handler by default
+        when(touchHandler.isEnabled()).thenReturn(true);
+        return touchHandler;
+    }
 }
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/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 14cae0b..2732047 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -86,7 +86,7 @@
 import com.android.keyguard.mediator.ScreenOnCoordinator;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.FalsingCollectorFake;
@@ -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;
@@ -187,7 +186,7 @@
     private @Mock ShadeController mShadeController;
     private NotificationShadeWindowController mNotificationShadeWindowController;
     private @Mock DreamOverlayStateController mDreamOverlayStateController;
-    private @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
+    private @Mock ActivityTransitionAnimator mActivityTransitionAnimator;
     private @Mock ScrimController mScrimController;
     private @Mock IActivityTaskManager mActivityTaskManagerService;
     private @Mock SysuiColorExtractor mColorExtractor;
@@ -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
@@ -764,7 +763,7 @@
 
     @Test
     public void testUpdateIsKeyguardAfterOccludeAnimationIsCancelled() {
-        mViewMediator.mOccludeAnimationController.onLaunchAnimationCancelled(
+        mViewMediator.mOccludeAnimationController.onTransitionAnimationCancelled(
                 null /* newKeyguardOccludedState */);
 
         // Since the updateIsKeyguard call is delayed during the animation, ensure it's called if
@@ -1232,7 +1231,7 @@
                 mWallpaperRepository,
                 () -> mShadeController,
                 () -> mNotificationShadeWindowController,
-                () -> mActivityLaunchAnimator,
+                () -> mActivityTransitionAnimator,
                 () -> mScrimController,
                 mActivityTaskManagerService,
                 mFeatureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index b4ae7e3..798c7f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -24,7 +24,7 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.ContentDescription
@@ -225,7 +225,7 @@
     @Mock private lateinit var lockPatternUtils: LockPatternUtils
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var activityStarter: ActivityStarter
-    @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+    @Mock private lateinit var animationController: ActivityTransitionAnimator.Controller
     @Mock private lateinit var expandable: Expandable
     @Mock private lateinit var launchAnimator: DialogLaunchAnimator
     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
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/media/OWNERS b/packages/SystemUI/tests/src/com/android/systemui/media/OWNERS
deleted file mode 100644
index 142862d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/media/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Haptics team also works on Ringtones (RingtonePlayer/NotificationPlayer)
-file:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index c6cfabc..32b6f38 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -72,7 +72,7 @@
 import com.android.settingslib.media.LocalMediaManager;
 import com.android.settingslib.media.MediaDevice;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
@@ -110,7 +110,7 @@
     @Mock
     private DialogLaunchAnimator mDialogLaunchAnimator;
     @Mock
-    private ActivityLaunchAnimator.Controller mActivityLaunchAnimatorController;
+    private ActivityTransitionAnimator.Controller mActivityTransitionAnimatorController;
     @Mock
     private NearbyMediaDevicesManager mNearbyMediaDevicesManager;
     // Mock
@@ -143,7 +143,7 @@
     @Mock
     private KeyguardManager mKeyguardManager;
     @Mock
-    private ActivityLaunchAnimator.Controller mController;
+    private ActivityTransitionAnimator.Controller mController;
     @Mock
     private PowerExemptionManager mPowerExemptionManager;
     @Mock
@@ -1122,7 +1122,7 @@
     @Test
     public void launchBluetoothPairing_isKeyguardLocked_dismissDialog() {
         when(mDialogLaunchAnimator.createActivityLaunchController(mDialogLaunchView)).thenReturn(
-                mActivityLaunchAnimatorController);
+                mActivityTransitionAnimatorController);
         when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
         mMediaOutputController.mCallback = this.mCallback;
 
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/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index ae47a7b..33f8f1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -38,7 +38,7 @@
 import android.view.View
 import com.android.internal.logging.MetricsLogger
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.animation.view.LaunchableFrameLayout
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.plugins.ActivityStarter
@@ -372,7 +372,7 @@
 
         verify(activityStarter)
             .startPendingIntentMaybeDismissingKeyguard(
-                eq(pi), nullable(), nullable<ActivityLaunchAnimator.Controller>())
+                eq(pi), nullable(), nullable<ActivityTransitionAnimator.Controller>())
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
index 23466cc..720c25a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
@@ -26,7 +26,7 @@
 import com.android.internal.logging.testing.FakeMetricsLogger
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.animation.Expandable
 import com.android.systemui.globalactions.GlobalActionsDialogLite
 import com.android.systemui.plugins.ActivityStarter
@@ -127,7 +127,7 @@
             .startActivity(
                 intentCaptor.capture(),
                 /* dismissShade= */ eq(true),
-                nullable() as? ActivityLaunchAnimator.Controller,
+                nullable() as? ActivityTransitionAnimator.Controller,
             )
         assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_SETTINGS)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
index 3bf59ca..874368b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -29,7 +29,7 @@
 import com.android.internal.logging.MetricsLogger
 import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlInfo
@@ -348,7 +348,7 @@
         verify(activityStarter).startActivity(
                 intentCaptor.capture(),
                 eq(true) /* dismissShade */,
-                nullable(ActivityLaunchAnimator.Controller::class.java),
+                nullable(ActivityTransitionAnimator.Controller::class.java),
                 eq(true) /* showOverLockscreenWhenLocked */)
         assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME)
     }
@@ -379,7 +379,7 @@
         verify(activityStarter).startActivity(
                 intentCaptor.capture(),
                 anyBoolean() /* dismissShade */,
-                nullable(ActivityLaunchAnimator.Controller::class.java),
+                nullable(ActivityTransitionAnimator.Controller::class.java),
                 eq(false) /* showOverLockscreenWhenLocked */)
         assertThat(intentCaptor.value.component?.className).isEqualTo(CONTROLS_ACTIVITY_CLASS_NAME)
     }
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 d757d71..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
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/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 9517f82..1dc5f7d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -103,8 +103,6 @@
             )
         testableLooper = TestableLooper.get(this)
 
-        communalRepository.setIsCommunalEnabled(true)
-
         whenever(keyguardTransitionInteractor.isFinishedInStateWhere(any()))
             .thenReturn(bouncerShowingFlow)
         whenever(shadeInteractor.isAnyFullyExpanded).thenReturn(shadeShowingFlow)
@@ -125,36 +123,6 @@
     }
 
     @Test
-    fun isEnabled_communalEnabled_returnsTrue() {
-        communalRepository.setIsCommunalEnabled(true)
-
-        assertThat(underTest.isEnabled()).isTrue()
-    }
-
-    @Test
-    fun isEnabled_communalDisabled_returnsFalse() {
-        communalRepository.setIsCommunalEnabled(false)
-
-        assertThat(underTest.isEnabled()).isFalse()
-    }
-
-    @Test
-    fun initView_notEnabled_throwsException() {
-        communalRepository.setIsCommunalEnabled(false)
-
-        underTest =
-            GlanceableHubContainerController(
-                communalInteractor,
-                communalViewModel,
-                keyguardTransitionInteractor,
-                shadeInteractor,
-                powerManager,
-            )
-
-        assertThrows(RuntimeException::class.java) { underTest.initView(context) }
-    }
-
-    @Test
     fun initView_calledTwice_throwsException() {
         underTest =
             GlanceableHubContainerController(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 22b05be..248ed24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -488,7 +488,8 @@
             return
         }
 
-        whenever(mGlanceableHubContainerController.isEnabled()).thenReturn(true)
+        whenever(mGlanceableHubContainerController.communalAvailable())
+            .thenReturn(MutableStateFlow(true))
 
         val mockCommunalView = mock(View::class.java)
         whenever(mGlanceableHubContainerController.initView(any<Context>()))
@@ -513,7 +514,6 @@
             return
         }
 
-        whenever(mGlanceableHubContainerController.isEnabled()).thenReturn(false)
         whenever(mGlanceableHubContainerController.communalAvailable())
             .thenReturn(MutableStateFlow(false))
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
index cd74410..6f16d65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
@@ -36,7 +36,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper
-class NotificationLaunchAnimatorControllerTest : SysuiTestCase() {
+class NotificationTransitionAnimatorControllerTest : SysuiTestCase() {
     @Mock lateinit var notificationListContainer: NotificationListContainer
     @Mock lateinit var headsUpManager: HeadsUpManager
     @Mock lateinit var jankMonitor: InteractionJankMonitor
@@ -44,7 +44,7 @@
 
     private lateinit var notificationTestHelper: NotificationTestHelper
     private lateinit var notification: ExpandableNotificationRow
-    private lateinit var controller: NotificationLaunchAnimatorController
+    private lateinit var controller: NotificationTransitionAnimatorController
     private val notificationLaunchAnimationInteractor =
         NotificationLaunchAnimationInteractor(NotificationLaunchAnimationRepository())
 
@@ -62,7 +62,7 @@
             NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
         notification = notificationTestHelper.createRow()
         controller =
-            NotificationLaunchAnimatorController(
+            NotificationTransitionAnimatorController(
                 notificationLaunchAnimationInteractor,
                 notificationListContainer,
                 headsUpManager,
@@ -97,7 +97,7 @@
     @Test
     fun testHunIsRemovedAndCallbackIsInvokedWhenAnimationIsCancelled() {
         flagNotificationAsHun()
-        controller.onLaunchAnimationCancelled()
+        controller.onTransitionAnimationCancelled()
 
         assertTrue(HeadsUpUtil.isClickedHeadsUpNotification(notification))
         assertFalse(notification.entry.isExpandAnimationRunning)
@@ -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/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
index a1daff1..b4dadaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
@@ -102,6 +102,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     fun testSetNotificationStats_clearableAlerting() {
         whenever(section.bucket).thenReturn(BUCKET_ALERTING)
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
@@ -109,6 +110,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     fun testSetNotificationStats_clearableSilent() {
         whenever(section.bucket).thenReturn(BUCKET_SILENT)
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
deleted file mode 100644
index 9b641f0..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
+++ /dev/null
@@ -1,153 +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.statusbar.notification.domain.interactor
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysUITestComponent
-import com.android.systemui.SysUITestModule
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.collectLastValue
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.runCurrent
-import com.android.systemui.runTest
-import com.android.systemui.statusbar.notification.collection.render.NotifStats
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
-import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
-import com.google.common.truth.Truth.assertThat
-import dagger.BindsInstance
-import dagger.Component
-import org.junit.Test
-
-@SmallTest
-class ActiveNotificationsInteractorTest : SysuiTestCase() {
-
-    @Component(modules = [SysUITestModule::class])
-    @SysUISingleton
-    interface TestComponent : SysUITestComponent<ActiveNotificationsInteractor> {
-        val activeNotificationListRepository: ActiveNotificationListRepository
-
-        @Component.Factory
-        interface Factory {
-            fun create(@BindsInstance test: SysuiTestCase): TestComponent
-        }
-    }
-
-    private val testComponent: TestComponent =
-        DaggerActiveNotificationsInteractorTest_TestComponent.factory().create(test = this)
-
-    @Test
-    fun testAllNotificationsCount() =
-        testComponent.runTest {
-            val count by collectLastValue(underTest.allNotificationsCount)
-
-            activeNotificationListRepository.setActiveNotifs(5)
-            runCurrent()
-
-            assertThat(count).isEqualTo(5)
-            assertThat(underTest.allNotificationsCountValue).isEqualTo(5)
-        }
-
-    @Test
-    fun testAreAnyNotificationsPresent_isTrue() =
-        testComponent.runTest {
-            val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent)
-
-            activeNotificationListRepository.setActiveNotifs(2)
-            runCurrent()
-
-            assertThat(areAnyNotificationsPresent).isTrue()
-            assertThat(underTest.areAnyNotificationsPresentValue).isTrue()
-        }
-
-    @Test
-    fun testAreAnyNotificationsPresent_isFalse() =
-        testComponent.runTest {
-            val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent)
-
-            activeNotificationListRepository.setActiveNotifs(0)
-            runCurrent()
-
-            assertThat(areAnyNotificationsPresent).isFalse()
-            assertThat(underTest.areAnyNotificationsPresentValue).isFalse()
-        }
-
-    @Test
-    fun testActiveNotificationRanks_sizeMatches() {
-        testComponent.runTest {
-            val activeNotificationRanks by collectLastValue(underTest.activeNotificationRanks)
-
-            activeNotificationListRepository.setActiveNotifs(5)
-            runCurrent()
-
-            assertThat(activeNotificationRanks!!.size).isEqualTo(5)
-        }
-    }
-
-    @Test
-    fun testHasClearableNotifications_whenHasClearableAlertingNotifs() =
-        testComponent.runTest {
-            val hasClearable by collectLastValue(underTest.hasClearableNotifications)
-
-            activeNotificationListRepository.notifStats.value =
-                NotifStats(
-                    numActiveNotifs = 2,
-                    hasNonClearableAlertingNotifs = false,
-                    hasClearableAlertingNotifs = true,
-                    hasNonClearableSilentNotifs = false,
-                    hasClearableSilentNotifs = false,
-                )
-            runCurrent()
-
-            assertThat(hasClearable).isTrue()
-        }
-
-    @Test
-    fun testHasClearableNotifications_whenHasClearableSilentNotifs() =
-        testComponent.runTest {
-            val hasClearable by collectLastValue(underTest.hasClearableNotifications)
-
-            activeNotificationListRepository.notifStats.value =
-                NotifStats(
-                    numActiveNotifs = 2,
-                    hasNonClearableAlertingNotifs = false,
-                    hasClearableAlertingNotifs = false,
-                    hasNonClearableSilentNotifs = false,
-                    hasClearableSilentNotifs = true,
-                )
-            runCurrent()
-
-            assertThat(hasClearable).isTrue()
-        }
-
-    @Test
-    fun testHasClearableNotifications_whenHasNoClearableNotifs() =
-        testComponent.runTest {
-            val hasClearable by collectLastValue(underTest.hasClearableNotifications)
-
-            activeNotificationListRepository.notifStats.value =
-                NotifStats(
-                    numActiveNotifs = 2,
-                    hasNonClearableAlertingNotifs = false,
-                    hasClearableAlertingNotifs = false,
-                    hasNonClearableSilentNotifs = false,
-                    hasClearableSilentNotifs = false,
-                )
-            runCurrent()
-
-            assertThat(hasClearable).isFalse()
-        }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
index b922ab3..3811f04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
@@ -472,6 +472,23 @@
     }
 
     @Test
+    public void publicMode_nullChannel_allowed() {
+        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
+        // GIVEN an 'unfiltered-keyguard-showing' state
+        setupUnfilteredState(mEntry);
+
+        // WHEN the notification's user is in public mode and settings are configured to disallow
+        // notifications in public mode
+        when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true);
+        mEntry.setRanking(new RankingBuilder()
+                .setKey(mEntry.getKey())
+                .setVisibilityOverride(VISIBILITY_SECRET).build());
+
+        // THEN allow the entry
+        assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+    }
+
+    @Test
     public void publicMode_notifDisallowed() {
         mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
         NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
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..f326cea 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));
     }
 
@@ -611,7 +614,9 @@
             selected[0] = selectedRows;
         });
 
-        mStackScroller.clearNotifications(ROWS_ALL, true);
+        mStackScroller.clearNotifications(ROWS_ALL,
+                /* closeShade = */ true,
+                /* hideSilentSection = */ true);
         assertEquals(1, numCalls[0]);
         assertEquals(ROWS_ALL, selected[0]);
     }
@@ -625,7 +630,9 @@
             selected[0] = selectedRows;
         });
 
-        mStackScroller.clearNotifications(NotificationStackScrollLayout.ROWS_GENTLE, false);
+        mStackScroller.clearNotifications(NotificationStackScrollLayout.ROWS_GENTLE,
+                /* closeShade = */ false,
+                /* hideSilentSection = */ true);
         assertEquals(1, numCalls[0]);
         assertEquals(ROWS_GENTLE, selected[0]);
     }
@@ -637,7 +644,9 @@
         doReturn(true).when(mStackScroller).isVisible(row);
         mStackScroller.addContainerView(row);
 
-        mStackScroller.clearNotifications(ROWS_ALL, false);
+        mStackScroller.clearNotifications(ROWS_ALL,
+                /* closeShade = */ false,
+                /* hideSilentSection = */ false);
 
         assertClearAllInProgress(true);
         verify(mNotificationRoundnessManager).setClearAllInProgress(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
index 5a57035..dfbe1ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt
@@ -18,8 +18,10 @@
 
 import android.platform.test.annotations.EnableFlags
 import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent
@@ -28,6 +30,7 @@
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -38,9 +41,12 @@
 import org.mockito.Mockito.verify
 
 private const val VIEW_HEIGHT = 100
+private const val FULL_SHADE_APPEAR_TRANSLATION = 300
+private const val HEADS_UP_ABOVE_SCREEN = 80
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
+@RunWithLooper
 class StackStateAnimatorTest : SysuiTestCase() {
 
     private lateinit var stackStateAnimator: StackStateAnimator
@@ -51,9 +57,15 @@
     private val runnableCaptor: ArgumentCaptor<Runnable> = argumentCaptor()
     @Before
     fun setUp() {
+        overrideResource(
+            R.dimen.go_to_full_shade_appearing_translation,
+            FULL_SHADE_APPEAR_TRANSLATION
+        )
+        overrideResource(R.dimen.heads_up_appear_y_above_screen, HEADS_UP_ABOVE_SCREEN)
+
         whenever(stackScroller.context).thenReturn(context)
         whenever(view.viewState).thenReturn(viewState)
-        stackStateAnimator = StackStateAnimator(stackScroller)
+        stackStateAnimator = StackStateAnimator(mContext, stackScroller)
     }
 
     @Test
@@ -122,4 +134,22 @@
         verify(view, description("should be called at the end of the animation"))
             .removeFromTransientContainer()
     }
+
+    @Test
+    fun initView_updatesResources() {
+        // Given: the resource values are initialized in the SSA
+        assertThat(stackStateAnimator.mGoToFullShadeAppearingTranslation)
+            .isEqualTo(FULL_SHADE_APPEAR_TRANSLATION)
+        assertThat(stackStateAnimator.mHeadsUpAppearStartAboveScreen)
+            .isEqualTo(HEADS_UP_ABOVE_SCREEN)
+
+        // When: initView is called after the resources have changed
+        overrideResource(R.dimen.go_to_full_shade_appearing_translation, 200)
+        overrideResource(R.dimen.heads_up_appear_y_above_screen, 100)
+        stackStateAnimator.initView(mContext)
+
+        // Then: the resource values are updated
+        assertThat(stackStateAnimator.mGoToFullShadeAppearingTranslation).isEqualTo(200)
+        assertThat(stackStateAnimator.mHeadsUpAppearStartAboveScreen).isEqualTo(100)
+    }
 }
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..0a18eb6 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,23 @@
 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.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.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
 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 +62,15 @@
             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 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
 
@@ -77,11 +85,7 @@
             val important by collectLastValue(underTest.isImportantForAccessibility)
 
             // WHEN on lockscreen
-            fakeKeyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.GONE,
-                to = KeyguardState.LOCKSCREEN,
-                testScope,
-            )
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
             // AND has no notifs
             activeNotificationListRepository.setActiveNotifs(count = 0)
             testScope.runCurrent()
@@ -96,11 +100,7 @@
             val important by collectLastValue(underTest.isImportantForAccessibility)
 
             // WHEN on lockscreen
-            fakeKeyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.GONE,
-                to = KeyguardState.LOCKSCREEN,
-                testScope,
-            )
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
             // AND has notifs
             activeNotificationListRepository.setActiveNotifs(count = 2)
             runCurrent()
@@ -115,11 +115,7 @@
             val important by collectLastValue(underTest.isImportantForAccessibility)
 
             // WHEN not on lockscreen
-            fakeKeyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.LOCKSCREEN,
-                to = KeyguardState.GONE,
-                testScope,
-            )
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
             // AND has no notifs
             activeNotificationListRepository.setActiveNotifs(count = 0)
             runCurrent()
@@ -137,7 +133,7 @@
             activeNotificationListRepository.setActiveNotifs(count = 0)
             runCurrent()
 
-            // THEN should show
+            // THEN empty shade is visible
             assertThat(shouldShow).isTrue()
         }
 
@@ -150,7 +146,7 @@
             activeNotificationListRepository.setActiveNotifs(count = 2)
             runCurrent()
 
-            // THEN should not show
+            // THEN empty shade is not visible
             assertThat(shouldShow).isFalse()
         }
 
@@ -165,7 +161,7 @@
             fakeShadeRepository.legacyQsFullscreen.value = true
             runCurrent()
 
-            // THEN should not show
+            // THEN empty shade is not visible
             assertThat(shouldShow).isFalse()
         }
 
@@ -183,48 +179,54 @@
             fakeConfigurationController.notifyConfigurationChanged()
             runCurrent()
 
-            // THEN should show
+            // THEN empty shade is visible
             assertThat(shouldShow).isTrue()
         }
 
     @Test
-    fun testShouldShowEmptyShadeView_falseWhenTransitioningToAOD() =
+    fun testShouldShowEmptyShadeView_trueWhenLockedShade() =
         testScope.runTest {
             val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
 
             // WHEN has no notifs
             activeNotificationListRepository.setActiveNotifs(count = 0)
-            // AND transitioning to AOD
-            fakeKeyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    transitionState = TransitionState.STARTED,
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.AOD,
-                    value = 0f,
-                )
-            )
+            // AND shade is open
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
             runCurrent()
 
-            // THEN should not show
+            // THEN empty shade is visible
+            assertThat(shouldShow).isTrue()
+        }
+
+    @Test
+    fun testShouldShowEmptyShadeView_falseWhenKeyguard() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
+
+            // WHEN has no notifs
+            activeNotificationListRepository.setActiveNotifs(count = 0)
+            // AND shade is open
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+            runCurrent()
+
+            // THEN empty shade is not visible
             assertThat(shouldShow).isFalse()
         }
 
     @Test
-    fun testShouldShowEmptyShadeView_falseWhenBouncerShowing() =
+    fun testShouldShowEmptyShadeView_falseWhenStartingToSleep() =
         testScope.runTest {
             val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView)
 
             // WHEN has no notifs
             activeNotificationListRepository.setActiveNotifs(count = 0)
-            // AND is on bouncer
-            fakeKeyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.LOCKSCREEN,
-                to = KeyguardState.PRIMARY_BOUNCER,
-                testScope,
-            )
+            // AND shade is open
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            // AND device is starting to go to sleep
+            fakePowerRepository.updateWakefulness(WakefulnessState.STARTING_TO_SLEEP)
             runCurrent()
 
-            // THEN should not show
+            // THEN empty shade is not visible
             assertThat(shouldShow).isFalse()
         }
 
@@ -273,4 +275,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/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index b048949..15421ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -83,7 +83,7 @@
 import com.android.systemui.InitController;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.back.domain.interactor.BackActionInteractor;
 import com.android.systemui.biometrics.AuthRippleController;
@@ -250,7 +250,8 @@
     @Mock private StatusBarStateControllerImpl mStatusBarStateController;
     @Mock private BatteryController mBatteryController;
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
-    @Mock private NotificationLaunchAnimatorControllerProvider mNotifLaunchAnimControllerProvider;
+    @Mock private NotificationLaunchAnimatorControllerProvider
+            mNotifTransitionAnimControllerProvider;
     @Mock private StatusBarNotificationPresenter mNotificationPresenter;
     @Mock private NotificationActivityStarter mNotificationActivityStarter;
     @Mock private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
@@ -307,7 +308,7 @@
     @Mock private StartingSurface mStartingSurface;
     @Mock private OperatorNameViewController mOperatorNameViewController;
     @Mock private OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
-    @Mock private ActivityLaunchAnimator mActivityLaunchAnimator;
+    @Mock private ActivityTransitionAnimator mActivityTransitionAnimator;
     @Mock private DeviceStateManager mDeviceStateManager;
     @Mock private WiredChargingRippleController mWiredChargingRippleController;
     @Mock private Lazy<CameraLauncher> mCameraLauncherLazy;
@@ -504,7 +505,7 @@
                 mStackScrollerController,
                 (Lazy<NotificationPresenter>) () -> mNotificationPresenter,
                 (Lazy<NotificationActivityStarter>) () -> mNotificationActivityStarter,
-                mNotifLaunchAnimControllerProvider,
+                mNotifTransitionAnimControllerProvider,
                 mDozeParameters,
                 mScrimController,
                 mBiometricUnlockControllerLazy,
@@ -543,7 +544,7 @@
                 new MessageRouterImpl(mMainExecutor),
                 mWallpaperManager,
                 Optional.of(mStartingSurface),
-                mActivityLaunchAnimator,
+                mActivityTransitionAnimator,
                 mDeviceStateManager,
                 mWiredChargingRippleController,
                 mDreamManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 597e2e3..41514ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -63,7 +63,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.ActivityIntentHelper;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.flags.FakeFeatureFlags;
@@ -159,7 +159,7 @@
     @Mock
     private StatusBarNotificationActivityStarter mNotificationActivityStarter;
     @Mock
-    private ActivityLaunchAnimator mActivityLaunchAnimator;
+    private ActivityTransitionAnimator mActivityTransitionAnimator;
     @Mock
     private InteractionJankMonitor mJankMonitor;
     private FakePowerRepository mPowerRepository;
@@ -255,7 +255,7 @@
                         mock(NotificationPresenter.class),
                         mock(ShadeViewController.class),
                         mock(NotificationShadeWindowController.class),
-                        mActivityLaunchAnimator,
+                        mActivityTransitionAnimator,
                         new ShadeAnimationInteractorLegacyImpl(
                                 new ShadeAnimationRepository(), new FakeShadeRepository()),
                         notificationAnimationProvider,
@@ -306,7 +306,7 @@
         // Then
         verify(mShadeController, atLeastOnce()).collapseShade();
 
-        verify(mActivityLaunchAnimator).startPendingIntentWithAnimation(any(),
+        verify(mActivityTransitionAnimator).startPendingIntentWithAnimation(any(),
                 eq(false) /* animate */, any(), any());
 
         verify(mAssistManager).hideAssist();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
index 614261d..203096a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController.Companion.AnimationState.EASE_OUT
 import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController.Companion.AnimationState.MAIN
 import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController.Companion.AnimationState.NOT_PLAYING
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -47,7 +48,7 @@
         assertThat(turbulenceNoiseController.state).isEqualTo(NOT_PLAYING)
 
         fakeExecutor.execute {
-            turbulenceNoiseController.play(config)
+            turbulenceNoiseController.play(SIMPLEX_NOISE, config)
 
             assertThat(turbulenceNoiseController.state).isEqualTo(EASE_IN)
 
@@ -75,7 +76,7 @@
 
         fakeExecutor.execute {
             // Request another animation
-            turbulenceNoiseController.play(config)
+            turbulenceNoiseController.play(SIMPLEX_NOISE, config)
 
             assertThat(turbulenceNoiseController.state).isEqualTo(MAIN)
         }
@@ -89,7 +90,7 @@
             TurbulenceNoiseController(turbulenceNoiseView).also { it.state = MAIN }
 
         fakeExecutor.execute {
-            turbulenceNoiseController.play(config)
+            turbulenceNoiseController.play(SIMPLEX_NOISE, config)
 
             fakeSystemClock.advanceTime(config.maxDuration.toLong() / 2)
 
@@ -107,7 +108,7 @@
             TurbulenceNoiseController(turbulenceNoiseView).also { it.state = EASE_IN }
 
         fakeExecutor.execute {
-            turbulenceNoiseController.play(config)
+            turbulenceNoiseController.play(SIMPLEX_NOISE, config)
 
             fakeSystemClock.advanceTime(config.maxDuration.toLong() / 2)
 
@@ -128,7 +129,7 @@
         assertThat(turbulenceNoiseView.noiseConfig).isNull()
 
         fakeExecutor.execute {
-            turbulenceNoiseController.play(config)
+            turbulenceNoiseController.play(SIMPLEX_NOISE, config)
 
             assertThat(turbulenceNoiseController.state).isEqualTo(EASE_IN)
             assertThat(turbulenceNoiseView.visibility).isEqualTo(VISIBLE)
@@ -156,7 +157,7 @@
         val turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
 
         fakeExecutor.execute {
-            turbulenceNoiseController.play(config)
+            turbulenceNoiseController.play(SIMPLEX_NOISE, config)
 
             turbulenceNoiseController.updateNoiseColor(expectedColor)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt
index 71bd511..549280a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt
@@ -18,6 +18,8 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE_FRACTAL
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -28,12 +30,12 @@
     private lateinit var turbulenceNoiseShader: TurbulenceNoiseShader
 
     @Test
-    fun compliesSimplexNoise() {
-        turbulenceNoiseShader = TurbulenceNoiseShader()
+    fun compilesSimplexNoise() {
+        turbulenceNoiseShader = TurbulenceNoiseShader(baseType = SIMPLEX_NOISE)
     }
 
     @Test
-    fun compliesFractalNoise() {
-        turbulenceNoiseShader = TurbulenceNoiseShader(useFractal = true)
+    fun compilesFractalNoise() {
+        turbulenceNoiseShader = TurbulenceNoiseShader(baseType = SIMPLEX_NOISE_FRACTAL)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt
index ce7f2f4..953071c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt
@@ -15,9 +15,12 @@
  */
 package com.android.systemui.surfaceeffects.turbulencenoise
 
+import android.graphics.Color
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE_FRACTAL
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -35,7 +38,8 @@
     @Test
     fun play_playsAnimation() {
         val config = TurbulenceNoiseAnimationConfig()
-        val turbulenceNoiseView = TurbulenceNoiseView(context, null).also { it.applyConfig(config) }
+        val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+        turbulenceNoiseView.initShader(SIMPLEX_NOISE, config)
         var onAnimationEndCalled = false
 
         fakeExecutor.execute {
@@ -50,7 +54,8 @@
     @Test
     fun playEaseIn_playsEaseInAnimation() {
         val config = TurbulenceNoiseAnimationConfig()
-        val turbulenceNoiseView = TurbulenceNoiseView(context, null).also { it.applyConfig(config) }
+        val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+        turbulenceNoiseView.initShader(SIMPLEX_NOISE, config)
         var onAnimationEndCalled = false
 
         fakeExecutor.execute {
@@ -65,7 +70,8 @@
     @Test
     fun playEaseOut_playsEaseOutAnimation() {
         val config = TurbulenceNoiseAnimationConfig()
-        val turbulenceNoiseView = TurbulenceNoiseView(context, null).also { it.applyConfig(config) }
+        val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+        turbulenceNoiseView.initShader(SIMPLEX_NOISE, config)
         var onAnimationEndCalled = false
 
         fakeExecutor.execute {
@@ -80,7 +86,8 @@
     @Test
     fun finish_animationPlaying_finishesAnimation() {
         val config = TurbulenceNoiseAnimationConfig()
-        val turbulenceNoiseView = TurbulenceNoiseView(context, null).also { it.applyConfig(config) }
+        val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+        turbulenceNoiseView.initShader(SIMPLEX_NOISE, config)
         var onAnimationEndCalled = false
 
         fakeExecutor.execute {
@@ -94,4 +101,51 @@
             assertThat(turbulenceNoiseView.currentAnimator).isNull()
         }
     }
+
+    @Test
+    fun initShader_createsShaderCorrectly() {
+        val config = TurbulenceNoiseAnimationConfig()
+        val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+
+        // To begin with, the shader is not initialized yet.
+        assertThat(turbulenceNoiseView.turbulenceNoiseShader).isNull()
+
+        turbulenceNoiseView.initShader(baseType = SIMPLEX_NOISE, config)
+
+        assertThat(turbulenceNoiseView.turbulenceNoiseShader).isNotNull()
+        assertThat(turbulenceNoiseView.turbulenceNoiseShader!!.baseType).isEqualTo(SIMPLEX_NOISE)
+    }
+
+    @Test
+    fun initShader_changesConfig_doesNotCreateNewShader() {
+        val config = TurbulenceNoiseAnimationConfig(color = Color.RED)
+        val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+        turbulenceNoiseView.initShader(baseType = SIMPLEX_NOISE, config)
+
+        val shader = turbulenceNoiseView.turbulenceNoiseShader
+        assertThat(shader).isNotNull()
+
+        val newConfig = TurbulenceNoiseAnimationConfig(color = Color.GREEN)
+        turbulenceNoiseView.initShader(baseType = SIMPLEX_NOISE, newConfig)
+
+        val newShader = turbulenceNoiseView.turbulenceNoiseShader
+        assertThat(newShader).isNotNull()
+        assertThat(newShader).isEqualTo(shader)
+    }
+
+    @Test
+    fun initShader_changesBaseType_createsNewShader() {
+        val config = TurbulenceNoiseAnimationConfig()
+        val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+        turbulenceNoiseView.initShader(baseType = SIMPLEX_NOISE, config)
+
+        val shader = turbulenceNoiseView.turbulenceNoiseShader
+        assertThat(shader).isNotNull()
+
+        turbulenceNoiseView.initShader(baseType = SIMPLEX_NOISE_FRACTAL, config)
+
+        val newShader = turbulenceNoiseView.turbulenceNoiseShader
+        assertThat(newShader).isNotNull()
+        assertThat(newShader).isNotEqualTo(shader)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
index 83439f0..8f4cbaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
@@ -104,9 +104,9 @@
     @Before
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mManager = new ThemeOverlayApplier(mOverlayManager,
-                MoreExecutors.directExecutor(),
-                LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager) {
+        mManager = new ThemeOverlayApplier(mOverlayManager, MoreExecutors.directExecutor(),
+                LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager,
+                MoreExecutors.directExecutor()) {
             @Override
             protected OverlayManagerTransaction.Builder getTransactionBuilder() {
                 return mTransactionBuilder;
@@ -179,7 +179,7 @@
     @Test
     public void allCategoriesSpecified_allEnabledExclusively() {
         mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(),
-                TEST_USER_HANDLES);
+                TEST_USER_HANDLES, null);
         verify(mOverlayManager).commit(any());
 
         for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) {
@@ -191,7 +191,7 @@
     @Test
     public void allCategoriesSpecified_sysuiCategoriesAlsoAppliedToSysuiUser() {
         mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(),
-                TEST_USER_HANDLES);
+                TEST_USER_HANDLES, null);
 
         for (Map.Entry<String, OverlayIdentifier> entry : ALL_CATEGORIES_MAP.entrySet()) {
             if (SYSTEM_USER_CATEGORIES.contains(entry.getKey())) {
@@ -208,7 +208,7 @@
     public void allCategoriesSpecified_enabledForAllUserHandles() {
         Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES);
         mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(),
-                userHandles);
+                userHandles, null);
 
         for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) {
             verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
@@ -225,7 +225,7 @@
 
         Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES);
         mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(),
-                userHandles);
+                userHandles, null);
 
         for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) {
             verify(mTransactionBuilder, never()).setEnabled(eq(overlayPackage), eq(true),
@@ -239,7 +239,7 @@
                 mock(FabricatedOverlay.class)
         };
         mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, pendingCreation,
-                TEST_USER.getIdentifier(), TEST_USER_HANDLES);
+                TEST_USER.getIdentifier(), TEST_USER_HANDLES, null);
 
         for (FabricatedOverlay overlay : pendingCreation) {
             verify(mTransactionBuilder).registerFabricatedOverlay(eq(overlay));
@@ -253,7 +253,7 @@
         categoryToPackage.remove(OVERLAY_CATEGORY_ICON_ANDROID);
 
         mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER.getIdentifier(),
-                TEST_USER_HANDLES);
+                TEST_USER_HANDLES, null);
 
         for (OverlayIdentifier overlayPackage : categoryToPackage.values()) {
             verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true),
@@ -270,7 +270,7 @@
     @Test
     public void zeroCategoriesSpecified_allDisabled() {
         mManager.applyCurrentUserOverlays(Maps.newArrayMap(), null, TEST_USER.getIdentifier(),
-                TEST_USER_HANDLES);
+                TEST_USER_HANDLES, null);
 
         for (String category : THEME_CATEGORIES) {
             verify(mTransactionBuilder).setEnabled(
@@ -285,7 +285,7 @@
         categoryToPackage.put("blah.category", new OverlayIdentifier("com.example.blah.category"));
 
         mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER.getIdentifier(),
-                TEST_USER_HANDLES);
+                TEST_USER_HANDLES, null);
 
         verify(mTransactionBuilder, never()).setEnabled(
                 eq(new OverlayIdentifier("com.example.blah.category")), eq(false),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index b58a41c..c02583a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -37,6 +37,7 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.app.UiModeManager;
 import android.app.WallpaperColors;
 import android.app.WallpaperManager;
@@ -129,6 +130,8 @@
     private WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock
     private UiModeManager mUiModeManager;
+    @Mock
+    private ActivityManager mActivityManager;
     @Captor
     private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiver;
     @Captor
@@ -164,7 +167,7 @@
                 mBroadcastDispatcher, mBgHandler, mMainExecutor, mBgExecutor, mThemeOverlayApplier,
                 mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
                 mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
-                mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager) {
+                mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager, mActivityManager) {
             @VisibleForTesting
             protected boolean isNightMode() {
                 return false;
@@ -224,7 +227,7 @@
                 ArgumentCaptor.forClass(Map.class);
 
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any());
 
         // Assert that we received the colors that we were expecting
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
@@ -249,7 +252,7 @@
         mBroadcastReceiver.getValue().onReceive(null, intent);
         mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK),
                 null, null), WallpaperManager.FLAG_SYSTEM, USER_SYSTEM);
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -263,7 +266,7 @@
                 ArgumentCaptor.forClass(Map.class);
 
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any());
 
         // Should not change theme after changing wallpapers, if intent doesn't have
         // WallpaperManager.EXTRA_FROM_FOREGROUND_APP set to true.
@@ -272,7 +275,7 @@
         mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK),
                 null, null), WallpaperManager.FLAG_SYSTEM, USER_SYSTEM);
         verify(mThemeOverlayApplier, never())
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -294,7 +297,7 @@
                 ArgumentCaptor.forClass(Map.class);
 
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any());
 
         // Assert that we received the colors that we were expecting
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
@@ -333,7 +336,7 @@
                 .isFalse();
 
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -367,8 +370,7 @@
         assertThat(updatedSetting.getValue().contains(
                 "android.theme.customization.color_both\":\"0")).isTrue();
 
-        verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -423,7 +425,7 @@
         assertThat(updatedSetting.getValue().contains(
                 "android.theme.customization.color_both\":\"1")).isTrue();
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -492,7 +494,7 @@
                 "android.theme.customization.color_both\":\"1")).isTrue();
 
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -523,7 +525,7 @@
         assertThat(updatedSetting.getValue().contains("android.theme.customization.color_index"))
                 .isFalse();
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -554,7 +556,7 @@
         assertThat(updatedSetting.getValue().contains("android.theme.customization.color_index"))
                 .isFalse();
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -587,7 +589,7 @@
                 anyInt());
 
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -620,7 +622,7 @@
                 eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture());
 
         // Apply overlay by existing theme from secure setting
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -653,7 +655,7 @@
 
 
         verify(mThemeOverlayApplier, never())
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -675,7 +677,7 @@
         ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
                 ArgumentCaptor.forClass(Map.class);
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any());
 
         // Assert that we received secondary user colors
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
@@ -689,7 +691,7 @@
         mBroadcastReceiver.getValue().onReceive(null,
                 new Intent(Intent.ACTION_PROFILE_ADDED)
                         .putExtra(Intent.EXTRA_USER, MANAGED_USER_HANDLE));
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -700,7 +702,7 @@
                 new Intent(Intent.ACTION_PROFILE_ADDED)
                         .putExtra(Intent.EXTRA_USER, MANAGED_USER_HANDLE));
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -711,7 +713,7 @@
                 new Intent(Intent.ACTION_PROFILE_ADDED)
                         .putExtra(Intent.EXTRA_USER, MANAGED_USER_HANDLE));
         verify(mThemeOverlayApplier, never())
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -723,7 +725,7 @@
                 (new Intent(Intent.ACTION_PROFILE_ADDED))
                         .putExtra(Intent.EXTRA_USER, PRIVATE_USER_HANDLE));
         verify(mThemeOverlayApplier, never())
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
 
@@ -737,7 +739,7 @@
         mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
                 USER_SYSTEM);
 
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
 
         // Regression test: null events should not reset the internal state and allow colors to be
         // applied again.
@@ -747,11 +749,11 @@
         mBroadcastReceiver.getValue().onReceive(null, intent);
         mColorsListener.getValue().onColorsChanged(null, WallpaperManager.FLAG_SYSTEM, USER_SYSTEM);
         verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(),
-                any());
+                any(), any());
         mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.GREEN),
                 null, null), WallpaperManager.FLAG_SYSTEM, USER_SYSTEM);
         verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(),
-                any());
+                any(), any());
     }
 
     @Test
@@ -770,7 +772,7 @@
                 mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier,
                 mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
                 mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
-                mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager) {
+                mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager, mActivityManager) {
             @VisibleForTesting
             protected boolean isNightMode() {
                 return false;
@@ -791,7 +793,7 @@
         verify(mDeviceProvisionedController).addCallback(mDeviceProvisionedListener.capture());
 
         // Colors were applied during controller initialization.
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
         clearInvocations(mThemeOverlayApplier);
     }
 
@@ -810,7 +812,7 @@
                 mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier,
                 mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
                 mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
-                mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager) {
+                mJavaAdapter, mKeyguardTransitionInteractor, mUiModeManager, mActivityManager) {
             @VisibleForTesting
             protected boolean isNightMode() {
                 return false;
@@ -831,7 +833,7 @@
         verify(mDeviceProvisionedController).addCallback(mDeviceProvisionedListener.capture());
 
         // Colors were applied during controller initialization.
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
         clearInvocations(mThemeOverlayApplier);
 
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
@@ -853,12 +855,12 @@
 
         // Defers event because we already have initial colors.
         verify(mThemeOverlayApplier, never())
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
 
         // Then event happens after setup phase is over.
         when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
         mDeviceProvisionedListener.getValue().onUserSetupChanged();
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -881,11 +883,11 @@
         mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
                 USER_SYSTEM);
         verify(mThemeOverlayApplier, never())
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
 
         mWakefulnessLifecycle.dispatchFinishedGoingToSleep();
         verify(mThemeOverlayApplier, never())
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -907,10 +909,10 @@
         mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
                 USER_SYSTEM);
         verify(mThemeOverlayApplier, never())
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
 
         mWakefulnessLifecycleObserver.getValue().onFinishedGoingToSleep();
-        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
     }
 
     @Test
@@ -930,7 +932,7 @@
                 ArgumentCaptor.forClass(Map.class);
 
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any(), any());
 
         // Assert that we received the colors that we were expecting
         assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
@@ -949,19 +951,19 @@
         mColorsListener.getValue().onColorsChanged(startingColors, WallpaperManager.FLAG_SYSTEM,
                 USER_SYSTEM);
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
         clearInvocations(mThemeOverlayApplier);
 
         // Set to the same colors.
         mColorsListener.getValue().onColorsChanged(sameColors, WallpaperManager.FLAG_SYSTEM,
                 USER_SYSTEM);
         verify(mThemeOverlayApplier, never())
-                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
 
         // Verify that no change resulted.
         mWakefulnessLifecycleObserver.getValue().onFinishedGoingToSleep();
         verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(),
-                any());
+                any(), any());
     }
 
     @Test
@@ -975,7 +977,7 @@
                 ArgumentCaptor.forClass(FabricatedOverlay[].class);
 
         verify(mThemeOverlayApplier)
-                .applyCurrentUserOverlays(any(), themeOverlays.capture(), anyInt(), any());
+                .applyCurrentUserOverlays(any(), themeOverlays.capture(), anyInt(), any(), any());
 
         FabricatedOverlay[] overlays = themeOverlays.getValue();
         FabricatedOverlay accents = overlays[0];
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/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/wallet/controller/QuickAccessWalletControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
index 8263174..fccb936 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java
@@ -37,10 +37,10 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.res.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.res.R;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -68,7 +68,7 @@
     @Mock
     private ActivityStarter mActivityStarter;
     @Mock
-    private ActivityLaunchAnimator.Controller mAnimationController;
+    private ActivityTransitionAnimator.Controller mAnimationController;
     @Captor
     private ArgumentCaptor<GetWalletCardsRequest> mRequestCaptor;
     @Captor
@@ -219,7 +219,7 @@
     public void getQuickAccessUiIntent_hasCards_noPendingIntent_startsWalletActivity() {
         mController.startQuickAccessUiIntent(mActivityStarter, mAnimationController, true);
         verify(mActivityStarter).startActivity(mIntentCaptor.capture(), eq(true),
-                any(ActivityLaunchAnimator.Controller.class), eq(true));
+                any(ActivityTransitionAnimator.Controller.class), eq(true));
         Intent intent = mIntentCaptor.getValue();
         assertEquals(intent.getAction(), Intent.ACTION_VIEW);
         assertEquals(
@@ -231,7 +231,7 @@
     public void getQuickAccessUiIntent_noCards_noPendingIntent_startsWalletActivity() {
         mController.startQuickAccessUiIntent(mActivityStarter, mAnimationController, false);
         verify(mActivityStarter).postStartActivityDismissingKeyguard(mIntentCaptor.capture(), eq(0),
-                any(ActivityLaunchAnimator.Controller.class));
+                any(ActivityTransitionAnimator.Controller.class));
         Intent intent = mIntentCaptor.getValue();
         assertEquals(intent.getAction(), Intent.ACTION_VIEW);
         assertEquals(
@@ -254,7 +254,7 @@
         }).when(mQuickAccessWalletClient).getWalletPendingIntent(any(), any());
         mController.startQuickAccessUiIntent(mActivityStarter, mAnimationController, true);
         verify(mActivityStarter).postStartActivityDismissingKeyguard(mPendingIntentCaptor.capture(),
-                any(ActivityLaunchAnimator.Controller.class));
+                any(ActivityTransitionAnimator.Controller.class));
         PendingIntent pendingIntent = mPendingIntentCaptor.getValue();
         Intent intent = pendingIntent.getIntent();
         assertEquals(intent.getAction(), Intent.ACTION_VIEW);
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 d45a9a9..8d933dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -176,9 +176,11 @@
 import com.android.wm.shell.bubbles.BubbleDataRepository;
 import com.android.wm.shell.bubbles.BubbleEducationController;
 import com.android.wm.shell.bubbles.BubbleEntry;
+import com.android.wm.shell.bubbles.BubbleExpandedViewManager;
 import com.android.wm.shell.bubbles.BubbleLogger;
 import com.android.wm.shell.bubbles.BubbleOverflow;
 import com.android.wm.shell.bubbles.BubbleStackView;
+import com.android.wm.shell.bubbles.BubbleTaskView;
 import com.android.wm.shell.bubbles.BubbleViewInfoTask;
 import com.android.wm.shell.bubbles.BubbleViewProvider;
 import com.android.wm.shell.bubbles.Bubbles;
@@ -195,6 +197,7 @@
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.taskview.TaskView;
 import com.android.wm.shell.taskview.TaskViewTransitions;
 import com.android.wm.shell.transition.Transitions;
 
@@ -214,6 +217,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Optional;
+import java.util.concurrent.Executor;
 
 import kotlinx.coroutines.test.TestScope;
 
@@ -1416,7 +1420,9 @@
                 .thenReturn(userContext);
 
         BubbleViewInfoTask.BubbleViewInfo info = BubbleViewInfoTask.BubbleViewInfo.populate(context,
-                mBubbleController,
+                BubbleExpandedViewManager.fromBubbleController(mBubbleController),
+                () -> new BubbleTaskView(mock(TaskView.class), mock(Executor.class)),
+                mPositioner,
                 mBubbleController.getStackView(),
                 new BubbleIconFactory(mContext,
                         mContext.getResources().getDimensionPixelSize(com.android.wm.shell.R.dimen.bubble_size),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
similarity index 88%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
index 128f58b..66c9afb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt
@@ -18,4 +18,4 @@
 
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.activityLaunchAnimator by Kosmos.Fixture { ActivityLaunchAnimator() }
+val Kosmos.activityTransitionAnimator by Kosmos.Fixture { ActivityTransitionAnimator() }
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/communal/data/repository/CommunalSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryKosmos.kt
new file mode 100644
index 0000000..5485f79
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.repository
+
+import android.app.admin.devicePolicyManager
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.util.settings.fakeSettings
+
+val Kosmos.communalSettingsRepository: CommunalSettingsRepository by
+    Kosmos.Fixture {
+        CommunalSettingsRepositoryImpl(
+            bgDispatcher = testDispatcher,
+            featureFlagsClassic = featureFlagsClassic,
+            secureSettings = fakeSettings,
+            broadcastDispatcher = broadcastDispatcher,
+            devicePolicyManager = devicePolicyManager,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
index cccd908..ae7d877 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
@@ -16,7 +16,6 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 class FakeCommunalRepository(
     applicationScope: CoroutineScope,
-    override var isCommunalEnabled: Boolean = true,
     override val desiredScene: MutableStateFlow<CommunalSceneKey> =
         MutableStateFlow(CommunalSceneKey.DEFAULT),
 ) : CommunalRepository {
@@ -40,21 +39,10 @@
         _transitionState.value = transitionState
     }
 
-    fun setIsCommunalEnabled(value: Boolean) {
-        isCommunalEnabled = value
-    }
-
     private val _isCommunalHubShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
     override val isCommunalHubShowing: Flow<Boolean> = _isCommunalHubShowing
 
     fun setIsCommunalHubShowing(isCommunalHubShowing: Boolean) {
         _isCommunalHubShowing.value = isCommunalHubShowing
     }
-
-    private val _communalEnabledState: MutableStateFlow<Boolean> = MutableStateFlow(false)
-    override val communalEnabledState: StateFlow<Boolean> = _communalEnabledState
-
-    fun setCommunalEnabledState(enabled: Boolean) {
-        _communalEnabledState.value = enabled
-    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index 7301404..fab64e3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -36,7 +36,7 @@
         }
     }
 
-    override fun deleteWidgetFromDb(widgetId: Int) {
+    override fun deleteWidget(widgetId: Int) {
         if (_communalWidgets.value.none { it.appWidgetId == widgetId }) {
             return
         }
@@ -44,10 +44,6 @@
         _communalWidgets.value = _communalWidgets.value.filter { it.appWidgetId != widgetId }
     }
 
-    override fun deleteWidgetFromHost(widgetId: Int) {
-        deleteWidgetFromDb(widgetId)
-    }
-
     private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, priority: Int) {
         _communalWidgets.value += listOf(CommunalWidgetContentModel(id, providerInfo, priority))
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index c47f020..f7e9a11 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -27,7 +27,6 @@
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.smartspace.data.repository.smartspaceRepository
-import com.android.systemui.user.data.repository.userRepository
 import com.android.systemui.util.mockito.mock
 
 val Kosmos.communalInteractor by Fixture {
@@ -38,12 +37,12 @@
         mediaRepository = communalMediaRepository,
         communalPrefsRepository = communalPrefsRepository,
         smartspaceRepository = smartspaceRepository,
-        userRepository = userRepository,
         appWidgetHost = mock(),
         keyguardInteractor = keyguardInteractor,
         editWidgetsActivityStarter = editWidgetsActivityStarter,
         logBuffer = logcatLogBuffer("CommunalInteractor"),
         tableLogBuffer = mock(),
+        communalSettingsInteractor = communalSettingsInteractor,
     )
 }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt
new file mode 100644
index 0000000..b4773f6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.domain.interactor
+
+import com.android.systemui.communal.data.repository.communalSettingsRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.communalSettingsInteractor by Fixture {
+    CommunalSettingsInteractor(
+        bgScope = applicationCoroutineScope,
+        repository = communalSettingsRepository,
+        userInteractor = selectedUserInteractor,
+        tableLogBuffer = mock(),
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
index 9776b43..00fdced 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
@@ -31,6 +31,7 @@
             keyguardInteractor = keyguardInteractor,
             communalRepository = communalRepository,
             communalInteractor = communalInteractor,
+            communalSettingsInteractor = communalSettingsInteractor,
             tableLogBuffer = mock(),
         )
     }
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/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/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/statusbar/notification/NotificationLaunchAnimatorControllerProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerProviderKosmos.kt
index c3db34b..dc5a2f8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerProviderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerProviderKosmos.kt
@@ -19,5 +19,5 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.util.mockito.mock
 
-var Kosmos.notificationLaunchAnimatorControllerProvider by
+var Kosmos.notificationTransitionAnimatorControllerProvider by
     Kosmos.Fixture { mock<NotificationLaunchAnimatorControllerProvider>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderControllerKosmos.kt
similarity index 76%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderControllerKosmos.kt
index 128f58b..3bbac32 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderControllerKosmos.kt
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.animation
+package com.android.systemui.statusbar.notification.collection.render
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
 
-val Kosmos.activityLaunchAnimator by Kosmos.Fixture { ActivityLaunchAnimator() }
+var Kosmos.silentHeaderController by Kosmos.Fixture { mock<SectionHeaderController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
index 489598c..2de26f1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt
@@ -22,10 +22,11 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.statusbar.notification.collection.render.silentHeaderController
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.notificationIconContainerShelfViewBinder
 import com.android.systemui.statusbar.notification.notificationActivityStarter
-import com.android.systemui.statusbar.notification.stack.ui.view.notificationStatsLogger
 import com.android.systemui.statusbar.notification.stack.displaySwitchNotificationsHiderTracker
+import com.android.systemui.statusbar.notification.stack.ui.view.notificationStatsLogger
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationListViewModel
 import com.android.systemui.statusbar.phone.notificationIconAreaController
 import java.util.Optional
@@ -42,5 +43,6 @@
         hiderTracker = displaySwitchNotificationsHiderTracker,
         nicBinder = notificationIconContainerShelfViewBinder,
         notificationActivityStarter = { notificationActivityStarter },
+        silentHeaderController = silentHeaderController,
     )
 }
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..25e3eac 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,12 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 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.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 +32,17 @@
 
 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,
+        powerInteractor,
+        remoteInputInteractor,
+        seenNotificationsInteractor,
+        shadeInteractor,
+        userSetupInteractor,
+        zenModeInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
index 8042b5c..41c11ad6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
@@ -23,7 +23,7 @@
 import com.android.internal.logging.metricsLogger
 import com.android.internal.widget.lockPatternUtils
 import com.android.systemui.activityIntentHelper
-import com.android.systemui.animation.activityLaunchAnimator
+import com.android.systemui.animation.activityTransitionAnimator
 import com.android.systemui.assist.assistManager
 import com.android.systemui.concurrency.fakeExecutor
 import com.android.systemui.kosmos.Kosmos
@@ -35,7 +35,7 @@
 import com.android.systemui.shade.shadeViewController
 import com.android.systemui.statusbar.notification.collection.provider.launchFullScreenIntentProvider
 import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider
-import com.android.systemui.statusbar.notification.notificationLaunchAnimatorControllerProvider
+import com.android.systemui.statusbar.notification.notificationTransitionAnimatorControllerProvider
 import com.android.systemui.statusbar.notification.row.onUserInteractionCallback
 import com.android.systemui.statusbar.notificationClickNotifier
 import com.android.systemui.statusbar.notificationLockscreenUserManager
@@ -78,9 +78,9 @@
             notificationPresenter,
             shadeViewController,
             notificationShadeWindowController,
-            activityLaunchAnimator,
+            activityTransitionAnimator,
             shadeAnimationInteractor,
-            notificationLaunchAnimatorControllerProvider,
+            notificationTransitionAnimatorControllerProvider,
             launchFullScreenIntentProvider,
             powerInteractor,
             userTracker,
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/animation/ActivityLaunchAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt
similarity index 73%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt
index 128f58b..bcb5848 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.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,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.animation
+package com.android.systemui.util.settings
 
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
 
-val Kosmos.activityLaunchAnimator by Kosmos.Fixture { ActivityLaunchAnimator() }
+val Kosmos.fakeSettings: FakeSettings by Fixture { FakeSettings() }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index a11cf8c..26c1bc9 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -928,17 +928,11 @@
          * Dumps all {@link AccessibilityWindowInfo}s here.
          */
         void dumpLocked(FileDescriptor fd, final PrintWriter pw, String[] args) {
-            pw.append("Global Info [ ");
-            pw.println("Top focused display Id = " + mTopFocusedDisplayId);
-            pw.println("     Active Window Id = " + mActiveWindowId);
-            pw.println("     Top Focused Window Id = " + mTopFocusedWindowId);
-            pw.println("     Accessibility Focused Window Id = " + mAccessibilityFocusedWindowId
-                    + " ]");
             if (mIsProxy) {
                 pw.println("Proxy accessibility focused window = "
                         + mProxyDisplayAccessibilityFocusedWindow);
+                pw.println();
             }
-            pw.println();
             if (mWindows != null) {
                 final int windowCount = mWindows.size();
                 for (int j = 0; j < windowCount; j++) {
@@ -2201,6 +2195,13 @@
      * Dumps all {@link AccessibilityWindowInfo}s here.
      */
     public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
+        pw.append("Global Info [ ");
+        pw.println("Top focused display Id = " + mTopFocusedDisplayId);
+        pw.println("     Active Window Id = " + mActiveWindowId);
+        pw.println("     Top Focused Window Id = " + mTopFocusedWindowId);
+        pw.println("     Accessibility Focused Window Id = " + mAccessibilityFocusedWindowId
+                + " ]");
+        pw.println();
         final int count = mDisplayWindowsObservers.size();
         for (int i = 0; i < count; i++) {
             final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i);
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index fd8ab96..e1291e5 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -451,7 +451,7 @@
 
         final Session.SaveResult saveResult = session.showSaveLocked();
 
-        session.logContextCommitted(saveResult.getNoSaveUiReason(), commitReason);
+        session.logContextCommittedLocked(saveResult.getNoSaveUiReason(), commitReason);
 
         if (saveResult.isLogSaveShown()) {
             session.logSaveUiShown();
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 83d9cdb..b89e0d8 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -16,6 +16,8 @@
 
 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;
@@ -113,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;
@@ -123,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;
@@ -153,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;
@@ -2507,7 +2514,7 @@
                         + id + " destroyed");
                 return;
             }
-            fillInIntent = createAuthFillInIntentLocked(requestId, extras, /* authExtras= */ null);
+            fillInIntent = createAuthFillInIntentLocked(requestId, extras);
             if (fillInIntent == null) {
                 forceRemoveFromServiceLocked();
                 return;
@@ -2808,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
@@ -2818,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,
@@ -2854,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);
@@ -3061,6 +3086,10 @@
      * when necessary.
      */
     public void logContextCommitted() {
+        if (sVerbose) {
+            Slog.v(TAG, "logContextCommitted (" + id + "): commit_reason:" + COMMIT_REASON_UNKNOWN
+                    + " no_save_reason:" + Event.NO_SAVE_UI_REASON_NONE);
+        }
         mHandler.sendMessage(obtainMessage(Session::handleLogContextCommitted, this,
                 Event.NO_SAVE_UI_REASON_NONE,
                 COMMIT_REASON_UNKNOWN));
@@ -3069,16 +3098,26 @@
 
     /**
      * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_CONTEXT_COMMITTED}
-     * when necessary.
+     * when necessary. Note that it could be called before save UI is shown and the session is
+     * committed.
      *
      * @param saveDialogNotShowReason The reason why a save dialog was not shown.
      * @param commitReason The reason why context is committed.
      */
-    public void logContextCommitted(@NoSaveReason int saveDialogNotShowReason,
+
+    @GuardedBy("mLock")
+    public void logContextCommittedLocked(@NoSaveReason int saveDialogNotShowReason,
             @AutofillCommitReason int commitReason) {
+        if (sVerbose) {
+            Slog.v(TAG, "logContextCommittedLocked (" + id + "): commit_reason:" + commitReason
+                    + " no_save_reason:" + saveDialogNotShowReason);
+        }
         mHandler.sendMessage(obtainMessage(Session::handleLogContextCommitted, this,
                 saveDialogNotShowReason, commitReason));
-        logAllEvents(commitReason);
+
+        mSessionCommittedEventLogger.maybeSetCommitReason(commitReason);
+        mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount);
+        mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_NONE);
     }
 
     private void handleLogContextCommitted(@NoSaveReason int saveDialogNotShowReason,
@@ -3134,6 +3173,10 @@
             @Nullable ArrayList<FieldClassification> detectedFieldClassifications,
             @NoSaveReason int saveDialogNotShowReason,
             @AutofillCommitReason int commitReason) {
+        if (sVerbose) {
+            Slog.v(TAG, "logContextCommittedLocked (" + id + "): commit_reason:" + commitReason
+                    + " no_save_reason:" + saveDialogNotShowReason);
+        }
         final FillResponse lastResponse = getLastResponseLocked("logContextCommited(%s)");
         if (lastResponse == null) return;
 
@@ -3310,7 +3353,9 @@
                 changedFieldIds, changedDatasetIds, manuallyFilledFieldIds,
                 manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications,
                 mComponentName, mCompatMode, saveDialogNotShowReason);
-        logAllEvents(commitReason);
+        mSessionCommittedEventLogger.maybeSetCommitReason(commitReason);
+        mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount);
+        mSaveEventLogger.maybeSetSaveUiNotShownReason(saveDialogNotShowReason);
     }
 
     /**
@@ -3751,11 +3796,6 @@
                     }
                 }
 
-                if (sDebug) {
-                    Slog.d(TAG, "Good news, everyone! All checks passed, show save UI for "
-                            + id + "!");
-                }
-
                 final IAutoFillManagerClient client = getClient();
                 mPendingSaveUi = new PendingUi(new Binder(), id, client);
 
@@ -3787,6 +3827,10 @@
                     }
                 }
                 mSessionFlags.mShowingSaveUi = true;
+                if (sDebug) {
+                    Slog.d(TAG, "Good news, everyone! All checks passed, show save UI for "
+                            + id + "!");
+                }
                 return new SaveResult(/* logSaveShown= */ true, /* removeSession= */ false,
                         Event.NO_SAVE_UI_REASON_NONE);
             }
@@ -4690,6 +4734,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)) {
@@ -4749,6 +4798,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);
@@ -4964,6 +5018,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;
@@ -5669,8 +5786,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;
@@ -5686,8 +5809,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);
@@ -5704,9 +5826,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;
     }
 
@@ -6286,6 +6405,9 @@
 
     @GuardedBy("mLock")
     private void logAllEvents(@AutofillCommitReason int val) {
+        if (sVerbose) {
+            Slog.v(TAG, "logAllEvents(" + id + "): commitReason: " + val);
+        }
         mSessionCommittedEventLogger.maybeSetCommitReason(val);
         mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount);
         mSessionCommittedEventLogger.maybeSetSessionDurationMillis(
@@ -6311,6 +6433,9 @@
     @GuardedBy("mLock")
     RemoteFillService destroyLocked() {
         // Log unlogged events.
+        if (sVerbose) {
+            Slog.v(TAG, "destroyLocked for session: " + id);
+        }
         logAllEvents(COMMIT_REASON_SESSION_DESTROYED);
 
         if (mDestroyed) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 0054bc8..b43f1a9 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -1305,6 +1305,8 @@
             mAssociationStore.dump(out);
             mDevicePresenceMonitor.dump(out);
             mCompanionAppController.dump(out);
+            mTransportManager.dump(out);
+            mSystemDataTransferRequestStore.dump(out);
         }
     }
 
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
index 51c5fd6..c4c80f9 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
@@ -48,6 +48,7 @@
 import java.io.ByteArrayInputStream;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -303,6 +304,32 @@
         }
     }
 
+
+
+    /**
+     * Dumps current system data transfer request states.
+     */
+    public void dump(@NonNull PrintWriter out) {
+        synchronized (mLock) {
+            out.append("System Data Transfer Requests (Cached): ");
+            if (mCachedPerUser.size() == 0) {
+                out.append("<empty>\n");
+            } else {
+                out.append("\n");
+                for (int i = 0; i < mCachedPerUser.size(); i++) {
+                    final int userId = mCachedPerUser.keyAt(i);
+                    for (SystemDataTransferRequest request : mCachedPerUser.get(userId)) {
+                        out.append("  u")
+                                .append(String.valueOf(userId))
+                                .append(" -> ")
+                                .append(request.toString())
+                                .append('\n');
+                    }
+                }
+            }
+        }
+    }
+
     private void writeRequestsToXml(@NonNull TypedXmlSerializer serializer,
             @Nullable Collection<SystemDataTransferRequest> requests) throws IOException {
         serializer.startTag(null, XML_TAG_REQUESTS);
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 3e45626..3861f99 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -36,6 +36,7 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
@@ -225,6 +226,25 @@
     }
 
     /**
+     * Dumps current list of active transports.
+     */
+    public void dump(@NonNull PrintWriter out) {
+        synchronized (mTransports) {
+            out.append("System Data Transports: ");
+            if (mTransports.size() == 0) {
+                out.append("<empty>\n");
+            } else {
+                out.append("\n");
+                for (int i = 0; i < mTransports.size(); i++) {
+                    final int associationId = mTransports.keyAt(i);
+                    final Transport transport = mTransports.get(associationId);
+                    out.append("  ").append(transport.toString()).append('\n');
+                }
+            }
+        }
+    }
+
+    /**
      * @hide
      */
     public void enableSecureTransport(boolean enabled) {
diff --git a/services/companion/java/com/android/server/companion/transport/RawTransport.java b/services/companion/java/com/android/server/companion/transport/RawTransport.java
index ca169aac..05703ce 100644
--- a/services/companion/java/com/android/server/companion/transport/RawTransport.java
+++ b/services/companion/java/com/android/server/companion/transport/RawTransport.java
@@ -94,6 +94,13 @@
         }
     }
 
+    @Override
+    public String toString() {
+        return "RawTransport{"
+                + "mAssociationId=" + mAssociationId
+                + '}';
+    }
+
     private void receiveMessage() throws IOException {
         synchronized (mRemoteIn) {
             final byte[] headerBytes = new byte[HEADER_LENGTH];
diff --git a/services/companion/java/com/android/server/companion/transport/SecureTransport.java b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
index 6e906eb..1e95e65 100644
--- a/services/companion/java/com/android/server/companion/transport/SecureTransport.java
+++ b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
@@ -152,4 +152,12 @@
             close();
         }
     }
+
+    @Override
+    public String toString() {
+        return "SecureTransport{"
+                + "mAssociationId=" + mAssociationId
+                + ", mSecureChannel=" + mSecureChannel
+                + '}';
+    }
 }
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/Android.bp b/services/core/Android.bp
index 89896c3..4692099 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -214,6 +214,7 @@
         "policy_flags_lib",
         "net_flags_lib",
         "stats_flags_lib",
+        "core_os_flags_lib",
     ],
     javac_shard_size: 50,
     javacflags: [
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/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index 627a62e..34c3d7e 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -246,16 +246,6 @@
 
         @Override
         public void run() {
-            if (mGuid.isEmpty()) {
-                Slog.e(TAG, "adbwifi guid was not set");
-                return;
-            }
-            mPort = native_pairing_start(mGuid, mPairingCode);
-            if (mPort <= 0 || mPort > 65535) {
-                Slog.e(TAG, "Unable to start pairing server");
-                return;
-            }
-
             // Register the mdns service
             NsdServiceInfo serviceInfo = new NsdServiceInfo();
             serviceInfo.setServiceName(mServiceName);
@@ -288,6 +278,28 @@
             mHandler.sendMessage(message);
         }
 
+        @Override
+        public void start() {
+            /*
+             * If a user is fast enough to click cancel, native_pairing_cancel can be invoked
+             * while native_pairing_start is running which run the destruction of the object
+             * while it is being constructed. Here we start the pairing server on foreground
+             * Thread so native_pairing_cancel can never be called concurrently. Then we let
+             * the pairing server run on a background Thread.
+             */
+            if (mGuid.isEmpty()) {
+                Slog.e(TAG, "adbwifi guid was not set");
+                return;
+            }
+            mPort = native_pairing_start(mGuid, mPairingCode);
+            if (mPort <= 0) {
+                Slog.e(TAG, "Unable to start pairing server");
+                return;
+            }
+
+            super.start();
+        }
+
         public void cancelPairing() {
             native_pairing_cancel();
         }
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/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a5531ae..2750344 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -187,6 +187,7 @@
 import static com.android.server.wm.ActivityTaskManagerService.DUMP_VISIBLE_ACTIVITIES;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
 import static com.android.server.wm.ActivityTaskManagerService.relaunchReasonToString;
+import static com.android.systemui.shared.Flags.enableHomeDelay;
 
 import android.Manifest;
 import android.Manifest.permission;
@@ -521,6 +522,7 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
@@ -921,6 +923,15 @@
     @GuardedBy("this")
     final ComponentAliasResolver mComponentAliasResolver;
 
+    private static final long HOME_LAUNCH_TIMEOUT_MS = 15000;
+    private final AtomicBoolean mHasHomeDelay = new AtomicBoolean(false);
+
+    /**
+     * Tracks all users with computed color resources by ThemeOverlaycvontroller
+     */
+    @GuardedBy("this")
+    private final Set<Integer> mThemeOverlayReadiness = new HashSet<>();
+
     /**
      * Tracks association information for a particular package along with debuggability.
      * <p> Associations for a package A are allowed to package B if B is part of the
@@ -2332,6 +2343,7 @@
                 mService.startBroadcastObservers();
             } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
                 mService.mPackageWatchdog.onPackagesReady();
+                mService.setHomeTimeout();
             }
         }
 
@@ -5304,6 +5316,59 @@
         }
     }
 
+    /**
+     * Starts Home if there is no completion signal from ThemeOverlayController
+     */
+    private void setHomeTimeout() {
+        if (enableHomeDelay() && mHasHomeDelay.compareAndSet(false, true)) {
+            mHandler.postDelayed(() -> {
+                if (!getThemeOverlayReadiness()) {
+                    Slog.d(TAG,
+                            "ThemeHomeDelay: ThemeOverlayController not responding, launching "
+                                    + "Home after "
+                                    + HOME_LAUNCH_TIMEOUT_MS + "ms");
+                    setThemeOverlayReady(true);
+                }
+            }, HOME_LAUNCH_TIMEOUT_MS);
+        }
+    }
+
+    /**
+     * Used by ThemeOverlayController to notify all listeners for
+     * color palette readiness.
+     * @hide
+     */
+    @Override
+    public void setThemeOverlayReady(boolean readiness) {
+        enforceCallingPermission(Manifest.permission.SET_THEME_OVERLAY_CONTROLLER_READY,
+                "setThemeOverlayReady");
+
+        int currentUserId = mUserController.getCurrentUserId();
+
+        boolean updateReadiness;
+        synchronized (mThemeOverlayReadiness) {
+            updateReadiness = readiness ? mThemeOverlayReadiness.add(currentUserId)
+                    : mThemeOverlayReadiness.remove(currentUserId);
+        }
+
+        if (updateReadiness && readiness && enableHomeDelay()) {
+            mAtmInternal.startHomeOnAllDisplays(currentUserId, "setThemeOverlayReady");
+        }
+    }
+
+    /**
+     * Returns current state of ThemeOverlayController color
+     * palette readiness.
+     *
+     * @hide
+     */
+    public boolean getThemeOverlayReadiness() {
+        int uid = mUserController.getCurrentUserId();
+        synchronized (mThemeOverlayReadiness) {
+            return mThemeOverlayReadiness.contains(uid);
+        }
+    }
+
     final void ensureBootCompleted() {
         boolean booting;
         boolean enableScreen;
@@ -18033,6 +18098,10 @@
             mAtmInternal.onUserStopped(userId);
             // Clean up various services by removing the user
             mBatteryStatsService.onUserRemoved(userId);
+
+            synchronized (mThemeOverlayReadiness) {
+                mThemeOverlayReadiness.remove(userId);
+            }
         }
 
         @Override
@@ -19391,6 +19460,11 @@
             return ActivityManagerService.this.clearApplicationUserData(packageName, keepState,
                     isRestore, observer, userId);
         }
+
+        @Override
+        public boolean getThemeOverlayReadiness() {
+            return ActivityManagerService.this.getThemeOverlayReadiness();
+        }
     }
 
     long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index bbbba26..04deb02 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -12458,6 +12458,20 @@
         return app;
     }
 
+    /**
+     * Retrieves all audioMixes registered with the AudioPolicyManager
+     * @return list of registered audio mixes
+     */
+    public List<AudioMix> getRegisteredPolicyMixes() {
+        if (!android.media.audiopolicy.Flags.audioMixTestApi()) {
+            return Collections.emptyList();
+        }
+
+        synchronized (mAudioPolicies) {
+            return mAudioSystem.getRegisteredPolicyMixes();
+        }
+    }
+
     public int addMixForPolicy(AudioPolicyConfig policyConfig, IAudioPolicyCallback pcb) {
         if (DEBUG_AP) { Log.d(TAG, "addMixForPolicy for " + pcb.asBinder()
                 + " with config:" + policyConfig); }
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 4f46dd1..49ab19a 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -27,6 +27,7 @@
 import android.media.ISoundDoseCallback;
 import android.media.audiopolicy.AudioMix;
 import android.media.audiopolicy.AudioMixingRule;
+import android.media.audiopolicy.Flags;
 import android.os.IBinder;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -42,6 +43,7 @@
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -602,6 +604,23 @@
     }
 
     /**
+     * @return a list of AudioMixes that are registered in the audio policy manager.
+     */
+    public List<AudioMix> getRegisteredPolicyMixes() {
+        if (!Flags.audioMixTestApi()) {
+            return Collections.emptyList();
+        }
+
+        List<AudioMix> audioMixes = new ArrayList<>();
+        int result = AudioSystem.getRegisteredPolicyMixes(audioMixes);
+        if (result != AudioSystem.SUCCESS) {
+            throw new IllegalStateException(
+                    "Cannot fetch registered policy mixes. Result: " + result);
+        }
+        return Collections.unmodifiableList(audioMixes);
+    }
+
+    /**
      * Update already {@link AudioMixingRule}-s for already registered {@link AudioMix}-es.
      *
      * @param mixes              - array of registered {@link AudioMix}-es to update.
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/LockoutResetDispatcher.java b/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java
index 92218b1..199db8c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java
+++ b/services/core/java/com/android/server/biometrics/sensors/LockoutResetDispatcher.java
@@ -27,9 +27,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.util.ArrayList;
 import java.util.Iterator;
-import java.util.List;
+import java.util.concurrent.ConcurrentLinkedQueue;
 
 /**
  * Allows clients (such as keyguard) to register for notifications on when biometric lockout
@@ -42,7 +41,7 @@
 
     private final Context mContext;
     @VisibleForTesting
-    final List<ClientCallback> mClientCallbacks = new ArrayList<>();
+    final ConcurrentLinkedQueue<ClientCallback> mClientCallbacks = new ConcurrentLinkedQueue<>();
 
     private static class ClientCallback {
         private static final long WAKELOCK_TIMEOUT_MS = 2000;
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/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/grammaticalinflection/GrammaticalInflectionManagerInternal.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
index c260f10..6a6e6ab 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
@@ -47,5 +47,19 @@
      * @see Configuration#getGrammaticalGender
      */
     public abstract @Configuration.GrammaticalGender int getSystemGrammaticalGender(int userId);
+
+    /**
+     * Retrieve the system grammatical gender.
+     *
+     * @return the value of grammatical gender
+     *
+     */
+    public abstract @Configuration.GrammaticalGender int retrieveSystemGrammaticalGender(
+            Configuration configuration);
+
+    /**
+     * Whether the package can get the system grammatical gender or not.
+     */
+    public abstract boolean canGetSystemGrammaticalGender(int uid, String packageName);
 }
 
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 6eb7e95..d01f54f 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -22,17 +22,21 @@
 import static com.android.server.grammaticalinflection.GrammaticalInflectionUtils.checkSystemGrammaticalGenderPermission;
 
 import android.annotation.Nullable;
+import android.app.ActivityTaskManager;
 import android.app.GrammaticalInflectionManager;
 import android.app.IGrammaticalInflectionManager;
 import android.content.AttributionSource;
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
+import android.content.res.Configuration;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.permission.PermissionManager;
 import android.util.AtomicFile;
 import android.util.Log;
@@ -43,6 +47,7 @@
 import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.IoThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -71,6 +76,7 @@
     private static final String TAG_GRAMMATICAL_INFLECTION = "grammatical_inflection";
     private static final String GRAMMATICAL_INFLECTION_ENABLED =
             "i18n.grammatical_Inflection.enabled";
+    private static final String GRAMMATICAL_GENDER_PROPERTY = "persist.sys.grammatical_gender";
 
     private final GrammaticalInflectionBackupHelper mBackupHelper;
     private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
@@ -121,16 +127,16 @@
         @Override
         public void setSystemWideGrammaticalGender(int grammaticalGender, int userId) {
             checkCallerIsSystem();
-            checkSystemTermsOfAddressIsEnabled();
             GrammaticalInflectionService.this.setSystemWideGrammaticalGender(grammaticalGender,
                     userId);
         }
 
         @Override
         public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
-            checkSystemTermsOfAddressIsEnabled();
-            return GrammaticalInflectionService.this.getSystemGrammaticalGender(attributionSource,
-                    userId);
+            return canGetSystemGrammaticalGender(attributionSource)
+                    ? GrammaticalInflectionService.this.getSystemGrammaticalGender(
+                    attributionSource, userId)
+                    : GRAMMATICAL_GENDER_NOT_SPECIFIED;
         }
 
         @Override
@@ -159,9 +165,33 @@
 
         @Override
         public int getSystemGrammaticalGender(int userId) {
-            checkCallerIsSystem();
-            return GrammaticalInflectionService.this.getSystemGrammaticalGender(
-                    mContext.getAttributionSource(), userId);
+            return checkSystemTermsOfAddressIsEnabled()
+                    ? GrammaticalInflectionService.this.getSystemGrammaticalGender(
+                    mContext.getAttributionSource(), userId)
+                    : GRAMMATICAL_GENDER_NOT_SPECIFIED;
+        }
+
+        @Override
+        public int retrieveSystemGrammaticalGender(Configuration configuration) {
+            int systemGrammaticalGender = getSystemGrammaticalGender(mContext.getUserId());
+            // Retrieve the grammatical gender from system property, set it into
+            // configuration which will get updated later if the grammatical gender raw value of
+            // current configuration is {@link Configuration#GRAMMATICAL_GENDER_UNDEFINED}.
+            if (configuration.getGrammaticalGenderRaw()
+                    == Configuration.GRAMMATICAL_GENDER_UNDEFINED
+                    || systemGrammaticalGender <= Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED) {
+                systemGrammaticalGender = SystemProperties.getInt(GRAMMATICAL_GENDER_PROPERTY,
+                        Configuration.GRAMMATICAL_GENDER_UNDEFINED);
+            }
+            return systemGrammaticalGender;
+        }
+
+        @Override
+        public boolean canGetSystemGrammaticalGender(int uid, String packageName) {
+            AttributionSource attributionSource = new AttributionSource.Builder(
+                    uid).setPackageName(packageName).build();
+            return GrammaticalInflectionService.this.canGetSystemGrammaticalGender(
+                    attributionSource);
         }
     }
 
@@ -202,11 +232,20 @@
     }
 
     protected void setSystemWideGrammaticalGender(int grammaticalGender, int userId) {
+        Trace.beginSection("GrammaticalInflectionService.setSystemWideGrammaticalGender");
         if (!GrammaticalInflectionManager.VALID_GRAMMATICAL_GENDER_VALUES.contains(
                 grammaticalGender)) {
             throw new IllegalArgumentException("Unknown grammatical gender");
         }
 
+        if (!checkSystemTermsOfAddressIsEnabled()) {
+            if (grammaticalGender == GRAMMATICAL_GENDER_NOT_SPECIFIED) {
+                return;
+            }
+            Log.d(TAG, "Clearing the system grammatical gender setting");
+            grammaticalGender = GRAMMATICAL_GENDER_NOT_SPECIFIED;
+        }
+
         synchronized (mLock) {
             final File file = getGrammaticalGenderFile(userId);
             final AtomicFile atomicFile = new AtomicFile(file);
@@ -224,6 +263,15 @@
                 throw new RuntimeException(e);
             }
         }
+
+        try {
+            Configuration config = new Configuration();
+            config.setGrammaticalGender(grammaticalGender);
+            ActivityTaskManager.getService().updateConfiguration(config);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Can not update configuration", e);
+        }
+        Trace.endSection();
     }
 
     public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
@@ -233,34 +281,9 @@
             return GRAMMATICAL_GENDER_NOT_SPECIFIED;
         }
 
-        int callingUid = Binder.getCallingUid();
-        if (mPackageManagerInternal.getPackageUid(packageName, 0, userId) != callingUid) {
-            Log.d(TAG,
-                    "Package " + packageName + " does not belong to the calling uid " + callingUid);
-            return GRAMMATICAL_GENDER_NOT_SPECIFIED;
-        }
-
-        if (!checkSystemGrammaticalGenderPermission(mPermissionManager, attributionSource)) {
-            return GRAMMATICAL_GENDER_NOT_SPECIFIED;
-        }
-
         synchronized (mLock) {
-            final File file = getGrammaticalGenderFile(userId);
-            if (!file.exists()) {
-                Log.d(TAG, "User " + userId + "doesn't have the grammatical gender file.");
-                return GRAMMATICAL_GENDER_NOT_SPECIFIED;
-            }
-
-            if (mGrammaticalGenderCache.indexOfKey(userId) < 0) {
-                try {
-                    InputStream in = new FileInputStream(file);
-                    final TypedXmlPullParser parser = Xml.resolvePullParser(in);
-                    mGrammaticalGenderCache.put(userId, getGrammaticalGenderFromXml(parser));
-                } catch (IOException | XmlPullParserException e) {
-                    Log.e(TAG, "Failed to parse XML configuration from " + file, e);
-                }
-            }
-            return mGrammaticalGenderCache.get(userId);
+            int grammaticalGender = mGrammaticalGenderCache.get(userId);
+            return grammaticalGender < 0 ? GRAMMATICAL_GENDER_NOT_SPECIFIED : grammaticalGender;
         }
     }
 
@@ -311,9 +334,39 @@
         }
     }
 
-    private void checkSystemTermsOfAddressIsEnabled() {
+    private boolean checkSystemTermsOfAddressIsEnabled() {
         if (!systemTermsOfAddressEnabled()) {
-            throw new RuntimeException("The flag must be enabled to allow calling the API.");
+            Log.d(TAG, "The flag must be enabled to allow calling the API.");
+            return false;
         }
+        return true;
+    }
+
+    private boolean canGetSystemGrammaticalGender(AttributionSource attributionSource) {
+        return checkSystemTermsOfAddressIsEnabled() && checkSystemGrammaticalGenderPermission(
+                mPermissionManager, attributionSource);
+    }
+
+    @Override
+    public void onUserUnlocked(TargetUser user) {
+        IoThread.getHandler().post(() -> {
+            int userId = user.getUserIdentifier();
+            final File file = getGrammaticalGenderFile(userId);
+            synchronized (mLock) {
+                if (!file.exists()) {
+                    Log.d(TAG, "User " + userId + "doesn't have the grammatical gender file.");
+                    return;
+                }
+                if (mGrammaticalGenderCache.indexOfKey(userId) < 0) {
+                    try {
+                        InputStream in = new FileInputStream(file);
+                        final TypedXmlPullParser parser = Xml.resolvePullParser(in);
+                        mGrammaticalGenderCache.put(userId, getGrammaticalGenderFromXml(parser));
+                    } catch (IOException | XmlPullParserException e) {
+                        Log.e(TAG, "Failed to parse XML configuration from " + file, e);
+                    }
+                }
+            }
+        });
     }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index e0e825d..c8c66238 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1366,6 +1366,12 @@
                                     // we don't call onInitializeCecComplete()
                                     // since we reallocate the logical address only.
                                     onInitializeCecComplete(initiatedBy);
+                                } else if (initiatedBy == INITIATED_BY_HOTPLUG
+                                        && mDisplayStatusCallback == null) {
+                                    // Force to update display status for hotplug event.
+                                    synchronized (mLock) {
+                                        announceHdmiControlStatusChange(mHdmiControlEnabled);
+                                    }
                                 }
                                 // We remove local devices here, instead of before the start of
                                 // address allocation, to prevent multiple local devices of the
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..c02d524 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/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 91706cf..7dbe880 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -727,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;
@@ -4759,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(),
@@ -7623,6 +7623,8 @@
             }
         }
 
+        notification.overrideAllowlistToken(ALLOWLIST_TOKEN);
+
         // Remote views? Are they too big?
         checkRemoteViews(pkg, tag, id, notification);
     }
diff --git a/services/core/java/com/android/server/os/Android.bp b/services/core/java/com/android/server/os/Android.bp
new file mode 100644
index 0000000..565dc3b
--- /dev/null
+++ b/services/core/java/com/android/server/os/Android.bp
@@ -0,0 +1,12 @@
+aconfig_declarations {
+    name: "core_os_flags",
+    package: "com.android.server.os",
+    srcs: [
+        "*.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "core_os_flags_lib",
+    aconfig_declarations: "core_os_flags",
+}
diff --git a/services/core/java/com/android/server/os/core_os_flags.aconfig b/services/core/java/com/android/server/os/core_os_flags.aconfig
new file mode 100644
index 0000000..cbc0d54
--- /dev/null
+++ b/services/core/java/com/android/server/os/core_os_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.os"
+
+flag {
+    name: "proto_tombstone"
+    namespace: "proto_tombstone_ns"
+    description: "Use proto tombstones as source of truth for adding to dropbox"
+    bug: "323857385"
+}
diff --git a/services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java
index 66ad716..a56406e 100644
--- a/services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java
@@ -49,4 +49,10 @@
 
     /** Retrieves the UID that can access the persistent data partition. */
     int getAllowedUid();
+
+    /**
+     * Attempt to deactivate Factory Reset Protection (FRP) without a secret.  Returns true if
+     * successful, false if not.
+     */
+    boolean deactivateFactoryResetProtectionWithoutSecret();
 }
diff --git a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
index b9b09fb..133fc8f 100644
--- a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
@@ -18,16 +18,31 @@
 
 import static com.android.internal.util.Preconditions.checkArgument;
 
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import static java.nio.file.StandardOpenOption.CREATE;
+import static java.nio.file.StandardOpenOption.SYNC;
+import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
+import static java.nio.file.StandardOpenOption.WRITE;
+
 import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
+import android.security.Flags;
 import android.service.persistentdata.IPersistentDataBlockService;
 import android.service.persistentdata.PersistentDataBlockManager;
 import android.text.TextUtils;
@@ -56,10 +71,10 @@
 import java.nio.channels.FileChannel;
 import java.nio.file.Files;
 import java.nio.file.Paths;
-import java.nio.file.StandardOpenOption;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
+import java.util.HexFormat;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -85,9 +100,14 @@
  * | --------------------------------------------|
  * | FRP data block length (4 bytes)             |
  * | --------------------------------------------|
- * | FRP data (variable length)                  |
+ * | FRP data (variable length; 100KB max)    |
  * | --------------------------------------------|
  * | ...                                         |
+ * | Empty space.                                |
+ * | ...                                         |
+ * | --------------------------------------------|
+ * | FRP secret magic (8 bytes)                  |
+ * | FRP secret (32 bytes)                       |
  * | --------------------------------------------|
  * | Test mode data block (10000 bytes)          |
  * | --------------------------------------------|
@@ -127,6 +147,14 @@
     /** Maximum size of the FRP credential handle that can be stored. */
     @VisibleForTesting
     static final int MAX_FRP_CREDENTIAL_HANDLE_SIZE = FRP_CREDENTIAL_RESERVED_SIZE - 4;
+    /** Size of the FRP mode deactivation secret, in bytes */
+    @VisibleForTesting
+    static final int FRP_SECRET_SIZE = 32;
+    /** Magic value to identify the FRP secret is present. */
+    @VisibleForTesting
+    static final byte[] FRP_SECRET_MAGIC = {(byte) 0xda, (byte) 0xc2, (byte) 0xfc,
+            (byte) 0xcd, (byte) 0xb9, 0x1b, 0x09, (byte) 0x88};
+
     /**
      * Size of the block reserved for Test Harness Mode data, including 4 bytes for the size header.
      */
@@ -145,21 +173,52 @@
     private static final String FLASH_LOCK_LOCKED = "1";
     private static final String FLASH_LOCK_UNLOCKED = "0";
 
+    /**
+     * Path to FRP secret stored on /data.  This file enables automatic deactivation of FRP mode if
+     * it contains the current FRP secret.  When /data is wiped in an untrusted reset this file is
+     * destroyed, blocking automatic deactivation.
+     */
+    private static final String FRP_SECRET_FILE = "/data/system/frp_secret";
+
+    /**
+     * Path to temp file used when changing the FRP secret.
+     */
+    private static final String FRP_SECRET_TMP_FILE = "/data/system/frp_secret_tmp";
+
+    public static final String BOOTLOADER_LOCK_STATE = "ro.boot.vbmeta.device_state";
+    public static final String VERIFIED_BOOT_STATE = "ro.boot.verifiedbootstate";
+    public static final int INIT_WAIT_TIMEOUT = 10;
+
     private final Context mContext;
     private final String mDataBlockFile;
     private final boolean mIsFileBacked;
     private final Object mLock = new Object();
     private final CountDownLatch mInitDoneSignal = new CountDownLatch(1);
+    private final String mFrpSecretFile;
+    private final String mFrpSecretTmpFile;
 
     private int mAllowedUid = -1;
     private long mBlockDeviceSize = -1; // Load lazily
 
+    private final boolean mFrpEnforced;
+
+    /**
+     * FRP active state.  When true (the default) we may have had an untrusted factory reset. In
+     * that case we block any updates of the persistent data block.  To exit active state, it's
+     * necessary for some caller to provide the FRP secret.
+     */
+    private boolean mFrpActive = false;
+
     @GuardedBy("mLock")
     private boolean mIsWritable = true;
 
     public PersistentDataBlockService(Context context) {
         super(context);
         mContext = context;
+        mFrpEnforced = Flags.frpEnforcement();
+        mFrpActive = mFrpEnforced;
+        mFrpSecretFile = FRP_SECRET_FILE;
+        mFrpSecretTmpFile = FRP_SECRET_TMP_FILE;
         if (SystemProperties.getBoolean(GSI_RUNNING_PROP, false)) {
             mIsFileBacked = true;
             mDataBlockFile = GSI_SANDBOX;
@@ -171,12 +230,17 @@
 
     @VisibleForTesting
     PersistentDataBlockService(Context context, boolean isFileBacked, String dataBlockFile,
-            long blockDeviceSize) {
+            long blockDeviceSize, boolean frpEnabled, String frpSecretFile,
+            String frpSecretTmpFile) {
         super(context);
         mContext = context;
         mIsFileBacked = isFileBacked;
         mDataBlockFile = dataBlockFile;
         mBlockDeviceSize = blockDeviceSize;
+        mFrpEnforced = frpEnabled;
+        mFrpActive = mFrpEnforced;
+        mFrpSecretFile = frpSecretFile;
+        mFrpSecretTmpFile = frpSecretTmpFile;
     }
 
     private int getAllowedUid() {
@@ -206,24 +270,35 @@
         // Do init on a separate thread, will join in PHASE_ACTIVITY_MANAGER_READY
         SystemServerInitThreadPool.submit(() -> {
             enforceChecksumValidity();
-            formatIfOemUnlockEnabled();
+            if (mFrpEnforced) {
+                automaticallyDeactivateFrpIfPossible();
+                setOemUnlockEnabledProperty(doGetOemUnlockEnabled());
+                // Set the SECURE_FRP_MODE flag, for backward compatibility with clients who use it.
+                // They should switch to calling #isFrpActive().
+                Settings.Global.putInt(mContext.getContentResolver(),
+                        Settings.Global.SECURE_FRP_MODE, mFrpActive ? 1 : 0);
+            } else {
+                formatIfOemUnlockEnabled();
+            }
             publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
-            mInitDoneSignal.countDown();
+            signalInitDone();
         }, TAG + ".onStart");
     }
 
+    @VisibleForTesting
+    void signalInitDone() {
+        mInitDoneSignal.countDown();
+    }
+
+    private void setOemUnlockEnabledProperty(boolean oemUnlockEnabled) {
+        setProperty(OEM_UNLOCK_PROP, oemUnlockEnabled ? "1" : "0");
+    }
+
     @Override
     public void onBootPhase(int phase) {
         // Wait for initialization in onStart to finish
         if (phase == PHASE_SYSTEM_SERVICES_READY) {
-            try {
-                if (!mInitDoneSignal.await(10, TimeUnit.SECONDS)) {
-                    throw new IllegalStateException("Service " + TAG + " init timeout");
-                }
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-                throw new IllegalStateException("Service " + TAG + " init interrupted", e);
-            }
+            waitForInitDoneSignal();
             // The user responsible for FRP should exist by now.
             mAllowedUid = getAllowedUid();
             LocalServices.addService(PersistentDataBlockManagerInternal.class, mInternalService);
@@ -231,6 +306,17 @@
         super.onBootPhase(phase);
     }
 
+    private void waitForInitDoneSignal() {
+        try {
+            if (!mInitDoneSignal.await(INIT_WAIT_TIMEOUT, TimeUnit.SECONDS)) {
+                throw new IllegalStateException("Service " + TAG + " init timeout");
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new IllegalStateException("Service " + TAG + " init interrupted", e);
+        }
+    }
+
     @VisibleForTesting
     void setAllowedUid(int uid) {
         mAllowedUid = uid;
@@ -243,8 +329,7 @@
                 formatPartitionLocked(true);
             }
         }
-
-        setProperty(OEM_UNLOCK_PROP, enabled ? "1" : "0");
+        setOemUnlockEnabledProperty(enabled);
     }
 
     private void enforceOemUnlockReadPermission() {
@@ -263,9 +348,18 @@
                 "Can't modify OEM unlock state");
     }
 
+    private void enforceConfigureFrpPermission() {
+        if (mFrpEnforced && mContext.checkCallingOrSelfPermission(
+                Manifest.permission.CONFIGURE_FACTORY_RESET_PROTECTION)
+                == PackageManager.PERMISSION_DENIED) {
+            throw new SecurityException(("Can't configure Factory Reset Protection. Requires "
+                    + "CONFIGURE_FACTORY_RESET_PROTECTION"));
+        }
+    }
+
     private void enforceUid(int callingUid) {
-        if (callingUid != mAllowedUid) {
-            throw new SecurityException("uid " + callingUid + " not allowed to access PST");
+        if (callingUid != mAllowedUid && callingUid != UserHandle.AID_ROOT) {
+            throw new SecurityException("uid " + callingUid + " not allowed to access PDB");
         }
     }
 
@@ -315,7 +409,9 @@
 
     @VisibleForTesting
     int getMaximumFrpDataSize() {
-        return (int) (getTestHarnessModeDataOffset() - DIGEST_SIZE_BYTES - HEADER_SIZE);
+        long frpSecretSize = mFrpEnforced ? FRP_SECRET_MAGIC.length + FRP_SECRET_SIZE : 0;
+        return (int) (getTestHarnessModeDataOffset() - DIGEST_SIZE_BYTES - HEADER_SIZE
+                - frpSecretSize);
     }
 
     @VisibleForTesting
@@ -324,6 +420,16 @@
     }
 
     @VisibleForTesting
+    long getFrpSecretMagicOffset() {
+        return getFrpSecretDataOffset() - FRP_SECRET_MAGIC.length;
+    }
+
+    @VisibleForTesting
+    long getFrpSecretDataOffset() {
+        return getTestHarnessModeDataOffset() - FRP_SECRET_SIZE;
+    }
+
+    @VisibleForTesting
     long getTestHarnessModeDataOffset() {
         return getFrpCredentialDataOffset() - TEST_MODE_RESERVED_SIZE;
     }
@@ -349,6 +455,11 @@
     }
 
     private FileChannel getBlockOutputChannel() throws IOException {
+        enforceFactoryResetProtectionInactive();
+        return getBlockOutputChannelIgnoringFrp();
+    }
+
+    private FileChannel getBlockOutputChannelIgnoringFrp() throws FileNotFoundException {
         return new RandomAccessFile(mDataBlockFile, "rw").getChannel();
     }
 
@@ -416,7 +527,7 @@
     @VisibleForTesting
     void formatPartitionLocked(boolean setOemUnlockEnabled) {
 
-        try (FileChannel channel = getBlockOutputChannel()) {
+        try (FileChannel channel = getBlockOutputChannelIgnoringFrp()) {
             // Format the data selectively.
             //
             // 1. write header, set length = 0
@@ -431,12 +542,18 @@
 
             // 2. corrupt the legacy FRP data explicitly
             int payload_size = (int) getBlockDeviceSize() - header_size;
-            buf = ByteBuffer.allocate(payload_size
-                          - TEST_MODE_RESERVED_SIZE - FRP_CREDENTIAL_RESERVED_SIZE - 1);
+            if (mFrpEnforced) {
+                buf = ByteBuffer.allocate(payload_size - TEST_MODE_RESERVED_SIZE
+                        - FRP_SECRET_MAGIC.length - FRP_SECRET_SIZE - FRP_CREDENTIAL_RESERVED_SIZE
+                        - 1);
+            } else {
+                buf = ByteBuffer.allocate(payload_size - TEST_MODE_RESERVED_SIZE
+                        - FRP_CREDENTIAL_RESERVED_SIZE - 1);
+            }
             channel.write(buf);
             channel.force(true);
 
-            // 3. skip the test mode data and leave it unformat
+            // 3. skip the test mode data and leave it unformatted.
             //    This is for a feature that enables testing.
             channel.position(channel.position() + TEST_MODE_RESERVED_SIZE);
 
@@ -451,6 +568,11 @@
             buf.flip();
             channel.write(buf);
             channel.force(true);
+
+            // 6. Write the default FRP secret (all zeros).
+            if (mFrpEnforced) {
+                writeFrpMagicAndDefaultSecret();
+            }
         } catch (IOException e) {
             Slog.e(TAG, "failed to format block", e);
             return;
@@ -460,10 +582,198 @@
         computeAndWriteDigestLocked();
     }
 
+    /**
+     * Try to deactivate FRP by presenting an FRP secret from the data partition, or the default
+     * secret if the secret(s) on the data partition are not present or don't work.
+     */
+    @VisibleForTesting
+    boolean automaticallyDeactivateFrpIfPossible() {
+        synchronized (mLock) {
+            if (deactivateFrpWithFileSecret(mFrpSecretFile)) {
+                return true;
+            }
+
+            Slog.w(TAG, "Failed to deactivate with primary secret file, trying backup.");
+            if (deactivateFrpWithFileSecret(mFrpSecretTmpFile)) {
+                // The backup file has the FRP secret, make it the primary file.
+                moveFrpTempFileToPrimary();
+                return true;
+            }
+
+            Slog.w(TAG, "Failed to deactivate with backup secret file, trying default secret.");
+            if (deactivateFrp(new byte[FRP_SECRET_SIZE])) {
+                return true;
+            }
+
+            // We could not deactivate FRP.  It's possible that we have hit an obscure corner case,
+            // a device that once ran a version of Android that set the FRP magic and a secret,
+            // then downgraded to a version that did not know about FRP, wiping the FRP secrets
+            // files, then upgraded to a version (the current one) that does know about FRP,
+            // potentially leaving the user unable to deactivate FRP because all copies of the
+            // secret are gone.
+            //
+            // To handle this case, we check to see if we have recently upgraded from a pre-V
+            // version.  If so, we deactivate FRP and set the secret to the default value.
+            if (isUpgradingFromPreVRelease()) {
+                Slog.w(TAG, "Upgrading from Android 14 or lower, defaulting FRP secret");
+                writeFrpMagicAndDefaultSecret();
+                mFrpActive = false;
+                return true;
+            }
+
+            Slog.e(TAG, "Did not find valid FRP secret, FRP remains active.");
+            return false;
+        }
+    }
+
+    private boolean deactivateFrpWithFileSecret(String frpSecretFile) {
+        try {
+            return deactivateFrp(Files.readAllBytes(Paths.get(frpSecretFile)));
+        } catch (IOException e) {
+            Slog.w(TAG, "Failed to read FRP secret file: " + frpSecretFile + " "
+                    + e.getClass().getSimpleName());
+            return false;
+        }
+    }
+
+    private void moveFrpTempFileToPrimary() {
+        try {
+            Files.move(Paths.get(mFrpSecretTmpFile), Paths.get(mFrpSecretFile), REPLACE_EXISTING);
+        } catch (IOException e) {
+            Slog.e(TAG, "Error moving FRP backup file to primary (ignored)", e);
+        }
+    }
+
+    @VisibleForTesting
+    boolean isFrpActive() {
+        waitForInitDoneSignal();
+        synchronized (mLock) {
+            return mFrpActive;
+        }
+    }
+
+    /**
+     * Write the provided secret to the FRP secret file in /data and to the /persist partition.
+     *
+     * Writing is a three-step process, to ensure that we can recover from a crash at any point.
+     */
+    private boolean updateFrpSecret(byte[] secret) {
+        // 1.  Write the new secret to a temporary file, and sync the write.
+        try {
+            Files.write(
+                    Paths.get(mFrpSecretTmpFile), secret, WRITE, CREATE, TRUNCATE_EXISTING, SYNC);
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to write FRP secret file", e);
+            return false;
+        }
+
+        // 2.  Write the new secret to /persist, and sync the write.
+        if (!mInternalService.writeDataBuffer(getFrpSecretDataOffset(), ByteBuffer.wrap(secret))) {
+            return false;
+        }
+
+        // 3.  Move the temporary file to the primary file location.  Syncing doesn't matter
+        //     here.  In the event this update doesn't complete it will get done by
+        //     #automaticallyDeactivateFrpIfPossible() during the next boot.
+        moveFrpTempFileToPrimary();
+        return true;
+    }
+
+    /**
+     * Only for testing, activate FRP.
+     */
+    @VisibleForTesting
+    void activateFrp() {
+        synchronized (mLock) {
+            mFrpActive = true;
+        }
+    }
+
+    private boolean hasFrpSecretMagic() {
+        final byte[] frpMagic =
+                readDataBlock(getFrpSecretMagicOffset(), FRP_SECRET_MAGIC.length);
+        if (frpMagic == null) {
+            // Transient read error on the partition?
+            Slog.e(TAG, "Failed to read FRP magic region.");
+            return false;
+        }
+        return Arrays.equals(frpMagic, FRP_SECRET_MAGIC);
+    }
+
+    private byte[] getFrpSecret() {
+        return readDataBlock(getFrpSecretDataOffset(), FRP_SECRET_SIZE);
+    }
+
+    private boolean deactivateFrp(byte[] secret) {
+        if (secret == null || secret.length != FRP_SECRET_SIZE) {
+            Slog.w(TAG, "Attempted to deactivate FRP with a null or incorrectly-sized secret");
+            return false;
+        }
+
+        synchronized (mLock) {
+            if (!hasFrpSecretMagic()) {
+                Slog.i(TAG, "No FRP secret magic, system must have been upgraded.");
+                writeFrpMagicAndDefaultSecret();
+            }
+        }
+
+        final byte[] partitionSecret = getFrpSecret();
+        if (partitionSecret == null || partitionSecret.length != FRP_SECRET_SIZE) {
+            Slog.e(TAG, "Failed to read FRP secret from persistent data partition");
+            return false;
+        }
+
+        // MessageDigest.isEqual is constant-time, to protect secret deduction by timing attack.
+        if (MessageDigest.isEqual(secret, partitionSecret)) {
+            mFrpActive = false;
+            Slog.i(TAG, "FRP secret matched, FRP deactivated.");
+            return true;
+        } else {
+            Slog.e(TAG,
+                    "FRP deactivation failed with secret " + HexFormat.of().formatHex(secret));
+            return false;
+        }
+    }
+
+    private void writeFrpMagicAndDefaultSecret() {
+        try (FileChannel channel = getBlockOutputChannelIgnoringFrp()) {
+            synchronized (mLock) {
+                // Write secret first in case we crash between the writes, causing the first write
+                // to be synced but the second to be lost.
+                Slog.i(TAG, "Writing default FRP secret");
+                channel.position(getFrpSecretDataOffset());
+                channel.write(ByteBuffer.allocate(FRP_SECRET_SIZE));
+                channel.force(true);
+
+                Slog.i(TAG, "Writing FRP secret magic");
+                channel.position(getFrpSecretMagicOffset());
+                channel.write(ByteBuffer.wrap(FRP_SECRET_MAGIC));
+                channel.force(true);
+
+                mFrpActive = false;
+            }
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to write FRP magic and default secret", e);
+        }
+    }
+
+    @VisibleForTesting
+    byte[] readDataBlock(long offset, int length) {
+        try (DataInputStream inputStream =
+                     new DataInputStream(new FileInputStream(new File(mDataBlockFile)))) {
+            synchronized (mLock) {
+                inputStream.skip(offset);
+                byte[] bytes = new byte[length];
+                inputStream.readFully(bytes);
+                return bytes;
+            }
+        } catch (IOException e) {
+            throw new IllegalStateException("persistent partition not readable", e);
+        }
+    }
+
     private void doSetOemUnlockEnabledLocked(boolean enabled) {
-
         try (FileChannel channel = getBlockOutputChannel()) {
-
             channel.position(getBlockDeviceSize() - 1);
 
             ByteBuffer data = ByteBuffer.allocate(1);
@@ -475,7 +785,7 @@
             Slog.e(TAG, "unable to access persistent partition", e);
             return;
         } finally {
-            setProperty(OEM_UNLOCK_PROP, enabled ? "1" : "0");
+            setOemUnlockEnabledProperty(enabled);
         }
     }
 
@@ -507,8 +817,10 @@
     }
 
     private long doGetMaximumDataBlockSize() {
-        long actualSize = getBlockDeviceSize() - HEADER_SIZE - DIGEST_SIZE_BYTES
-                - TEST_MODE_RESERVED_SIZE - FRP_CREDENTIAL_RESERVED_SIZE - 1;
+        final long frpSecretSize =
+                mFrpEnforced ? (FRP_SECRET_MAGIC.length + FRP_SECRET_SIZE) : 0;
+        final long actualSize = getBlockDeviceSize() - HEADER_SIZE - DIGEST_SIZE_BYTES
+                - TEST_MODE_RESERVED_SIZE - frpSecretSize - FRP_CREDENTIAL_RESERVED_SIZE - 1;
         return actualSize <= MAX_DATA_BLOCK_SIZE ? actualSize : MAX_DATA_BLOCK_SIZE;
     }
 
@@ -526,6 +838,140 @@
     }
 
     private final IBinder mService = new IPersistentDataBlockService.Stub() {
+        private int printFrpStatus(PrintWriter pw, boolean printSecrets) {
+            enforceUid(Binder.getCallingUid());
+
+            pw.println("FRP state");
+            pw.println("=========");
+            pw.println("Enforcement enabled: " + mFrpEnforced);
+            pw.println("FRP state: " + mFrpActive);
+            printFrpDataFilesContents(pw, printSecrets);
+            printFrpSecret(pw, printSecrets);
+            pw.println("OEM unlock state: " + getOemUnlockEnabled());
+            pw.println("Bootloader lock state: " + getFlashLockState());
+            pw.println("Verified boot state: " + getVerifiedBootState());
+            pw.println("Has FRP credential handle: " + hasFrpCredentialHandle());
+            pw.println("FRP challenge block size: " + getDataBlockSize());
+            return 1;
+        }
+
+        private void printFrpSecret(PrintWriter pw, boolean printSecret) {
+            if (hasFrpSecretMagic()) {
+                if (printSecret) {
+                    pw.println("FRP secret in PDB: " + HexFormat.of().formatHex(
+                            readDataBlock(getFrpSecretDataOffset(), FRP_SECRET_SIZE)));
+                } else {
+                    pw.println("FRP secret present but omitted.");
+                }
+            } else {
+                pw.println("FRP magic not found");
+            }
+        }
+
+        private void printFrpDataFilesContents(PrintWriter pw, boolean printSecrets) {
+            printFrpDataFileContents(pw, mFrpSecretFile, printSecrets);
+            printFrpDataFileContents(pw, mFrpSecretTmpFile, printSecrets);
+        }
+
+        private void printFrpDataFileContents(
+                PrintWriter pw, String frpSecretFile, boolean printSecret) {
+            if (Files.exists(Paths.get(frpSecretFile))) {
+                if (printSecret) {
+                    try {
+                        pw.println("FRP secret in " + frpSecretFile + ": " + HexFormat.of()
+                                .formatHex(Files.readAllBytes(Paths.get(mFrpSecretFile))));
+                    } catch (IOException e) {
+                        Slog.e(TAG, "Failed to read " + frpSecretFile, e);
+                    }
+                } else {
+                    pw.println(
+                            "FRP secret file " + frpSecretFile + " exists, contents omitted.");
+                }
+            }
+        }
+
+        @Override
+        public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+                @Nullable FileDescriptor err,
+                @NonNull String[] args, @Nullable ShellCallback callback,
+                @NonNull ResultReceiver resultReceiver) throws RemoteException {
+            if (!mFrpEnforced) {
+                super.onShellCommand(in, out, err, args, callback, resultReceiver);
+                return;
+            }
+            new ShellCommand(){
+                @Override
+                public int onCommand(final String cmd) {
+                    if (cmd == null) {
+                        return handleDefaultCommands(cmd);
+                    }
+
+                    final PrintWriter pw = getOutPrintWriter();
+                    return switch (cmd) {
+                        case "status" -> printFrpStatus(pw, /* printSecrets */ !mFrpActive);
+                        case "activate" -> {
+                            activateFrp();
+                            yield printFrpStatus(pw, /* printSecrets */ !mFrpActive);
+                        }
+
+                        case "deactivate" -> {
+                            byte[] secret = hashSecretString(getNextArg());
+                            pw.println("Attempting to deactivate with: " + HexFormat.of().formatHex(
+                                    secret));
+                            pw.println("Deactivation "
+                                    + (deactivateFrp(secret) ? "succeeded" : "failed"));
+                            yield printFrpStatus(pw, /* printSecrets */ !mFrpActive);
+                        }
+
+                        case "auto_deactivate" -> {
+                            boolean result = automaticallyDeactivateFrpIfPossible();
+                            pw.println(
+                                    "Automatic deactivation " + (result ? "succeeded" : "failed"));
+                            yield printFrpStatus(pw, /* printSecrets */ !mFrpActive);
+                        }
+
+                        case "set_secret" -> {
+                            byte[] secret = new byte[FRP_SECRET_SIZE];
+                            String secretString = getNextArg();
+                            if (!secretString.equals("default")) {
+                                secret = hashSecretString(secretString);
+                            }
+                            pw.println("Setting FRP secret to: " + HexFormat.of()
+                                    .formatHex(secret) + " length: " + secret.length);
+                            setFactoryResetProtectionSecret(secret);
+                            yield printFrpStatus(pw, /* printSecrets */ !mFrpActive);
+                        }
+
+                        default -> handleDefaultCommands(cmd);
+                    };
+                }
+
+                @Override
+                public void onHelp() {
+                    final PrintWriter pw = getOutPrintWriter();
+                    pw.println("Commands");
+                    pw.println("status: Print the FRP state and associated information.");
+                    pw.println("activate:  Put FRP into \"active\" mode.");
+                    pw.println("deactivate <secret>:  Deactivate with a hash of 'secret'.");
+                    pw.println("auto_deactivate: Deactivate with the stored secret or the default");
+                    pw.println("set_secret <secret>:  Set the stored secret to a hash of `secret`");
+                }
+
+                private static byte[] hashSecretString(String secretInput) {
+                    try {
+                        // SHA-256 produces 32-byte outputs, same as the FRP secret size, so it's
+                        // a convenient way to "normalize" the length of whatever the user provided.
+                        // Also, hashing makes it difficult for an attacker to set the secret to a
+                        // known value that was randomly generated.
+                        MessageDigest md = MessageDigest.getInstance("SHA-256");
+                        return md.digest(secretInput.getBytes());
+                    } catch (NoSuchAlgorithmException e) {
+                        Slog.e(TAG, "Can't happen", e);
+                        return new byte[FRP_SECRET_SIZE];
+                    }
+                }
+            }.exec(this, in, out, err, args, callback, resultReceiver);
+        }
 
         /**
          * Write the data to the persistent data block.
@@ -545,7 +991,7 @@
             }
 
             ByteBuffer headerAndData = ByteBuffer.allocate(
-                                           data.length + HEADER_SIZE + DIGEST_SIZE_BYTES);
+                    data.length + HEADER_SIZE + DIGEST_SIZE_BYTES);
             headerAndData.put(new byte[DIGEST_SIZE_BYTES]);
             headerAndData.putInt(PARTITION_TYPE_MARKER);
             headerAndData.putInt(data.length);
@@ -619,6 +1065,7 @@
 
         @Override
         public void wipe() {
+            enforceFactoryResetProtectionInactive();
             enforceOemUnlockWritePermission();
 
             synchronized (mLock) {
@@ -626,7 +1073,7 @@
                 if (mIsFileBacked) {
                     try {
                         Files.write(Paths.get(mDataBlockFile), new byte[MAX_DATA_BLOCK_SIZE],
-                                StandardOpenOption.TRUNCATE_EXISTING);
+                                TRUNCATE_EXISTING);
                         ret = 0;
                     } catch (IOException e) {
                         ret = -1;
@@ -685,6 +1132,10 @@
             }
         }
 
+        private static String getVerifiedBootState() {
+            return SystemProperties.get(VERIFIED_BOOT_STATE);
+        }
+
         @Override
         public int getDataBlockSize() {
             enforcePersistentDataBlockAccess();
@@ -716,6 +1167,18 @@
             }
         }
 
+        private void enforceConfigureFrpPermissionOrPersistentDataBlockAccess() {
+            if (!mFrpEnforced) {
+                enforcePersistentDataBlockAccess();
+            } else {
+                if (mContext.checkCallingOrSelfPermission(
+                        Manifest.permission.CONFIGURE_FACTORY_RESET_PROTECTION)
+                        == PackageManager.PERMISSION_DENIED) {
+                    enforcePersistentDataBlockAccess();
+                }
+            }
+        }
+
         @Override
         public long getMaximumDataBlockSize() {
             enforceUid(Binder.getCallingUid());
@@ -724,7 +1187,7 @@
 
         @Override
         public boolean hasFrpCredentialHandle() {
-            enforcePersistentDataBlockAccess();
+            enforceConfigureFrpPermissionOrPersistentDataBlockAccess();
             try {
                 return mInternalService.getFrpCredentialHandle() != null;
             } catch (IllegalStateException e) {
@@ -751,9 +1214,51 @@
             synchronized (mLock) {
                 pw.println("mIsWritable: " + mIsWritable);
             }
+            printFrpStatus(pw, /* printSecrets */ false);
+        }
+
+        @Override
+        public boolean isFactoryResetProtectionActive() {
+            return isFrpActive();
+        }
+
+        @Override
+        public boolean deactivateFactoryResetProtection(byte[] secret) {
+            enforceConfigureFrpPermission();
+            return deactivateFrp(secret);
+        }
+
+        @Override
+        public boolean setFactoryResetProtectionSecret(byte[] secret) {
+            enforceUid(Binder.getCallingUid());
+            if (secret == null || secret.length != FRP_SECRET_SIZE) {
+                throw new IllegalArgumentException(
+                        "Invalid FRP secret: " + HexFormat.of().formatHex(secret));
+            }
+            enforceFactoryResetProtectionInactive();
+            return updateFrpSecret(secret);
         }
     };
 
+    private void enforceFactoryResetProtectionInactive() {
+        if (mFrpEnforced && isFrpActive()) {
+            throw new SecurityException("FRP is active");
+        }
+    }
+
+    @VisibleForTesting
+    boolean isUpgradingFromPreVRelease() {
+        PackageManagerInternal packageManagerInternal =
+                LocalServices.getService(PackageManagerInternal.class);
+        if (packageManagerInternal == null) {
+            Slog.e(TAG, "Unable to retrieve PackageManagerInternal");
+            return false;
+        }
+
+        return packageManagerInternal
+                .isUpgradingFromLowerThan(Build.VERSION_CODES.VANILLA_ICE_CREAM);
+    }
+
     private InternalService mInternalService = new InternalService();
 
     private class InternalService implements PersistentDataBlockManagerInternal {
@@ -792,6 +1297,14 @@
             return mAllowedUid;
         }
 
+        @Override
+        public boolean deactivateFactoryResetProtectionWithoutSecret() {
+            synchronized (mLock) {
+                mFrpActive = false;
+            }
+            return true;
+        }
+
         private void writeInternal(byte[] data, long offset, int dataLength) {
             checkArgument(data == null || data.length > 0, "data must be null or non-empty");
             checkArgument(
@@ -808,10 +1321,10 @@
             writeDataBuffer(offset, dataBuffer);
         }
 
-        private void writeDataBuffer(long offset, ByteBuffer dataBuffer) {
+        private boolean writeDataBuffer(long offset, ByteBuffer dataBuffer) {
             synchronized (mLock) {
                 if (!mIsWritable) {
-                    return;
+                    return false;
                 }
                 try (FileChannel channel = getBlockOutputChannel()) {
                     channel.position(offset);
@@ -819,10 +1332,10 @@
                     channel.force(true);
                 } catch (IOException e) {
                     Slog.e(TAG, "unable to access persistent partition", e);
-                    return;
+                    return false;
                 }
 
-                computeAndWriteDigestLocked();
+                return computeAndWriteDigestLocked();
             }
         }
 
@@ -864,5 +1377,5 @@
                 computeAndWriteDigestLocked();
             }
         }
-    };
+    }
 }
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 931fe29..524bad5 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -37,6 +37,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;
@@ -94,6 +95,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
@@ -113,6 +116,7 @@
         mHandler = new EventHandler(injector.getLooper(), this);
         mDiskFile = injector.getDiskFile();
         mContext = injector.getContext();
+        mCallbackHelper = injector.getBackgroundInstallControlCallbackHelper();
         UsageStatsManagerInternal usageStatsManagerInternal =
                 injector.getUsageStatsManagerInternal();
         usageStatsManagerInternal.registerListener(
@@ -151,6 +155,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)
@@ -275,6 +289,7 @@
 
         initBackgroundInstalledPackages();
         mBackgroundInstalledPackages.add(userId, packageName);
+        mCallbackHelper.notifyAllCallbacks(userId, packageName);
         writeBackgroundInstalledPackagesToDisk();
     }
 
@@ -569,6 +584,8 @@
 
         File getDiskFile();
 
+        BackgroundInstallControlCallbackHelper getBackgroundInstallControlCallbackHelper();
+
     }
 
     private static final class InjectorImpl implements Injector {
@@ -618,5 +635,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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 83dd0e8..dadafd7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -570,7 +570,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.
@@ -6484,6 +6485,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 e329f09..89589ed 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -396,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()) {
@@ -4794,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();
@@ -5194,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/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/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index a9e5a54..1c70af0 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -594,6 +594,7 @@
         }
         ai.applicationInfo = applicationInfo;
         ai.requiredDisplayCategory = a.getRequiredDisplayCategory();
+        ai.requireContentUriPermissionFromCaller = a.getRequireContentUriPermissionFromCaller();
         ai.setKnownActivityEmbeddingCerts(a.getKnownActivityEmbeddingCerts());
         assignFieldsComponentInfoParsedMainComponent(ai, a, pkgSetting, userId);
         return ai;
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/uri/UriGrantsManagerInternal.java b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
index 03c75e0..195e91c 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerInternal.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Intent;
+import android.content.pm.ActivityInfo.RequiredContentUriPermission;
 import android.content.pm.ProviderInfo;
 import android.net.Uri;
 import android.os.IBinder;
@@ -63,6 +64,15 @@
             String targetPkg, int targetUserId);
 
     /**
+     * Same as {@link #checkGrantUriPermissionFromIntent(Intent, int, String, int)}, but with an
+     * extra parameter {@code requireContentUriPermissionFromCaller}, which is the value from {@link
+     * android.R.attr#requireContentUriPermissionFromCaller} attribute.
+     */
+    NeededUriGrants checkGrantUriPermissionFromIntent(Intent intent, int callingUid,
+            String targetPkg, int targetUserId,
+            @RequiredContentUriPermission int requireContentUriPermissionFromCaller);
+
+    /**
      * Extend a previously calculated set of permissions grants to the given
      * owner. All security checks will have already been performed as part of
      * calculating {@link NeededUriGrants}.
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index ce2cbed..d2f6701 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -23,6 +23,10 @@
 import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
 import static android.content.Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
 import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+import static android.content.pm.ActivityInfo.CONTENT_URI_PERMISSION_NONE;
+import static android.content.pm.ActivityInfo.CONTENT_URI_PERMISSION_READ_OR_WRITE;
+import static android.content.pm.ActivityInfo.isRequiredContentUriPermissionRead;
+import static android.content.pm.ActivityInfo.isRequiredContentUriPermissionWrite;
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
 import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
@@ -53,6 +57,8 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ActivityInfo.RequiredContentUriPermission;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
@@ -609,7 +615,8 @@
 
     /** Like checkGrantUriPermission, but takes an Intent. */
     private NeededUriGrants checkGrantUriPermissionFromIntentUnlocked(int callingUid,
-            String targetPkg, Intent intent, int mode, NeededUriGrants needed, int targetUserId) {
+            String targetPkg, Intent intent, int mode, NeededUriGrants needed, int targetUserId,
+            @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller) {
         if (DEBUG) Slog.v(TAG,
                 "Checking URI perm to data=" + (intent != null ? intent.getData() : null)
                         + " clip=" + (intent != null ? intent.getClipData() : null)
@@ -647,6 +654,10 @@
         }
         if (data != null) {
             GrantUri grantUri = GrantUri.resolve(contentUserHint, data, mode);
+            if (android.security.Flags.contentUriPermissionApis()) {
+                enforceRequireContentUriPermissionFromCaller(requireContentUriPermissionFromCaller,
+                        grantUri, callingUid);
+            }
             targetUid = checkGrantUriPermissionUnlocked(callingUid, targetPkg, grantUri, mode,
                     targetUid);
             if (targetUid > 0) {
@@ -661,6 +672,10 @@
                 Uri uri = clip.getItemAt(i).getUri();
                 if (uri != null) {
                     GrantUri grantUri = GrantUri.resolve(contentUserHint, uri, mode);
+                    if (android.security.Flags.contentUriPermissionApis()) {
+                        enforceRequireContentUriPermissionFromCaller(
+                                requireContentUriPermissionFromCaller, grantUri, callingUid);
+                    }
                     targetUid = checkGrantUriPermissionUnlocked(callingUid, targetPkg,
                             grantUri, mode, targetUid);
                     if (targetUid > 0) {
@@ -673,7 +688,8 @@
                     Intent clipIntent = clip.getItemAt(i).getIntent();
                     if (clipIntent != null) {
                         NeededUriGrants newNeeded = checkGrantUriPermissionFromIntentUnlocked(
-                                callingUid, targetPkg, clipIntent, mode, needed, targetUserId);
+                                callingUid, targetPkg, clipIntent, mode, needed, targetUserId,
+                                requireContentUriPermissionFromCaller);
                         if (newNeeded != null) {
                             needed = newNeeded;
                         }
@@ -685,6 +701,38 @@
         return needed;
     }
 
+    private void enforceRequireContentUriPermissionFromCaller(
+            @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller,
+            GrantUri grantUri, int uid) {
+        // Ignore if requireContentUriPermissionFromCaller hasn't been set or the URI is a
+        // non-content URI.
+        if (requireContentUriPermissionFromCaller == null
+                || requireContentUriPermissionFromCaller == CONTENT_URI_PERMISSION_NONE
+                || !ContentResolver.SCHEME_CONTENT.equals(grantUri.uri.getScheme())) {
+            return;
+        }
+
+        final boolean readMet = !isRequiredContentUriPermissionRead(
+                requireContentUriPermissionFromCaller)
+                || checkContentUriPermissionFullUnlocked(grantUri, uid,
+                Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+        final boolean writeMet = !isRequiredContentUriPermissionWrite(
+                requireContentUriPermissionFromCaller)
+                || checkContentUriPermissionFullUnlocked(grantUri, uid,
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+        boolean hasPermission =
+                requireContentUriPermissionFromCaller == CONTENT_URI_PERMISSION_READ_OR_WRITE
+                        ? (readMet || writeMet) : (readMet && writeMet);
+
+        if (!hasPermission) {
+            throw new SecurityException("You can't launch this activity because you don't have the"
+                    + " required " + ActivityInfo.requiredContentUriPermissionToShortString(
+                            requireContentUriPermissionFromCaller) + " access to " + grantUri.uri);
+        }
+    }
+
     @GuardedBy("mLock")
     private void readGrantedUriPermissionsLocked() {
         if (DEBUG) Slog.v(TAG, "readGrantedUriPermissions()");
@@ -1560,9 +1608,24 @@
         @Override
         public NeededUriGrants checkGrantUriPermissionFromIntent(Intent intent, int callingUid,
                 String targetPkg, int targetUserId) {
+            return internalCheckGrantUriPermissionFromIntent(intent, callingUid, targetPkg,
+                    targetUserId, /* requireContentUriPermissionFromCaller */ null);
+        }
+
+        @Override
+        public NeededUriGrants checkGrantUriPermissionFromIntent(Intent intent, int callingUid,
+                String targetPkg, int targetUserId, int requireContentUriPermissionFromCaller) {
+            return internalCheckGrantUriPermissionFromIntent(intent, callingUid, targetPkg,
+                    targetUserId, requireContentUriPermissionFromCaller);
+        }
+
+        private NeededUriGrants internalCheckGrantUriPermissionFromIntent(Intent intent,
+                int callingUid, String targetPkg, int targetUserId,
+                @Nullable Integer requireContentUriPermissionFromCaller) {
             final int mode = (intent != null) ? intent.getFlags() : 0;
             return UriGrantsManagerService.this.checkGrantUriPermissionFromIntentUnlocked(
-                    callingUid, targetPkg, intent, mode, null, targetUserId);
+                    callingUid, targetPkg, intent, mode, null, targetUserId,
+                    requireContentUriPermissionFromCaller);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index 8f8fe3c..17a9e33 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -248,8 +248,6 @@
             IVibratorController vibratorController =
                     mVibratorControllerHolder.getVibratorController();
             if (vibratorController == null) {
-                Slog.d(TAG, "Unable to check if should request vibration params. "
-                        + "There is no registered IVibrationController.");
                 return false;
             }
 
diff --git a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
index b2bbcda..88d3daf 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);
+                    }
+                });
     }
 
     /**
@@ -84,4 +118,62 @@
         }
         post(service -> service.provideData(data, sharedMemory, callback));
     }
+
+    /**
+     * Registers a data request observer with WearableSensingService.
+     *
+     * @param dataType The data type to listen to. Values are defined by the application that
+     *     implements WearableSensingService.
+     * @param dataRequestCallback The observer to send data requests to.
+     * @param dataRequestObserverId The unique ID for the data request observer. It will be used for
+     *     unregistering the observer.
+     * @param packageName The package name of the app that will receive the data requests.
+     * @param statusCallback The callback for status of the method call.
+     */
+    public void registerDataRequestObserver(
+            int dataType,
+            RemoteCallback dataRequestCallback,
+            int dataRequestObserverId,
+            String packageName,
+            RemoteCallback statusCallback) {
+        if (DEBUG) {
+            Slog.i(TAG, "Registering data request observer.");
+        }
+        var unused =
+                post(
+                        service ->
+                                service.registerDataRequestObserver(
+                                        dataType,
+                                        dataRequestCallback,
+                                        dataRequestObserverId,
+                                        packageName,
+                                        statusCallback));
+    }
+
+    /**
+     * Unregisters a previously registered data request observer.
+     *
+     * @param dataType The data type the observer was registered against.
+     * @param dataRequestObserverId The unique ID of the observer to unregister.
+     * @param packageName The package name of the app that will receive requests sent to the
+     *     observer.
+     * @param statusCallback The callback for status of the method call.
+     */
+    public void unregisterDataRequestObserver(
+            int dataType,
+            int dataRequestObserverId,
+            String packageName,
+            RemoteCallback statusCallback) {
+        if (DEBUG) {
+            Slog.i(TAG, "Unregistering data request observer.");
+        }
+        var unused =
+                post(
+                        service ->
+                                service.unregisterDataRequestObserver(
+                                        dataType,
+                                        dataRequestObserverId,
+                                        packageName,
+                                        statusCallback));
+    }
 }
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
index e73fd0f..0e8b82f 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(
@@ -193,4 +262,65 @@
             mRemoteService.provideData(data, sharedMemory, callback);
         }
     }
+
+    /**
+     * Handles registering a data request observer.
+     *
+     * @param dataType The data type to listen to. Values are defined by the application that
+     *     implements WearableSensingService.
+     * @param dataRequestObserver The observer to register.
+     * @param dataRequestObserverId The unique ID for the data request observer. It will be used for
+     *     unregistering the observer.
+     * @param packageName The package name of the app that will receive the data requests.
+     * @param statusCallback The callback for status of the method call.
+     */
+    public void onRegisterDataRequestObserver(
+            int dataType,
+            RemoteCallback dataRequestObserver,
+            int dataRequestObserverId,
+            String packageName,
+            RemoteCallback statusCallback) {
+        synchronized (mLock) {
+            if (!setUpServiceIfNeeded()) {
+                Slog.w(TAG, "Detection service is not available at this moment.");
+                notifyStatusCallback(
+                        statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+            ensureRemoteServiceInitiated();
+            mRemoteService.registerDataRequestObserver(
+                    dataType,
+                    dataRequestObserver,
+                    dataRequestObserverId,
+                    packageName,
+                    statusCallback);
+        }
+    }
+
+    /**
+     * Handles unregistering a previously registered data request observer.
+     *
+     * @param dataType The data type the observer was registered against.
+     * @param dataRequestObserverId The unique ID of the observer to unregister.
+     * @param packageName The package name of the app that will receive requests sent to the
+     *     observer.
+     * @param statusCallback The callback for status of the method call.
+     */
+    public void onUnregisterDataRequestObserver(
+            int dataType,
+            int dataRequestObserverId,
+            String packageName,
+            RemoteCallback statusCallback) {
+        synchronized (mLock) {
+            if (!setUpServiceIfNeeded()) {
+                Slog.w(TAG, "Detection service is not available at this moment.");
+                notifyStatusCallback(
+                        statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+            ensureRemoteServiceInitiated();
+            mRemoteService.unregisterDataRequestObserver(
+                    dataType, dataRequestObserverId, packageName, statusCallback);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index 4cc2c02..78952fa 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -21,12 +21,18 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.BroadcastOptions;
+import android.app.ComponentOptions;
+import android.app.PendingIntent;
 import android.app.ambientcontext.AmbientContextEvent;
 import android.app.wearable.IWearableSensingManager;
+import android.app.wearable.WearableSensingDataRequest;
 import android.app.wearable.WearableSensingManager;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.os.RemoteCallback;
@@ -35,6 +41,8 @@
 import android.os.ShellCallback;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
+import android.service.wearable.WearableSensingDataRequester;
+import android.text.TextUtils;
 import android.util.Slog;
 
 import com.android.internal.R;
@@ -44,10 +52,13 @@
 import com.android.server.infra.AbstractMasterSystemService;
 import com.android.server.infra.FrameworkResourcesServiceNameResolver;
 import com.android.server.pm.KnownPackages;
+import com.android.server.utils.quota.MultiRateLimiter;
 
 import java.io.FileDescriptor;
+import java.util.HashSet;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 
 /**
@@ -64,9 +75,38 @@
 
     /** Default value in absence of {@link DeviceConfig} override. */
     private static final boolean DEFAULT_SERVICE_ENABLED = true;
+
     public static final int MAX_TEMPORARY_SERVICE_DURATION_MS = 30000;
 
+    private static final String RATE_LIMITER_PACKAGE_NAME = "android";
+    private static final String RATE_LIMITER_TAG =
+            WearableSensingManagerService.class.getSimpleName();
+
+    private static final class DataRequestObserverContext {
+        final int mDataType;
+        final int mUserId;
+        final int mDataRequestObserverId;
+        @NonNull final PendingIntent mDataRequestPendingIntent;
+        @NonNull final RemoteCallback mDataRequestRemoteCallback;
+
+        DataRequestObserverContext(
+                int dataType,
+                int userId,
+                int dataRequestObserverId,
+                PendingIntent dataRequestPendingIntent,
+                RemoteCallback dataRequestRemoteCallback) {
+            mDataType = dataType;
+            mUserId = userId;
+            mDataRequestObserverId = dataRequestObserverId;
+            mDataRequestPendingIntent = dataRequestPendingIntent;
+            mDataRequestRemoteCallback = dataRequestRemoteCallback;
+        }
+    }
+
     private final Context mContext;
+    private final AtomicInteger mNextDataRequestObserverId = new AtomicInteger(1);
+    private final Set<DataRequestObserverContext> mDataRequestObserverContexts = new HashSet<>();
+    private final MultiRateLimiter mDataRequestRateLimiter;
     volatile boolean mIsServiceEnabled;
 
     public WearableSensingManagerService(Context context) {
@@ -78,6 +118,12 @@
                 PACKAGE_UPDATE_POLICY_REFRESH_EAGER
                         | /*To avoid high latency*/ PACKAGE_RESTART_POLICY_REFRESH_EAGER);
         mContext = context;
+        mDataRequestRateLimiter =
+                new MultiRateLimiter.Builder(context)
+                        .addRateLimit(
+                                WearableSensingDataRequest.getRateLimit(),
+                                WearableSensingDataRequest.getRateLimitWindowSize())
+                        .build();
     }
 
     @Override
@@ -192,6 +238,96 @@
         }
     }
 
+    private DataRequestObserverContext getDataRequestObserverContext(
+            int dataType, int userId, PendingIntent dataRequestPendingIntent) {
+        synchronized (mDataRequestObserverContexts) {
+            for (DataRequestObserverContext observerContext : mDataRequestObserverContexts) {
+                if (observerContext.mDataType == dataType
+                        && observerContext.mUserId == userId
+                        && observerContext.mDataRequestPendingIntent.equals(
+                                dataRequestPendingIntent)) {
+                    return observerContext;
+                }
+            }
+        }
+        return null;
+    }
+
+    @NonNull
+    private RemoteCallback createDataRequestRemoteCallback(
+            PendingIntent dataRequestPendingIntent, int userId) {
+        return new RemoteCallback(
+                bundle -> {
+                    WearableSensingDataRequest dataRequest =
+                            bundle.getParcelable(
+                                    WearableSensingDataRequest.REQUEST_BUNDLE_KEY,
+                                    WearableSensingDataRequest.class);
+                    if (dataRequest == null) {
+                        Slog.e(TAG, "Received data request callback without a request.");
+                        return;
+                    }
+                    RemoteCallback dataRequestStatusCallback =
+                            bundle.getParcelable(
+                                    WearableSensingDataRequest.REQUEST_STATUS_CALLBACK_BUNDLE_KEY,
+                                    RemoteCallback.class);
+                    if (dataRequestStatusCallback == null) {
+                        Slog.e(TAG, "Received data request callback without a status callback.");
+                        return;
+                    }
+                    if (dataRequest.getDataSize()
+                            > WearableSensingDataRequest.getMaxRequestSize()) {
+                        Slog.w(
+                                TAG,
+                                TextUtils.formatSimple(
+                                        "WearableSensingDataRequest size exceeds the maximum"
+                                            + " allowed size of %s bytes. Dropping the request.",
+                                        WearableSensingDataRequest.getMaxRequestSize()));
+                        WearableSensingManagerPerUserService.notifyStatusCallback(
+                                dataRequestStatusCallback,
+                                WearableSensingDataRequester.STATUS_TOO_LARGE);
+                        return;
+                    }
+                    if (!mDataRequestRateLimiter.isWithinQuota(
+                            userId, RATE_LIMITER_PACKAGE_NAME, RATE_LIMITER_TAG)) {
+                        Slog.w(TAG, "Data request exceeded rate limit. Dropping the request.");
+                        WearableSensingManagerPerUserService.notifyStatusCallback(
+                                dataRequestStatusCallback,
+                                WearableSensingDataRequester.STATUS_TOO_FREQUENT);
+                        return;
+                    }
+                    Intent intent = new Intent();
+                    intent.putExtra(
+                            WearableSensingManager.EXTRA_WEARABLE_SENSING_DATA_REQUEST,
+                            dataRequest);
+                    BroadcastOptions options = BroadcastOptions.makeBasic();
+                    options.setPendingIntentBackgroundActivityStartMode(
+                            ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED);
+                    mDataRequestRateLimiter.noteEvent(
+                            userId, RATE_LIMITER_PACKAGE_NAME, RATE_LIMITER_TAG);
+                    final long previousCallingIdentity = Binder.clearCallingIdentity();
+                    try {
+                        dataRequestPendingIntent.send(
+                                getContext(), 0, intent, null, null, null, options.toBundle());
+                        WearableSensingManagerPerUserService.notifyStatusCallback(
+                                dataRequestStatusCallback,
+                                WearableSensingDataRequester.STATUS_SUCCESS);
+                        Slog.i(
+                                TAG,
+                                TextUtils.formatSimple(
+                                        "Sending data request to %s: %s",
+                                        dataRequestPendingIntent.getCreatorPackage(),
+                                        dataRequest.toExpandedString()));
+                    } catch (PendingIntent.CanceledException e) {
+                        Slog.w(TAG, "Could not deliver pendingIntent: " + dataRequestPendingIntent);
+                        WearableSensingManagerPerUserService.notifyStatusCallback(
+                                dataRequestStatusCallback,
+                                WearableSensingDataRequester.STATUS_OBSERVER_CANCELLED);
+                    } finally {
+                        Binder.restoreCallingIdentity(previousCallingIdentity);
+                    }
+                });
+    }
+
     private void callPerUserServiceIfExist(
             Consumer<WearableSensingManagerPerUserService> serviceConsumer,
             RemoteCallback statusCallback) {
@@ -211,9 +347,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);
@@ -242,8 +396,8 @@
                     Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG);
             if (!mIsServiceEnabled) {
                 Slog.w(TAG, "Service not available.");
-                WearableSensingManagerPerUserService.notifyStatusCallback(callback,
-                        WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+                WearableSensingManagerPerUserService.notifyStatusCallback(
+                        callback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
                 return;
             }
             callPerUserServiceIfExist(
@@ -252,6 +406,96 @@
         }
 
         @Override
+        public void registerDataRequestObserver(
+                int dataType,
+                PendingIntent dataRequestPendingIntent,
+                RemoteCallback statusCallback) {
+            Slog.i(TAG, "WearableSensingManagerInternal registerDataRequestObserver.");
+            Objects.requireNonNull(dataRequestPendingIntent);
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG);
+            if (!mIsServiceEnabled) {
+                Slog.w(TAG, "Service not available.");
+                WearableSensingManagerPerUserService.notifyStatusCallback(
+                        statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+            int userId = UserHandle.getCallingUserId();
+            RemoteCallback dataRequestCallback;
+            int dataRequestObserverId;
+            synchronized (mDataRequestObserverContexts) {
+                DataRequestObserverContext previousObserverContext =
+                        getDataRequestObserverContext(dataType, userId, dataRequestPendingIntent);
+                if (previousObserverContext != null) {
+                    Slog.i(TAG, "Received duplicate data request observer.");
+                    dataRequestCallback = previousObserverContext.mDataRequestRemoteCallback;
+                    dataRequestObserverId = previousObserverContext.mDataRequestObserverId;
+                } else {
+                    dataRequestCallback =
+                            createDataRequestRemoteCallback(dataRequestPendingIntent, userId);
+                    dataRequestObserverId = mNextDataRequestObserverId.getAndIncrement();
+                    mDataRequestObserverContexts.add(
+                            new DataRequestObserverContext(
+                                    dataType,
+                                    userId,
+                                    dataRequestObserverId,
+                                    dataRequestPendingIntent,
+                                    dataRequestCallback));
+                }
+            }
+            callPerUserServiceIfExist(
+                    service ->
+                            service.onRegisterDataRequestObserver(
+                                    dataType,
+                                    dataRequestCallback,
+                                    dataRequestObserverId,
+                                    dataRequestPendingIntent.getCreatorPackage(),
+                                    statusCallback),
+                    statusCallback);
+        }
+
+        @Override
+        public void unregisterDataRequestObserver(
+                int dataType,
+                PendingIntent dataRequestPendingIntent,
+                RemoteCallback statusCallback) {
+            Slog.i(TAG, "WearableSensingManagerInternal unregisterDataRequestObserver.");
+            Objects.requireNonNull(dataRequestPendingIntent);
+            Objects.requireNonNull(statusCallback);
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG);
+            if (!mIsServiceEnabled) {
+                Slog.w(TAG, "Service not available.");
+                WearableSensingManagerPerUserService.notifyStatusCallback(
+                        statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+            int userId = UserHandle.getCallingUserId();
+            int previousDataRequestObserverId;
+            String pendingIntentCreatorPackage;
+            synchronized (mDataRequestObserverContexts) {
+                DataRequestObserverContext previousObserverContext =
+                        getDataRequestObserverContext(dataType, userId, dataRequestPendingIntent);
+                if (previousObserverContext == null) {
+                    Slog.w(TAG, "Previous observer not found, cannot unregister.");
+                    return;
+                }
+                mDataRequestObserverContexts.remove(previousObserverContext);
+                previousDataRequestObserverId = previousObserverContext.mDataRequestObserverId;
+                pendingIntentCreatorPackage =
+                        previousObserverContext.mDataRequestPendingIntent.getCreatorPackage();
+            }
+            callPerUserServiceIfExist(
+                    service ->
+                            service.onUnregisterDataRequestObserver(
+                                    dataType,
+                                    previousDataRequestObserverId,
+                                    pendingIntentCreatorPackage,
+                                    statusCallback),
+                    statusCallback);
+        }
+
+        @Override
         public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
                 String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
             new WearableSensingShellCommand(WearableSensingManagerService.this).exec(
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/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 19ea9f9..ee865d3 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1610,7 +1610,7 @@
                 Slog.i(LOG_TAG, "computeChangedWindows()");
             }
 
-            final List<WindowInfo> windows = new ArrayList<>();
+            final List<WindowInfo> windows;
             final List<AccessibilityWindow> visibleWindows = new ArrayList<>();
             final int topFocusedDisplayId;
             IBinder topFocusedWindowToken = null;
@@ -1640,69 +1640,11 @@
                 }
                 final Display display = dc.getDisplay();
                 display.getRealSize(mTempPoint);
-                final int screenWidth = mTempPoint.x;
-                final int screenHeight = mTempPoint.y;
-
-                Region unaccountedSpace = mTempRegion;
-                unaccountedSpace.set(0, 0, screenWidth, screenHeight);
 
                 mA11yWindowsPopulator.populateVisibleWindowsOnScreenLocked(
                         mDisplayId, visibleWindows);
-                Set<IBinder> addedWindows = mTempBinderSet;
-                addedWindows.clear();
 
-                boolean focusedWindowAdded = false;
-
-                final int visibleWindowCount = visibleWindows.size();
-
-                // Iterate until we figure out what is touchable for the entire screen.
-                for (int i = 0; i < visibleWindowCount; i++) {
-                    final AccessibilityWindow a11yWindow = visibleWindows.get(i);
-                    final Region regionInWindow = new Region();
-                    a11yWindow.getTouchableRegionInWindow(regionInWindow);
-                    if (windowMattersToAccessibility(a11yWindow, regionInWindow,
-                            unaccountedSpace)) {
-                        addPopulatedWindowInfo(a11yWindow, regionInWindow, windows, addedWindows);
-                        if (windowMattersToUnaccountedSpaceComputation(a11yWindow)) {
-                            updateUnaccountedSpace(a11yWindow, unaccountedSpace);
-                        }
-                        focusedWindowAdded |= a11yWindow.isFocused();
-                    } else if (a11yWindow.isUntouchableNavigationBar()) {
-                        // If this widow is navigation bar without touchable region, accounting the
-                        // region of navigation bar inset because all touch events from this region
-                        // would be received by launcher, i.e. this region is a un-touchable one
-                        // for the application.
-                        unaccountedSpace.op(
-                                getSystemBarInsetsFrame(
-                                        mService.mWindowMap.get(a11yWindow.getWindowInfo().token)),
-                                unaccountedSpace,
-                                Region.Op.REVERSE_DIFFERENCE);
-                    }
-
-                    if (unaccountedSpace.isEmpty() && focusedWindowAdded) {
-                        break;
-                    }
-                }
-
-                // Remove child/parent references to windows that were not added.
-                final int windowCount = windows.size();
-                for (int i = 0; i < windowCount; i++) {
-                    WindowInfo window = windows.get(i);
-                    if (!addedWindows.contains(window.parentToken)) {
-                        window.parentToken = null;
-                    }
-                    if (window.childTokens != null) {
-                        final int childTokenCount = window.childTokens.size();
-                        for (int j = childTokenCount - 1; j >= 0; j--) {
-                            if (!addedWindows.contains(window.childTokens.get(j))) {
-                                window.childTokens.remove(j);
-                            }
-                        }
-                        // Leave the child token list if empty.
-                    }
-                }
-
-                addedWindows.clear();
+                windows = buildWindowInfoListLocked(visibleWindows, mTempPoint);
 
                 // Gets the top focused display Id and window token for supporting multi-display.
                 topFocusedDisplayId = mService.mRoot.getTopFocusedDisplayContent().getDisplayId();
@@ -1718,6 +1660,74 @@
             mInitialized = true;
         }
 
+        /**
+         * From a list of windows, decides windows to be exposed to accessibility based on touchable
+         * region in the screen.
+         */
+        private List<WindowInfo> buildWindowInfoListLocked(List<AccessibilityWindow> visibleWindows,
+                Point screenSize) {
+            final List<WindowInfo> windows = new ArrayList<>();
+            final Set<IBinder> addedWindows = mTempBinderSet;
+            addedWindows.clear();
+
+            boolean focusedWindowAdded = false;
+
+            final int visibleWindowCount = visibleWindows.size();
+
+            Region unaccountedSpace = mTempRegion;
+            unaccountedSpace.set(0, 0, screenSize.x, screenSize.y);
+
+            // Iterate until we figure out what is touchable for the entire screen.
+            for (int i = 0; i < visibleWindowCount; i++) {
+                final AccessibilityWindow a11yWindow = visibleWindows.get(i);
+                final Region regionInWindow = new Region();
+                a11yWindow.getTouchableRegionInWindow(regionInWindow);
+                if (windowMattersToAccessibility(a11yWindow, regionInWindow, unaccountedSpace)) {
+                    addPopulatedWindowInfo(a11yWindow, regionInWindow, windows, addedWindows);
+                    if (windowMattersToUnaccountedSpaceComputation(a11yWindow)) {
+                        updateUnaccountedSpace(a11yWindow, unaccountedSpace);
+                    }
+                    focusedWindowAdded |= a11yWindow.isFocused();
+                } else if (a11yWindow.isUntouchableNavigationBar()) {
+                    // If this widow is navigation bar without touchable region, accounting the
+                    // region of navigation bar inset because all touch events from this region
+                    // would be received by launcher, i.e. this region is a un-touchable one
+                    // for the application.
+                    unaccountedSpace.op(
+                            getSystemBarInsetsFrame(
+                                    mService.mWindowMap.get(a11yWindow.getWindowInfo().token)),
+                            unaccountedSpace,
+                            Region.Op.REVERSE_DIFFERENCE);
+                }
+
+                if (unaccountedSpace.isEmpty() && focusedWindowAdded) {
+                    break;
+                }
+            }
+
+            // Remove child/parent references to windows that were not added.
+            final int windowCount = windows.size();
+            for (int i = 0; i < windowCount; i++) {
+                WindowInfo window = windows.get(i);
+                if (!addedWindows.contains(window.parentToken)) {
+                    window.parentToken = null;
+                }
+                if (window.childTokens != null) {
+                    final int childTokenCount = window.childTokens.size();
+                    for (int j = childTokenCount - 1; j >= 0; j--) {
+                        if (!addedWindows.contains(window.childTokens.get(j))) {
+                            window.childTokens.remove(j);
+                        }
+                    }
+                    // Leave the child token list if empty.
+                }
+            }
+
+            addedWindows.clear();
+
+            return windows;
+        }
+
         // Some windows should be excluded from unaccounted space computation, though they still
         // should be reported
         private boolean windowMattersToUnaccountedSpaceComputation(AccessibilityWindow a11yWindow) {
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/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 85580ac..d99000e 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -591,9 +591,18 @@
 
             // Carefully collect grants without holding lock
             if (activityInfo != null) {
-                intentGrants = supervisor.mService.mUgmInternal.checkGrantUriPermissionFromIntent(
-                        intent, resolvedCallingUid, activityInfo.applicationInfo.packageName,
-                        UserHandle.getUserId(activityInfo.applicationInfo.uid));
+                if (android.security.Flags.contentUriPermissionApis()) {
+                    intentGrants = supervisor.mService.mUgmInternal
+                            .checkGrantUriPermissionFromIntent(intent, resolvedCallingUid,
+                                    activityInfo.applicationInfo.packageName,
+                                    UserHandle.getUserId(activityInfo.applicationInfo.uid),
+                                    activityInfo.requireContentUriPermissionFromCaller);
+                } else {
+                    intentGrants = supervisor.mService.mUgmInternal
+                            .checkGrantUriPermissionFromIntent(intent, resolvedCallingUid,
+                                    activityInfo.applicationInfo.packageName,
+                                    UserHandle.getUserId(activityInfo.applicationInfo.uid));
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 0def5a1..8773366 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -276,6 +276,7 @@
 import com.android.server.am.PendingIntentRecord;
 import com.android.server.am.UserState;
 import com.android.server.firewall.IntentFirewall;
+import com.android.server.grammaticalinflection.GrammaticalInflectionManagerInternal;
 import com.android.server.pm.UserManagerService;
 import com.android.server.policy.PermissionPolicyInternal;
 import com.android.server.sdksandbox.SdkSandboxManagerLocal;
@@ -317,7 +318,6 @@
  * {@hide}
  */
 public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
-    private static final String GRAMMATICAL_GENDER_PROPERTY = "persist.sys.grammatical_gender";
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityTaskManagerService" : TAG_ATM;
     static final String TAG_ROOT_TASK = TAG + POSTFIX_ROOT_TASK;
     static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
@@ -381,6 +381,7 @@
     private PowerManagerInternal mPowerManagerInternal;
     private UsageStatsManagerInternal mUsageStatsInternal;
 
+    GrammaticalInflectionManagerInternal mGrammaticalManagerInternal;
     PendingIntentController mPendingIntentController;
     IntentFirewall mIntentFirewall;
 
@@ -881,6 +882,8 @@
             mActivityClientController.onSystemReady();
             // TODO(b/258792202) Cleanup once ASM is ready to launch
             ActivitySecurityModelFeatureFlags.initialize(mContext.getMainExecutor(), pm);
+            mGrammaticalManagerInternal = LocalServices.getService(
+                    GrammaticalInflectionManagerInternal.class);
         }
     }
 
@@ -938,13 +941,8 @@
             configuration.setLayoutDirection(configuration.locale);
         }
 
-        // Retrieve the grammatical gender from system property, set it into configuration which
-        // will get updated later if the grammatical gender raw value of current configuration is
-        // {@link Configuration#GRAMMATICAL_GENDER_UNDEFINED}.
-        if (configuration.getGrammaticalGenderRaw() == Configuration.GRAMMATICAL_GENDER_UNDEFINED) {
-            configuration.setGrammaticalGender(SystemProperties.getInt(GRAMMATICAL_GENDER_PROPERTY,
-                    Configuration.GRAMMATICAL_GENDER_UNDEFINED));
-        }
+        configuration.setGrammaticalGender(
+                mGrammaticalManagerInternal.retrieveSystemGrammaticalGender(configuration));
 
         synchronized (mGlobalLock) {
             mForceResizableActivities = forceResizable;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 25646f1..609ad1e 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -83,6 +83,7 @@
 import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_NONE;
 import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION;
 import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_ACTION_PENDING;
+import static com.android.systemui.shared.Flags.enableHomeDelay;
 
 import static java.lang.Integer.MAX_VALUE;
 
@@ -1444,6 +1445,7 @@
             aInfo = info.first;
             homeIntent = info.second;
         }
+
         if (aInfo == null || homeIntent == null) {
             return false;
         }
@@ -1452,6 +1454,11 @@
             return false;
         }
 
+        if (enableHomeDelay() && !mService.mAmInternal.getThemeOverlayReadiness()) {
+            Slog.d(TAG, "ThemeHomeDelay: Home launch was deferred.");
+            return false;
+        }
+
         // Updates the home component of the intent.
         homeIntent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
         homeIntent.setFlags(homeIntent.getFlags() | FLAG_ACTIVITY_NEW_TASK);
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 57448cb..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;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 6d2e8cc..6acf1f3 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -28,6 +28,7 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.server.am.ProcessList.INVALID_ADJ;
+import static com.android.server.grammaticalinflection.GrammaticalInflectionUtils.checkSystemGrammaticalGenderPermission;
 import static com.android.server.wm.ActivityRecord.State.DESTROYED;
 import static com.android.server.wm.ActivityRecord.State.DESTROYING;
 import static com.android.server.wm.ActivityRecord.State.PAUSED;
@@ -298,6 +299,8 @@
      */
     private volatile int mActivityStateFlags = ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER;
 
+    private boolean mCanUseSystemGrammaticalGender;
+
     public WindowProcessController(@NonNull ActivityTaskManagerService atm,
             @NonNull ApplicationInfo info, String name, int uid, int userId, Object owner,
             @NonNull WindowProcessListener listener) {
@@ -319,6 +322,9 @@
             mIsActivityConfigOverrideAllowed = false;
         }
 
+        mCanUseSystemGrammaticalGender = mAtm.mGrammaticalManagerInternal != null
+                && mAtm.mGrammaticalManagerInternal.canGetSystemGrammaticalGender(mUid,
+                mInfo.packageName);
         onConfigurationChanged(atm.getGlobalConfiguration());
         mAtm.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, mInfo.packageName);
     }
@@ -1568,6 +1574,11 @@
             return;
         }
 
+        if (mCanUseSystemGrammaticalGender) {
+            config.setGrammaticalGender(
+                    mAtm.mGrammaticalManagerInternal.getSystemGrammaticalGender(mUserId));
+        }
+
         if (mPauseConfigurationDispatchCount > 0) {
             mHasPendingConfigurationChange = true;
             return;
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 810090a..cbbcd96 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -238,7 +238,7 @@
     // to the underlying bitmap, so even if the java object is released, we will still have access
     // to it.
     return SpriteIcon(pointerIcon.bitmap, pointerIcon.style, pointerIcon.hotSpotX,
-                      pointerIcon.hotSpotY);
+                      pointerIcon.hotSpotY, pointerIcon.drawNativeDropShadow);
 }
 
 enum {
@@ -1760,13 +1760,14 @@
             animationData.durationPerFrame =
                     milliseconds_to_nanoseconds(pointerIcon.durationPerFrame);
             animationData.animationFrames.reserve(numFrames);
-            animationData.animationFrames.push_back(SpriteIcon(
-                    pointerIcon.bitmap, pointerIcon.style,
-                    pointerIcon.hotSpotX, pointerIcon.hotSpotY));
+            animationData.animationFrames.emplace_back(pointerIcon.bitmap, pointerIcon.style,
+                                                       pointerIcon.hotSpotX, pointerIcon.hotSpotY,
+                                                       pointerIcon.drawNativeDropShadow);
             for (size_t i = 0; i < numFrames - 1; ++i) {
-              animationData.animationFrames.push_back(SpriteIcon(
-                      pointerIcon.bitmapFrames[i], pointerIcon.style,
-                      pointerIcon.hotSpotX, pointerIcon.hotSpotY));
+                animationData.animationFrames.emplace_back(pointerIcon.bitmapFrames[i],
+                                                           pointerIcon.style, pointerIcon.hotSpotX,
+                                                           pointerIcon.hotSpotY,
+                                                           pointerIcon.drawNativeDropShadow);
             }
         }
     }
@@ -2610,7 +2611,7 @@
         icon = std::make_unique<SpriteIcon>(pointerIcon.bitmap.copy(
                                                     ANDROID_BITMAP_FORMAT_RGBA_8888),
                                             pointerIcon.style, pointerIcon.hotSpotX,
-                                            pointerIcon.hotSpotY);
+                                            pointerIcon.hotSpotY, pointerIcon.drawNativeDropShadow);
     } else {
         icon = pointerIcon.style;
     }
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 b6f7eb3..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;
@@ -163,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 9e362b3..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;
 
@@ -153,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) {
@@ -161,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
@@ -197,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");
+        }
     }
 
     /**
@@ -212,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 4068d7b..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;
@@ -165,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/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..f87fd8d 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;
@@ -6012,10 +6016,10 @@
             // Make sure the caller has any active admin with the right policy or
             // the required permission.
             if (isUnicornFlagEnabled()) {
-                admin = enforcePermissionAndGetEnforcingAdmin(
+                admin = enforcePermissionsAndGetEnforcingAdmin(
                         /* admin= */ null,
-                        /* permission= */ MANAGE_DEVICE_POLICY_LOCK,
-                        USES_POLICY_FORCE_LOCK,
+                        /* permissions= */ new String[]{MANAGE_DEVICE_POLICY_LOCK, LOCK_DEVICE},
+                        /* deviceAdminPolicy= */ USES_POLICY_FORCE_LOCK,
                         caller.getPackageName(),
                         getAffectedUser(parent)
                  ).getActiveAdmin();
@@ -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/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/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
index 2c8b1cd..349b831 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
@@ -54,7 +54,8 @@
         ParsedActivity::getTheme,
         ParsedActivity::getUiOptions,
         ParsedActivity::isSupportsSizeChanges,
-        ParsedActivity::getRequiredDisplayCategory
+        ParsedActivity::getRequiredDisplayCategory,
+        ParsedActivity::getRequireContentUriPermissionFromCaller
     )
 
     override fun mainComponentSubclassExtraParams() = 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/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/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index ad6e2c6..3743483 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -154,6 +154,7 @@
         "androidx.annotation_annotation",
         "androidx.test.rules",
         "services.core",
+        "flag-junit",
     ],
     srcs: [
         "src/com/android/server/uri/**/*.java",
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/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/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 99fa30c..1d3dacc 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -169,14 +169,14 @@
                         .setEarcSupported(true)
                         .build();
         mHdmiPortInfo[3] =
-                new HdmiPortInfo.Builder(4, HdmiPortInfo.PORT_INPUT, 0x3000)
+                new HdmiPortInfo.Builder(4, HdmiPortInfo.PORT_OUTPUT, 0x3000)
                         .setCecSupported(true)
                         .setMhlSupported(false)
                         .setArcSupported(false)
                         .setEarcSupported(false)
                         .build();
         mHdmiPortInfo[4] =
-                new HdmiPortInfo.Builder(4, HdmiPortInfo.PORT_OUTPUT, 0x3000)
+                new HdmiPortInfo.Builder(5, HdmiPortInfo.PORT_OUTPUT, 0x3000)
                         .setCecSupported(true)
                         .setMhlSupported(false)
                         .setArcSupported(false)
@@ -841,6 +841,65 @@
     }
 
     @Test
+    public void onHotPlugIn_CecDisabledOnTv_CecNotAvailable() {
+        HdmiControlStatusCallback hdmiControlStatusCallback = new HdmiControlStatusCallback();
+        mHdmiControlServiceSpy.addHdmiControlStatusChangeListener(hdmiControlStatusCallback);
+        mTestLooper.dispatchAll();
+
+        mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
+        mHdmiControlServiceSpy.playback().removeAction(DevicePowerStatusAction.class);
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.dispatchAll();
+
+        mHdmiControlServiceSpy.onHotplug(4, true);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage giveDevicePowerStatus =
+                HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                        mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress(),
+                        Constants.ADDR_TV);
+        assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+        // Wait for DevicePowerStatusAction to finish.
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        assertThat(hdmiControlStatusCallback.mCecEnabled).isTrue();
+        assertThat(hdmiControlStatusCallback.mCecAvailable).isFalse();
+    }
+
+    @Test
+    public void onHotPlugIn_CecEnabledOnTv_CecAvailable() {
+        HdmiControlStatusCallback hdmiControlStatusCallback = new HdmiControlStatusCallback();
+        mHdmiControlServiceSpy.addHdmiControlStatusChangeListener(hdmiControlStatusCallback);
+        mTestLooper.dispatchAll();
+
+        mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
+        mHdmiControlServiceSpy.playback().removeAction(DevicePowerStatusAction.class);
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.dispatchAll();
+
+        mHdmiControlServiceSpy.onHotplug(4, true);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage giveDevicePowerStatus =
+                HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                        mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress(),
+                        Constants.ADDR_TV);
+        HdmiCecMessage reportPowerStatus =
+                HdmiCecMessageBuilder.buildReportPowerStatus(
+                        Constants.ADDR_TV,
+                        mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress(),
+                        HdmiControlManager.POWER_STATUS_ON);
+        assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+        mNativeWrapper.onCecMessage(reportPowerStatus);
+        mTestLooper.dispatchAll();
+
+        assertThat(hdmiControlStatusCallback.mCecEnabled).isTrue();
+        assertThat(hdmiControlStatusCallback.mCecAvailable).isTrue();
+    }
+    @Test
     public void handleCecCommand_errorParameter_returnsAbortInvalidOperand() {
         // Validity ERROR_PARAMETER. Taken from HdmiCecMessageValidatorTest#isValid_menuStatus
         HdmiCecMessage message = HdmiUtils.buildMessage("80:8D:03");
diff --git a/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java b/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java
index f537efd..da8ec2e 100644
--- a/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java
@@ -17,12 +17,14 @@
 package com.android.server.pdb;
 
 import static com.android.server.pdb.PersistentDataBlockService.DIGEST_SIZE_BYTES;
+import static com.android.server.pdb.PersistentDataBlockService.FRP_SECRET_SIZE;
 import static com.android.server.pdb.PersistentDataBlockService.MAX_DATA_BLOCK_SIZE;
 import static com.android.server.pdb.PersistentDataBlockService.MAX_FRP_CREDENTIAL_HANDLE_SIZE;
 import static com.android.server.pdb.PersistentDataBlockService.MAX_TEST_MODE_DATA_SIZE;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doNothing;
@@ -30,7 +32,8 @@
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
-import static org.junit.Assert.assertThrows;
+
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
 
 import android.Manifest;
 import android.content.Context;
@@ -45,7 +48,6 @@
 import junitparams.JUnitParamsRunner;
 import junitparams.Parameters;
 
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -54,9 +56,13 @@
 import org.mockito.MockitoAnnotations;
 
 import java.io.File;
+import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
+import java.nio.file.Files;
 import java.nio.file.StandardOpenOption;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 
 @RunWith(JUnitParamsRunner.class)
 public class PersistentDataBlockServiceTest {
@@ -64,20 +70,31 @@
 
     private static final byte[] SMALL_DATA = "data to write".getBytes();
     private static final byte[] ANOTHER_SMALL_DATA = "something else".getBytes();
+    public static final int DEFAULT_BLOCK_DEVICE_SIZE = -1;
 
     private Context mContext;
     private PersistentDataBlockService mPdbService;
     private IPersistentDataBlockService mInterface;
     private PersistentDataBlockManagerInternal mInternalInterface;
     private File mDataBlockFile;
+    private File mFrpSecretFile;
+    private File mFrpSecretTmpFile;
     private String mOemUnlockPropertyValue;
+    private boolean mIsUpgradingFromPreV = false;
 
     @Mock private UserManager mUserManager;
 
     private class FakePersistentDataBlockService extends PersistentDataBlockService {
+
         FakePersistentDataBlockService(Context context, String dataBlockFile,
-                long blockDeviceSize) {
-            super(context, /* isFileBacked */ true, dataBlockFile, blockDeviceSize);
+                long blockDeviceSize, boolean frpEnabled, String frpSecretFile,
+                String frpSecretTmpFile) {
+            super(context, /* isFileBacked */ true, dataBlockFile, blockDeviceSize, frpEnabled,
+                    frpSecretFile, frpSecretTmpFile);
+            // In the real service, this is done by onStart(), which we don't want to call because
+            // it registers the service, etc.  But we need to signal init done to prevent
+            // `isFrpActive` from blocking.
+            signalInitDone();
         }
 
         @Override
@@ -86,18 +103,25 @@
             assertThat(key).isEqualTo("sys.oem_unlock_allowed");
             mOemUnlockPropertyValue = value;
         }
+
+        @Override
+        boolean isUpgradingFromPreVRelease() {
+            return mIsUpgradingFromPreV;
+        }
     }
 
     @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
 
-    @Before
-    public void setUp() throws Exception {
+    private void setUp(boolean frpEnabled) throws Exception {
         MockitoAnnotations.initMocks(this);
 
         mDataBlockFile = mTemporaryFolder.newFile();
+        mFrpSecretFile = mTemporaryFolder.newFile();
+        mFrpSecretTmpFile = mTemporaryFolder.newFile();
         mContext = spy(ApplicationProvider.getApplicationContext());
         mPdbService = new FakePersistentDataBlockService(mContext, mDataBlockFile.getPath(),
-                /* blockDeviceSize */ -1);
+                DEFAULT_BLOCK_DEVICE_SIZE, frpEnabled, mFrpSecretFile.getPath(),
+                mFrpSecretTmpFile.getPath());
         mPdbService.setAllowedUid(Binder.getCallingUid());
         mPdbService.formatPartitionLocked(/* setOemUnlockEnabled */ false);
         mInterface = mPdbService.getInterfaceForTesting();
@@ -119,9 +143,7 @@
      * a block implementation for the read/write operations.
      */
     public Object[][] getTestParametersForBlocks() {
-        return new Object[][] {
-            {
-                new Block() {
+        Block simpleReadWrite = new Block() {
                     @Override public int write(byte[] data) throws RemoteException {
                         return service.getInterfaceForTesting().write(data);
                     }
@@ -129,10 +151,8 @@
                     @Override public byte[] read() throws RemoteException {
                         return service.getInterfaceForTesting().read();
                     }
-                },
-            },
-            {
-                new Block() {
+                };
+        Block credHandle =  new Block() {
                     @Override public int write(byte[] data) {
                         service.getInternalInterfaceForTesting().setFrpCredentialHandle(data);
                         // The written size isn't returned. Pretend it's fully written in the
@@ -143,10 +163,8 @@
                     @Override public byte[] read() {
                         return service.getInternalInterfaceForTesting().getFrpCredentialHandle();
                     }
-                },
-            },
-            {
-                new Block() {
+                };
+        Block testHarness = new Block() {
                     @Override public int write(byte[] data) {
                         service.getInternalInterfaceForTesting().setTestHarnessModeData(data);
                         // The written size isn't returned. Pretend it's fully written in the
@@ -157,14 +175,21 @@
                     @Override public byte[] read() {
                         return service.getInternalInterfaceForTesting().getTestHarnessModeData();
                     }
-                },
-            },
+                };
+        return new Object[][] {
+                { simpleReadWrite, false },
+                { simpleReadWrite, true },
+                { credHandle, false },
+                { credHandle, true },
+                { testHarness, false },
+                { testHarness, true },
         };
     }
 
     @Test
     @Parameters(method = "getTestParametersForBlocks")
-    public void writeThenRead(Block block) throws Exception {
+    public void writeThenRead(Block block, boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         block.service = mPdbService;
         assertThat(block.write(SMALL_DATA)).isEqualTo(SMALL_DATA.length);
         assertThat(block.read()).isEqualTo(SMALL_DATA);
@@ -172,7 +197,8 @@
 
     @Test
     @Parameters(method = "getTestParametersForBlocks")
-    public void writeWhileAlreadyCorrupted(Block block) throws Exception {
+    public void writeWhileAlreadyCorrupted(Block block, boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         block.service = mPdbService;
         assertThat(block.write(SMALL_DATA)).isEqualTo(SMALL_DATA.length);
         assertThat(block.read()).isEqualTo(SMALL_DATA);
@@ -184,7 +210,9 @@
     }
 
     @Test
-    public void frpWriteOutOfBound() throws Exception {
+    @Parameters({"false", "true"})
+    public void frpWriteOutOfBound(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         byte[] maxData = new byte[mPdbService.getMaximumFrpDataSize()];
         assertThat(mInterface.write(maxData)).isEqualTo(maxData.length);
 
@@ -193,7 +221,9 @@
     }
 
     @Test
-    public void frpCredentialWriteOutOfBound() throws Exception {
+    @Parameters({"false", "true"})
+    public void frpCredentialWriteOutOfBound(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         byte[] maxData = new byte[MAX_FRP_CREDENTIAL_HANDLE_SIZE];
         mInternalInterface.setFrpCredentialHandle(maxData);
 
@@ -203,7 +233,9 @@
     }
 
     @Test
-    public void testHardnessWriteOutOfBound() throws Exception {
+    @Parameters({"false", "true"})
+    public void testHardnessWriteOutOfBound(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         byte[] maxData = new byte[MAX_TEST_MODE_DATA_SIZE];
         mInternalInterface.setTestHarnessModeData(maxData);
 
@@ -213,7 +245,9 @@
     }
 
     @Test
-    public void readCorruptedFrpData() throws Exception {
+    @Parameters({"false", "true"})
+    public void readCorruptedFrpData(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         assertThat(mInterface.write(SMALL_DATA)).isEqualTo(SMALL_DATA.length);
         assertThat(mInterface.read()).isEqualTo(SMALL_DATA);
 
@@ -224,7 +258,9 @@
     }
 
     @Test
-    public void readCorruptedFrpCredentialData() throws Exception {
+    @Parameters({"false", "true"})
+    public void readCorruptedFrpCredentialData(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         mInternalInterface.setFrpCredentialHandle(SMALL_DATA);
         assertThat(mInternalInterface.getFrpCredentialHandle()).isEqualTo(SMALL_DATA);
 
@@ -235,7 +271,9 @@
     }
 
     @Test
-    public void readCorruptedTestHarnessData() throws Exception {
+    @Parameters({"false", "true"})
+    public void readCorruptedTestHarnessData(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         mInternalInterface.setTestHarnessModeData(SMALL_DATA);
         assertThat(mInternalInterface.getTestHarnessModeData()).isEqualTo(SMALL_DATA);
 
@@ -246,14 +284,18 @@
     }
 
     @Test
-    public void nullWrite() throws Exception {
+    @Parameters({"false", "true"})
+    public void nullWrite(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         assertThrows(NullPointerException.class, () -> mInterface.write(null));
         mInternalInterface.setFrpCredentialHandle(null);  // no exception
         mInternalInterface.setTestHarnessModeData(null);  // no exception
     }
 
     @Test
-    public void emptyDataWrite() throws Exception {
+    @Parameters({"false", "true"})
+    public void emptyDataWrite(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         var empty = new byte[0];
         assertThat(mInterface.write(empty)).isEqualTo(0);
 
@@ -264,10 +306,13 @@
     }
 
     @Test
-    public void frpWriteMoreThan100K() throws Exception {
+    @Parameters({"false", "true"})
+    public void frpWriteMoreThan100K(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         File dataBlockFile = mTemporaryFolder.newFile();
         PersistentDataBlockService pdbService = new FakePersistentDataBlockService(mContext,
-                dataBlockFile.getPath(), /* blockDeviceSize */ 128 * 1000);
+                dataBlockFile.getPath(), /* blockDeviceSize */ 128 * 1000, frpEnabled,
+                /* frpSecretFile */ null, /* frpSecretTmpFile */ null);
         pdbService.setAllowedUid(Binder.getCallingUid());
         pdbService.formatPartitionLocked(/* setOemUnlockEnabled */ false);
 
@@ -278,30 +323,39 @@
     }
 
     @Test
-    public void frpBlockReadWriteWithoutPermission() throws Exception {
+    @Parameters({"false", "true"})
+    public void frpBlockReadWriteWithoutPermission(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         mPdbService.setAllowedUid(Binder.getCallingUid() + 1);  // unexpected uid
         assertThrows(SecurityException.class, () -> mInterface.write(SMALL_DATA));
         assertThrows(SecurityException.class, () -> mInterface.read());
     }
 
     @Test
-    public void getMaximumDataBlockSizeDenied() throws Exception {
+    @Parameters({"false", "true"})
+    public void getMaximumDataBlockSizeDenied(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         mPdbService.setAllowedUid(Binder.getCallingUid() + 1);  // unexpected uid
         assertThrows(SecurityException.class, () -> mInterface.getMaximumDataBlockSize());
     }
 
     @Test
-    public void getMaximumDataBlockSize() throws Exception {
+    @Parameters({"false", "true"})
+    public void getMaximumDataBlockSize(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         mPdbService.setAllowedUid(Binder.getCallingUid());
         assertThat(mInterface.getMaximumDataBlockSize())
                 .isEqualTo(mPdbService.getMaximumFrpDataSize());
     }
 
     @Test
-    public void getMaximumDataBlockSizeOfLargerPartition() throws Exception {
+    @Parameters({"false", "true"})
+    public void getMaximumDataBlockSizeOfLargerPartition(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         File dataBlockFile = mTemporaryFolder.newFile();
         PersistentDataBlockService pdbService = new FakePersistentDataBlockService(mContext,
-                dataBlockFile.getPath(), /* blockDeviceSize */ 128 * 1000);
+                dataBlockFile.getPath(), /* blockDeviceSize */ 128 * 1000, frpEnabled,
+                /* frpSecretFile */null, /* mFrpSecretTmpFile */ null);
         pdbService.setAllowedUid(Binder.getCallingUid());
         pdbService.formatPartitionLocked(/* setOemUnlockEnabled */ false);
 
@@ -310,7 +364,9 @@
     }
 
     @Test
-    public void getFrpDataBlockSizeGrantedByUid() throws Exception {
+    @Parameters({"false", "true"})
+    public void getFrpDataBlockSizeGrantedByUid(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         assertThat(mInterface.write(SMALL_DATA)).isEqualTo(SMALL_DATA.length);
 
         mPdbService.setAllowedUid(Binder.getCallingUid());
@@ -323,7 +379,9 @@
     }
 
     @Test
-    public void getFrpDataBlockSizeGrantedByPermission() throws Exception {
+    @Parameters({"false", "true"})
+    public void getFrpDataBlockSizeGrantedByPermission(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         assertThat(mInterface.write(SMALL_DATA)).isEqualTo(SMALL_DATA.length);
 
         mPdbService.setAllowedUid(Binder.getCallingUid() + 1);  // unexpected uid
@@ -338,13 +396,17 @@
     }
 
     @Test
-    public void wipePermissionCheck() throws Exception {
+    @Parameters({"false", "true"})
+    public void wipePermissionCheck(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         denyOemUnlockPermission();
         assertThrows(SecurityException.class, () -> mInterface.wipe());
     }
 
     @Test
-    public void wipeMakesItNotWritable() throws Exception {
+    @Parameters({"false", "true"})
+    public void wipeMakesItNotWritable(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         grantOemUnlockPermission();
         mInterface.wipe();
 
@@ -368,7 +430,9 @@
     }
 
     @Test
-    public void hasFrpCredentialHandleGrantedByUid() throws Exception {
+    @Parameters({"false", "true"})
+    public void hasFrpCredentialHandle_GrantedByUid(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         mPdbService.setAllowedUid(Binder.getCallingUid());
 
         assertThat(mInterface.hasFrpCredentialHandle()).isFalse();
@@ -377,17 +441,51 @@
     }
 
     @Test
-    public void hasFrpCredentialHandleGrantedByPermission() throws Exception {
+    @Parameters({"false", "true"})
+    public void hasFrpCredentialHandle_GrantedByConfigureFrpPermission(boolean frpEnabled)
+            throws Exception {
+        setUp(frpEnabled);
+        grantConfigureFrpPermission();
+
         mPdbService.setAllowedUid(Binder.getCallingUid() + 1);  // unexpected uid
+
+        if (frpEnabled) {
+            assertThat(mInterface.hasFrpCredentialHandle()).isFalse();
+            mInternalInterface.setFrpCredentialHandle(SMALL_DATA);
+            assertThat(mInterface.hasFrpCredentialHandle()).isTrue();
+        } else {
+            assertThrows(SecurityException.class, () -> mInterface.hasFrpCredentialHandle());
+        }
+    }
+
+    @Test
+    @Parameters({"false", "true"})
+    public void hasFrpCredentialHandle_GrantedByAccessPdbStatePermission(boolean frpEnabled)
+            throws Exception {
+        setUp(frpEnabled);
         grantAccessPdbStatePermission();
 
+        mPdbService.setAllowedUid(Binder.getCallingUid() + 1);  // unexpected uid
+
         assertThat(mInterface.hasFrpCredentialHandle()).isFalse();
         mInternalInterface.setFrpCredentialHandle(SMALL_DATA);
         assertThat(mInterface.hasFrpCredentialHandle()).isTrue();
     }
 
     @Test
-    public void clearTestHarnessModeData() throws Exception {
+    @Parameters({"false", "true"})
+    public void hasFrpCredentialHandle_Unauthorized(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
+
+        mPdbService.setAllowedUid(Binder.getCallingUid() + 1);  // unexpected uid
+
+        assertThrows(SecurityException.class, () -> mInterface.hasFrpCredentialHandle());
+    }
+
+    @Test
+    @Parameters({"false", "true"})
+    public void clearTestHarnessModeData(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         mInternalInterface.setTestHarnessModeData(SMALL_DATA);
         mInternalInterface.clearTestHarnessModeData();
 
@@ -397,19 +495,25 @@
     }
 
     @Test
-    public void getAllowedUid() throws Exception {
+    @Parameters({"false", "true"})
+    public void getAllowedUid(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         assertThat(mInternalInterface.getAllowedUid()).isEqualTo(Binder.getCallingUid());
     }
 
     @Test
-    public void oemUnlockWithoutPermission() throws Exception {
+    @Parameters({"false", "true"})
+    public void oemUnlockWithoutPermission(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         denyOemUnlockPermission();
 
         assertThrows(SecurityException.class, () -> mInterface.setOemUnlockEnabled(true));
     }
 
     @Test
-    public void oemUnlockNotAdmin() throws Exception {
+    @Parameters({"false", "true"})
+    public void oemUnlockNotAdmin(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         grantOemUnlockPermission();
         makeUserAdmin(false);
 
@@ -417,7 +521,9 @@
     }
 
     @Test
-    public void oemUnlock() throws Exception {
+    @Parameters({"false", "true"})
+    public void oemUnlock(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         grantOemUnlockPermission();
         makeUserAdmin(true);
 
@@ -427,7 +533,9 @@
     }
 
     @Test
-    public void oemUnlockUserRestriction_OemUnlock() throws Exception {
+    @Parameters({"false", "true"})
+    public void oemUnlockUserRestriction_OemUnlock(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         grantOemUnlockPermission();
         makeUserAdmin(true);
         when(mUserManager.hasUserRestriction(eq(UserManager.DISALLOW_OEM_UNLOCK)))
@@ -437,7 +545,9 @@
     }
 
     @Test
-    public void oemUnlockUserRestriction_FactoryReset() throws Exception {
+    @Parameters({"false", "true"})
+    public void oemUnlockUserRestriction_FactoryReset(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         grantOemUnlockPermission();
         makeUserAdmin(true);
         when(mUserManager.hasUserRestriction(eq(UserManager.DISALLOW_FACTORY_RESET)))
@@ -447,7 +557,9 @@
     }
 
     @Test
-    public void oemUnlockIgnoreTampering() throws Exception {
+    @Parameters({"false", "true"})
+    public void oemUnlockIgnoreTampering(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         grantOemUnlockPermission();
         makeUserAdmin(true);
 
@@ -460,26 +572,37 @@
     }
 
     @Test
-    public void getOemUnlockEnabledPermissionCheck_NoPermission() throws Exception {
+    @Parameters({"false", "true"})
+    public void getOemUnlockEnabledPermissionCheck_NoPermission(boolean frpEnabled)
+            throws Exception {
+        setUp(frpEnabled);
         assertThrows(SecurityException.class, () -> mInterface.getOemUnlockEnabled());
     }
 
     @Test
-    public void getOemUnlockEnabledPermissionCheck_OemUnlcokState() throws Exception {
+    @Parameters({"false", "true"})
+    public void getOemUnlockEnabledPermissionCheck_OemUnlockState(boolean frpEnabled)
+            throws Exception {
+        setUp(frpEnabled);
         doReturn(PackageManager.PERMISSION_GRANTED).when(mContext)
                 .checkCallingOrSelfPermission(eq(Manifest.permission.OEM_UNLOCK_STATE));
         assertThat(mInterface.getOemUnlockEnabled()).isFalse();
     }
 
     @Test
-    public void getOemUnlockEnabledPermissionCheck_ReadOemUnlcokState() throws Exception {
+    @Parameters({"false", "true"})
+    public void getOemUnlockEnabledPermissionCheck_ReadOemUnlockState(boolean frpEnabled)
+            throws Exception {
+        setUp(frpEnabled);
         doReturn(PackageManager.PERMISSION_GRANTED).when(mContext)
                 .checkCallingOrSelfPermission(eq(Manifest.permission.READ_OEM_UNLOCK_STATE));
         assertThat(mInterface.getOemUnlockEnabled()).isFalse();
     }
 
     @Test
-    public void forceOemUnlock_RequiresNoPermission() throws Exception {
+    @Parameters({"false", "true"})
+    public void forceOemUnlock_RequiresNoPermission(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         denyOemUnlockPermission();
 
         mInternalInterface.forceOemUnlockEnabled(true);
@@ -490,24 +613,331 @@
     }
 
     @Test
-    public void getFlashLockStatePermissionCheck_NoPermission() throws Exception {
+    @Parameters({"false", "true"})
+    public void getFlashLockStatePermissionCheck_NoPermission(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
         assertThrows(SecurityException.class, () -> mInterface.getFlashLockState());
     }
 
     @Test
-    public void getFlashLockStatePermissionCheck_OemUnlcokState() throws Exception {
+    @Parameters({"false", "true"})
+    public void getFlashLockStatePermissionCheck_OemUnlockState(boolean frpEnabled)
+            throws Exception {
+        setUp(frpEnabled);
         doReturn(PackageManager.PERMISSION_GRANTED).when(mContext)
                 .checkCallingOrSelfPermission(eq(Manifest.permission.OEM_UNLOCK_STATE));
         mInterface.getFlashLockState();  // Do not throw
     }
 
     @Test
-    public void getFlashLockStatePermissionCheck_ReadOemUnlcokState() throws Exception {
+    @Parameters({"false", "true"})
+    public void getFlashLockStatePermissionCheck_ReadOemUnlockState(boolean frpEnabled)
+            throws Exception {
+        setUp(frpEnabled);
         doReturn(PackageManager.PERMISSION_GRANTED).when(mContext)
                 .checkCallingOrSelfPermission(eq(Manifest.permission.READ_OEM_UNLOCK_STATE));
         mInterface.getFlashLockState();  // Do not throw
     }
 
+    @Test
+    @Parameters({"false", "true"})
+    public void frpMagicTest(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
+        byte[] magicField = mPdbService.readDataBlock(mPdbService.getFrpSecretMagicOffset(),
+                PersistentDataBlockService.FRP_SECRET_MAGIC.length);
+        if (frpEnabled) {
+            assertThat(magicField).isEqualTo(PersistentDataBlockService.FRP_SECRET_MAGIC);
+        } else {
+            assertThat(magicField).isNotEqualTo(PersistentDataBlockService.FRP_SECRET_MAGIC);
+        }
+    }
+
+    @Test
+    public void frpSecret_StartsAsDefault() throws Exception {
+        setUp(/* frpEnabled */ true);
+
+        byte[] secretField = mPdbService.readDataBlock(
+                mPdbService.getFrpSecretDataOffset(), PersistentDataBlockService.FRP_SECRET_SIZE);
+        assertThat(secretField).isEqualTo(new byte[PersistentDataBlockService.FRP_SECRET_SIZE]);
+    }
+
+    @Test
+    public void frpSecret_SetSecret() throws Exception {
+        setUp(/* frpEnforcement */ true);
+        grantConfigureFrpPermission();
+
+        byte[] hashedSecret = hashStringto32Bytes("secret");
+        assertThat(mInterface.setFactoryResetProtectionSecret(hashedSecret)).isTrue();
+
+        byte[] secretField = mPdbService.readDataBlock(
+                mPdbService.getFrpSecretDataOffset(), PersistentDataBlockService.FRP_SECRET_SIZE);
+        assertThat(secretField).isEqualTo(hashedSecret);
+
+        assertThat(mFrpSecretFile.exists()).isTrue();
+        byte[] secretFileData = Files.readAllBytes(mFrpSecretFile.toPath());
+        assertThat(secretFileData).isEqualTo(hashedSecret);
+
+        assertThat(mFrpSecretTmpFile.exists()).isFalse();
+    }
+
+    @Test
+    public void frpSecret_SetSecretByUnauthorizedCaller() throws Exception {
+        setUp(/* frpEnforcement */ true);
+
+        mPdbService.setAllowedUid(Binder.getCallingUid() + 1);  // unexpected uid
+        assertThrows(SecurityException.class,
+                () -> mInterface.setFactoryResetProtectionSecret(hashStringto32Bytes("secret")));
+    }
+
+    /**
+     * Verify that FRP always starts in active state (if flag-enabled), until something is done to
+     * deactivate it.
+     */
+    @Test
+    @Parameters({"false", "true"})
+    public void frpState_StartsActive(boolean frpEnabled) throws Exception {
+        setUp(frpEnabled);
+        // Create a service without calling formatPartition, which deactivates FRP.
+        PersistentDataBlockService pdbService = new FakePersistentDataBlockService(mContext,
+                mDataBlockFile.getPath(), DEFAULT_BLOCK_DEVICE_SIZE, frpEnabled,
+                mFrpSecretFile.getPath(), mFrpSecretTmpFile.getPath());
+        assertThat(pdbService.isFrpActive()).isEqualTo(frpEnabled);
+    }
+
+    @Test
+    public void frpState_AutomaticallyDeactivateWithDefault() throws Exception {
+        setUp(/* frpEnforcement */ true);
+
+        mPdbService.activateFrp();
+        assertThat(mPdbService.isFrpActive()).isTrue();
+
+        assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isTrue();
+        assertThat(mPdbService.isFrpActive()).isFalse();
+    }
+
+    @Test
+    public void frpState_AutomaticallyDeactivateWithPrimaryDataFile() throws Exception {
+        setUp(/* frpEnforcement */ true);
+        grantConfigureFrpPermission();
+
+        mInterface.setFactoryResetProtectionSecret(hashStringto32Bytes("secret"));
+
+        mPdbService.activateFrp();
+        assertThat(mPdbService.isFrpActive()).isTrue();
+        assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isTrue();
+        assertThat(mPdbService.isFrpActive()).isFalse();
+    }
+
+    @Test
+    public void frpState_AutomaticallyDeactivateWithBackupDataFile() throws Exception {
+        setUp(/* frpEnforcement */ true);
+        grantConfigureFrpPermission();
+
+        mInterface.setFactoryResetProtectionSecret(hashStringto32Bytes("secret"));
+        Files.move(mFrpSecretFile.toPath(), mFrpSecretTmpFile.toPath(), REPLACE_EXISTING);
+
+        mPdbService.activateFrp();
+        assertThat(mPdbService.isFrpActive()).isTrue();
+        assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isTrue();
+        assertThat(mPdbService.isFrpActive()).isFalse();
+    }
+
+    @Test
+    public void frpState_DeactivateWithSecret() throws Exception {
+        setUp(/* frpEnforcement */ true);
+        grantConfigureFrpPermission();
+
+        mInterface.setFactoryResetProtectionSecret(hashStringto32Bytes("secret"));
+        simulateDataWipe();
+
+        assertThat(mPdbService.isFrpActive()).isFalse();
+        mPdbService.activateFrp();
+        assertThat(mPdbService.isFrpActive()).isTrue();
+
+        assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isFalse();
+        assertThat(mPdbService.isFrpActive()).isTrue();
+
+        assertThat(mInterface.deactivateFactoryResetProtection(hashStringto32Bytes("wrongSecret")))
+                .isFalse();
+        assertThat(mPdbService.isFrpActive()).isTrue();
+
+        assertThat(mInterface.deactivateFactoryResetProtection(hashStringto32Bytes("secret")))
+                .isTrue();
+        assertThat(mPdbService.isFrpActive()).isFalse();
+
+        assertThat(mInterface.setFactoryResetProtectionSecret(new byte[FRP_SECRET_SIZE])).isTrue();
+        assertThat(mPdbService.isFrpActive()).isFalse();
+
+        mPdbService.activateFrp();
+        assertThat(mPdbService.isFrpActive()).isTrue();
+        assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isTrue();
+        assertThat(mPdbService.isFrpActive()).isFalse();
+    }
+
+    @Test
+    public void frpState_DeactivateOnUpgradeFromPreV() throws Exception {
+        setUp(/* frpEnforcement */ true);
+        grantConfigureFrpPermission();
+
+        mInterface.setFactoryResetProtectionSecret(hashStringto32Bytes("secret"));
+        // If the /data files are still present, deactivation will use them.  We want to verify
+        // that deactivation will succeed even if they are not present, so remove them.
+        simulateDataWipe();
+
+        // Verify that automatic deactivation fails without the /data files when we're not
+        // upgrading from pre-V.
+        mPdbService.activateFrp();
+        assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isFalse();
+        assertThat(mPdbService.isFrpActive()).isTrue();
+
+        // Verify that automatic deactivation succeeds when upgrading from pre-V.
+        mIsUpgradingFromPreV = true;
+        assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isTrue();
+        assertThat(mPdbService.isFrpActive()).isFalse();
+    }
+
+    /**
+     * There is code in PersistentDataBlockService to handle a specific corner case, that of a
+     * device that is upgraded from pre-V to V+, downgraded to pre-V and then upgraded to V+. In
+     * this scenario, the following happens:
+     *
+     * 1. When the device is upgraded to V+ and the user sets an LSKF and GAIA creds, FRP
+     *    enforcement is activated and three copies of the FRP secret are written to:
+     *     a.  The FRP secret field in PDB (plaintext).
+     *     b.  The GAIA challenge in PDB (encrypted).
+     *     c.  The FRP secret file in /data (plaintext).
+     * 2. When the device is downgraded to pre-V, /data is wiped, so copy (c) is destroyed. When the
+     *    user sets LSKF and GAIA creds, copy (b) is overwritten.  Copy (a) survives.
+     * 3. When the device is upgraded to V and boots the first time, FRP cannot be automatically
+     *    deactivated using copy (c), nor can the user deactivate FRP using copy (b), because both
+     *    are gone. Absent some special handling of this case, the device would be unusable.
+     *
+     *  To address this problem, if PersistentDataBlockService finds an FRP secret in (a) but none
+     *  in (b) or (c), and PackageManager reports that the device has just upgraded from pre-V to
+     *  V+, it zeros the FRP secret in (a).
+     *
+     * This test checks that the service handles this sequence of events correctly.
+     */
+    @Test
+    public void frpState_TestDowngradeUpgradeSequence() throws Exception {
+        // Simulate device in V+, with FRP configured.
+        setUp(/* frpEnforcement */ true);
+        grantConfigureFrpPermission();
+
+        assertThat(mInterface.setFactoryResetProtectionSecret(hashStringto32Bytes("secret")))
+                .isTrue();
+        assertThat(mPdbService.isFrpActive()).isFalse();
+
+        // Simulate reboot, still in V+.
+        boolean frpEnabled = true;
+        mPdbService = new FakePersistentDataBlockService(mContext, mDataBlockFile.getPath(),
+                DEFAULT_BLOCK_DEVICE_SIZE, frpEnabled, mFrpSecretFile.getPath(),
+                mFrpSecretTmpFile.getPath());
+        assertThat(mPdbService.isFrpActive()).isTrue();
+        assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isTrue();
+        assertThat(mPdbService.isFrpActive()).isFalse();
+
+        // Simulate reboot after data wipe and downgrade to pre-V.
+        simulateDataWipe();
+        frpEnabled = false;
+        mPdbService = new FakePersistentDataBlockService(mContext, mDataBlockFile.getPath(),
+                DEFAULT_BLOCK_DEVICE_SIZE, frpEnabled, mFrpSecretFile.getPath(),
+                mFrpSecretTmpFile.getPath());
+        assertThat(mPdbService.isFrpActive()).isFalse();
+
+        // Simulate reboot after upgrade to V+, no data wipe.
+        frpEnabled = true;
+        mIsUpgradingFromPreV = true;
+        mPdbService = new FakePersistentDataBlockService(mContext, mDataBlockFile.getPath(),
+                DEFAULT_BLOCK_DEVICE_SIZE, frpEnabled, mFrpSecretTmpFile.getPath(),
+                mFrpSecretTmpFile.getPath());
+        mPdbService.setAllowedUid(Binder.getCallingUid()); // Needed for setFrpSecret().
+        assertThat(mPdbService.isFrpActive()).isTrue();
+        assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isTrue();
+        assertThat(mPdbService.isFrpActive()).isFalse();
+        assertThat(mPdbService.getInterfaceForTesting()
+                .setFactoryResetProtectionSecret(new byte[FRP_SECRET_SIZE])).isTrue();
+
+        // Simulate one more reboot.
+        mIsUpgradingFromPreV = false;
+        mPdbService = new FakePersistentDataBlockService(mContext, mDataBlockFile.getPath(),
+                DEFAULT_BLOCK_DEVICE_SIZE, frpEnabled, mFrpSecretTmpFile.getPath(),
+                mFrpSecretTmpFile.getPath());
+        assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isTrue();
+        assertThat(mPdbService.isFrpActive()).isFalse();
+    }
+
+    @Test
+    public void frpState_PrivilegedDeactivationByAuthorizedCaller() throws Exception {
+        setUp(/* frpEnforcement */ true);
+        grantConfigureFrpPermission();
+
+        assertThat(mPdbService.isFrpActive()).isFalse();
+        assertThat(mInterface.setFactoryResetProtectionSecret(hashStringto32Bytes("secret")))
+                .isTrue();
+
+        simulateDataWipe();
+        mPdbService.activateFrp();
+        assertThat(mPdbService.isFrpActive()).isTrue();
+
+        assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isFalse();
+        assertThat(mPdbService.isFrpActive()).isTrue();
+
+        assertThat(mInternalInterface.deactivateFactoryResetProtectionWithoutSecret()).isTrue();
+        assertThat(mPdbService.isFrpActive()).isFalse();
+    }
+
+    @Test
+    public void frpActive_WipeFails() throws Exception {
+        setUp(/* frpEnforcement */ true);
+
+        grantOemUnlockPermission();
+        mPdbService.activateFrp();
+        SecurityException e = assertThrows(SecurityException.class, () -> mInterface.wipe());
+        assertThat(e).hasMessageThat().contains("FRP is active");
+    }
+
+    @Test
+    public void frpActive_WriteFails() throws Exception {
+        setUp(/* frpEnforcement */ true);
+
+        mPdbService.activateFrp();
+        SecurityException e =
+                assertThrows(SecurityException.class, () -> mInterface.write("data".getBytes()));
+        assertThat(e).hasMessageThat().contains("FRP is active");
+    }
+
+    @Test
+    public void frpActive_SetSecretFails() throws Exception {
+        setUp(/* frpEnforcement */ true);
+        grantConfigureFrpPermission();
+
+        mPdbService.activateFrp();
+
+        byte[] hashedSecret = hashStringto32Bytes("secret");
+        SecurityException e = assertThrows(SecurityException.class, ()
+                -> mInterface.setFactoryResetProtectionSecret(hashedSecret));
+        assertThat(e).hasMessageThat().contains("FRP is active");
+        assertThat(mPdbService.isFrpActive()).isTrue();
+
+        // Verify that secret we failed to set isn't accepted.
+        assertThat(mInterface.deactivateFactoryResetProtection(hashedSecret)).isFalse();
+        assertThat(mPdbService.isFrpActive()).isTrue();
+
+        // Default should work, since it should never have been changed.
+        assertThat(mPdbService.automaticallyDeactivateFrpIfPossible()).isTrue();
+        assertThat(mPdbService.isFrpActive()).isFalse();
+    }
+
+    private void simulateDataWipe() throws IOException {
+        Files.deleteIfExists(mFrpSecretFile.toPath());
+        Files.deleteIfExists(mFrpSecretTmpFile.toPath());
+    }
+
+    private static byte[] hashStringto32Bytes(String secret) throws NoSuchAlgorithmException {
+        return MessageDigest.getInstance("SHA-256").digest(secret.getBytes());
+    }
+
     private void tamperWithDigest() throws Exception {
         try (var ch = FileChannel.open(mDataBlockFile.toPath(), StandardOpenOption.WRITE)) {
             ch.write(ByteBuffer.wrap("tampered-digest".getBytes()));
@@ -542,6 +972,14 @@
                 .checkCallingPermission(eq(Manifest.permission.ACCESS_PDB_STATE));
     }
 
+    private void grantConfigureFrpPermission() {
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(
+                eq(Manifest.permission.CONFIGURE_FACTORY_RESET_PROTECTION));
+        doNothing().when(mContext).enforceCallingOrSelfPermission(
+                eq(Manifest.permission.CONFIGURE_FACTORY_RESET_PROTECTION),
+                anyString());
+    }
+
     private ByteBuffer readBackingFile(long position, int size) throws Exception {
         try (var ch = FileChannel.open(mDataBlockFile.toPath(), StandardOpenOption.READ)) {
             var buffer = ByteBuffer.allocate(size);
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 8ef716f..1ae6e63 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/uri/UriGrantsManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
index 3218586..24abc18 100644
--- a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.uri;
 
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.NULL_DEFAULT;
+
 import static com.android.server.uri.UriGrantsMockContext.FLAG_PERSISTABLE;
 import static com.android.server.uri.UriGrantsMockContext.FLAG_PREFIX;
 import static com.android.server.uri.UriGrantsMockContext.FLAG_READ;
@@ -57,22 +59,49 @@
 import android.net.Uri;
 import android.os.Process;
 import android.os.UserHandle;
+import android.platform.test.flag.junit.FlagsParameterization;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.platform.test.ravenwood.RavenwoodRule;
 import android.util.ArraySet;
 
-import androidx.test.InstrumentationRegistry;
-
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 import java.util.Arrays;
+import java.util.List;
 import java.util.Set;
 
+@RunWith(Parameterized.class)
 public class UriGrantsManagerServiceTest {
     @Rule
     public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
+    /**
+     * Why this class needs to test all combinations of
+     * {@link android.security.Flags#FLAG_CONTENT_URI_PERMISSION_APIS}:
+     *
+     * <p>Although tests in this class don't directly query the flag, its value
+     * is needed for {@link UriGrantsManagerInternal#checkGrantUriPermissionFromIntent}. This is
+     * particularly important for host side tests (Ravenwood), which cannot read flag values from
+     * the device and must have them set explicitly.
+     */
+    @Parameters(name = "{0}")
+    public static List<FlagsParameterization> getFlags() {
+        return FlagsParameterization.allCombinationsOf(
+                android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS);
+    }
+
+    public UriGrantsManagerServiceTest(FlagsParameterization flags) {
+        mSetFlagsRule = new SetFlagsRule(NULL_DEFAULT, flags);
+    }
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule;
+
     private UriGrantsMockContext mContext;
     private UriGrantsManagerInternal mService;
 
diff --git a/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java b/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java
index 4d4f5ed..611c514 100644
--- a/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java
+++ b/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java
@@ -33,8 +33,10 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.os.SystemClock;
 import android.platform.test.ravenwood.RavenwoodRule;
 
 import org.junit.Before;
@@ -154,10 +156,12 @@
             assertEquals(FLAG_WRITE, perm.persistableModeFlags);
             assertEquals(FLAG_WRITE, perm.persistedModeFlags);
 
-            // Attempting to take a second time should be a no-op
+            // Attempting to take a second time should "touch" timestamp, per public API
+            // docs on ContentResolver.takePersistableUriPermission()
             final long createTime = perm.persistedCreateTime;
+            SystemClock.sleep(10);
             assertFalse(perm.takePersistableModes(FLAG_WRITE));
-            assertEquals(createTime, perm.persistedCreateTime);
+            assertNotEquals(createTime, perm.persistedCreateTime);
 
             assertTrue(perm.releasePersistableModes(FLAG_WRITE));
             assertEquals(FLAG_WRITE, perm.persistableModeFlags);
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 f6cf4da..77be01c 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -323,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;
@@ -12971,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/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 90493d4..295b124 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -346,6 +346,7 @@
         doReturn(true).when(amInternal).hasStartedUserState(anyInt());
         doReturn(false).when(amInternal).shouldConfirmCredentials(anyInt());
         doReturn(false).when(amInternal).isActivityStartsLoggingEnabled();
+        doReturn(true).when(amInternal).getThemeOverlayReadiness();
         LocalServices.addService(ActivityManagerInternal.class, amInternal);
 
         final ActivityManagerService amService =
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index 331caa1..7ad26c9 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -16,7 +16,10 @@
 
 package android.telecom;
 
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.media.ToneGenerator;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -25,6 +28,8 @@
 import android.telephony.ims.ImsReasonInfo;
 import android.text.TextUtils;
 
+import com.android.server.telecom.flags.Flags;
+
 import java.util.Objects;
 
 /**
@@ -169,7 +174,9 @@
     }
 
     /**
-     * Creates a new DisconnectCause instance.
+     * Creates a new DisconnectCause instance. This is used by Telephony to pass in extra debug
+     * info to Telecom regarding the disconnect cause.
+     *
      * @param code The code for the disconnect cause.
      * @param label The localized label to show to the user to explain the disconnect.
      * @param description The localized description to show to the user to explain the disconnect.
@@ -180,7 +187,10 @@
      * @param imsReasonInfo The relevant {@link ImsReasonInfo}, or {@code null} if not available.
      * @hide
      */
-    public DisconnectCause(int code, CharSequence label, CharSequence description, String reason,
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+    public DisconnectCause(int code, @NonNull CharSequence label,
+            @NonNull CharSequence description, @NonNull String reason,
             int toneToPlay, @Annotation.DisconnectCauses int telephonyDisconnectCause,
             @Annotation.PreciseDisconnectCauses int telephonyPreciseDisconnectCause,
             @Nullable ImsReasonInfo imsReasonInfo) {
@@ -241,28 +251,40 @@
     }
 
     /**
-     * Returns the telephony {@link android.telephony.DisconnectCause} for the call.
+     * Returns the telephony {@link android.telephony.DisconnectCause} for the call. This is only
+     * used internally by Telecom for providing extra debug information from Telephony.
+     *
      * @return The disconnect cause.
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
     public @Annotation.DisconnectCauses int getTelephonyDisconnectCause() {
         return mTelephonyDisconnectCause;
     }
 
     /**
-     * Returns the telephony {@link android.telephony.PreciseDisconnectCause} for the call.
+     * Returns the telephony {@link android.telephony.PreciseDisconnectCause} for the call. This is
+     * only used internally by Telecom for providing extra debug information from Telephony.
+     *
      * @return The precise disconnect cause.
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
     public @Annotation.PreciseDisconnectCauses int getTelephonyPreciseDisconnectCause() {
         return mTelephonyPreciseDisconnectCause;
     }
 
     /**
-     * Returns the telephony {@link ImsReasonInfo} associated with the call disconnection.
+     * Returns the telephony {@link ImsReasonInfo} associated with the call disconnection. This is
+     * only used internally by Telecom for providing extra debug information from Telephony.
+     *
      * @return The {@link ImsReasonInfo} or {@code null} if not known.
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
     public @Nullable ImsReasonInfo getImsReasonInfo() {
         return mImsReasonInfo;
     }
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index e62bd90..3daa014 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -2753,23 +2753,25 @@
      *
      * @param packageName the package name of the app to check calls for.
      * @param userHandle the user handle on which to check for calls.
-     * @param hasCrossUserAccess indicates if calls should be detected across all users.
+     * @param detectForAllUsers indicates if calls should be detected across all users. If it is
+     *                          set to {@code true}, and the caller has the ability to interact
+     *                          across users, the userHandle parameter is disregarded.
      * @return {@code true} if there are ongoing calls, {@code false} otherwise.
+     * @throws SecurityException if detectForAllUsers is true or userHandle is not the calling user
+     * and the caller does not grant the ability to interact across users.
      * @hide
      */
     @SystemApi
     @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
-    @RequiresPermission(allOf = {
-            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
-            Manifest.permission.INTERACT_ACROSS_USERS
-    })
+    @RequiresPermission(allOf = {Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
     public boolean isInSelfManagedCall(@NonNull String packageName,
-            @NonNull UserHandle userHandle, boolean hasCrossUserAccess) {
+            @NonNull UserHandle userHandle, boolean detectForAllUsers) {
         ITelecomService service = getTelecomService();
         if (service != null) {
             try {
                 return service.isInSelfManagedCall(packageName, userHandle,
-                        mContext.getOpPackageName(), hasCrossUserAccess);
+                        mContext.getOpPackageName(), detectForAllUsers);
             } catch (RemoteException e) {
                 Log.e(TAG, "RemoteException isInSelfManagedCall: " + e);
                 e.rethrowFromSystemServer();
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 412e827..7dba799e 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -395,7 +395,7 @@
      * @see TelecomServiceImpl#isInSelfManagedCall
      */
     boolean isInSelfManagedCall(String packageName, in UserHandle userHandle,
-        String callingPackage, boolean hasCrossUserAccess);
+        String callingPackage, boolean detectForAllUsers);
 
     /**
      * @see TelecomServiceImpl#addCall
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/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt b/telephony/java/android/service/euicc/IGetAvailableMemoryInBytesCallback.aidl
similarity index 71%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
copy to telephony/java/android/service/euicc/IGetAvailableMemoryInBytesCallback.aidl
index 128f58b..bd6d19b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityLaunchAnimatorKosmos.kt
+++ b/telephony/java/android/service/euicc/IGetAvailableMemoryInBytesCallback.aidl
@@ -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,8 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.systemui.animation
+package android.service.euicc;
 
-import com.android.systemui.kosmos.Kosmos
-
-val Kosmos.activityLaunchAnimator by Kosmos.Fixture { ActivityLaunchAnimator() }
+/** @hide */
+oneway interface IGetAvailableMemoryInBytesCallback {
+    void onSuccess(long availableMemoryInBytes);
+}
diff --git a/telephony/java/android/telephony/DomainSelectionService.java b/telephony/java/android/telephony/DomainSelectionService.java
index 0f54e8d..3c11da5 100644
--- a/telephony/java/android/telephony/DomainSelectionService.java
+++ b/telephony/java/android/telephony/DomainSelectionService.java
@@ -90,7 +90,7 @@
  */
 @SystemApi
 @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
-public class DomainSelectionService extends Service {
+public abstract class DomainSelectionService extends Service {
 
     private static final String LOG_TAG = "DomainSelectionService";
 
@@ -152,7 +152,7 @@
         private boolean mIsExitedFromAirplaneMode;
         private @Nullable ImsReasonInfo mImsReasonInfo;
         private @PreciseDisconnectCauses int mCause;
-        private @Nullable EmergencyRegResult mEmergencyRegResult;
+        private @Nullable EmergencyRegistrationResult mEmergencyRegistrationResult;
 
         /**
          * @param slotIndex The logical slot index.
@@ -172,7 +172,7 @@
                 @Nullable Uri address, @SelectorType int selectorType,
                 boolean video, boolean emergency, boolean isTest, boolean exited,
                 @Nullable ImsReasonInfo imsReasonInfo, @PreciseDisconnectCauses int cause,
-                @Nullable EmergencyRegResult regResult) {
+                @Nullable EmergencyRegistrationResult regResult) {
             mSlotIndex = slotIndex;
             mSubId = subscriptionId;
             mCallId = callId;
@@ -184,7 +184,7 @@
             mIsExitedFromAirplaneMode = exited;
             mImsReasonInfo = imsReasonInfo;
             mCause = cause;
-            mEmergencyRegResult = regResult;
+            mEmergencyRegistrationResult = regResult;
         }
 
         /**
@@ -204,7 +204,7 @@
             mIsExitedFromAirplaneMode = s.mIsExitedFromAirplaneMode;
             mImsReasonInfo = s.mImsReasonInfo;
             mCause = s.mCause;
-            mEmergencyRegResult = s.mEmergencyRegResult;
+            mEmergencyRegistrationResult = s.mEmergencyRegistrationResult;
         }
 
         /**
@@ -296,8 +296,8 @@
         /**
          * @return The current registration state of cellular network.
          */
-        public @Nullable EmergencyRegResult getEmergencyRegResult() {
-            return mEmergencyRegResult;
+        public @Nullable EmergencyRegistrationResult getEmergencyRegistrationResult() {
+            return mEmergencyRegistrationResult;
         }
 
         @Override
@@ -313,7 +313,7 @@
                     + ", airplaneMode=" + mIsExitedFromAirplaneMode
                     + ", reasonInfo=" + mImsReasonInfo
                     + ", cause=" + mCause
-                    + ", regResult=" + mEmergencyRegResult
+                    + ", regResult=" + mEmergencyRegistrationResult
                     + " }";
         }
 
@@ -331,14 +331,15 @@
                     && mIsExitedFromAirplaneMode == that.mIsExitedFromAirplaneMode
                     && equalsHandlesNulls(mImsReasonInfo, that.mImsReasonInfo)
                     && mCause == that.mCause
-                    && equalsHandlesNulls(mEmergencyRegResult, that.mEmergencyRegResult);
+                    && equalsHandlesNulls(mEmergencyRegistrationResult,
+                            that.mEmergencyRegistrationResult);
         }
 
         @Override
         public int hashCode() {
             return Objects.hash(mCallId, mAddress, mImsReasonInfo,
                     mIsVideoCall, mIsEmergency, mIsTestEmergencyNumber, mIsExitedFromAirplaneMode,
-                    mEmergencyRegResult, mSlotIndex, mSubId, mSelectorType, mCause);
+                    mEmergencyRegistrationResult, mSlotIndex, mSubId, mSelectorType, mCause);
         }
 
         @Override
@@ -359,7 +360,7 @@
             out.writeBoolean(mIsExitedFromAirplaneMode);
             out.writeParcelable(mImsReasonInfo, 0);
             out.writeInt(mCause);
-            out.writeParcelable(mEmergencyRegResult, 0);
+            out.writeParcelable(mEmergencyRegistrationResult, 0);
         }
 
         private void readFromParcel(@NonNull Parcel in) {
@@ -376,8 +377,9 @@
             mImsReasonInfo = in.readParcelable(ImsReasonInfo.class.getClassLoader(),
                     android.telephony.ims.ImsReasonInfo.class);
             mCause = in.readInt();
-            mEmergencyRegResult = in.readParcelable(EmergencyRegResult.class.getClassLoader(),
-                    EmergencyRegResult.class);
+            mEmergencyRegistrationResult = in.readParcelable(
+                    EmergencyRegistrationResult.class.getClassLoader(),
+                    EmergencyRegistrationResult.class);
         }
 
         public static final @NonNull Creator<SelectionAttributes> CREATOR =
@@ -413,7 +415,7 @@
             private boolean mIsExitedFromAirplaneMode;
             private @Nullable ImsReasonInfo mImsReasonInfo;
             private @PreciseDisconnectCauses int mCause;
-            private @Nullable EmergencyRegResult mEmergencyRegResult;
+            private @Nullable EmergencyRegistrationResult mEmergencyRegistrationResult;
 
             /**
              * Default constructor for Builder.
@@ -430,7 +432,7 @@
              * @param callId The call identifier.
              * @return The same instance of the builder.
              */
-            public @NonNull Builder setCallId(@NonNull String callId) {
+            public @NonNull Builder setCallId(@Nullable String callId) {
                 mCallId = callId;
                 return this;
             }
@@ -441,7 +443,7 @@
              * @param address The dialed address.
              * @return The same instance of the builder.
              */
-            public @NonNull Builder setAddress(@NonNull Uri address) {
+            public @NonNull Builder setAddress(@Nullable Uri address) {
                 mAddress = address;
                 return this;
             }
@@ -497,7 +499,7 @@
              * @param info The reason why the last PS attempt failed.
              * @return The same instance of the builder.
              */
-            public @NonNull Builder setPsDisconnectCause(@NonNull ImsReasonInfo info) {
+            public @NonNull Builder setPsDisconnectCause(@Nullable ImsReasonInfo info) {
                 mImsReasonInfo = info;
                 return this;
             }
@@ -519,8 +521,9 @@
              * @param regResult The current registration result for emergency services.
              * @return The same instance of the builder.
              */
-            public @NonNull Builder setEmergencyRegResult(@NonNull EmergencyRegResult regResult) {
-                mEmergencyRegResult = regResult;
+            public @NonNull Builder setEmergencyRegistrationResult(
+                    @Nullable EmergencyRegistrationResult regResult) {
+                mEmergencyRegistrationResult = regResult;
                 return this;
             }
 
@@ -532,7 +535,7 @@
                 return new SelectionAttributes(mSlotIndex, mSubId, mCallId, mAddress,
                         mSelectorType, mIsVideoCall, mIsEmergency, mIsTestEmergencyNumber,
                         mIsExitedFromAirplaneMode, mImsReasonInfo,
-                        mCause, mEmergencyRegResult);
+                        mCause, mEmergencyRegistrationResult);
             }
         }
     }
@@ -697,7 +700,7 @@
         public void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
                 @EmergencyScanType int scanType, boolean resetScan,
                 @NonNull CancellationSignal signal,
-                @NonNull Consumer<EmergencyRegResult> consumer) {
+                @NonNull Consumer<EmergencyRegistrationResult> consumer) {
             try {
                 if (signal != null) signal.setOnCancelListener(this);
                 mResultCallback = new IWwanSelectorResultCallbackAdapter(consumer, mExecutor);
@@ -721,17 +724,18 @@
 
         private class IWwanSelectorResultCallbackAdapter
                 extends IWwanSelectorResultCallback.Stub {
-            private final @NonNull Consumer<EmergencyRegResult> mConsumer;
+            private final @NonNull Consumer<EmergencyRegistrationResult> mConsumer;
             private final @NonNull Executor mExecutor;
 
-            IWwanSelectorResultCallbackAdapter(@NonNull Consumer<EmergencyRegResult> consumer,
+            IWwanSelectorResultCallbackAdapter(
+                    @NonNull Consumer<EmergencyRegistrationResult> consumer,
                     @NonNull Executor executor) {
                 mConsumer = consumer;
                 mExecutor = executor;
             }
 
             @Override
-            public void onComplete(@NonNull EmergencyRegResult result) {
+            public void onComplete(@NonNull EmergencyRegistrationResult result) {
                 if (mConsumer == null) return;
 
                 executeMethodAsyncNoException(mExecutor,
@@ -759,9 +763,8 @@
      * @param attr Required to determine the domain.
      * @param callback The callback instance being registered.
      */
-    public void onDomainSelection(@NonNull SelectionAttributes attr,
-            @NonNull TransportSelectorCallback callback) {
-    }
+    public abstract void onDomainSelection(@NonNull SelectionAttributes attr,
+            @NonNull TransportSelectorCallback callback);
 
     /**
      * Notifies the change in {@link ServiceState} for a specific logical slot index.
@@ -836,7 +839,7 @@
 
     /** @hide */
     @Override
-    public @Nullable IBinder onBind(@Nullable Intent intent) {
+    public final @Nullable IBinder onBind(@Nullable Intent intent) {
         if (intent == null) return null;
         if (SERVICE_INTERFACE.equals(intent.getAction())) {
             Log.i(LOG_TAG, "DomainSelectionService Bound.");
@@ -863,7 +866,7 @@
      * @return {@link Executor} instance.
      * @hide
      */
-    public @NonNull Executor getCachedExecutor() {
+    public final @NonNull Executor getCachedExecutor() {
         synchronized (mExecutorLock) {
             if (mExecutor == null) {
                 Executor e = onCreateExecutor();
diff --git a/telephony/java/android/telephony/EmergencyRegResult.aidl b/telephony/java/android/telephony/EmergencyRegistrationResult.aidl
similarity index 93%
rename from telephony/java/android/telephony/EmergencyRegResult.aidl
rename to telephony/java/android/telephony/EmergencyRegistrationResult.aidl
index f722962..3056031 100644
--- a/telephony/java/android/telephony/EmergencyRegResult.aidl
+++ b/telephony/java/android/telephony/EmergencyRegistrationResult.aidl
@@ -16,4 +16,4 @@
 
 package android.telephony;
 
-parcelable EmergencyRegResult;
+parcelable EmergencyRegistrationResult;
diff --git a/telephony/java/android/telephony/EmergencyRegResult.java b/telephony/java/android/telephony/EmergencyRegistrationResult.java
similarity index 91%
rename from telephony/java/android/telephony/EmergencyRegResult.java
rename to telephony/java/android/telephony/EmergencyRegistrationResult.java
index 15579be..7041f5b 100644
--- a/telephony/java/android/telephony/EmergencyRegResult.java
+++ b/telephony/java/android/telephony/EmergencyRegistrationResult.java
@@ -35,7 +35,7 @@
  */
 @SystemApi
 @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE)
-public final class EmergencyRegResult implements Parcelable {
+public final class EmergencyRegistrationResult implements Parcelable {
 
     /**
      * Indicates the cellular network type of the acquired system.
@@ -101,7 +101,7 @@
      * @param iso The ISO-3166-1 alpha-2 country code equivalent, empty string if unknown.
      * @hide
      */
-    public EmergencyRegResult(
+    public EmergencyRegistrationResult(
             @AccessNetworkConstants.RadioAccessNetworkType int accessNetwork,
             @NetworkRegistrationInfo.RegistrationState int regState,
             @NetworkRegistrationInfo.Domain int domain,
@@ -125,7 +125,7 @@
      * @param s Source emergency scan result
      * @hide
      */
-    public EmergencyRegResult(@NonNull EmergencyRegResult s) {
+    public EmergencyRegistrationResult(@NonNull EmergencyRegistrationResult s) {
         mAccessNetworkType = s.mAccessNetworkType;
         mRegState = s.mRegState;
         mDomain = s.mDomain;
@@ -139,9 +139,9 @@
     }
 
     /**
-     * Construct a EmergencyRegResult object from the given parcel.
+     * Construct a EmergencyRegistrationResult object from the given parcel.
      */
-    private EmergencyRegResult(@NonNull Parcel in) {
+    private EmergencyRegistrationResult(@NonNull Parcel in) {
         readFromParcel(in);
     }
 
@@ -258,7 +258,7 @@
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
-        EmergencyRegResult that = (EmergencyRegResult) o;
+        EmergencyRegistrationResult that = (EmergencyRegistrationResult) o;
         return mAccessNetworkType == that.mAccessNetworkType
                 && mRegState == that.mRegState
                 && mDomain == that.mDomain
@@ -311,16 +311,16 @@
         mCountryIso = in.readString8();
     }
 
-    public static final @NonNull Creator<EmergencyRegResult> CREATOR =
-            new Creator<EmergencyRegResult>() {
-        @Override
-        public EmergencyRegResult createFromParcel(@NonNull Parcel in) {
-            return new EmergencyRegResult(in);
-        }
+    public static final @NonNull Creator<EmergencyRegistrationResult> CREATOR =
+            new Creator<EmergencyRegistrationResult>() {
+                @Override
+                public EmergencyRegistrationResult createFromParcel(@NonNull Parcel in) {
+                    return new EmergencyRegistrationResult(in);
+                }
 
-        @Override
-        public EmergencyRegResult[] newArray(int size) {
-            return new EmergencyRegResult[size];
-        }
-    };
+                @Override
+                public EmergencyRegistrationResult[] newArray(int size) {
+                    return new EmergencyRegistrationResult[size];
+                }
+            };
 }
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index fdd0ba9..82ed340 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -399,7 +399,7 @@
         this.mUsageSetting = usageSetting;
         this.mIsOnlyNonTerrestrialNetwork = false;
         this.mServiceCapabilities = 0;
-        this.mTransferStatus = SubscriptionManager.TRANSFER_STATUS_NONE;
+        this.mTransferStatus = 0;
     }
 
     /**
@@ -1340,7 +1340,7 @@
          */
         private boolean mIsOnlyNonTerrestrialNetwork = false;
 
-        private int mTransferStatus = SubscriptionManager.TRANSFER_STATUS_NONE;
+        private int mTransferStatus = 0;
 
         /**
          * Service capabilities bitmasks the subscription supports.
diff --git a/telephony/java/android/telephony/WwanSelectorCallback.java b/telephony/java/android/telephony/WwanSelectorCallback.java
index ea83815..b900af3 100644
--- a/telephony/java/android/telephony/WwanSelectorCallback.java
+++ b/telephony/java/android/telephony/WwanSelectorCallback.java
@@ -48,7 +48,8 @@
      */
     void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
             @EmergencyScanType int scanType, boolean resetScan,
-            @NonNull CancellationSignal signal, @NonNull Consumer<EmergencyRegResult> consumer);
+            @NonNull CancellationSignal signal,
+            @NonNull Consumer<EmergencyRegistrationResult> consumer);
 
     /**
      * Notifies the FW that the domain has been selected. After this method is called,
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 0fe43b3..7935d24 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -23,6 +23,7 @@
 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;
@@ -863,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.
@@ -963,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.
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..4a61114 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,20 +497,18 @@
      * 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.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @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,14 +522,17 @@
                                 () -> 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.");
+                Rlog.e(TAG, "requestEnabled() invalid telephony");
+                executor.execute(() -> Binder.withCleanCallingIdentity(
+                        () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
-            Rlog.e(TAG, "requestSatelliteEnabled() RemoteException: ", ex);
-            ex.rethrowAsRuntimeException();
+            Rlog.e(TAG, "requestEnabled() exception: ", ex);
+            executor.execute(() -> Binder.withCleanCallingIdentity(
+                    () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
         }
     }
 
@@ -566,12 +582,14 @@
                 };
                 telephony.requestIsSatelliteEnabled(mSubId, receiver);
             } else {
+                loge("requestIsEnabled() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
                         new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
-            loge("requestIsSatelliteEnabled() RemoteException: " + ex);
-            ex.rethrowAsRuntimeException();
+            loge("requestIsEnabled() RemoteException: " + ex);
+            executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                    new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
         }
     }
 
@@ -621,11 +639,68 @@
                 };
                 telephony.requestIsDemoModeEnabled(mSubId, receiver);
             } else {
+                loge("requestIsDemoModeEnabled() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
                         new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
             loge("requestIsDemoModeEnabled() RemoteException: " + ex);
+            executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                    new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+        }
+    }
+
+    /**
+     * 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();
         }
     }
@@ -678,12 +753,14 @@
                 };
                 telephony.requestIsSatelliteSupported(mSubId, receiver);
             } else {
+                loge("requestIsSupported() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
                         new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
-            loge("requestIsSatelliteSupported() RemoteException: " + ex);
-            ex.rethrowAsRuntimeException();
+            loge("requestIsSupported() RemoteException: " + ex);
+            executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                    new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
         }
     }
 
@@ -733,12 +810,14 @@
                 };
                 telephony.requestSatelliteCapabilities(mSubId, receiver);
             } else {
+                loge("requestCapabilities() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
                         new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
-            loge("requestSatelliteCapabilities() RemoteException: " + ex);
-            ex.rethrowAsRuntimeException();
+            loge("requestCapabilities() RemoteException: " + ex);
+            executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                    new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
         }
     }
 
@@ -1014,12 +1093,14 @@
                 telephony.startSatelliteTransmissionUpdates(mSubId, errorCallback,
                         internalCallback);
             } else {
+                loge("startTransmissionUpdates() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(
                         () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
-            loge("startSatelliteTransmissionUpdates() RemoteException: " + ex);
-            ex.rethrowAsRuntimeException();
+            loge("startTransmissionUpdates() RemoteException: " + ex);
+            executor.execute(() -> Binder.withCleanCallingIdentity(
+                    () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
         }
     }
 
@@ -1069,12 +1150,14 @@
                             () -> resultListener.accept(SATELLITE_RESULT_INVALID_ARGUMENTS)));
                 }
             } else {
+                loge("stopTransmissionUpdates() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(
                         () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
-            loge("stopSatelliteTransmissionUpdates() RemoteException: " + ex);
-            ex.rethrowAsRuntimeException();
+            loge("stopTransmissionUpdates() RemoteException: " + ex);
+            executor.execute(() -> Binder.withCleanCallingIdentity(
+                    () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
         }
     }
 
@@ -1092,7 +1175,6 @@
      * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
-     * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1119,12 +1201,14 @@
                 cancelRemote = telephony.provisionSatelliteService(mSubId, token, provisionData,
                         errorCallback);
             } else {
+                loge("provisionService() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(
                         () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
-            loge("provisionSatelliteService() RemoteException=" + ex);
-            ex.rethrowAsRuntimeException();
+            loge("provisionService() RemoteException=" + ex);
+            executor.execute(() -> Binder.withCleanCallingIdentity(
+                    () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
         }
         if (cancellationSignal != null) {
             cancellationSignal.setRemote(cancelRemote);
@@ -1168,12 +1252,14 @@
                 };
                 telephony.deprovisionSatelliteService(mSubId, token, errorCallback);
             } else {
+                loge("deprovisionService() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(
                         () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
-            loge("deprovisionSatelliteService() RemoteException=" + ex);
-            ex.rethrowAsRuntimeException();
+            loge("deprovisionService() RemoteException ex=" + ex);
+            executor.execute(() -> Binder.withCleanCallingIdentity(
+                    () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
         }
     }
 
@@ -1215,7 +1301,7 @@
                 throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException ex) {
-            loge("registerForSatelliteProvisionStateChanged() RemoteException: " + ex);
+            loge("registerForProvisionStateChanged() RemoteException: " + ex);
             ex.rethrowAsRuntimeException();
         }
         return SATELLITE_RESULT_REQUEST_FAILED;
@@ -1302,12 +1388,14 @@
                 };
                 telephony.requestIsSatelliteProvisioned(mSubId, receiver);
             } else {
+                loge("requestIsProvisioned() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
                         new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
-            loge("requestIsSatelliteProvisioned() RemoteException: " + ex);
-            ex.rethrowAsRuntimeException();
+            loge("requestIsProvisioned() RemoteException: " + ex);
+            executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                    new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
         }
     }
 
@@ -1347,7 +1435,7 @@
                 throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException ex) {
-            loge("registerForSatelliteModemStateChanged() RemoteException:" + ex);
+            loge("registerForModemStateChanged() RemoteException:" + ex);
             ex.rethrowAsRuntimeException();
         }
         return SATELLITE_RESULT_REQUEST_FAILED;
@@ -1516,12 +1604,14 @@
                 };
                 telephony.pollPendingDatagrams(mSubId, internalCallback);
             } else {
+                loge("pollPendingDatagrams() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(
                         () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
             loge("pollPendingDatagrams() RemoteException:" + ex);
-            ex.rethrowAsRuntimeException();
+            executor.execute(() -> Binder.withCleanCallingIdentity(
+                    () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
         }
     }
 
@@ -1573,12 +1663,14 @@
                 telephony.sendDatagram(mSubId, datagramType, datagram,
                         needFullScreenPointingUI, internalCallback);
             } else {
+                loge("sendDatagram() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(
                         () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
             loge("sendDatagram() RemoteException:" + ex);
-            ex.rethrowAsRuntimeException();
+            executor.execute(() -> Binder.withCleanCallingIdentity(
+                    () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
         }
     }
 
@@ -1628,16 +1720,16 @@
                         }
                     }
                 };
-                telephony.requestIsCommunicationAllowedForCurrentLocation(mSubId,
-                        receiver);
+                telephony.requestIsCommunicationAllowedForCurrentLocation(mSubId, receiver);
             } else {
+                loge("requestIsCommunicationAllowedForCurrentLocation() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
                         new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
-            loge("requestIsCommunicationAllowedForCurrentLocation() RemoteException: "
-                    + ex);
-            ex.rethrowAsRuntimeException();
+            loge("requestIsCommunicationAllowedForCurrentLocation() RemoteException: " + ex);
+            executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                    new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
         }
     }
 
@@ -1688,12 +1780,14 @@
                 };
                 telephony.requestTimeForNextSatelliteVisibility(mSubId, receiver);
             } else {
+                loge("requestTimeForNextSatelliteVisibility() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
                         new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
             loge("requestTimeForNextSatelliteVisibility() RemoteException: " + ex);
-            ex.rethrowAsRuntimeException();
+            executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                    new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
         }
     }
 
@@ -1720,7 +1814,7 @@
                 throw new IllegalStateException("telephony service is null.");
             }
         } catch (RemoteException ex) {
-            loge("informDeviceAlignedToSatellite() RemoteException:" + ex);
+            loge("setDeviceAlignedWithSatellite() RemoteException:" + ex);
             ex.rethrowAsRuntimeException();
         }
     }
@@ -1830,12 +1924,14 @@
                 };
                 telephony.addAttachRestrictionForCarrier(subId, reason, errorCallback);
             } else {
+                loge("addAttachRestrictionForCarrier() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(
                         () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
             loge("addAttachRestrictionForCarrier() RemoteException:" + ex);
-            ex.rethrowAsRuntimeException();
+            executor.execute(() -> Binder.withCleanCallingIdentity(
+                    () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
         }
     }
 
@@ -1873,12 +1969,14 @@
                 };
                 telephony.removeAttachRestrictionForCarrier(subId, reason, errorCallback);
             } else {
+                loge("removeAttachRestrictionForCarrier() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(
                         () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
             }
         } catch (RemoteException ex) {
             loge("removeAttachRestrictionForCarrier() RemoteException:" + ex);
-            ex.rethrowAsRuntimeException();
+            executor.execute(() -> Binder.withCleanCallingIdentity(
+                    () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
         }
     }
 
@@ -1939,10 +2037,7 @@
      * The {@link NtnSignalStrength#NTN_SIGNAL_STRENGTH_NONE} will be returned if there is no
      * signal strength data available.
      * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)} will return a
-     * {@link SatelliteException} with the {@link SatelliteResult}, or return a
-     * {@link IllegalStateException} if the Telephony process is not currently available or
-     * satellite is not supported, or return a {@link RuntimeException} when remote procedure call
-     * has failed.
+     * {@link SatelliteException} with the {@link SatelliteResult}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      */
@@ -1980,12 +2075,14 @@
                 };
                 telephony.requestNtnSignalStrength(mSubId, receiver);
             } else {
+                loge("requestNtnSignalStrength() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
                         new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
             loge("requestNtnSignalStrength() RemoteException: " + ex);
-            ex.rethrowAsRuntimeException();
+            executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                    new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
         }
     }
 
@@ -2187,14 +2284,11 @@
         return new ArrayList<>();
     }
 
-    private static ITelephony getITelephony() {
+    @Nullable private static ITelephony getITelephony() {
         ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
                 .getTelephonyServiceManager()
                 .getTelephonyServiceRegisterer()
                 .get());
-        if (binder == null) {
-            throw new RuntimeException("Could not find Telephony Service.");
-        }
         return binder;
     }
 
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/IWwanSelectorResultCallback.aidl b/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl
index 0d61fcb..091974a 100644
--- a/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl
+++ b/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl
@@ -16,8 +16,8 @@
 
 package com.android.internal.telephony;
 
-import android.telephony.EmergencyRegResult;
+import android.telephony.EmergencyRegistrationResult;
 
 oneway interface IWwanSelectorResultCallback {
-    void onComplete(in EmergencyRegResult result);
+    void onComplete(in EmergencyRegistrationResult result);
 }
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 d417772..053bc7d 100644
--- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
@@ -59,4 +59,5 @@
     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)