Merge "Stylus: Introduce Show Stylus Hover Pointer Setting (1/2)" into main
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/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/core/api/current.txt b/core/api/current.txt
index 2e3fe5b..bca15bd 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -765,7 +765,7 @@
     field public static final int endY = 16844051; // 0x1010513
     field @Deprecated public static final int endYear = 16843133; // 0x101017d
     field public static final int enforceNavigationBarContrast = 16844293; // 0x1010605
-    field public static final int enforceStatusBarContrast = 16844292; // 0x1010604
+    field @Deprecated public static final int enforceStatusBarContrast = 16844292; // 0x1010604
     field public static final int enterFadeDuration = 16843532; // 0x101030c
     field public static final int entries = 16842930; // 0x10100b2
     field public static final int entryValues = 16843256; // 0x10101f8
@@ -1196,8 +1196,8 @@
     field public static final int multiprocess = 16842771; // 0x1010013
     field public static final int name = 16842755; // 0x1010003
     field public static final int nativeHeapZeroInitialized = 16844325; // 0x1010625
-    field public static final int navigationBarColor = 16843858; // 0x1010452
-    field public static final int navigationBarDividerColor = 16844141; // 0x101056d
+    field @Deprecated public static final int navigationBarColor = 16843858; // 0x1010452
+    field @Deprecated public static final int navigationBarDividerColor = 16844141; // 0x101056d
     field public static final int navigationContentDescription = 16843969; // 0x10104c1
     field public static final int navigationIcon = 16843968; // 0x10104c0
     field public static final int navigationMode = 16843471; // 0x10102cf
@@ -1568,7 +1568,7 @@
     field public static final int state_single = 16842915; // 0x10100a3
     field public static final int state_window_focused = 16842909; // 0x101009d
     field public static final int staticWallpaperPreview = 16843569; // 0x1010331
-    field public static final int statusBarColor = 16843857; // 0x1010451
+    field @Deprecated public static final int statusBarColor = 16843857; // 0x1010451
     field public static final int stepSize = 16843078; // 0x1010146
     field public static final int stopWithTask = 16843626; // 0x101036a
     field public static final int streamType = 16843273; // 0x1010209
@@ -1890,6 +1890,7 @@
     field public static final int windowNoDisplay = 16843294; // 0x101021e
     field public static final int windowNoMoveAnimation = 16844421; // 0x1010685
     field public static final int windowNoTitle = 16842838; // 0x1010056
+    field @FlaggedApi("com.android.window.flags.enforce_edge_to_edge") public static final int windowOptOutEdgeToEdgeEnforcement;
     field @Deprecated public static final int windowOverscan = 16843727; // 0x10103cf
     field public static final int windowReenterTransition = 16843951; // 0x10104af
     field public static final int windowReturnTransition = 16843950; // 0x10104ae
@@ -7894,6 +7895,7 @@
     field public static final String AUTO_TIME_POLICY = "autoTime";
     field public static final String BACKUP_SERVICE_POLICY = "backupService";
     field public static final String CAMERA_DISABLED_POLICY = "cameraDisabled";
+    field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final String CONTENT_PROTECTION_POLICY = "contentProtection";
     field public static final String KEYGUARD_DISABLED_FEATURES_POLICY = "keyguardDisabledFeatures";
     field public static final String LOCK_TASK_POLICY = "lockTask";
     field public static final String PACKAGES_SUSPENDED_POLICY = "packagesSuspended";
@@ -7944,6 +7946,7 @@
     method public boolean getBluetoothContactSharingDisabled(@NonNull android.content.ComponentName);
     method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CAMERA, conditional=true) public boolean getCameraDisabled(@Nullable android.content.ComponentName);
     method @Deprecated @Nullable public String getCertInstallerPackage(@NonNull android.content.ComponentName) throws java.lang.SecurityException;
+    method @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, conditional=true) public int getContentProtectionPolicy(@Nullable android.content.ComponentName);
     method @Nullable public android.app.admin.PackagePolicy getCredentialManagerPolicy();
     method @Deprecated @Nullable public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName);
     method @Deprecated public boolean getCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName);
@@ -8100,6 +8103,7 @@
     method @Deprecated public void setCertInstallerPackage(@NonNull android.content.ComponentName, @Nullable String) throws java.lang.SecurityException;
     method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE, conditional=true) public void setCommonCriteriaModeEnabled(@Nullable android.content.ComponentName, boolean);
     method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_WIFI, conditional=true) public void setConfiguredNetworksLockdownState(@Nullable android.content.ComponentName, boolean);
+    method @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, conditional=true) public void setContentProtectionPolicy(@Nullable android.content.ComponentName, int);
     method public void setCredentialManagerPolicy(@Nullable android.app.admin.PackagePolicy);
     method @Deprecated public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>);
     method @Deprecated public void setCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName, boolean);
@@ -8525,6 +8529,7 @@
     field public static final int TAG_ADB_SHELL_CMD = 210002; // 0x33452
     field public static final int TAG_ADB_SHELL_INTERACTIVE = 210001; // 0x33451
     field public static final int TAG_APP_PROCESS_START = 210005; // 0x33455
+    field @FlaggedApi("android.app.admin.flags.backup_service_security_log_event_enabled") public static final int TAG_BACKUP_SERVICE_TOGGLED = 210044; // 0x3347c
     field public static final int TAG_BLUETOOTH_CONNECTION = 210039; // 0x33477
     field public static final int TAG_BLUETOOTH_DISCONNECTION = 210040; // 0x33478
     field public static final int TAG_CAMERA_POLICY_SET = 210034; // 0x33472
@@ -16525,8 +16530,8 @@
     field public static final int START_HYPHEN_EDIT_NO_EDIT = 0; // 0x0
     field public static final int STRIKE_THRU_TEXT_FLAG = 16; // 0x10
     field public static final int SUBPIXEL_TEXT_FLAG = 128; // 0x80
-    field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_LEFT_EDGE = 8192; // 0x2000
-    field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 16384; // 0x4000
+    field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int TEXT_RUN_FLAG_LEFT_EDGE = 8192; // 0x2000
+    field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 16384; // 0x4000
     field public static final int UNDERLINE_TEXT_FLAG = 8; // 0x8
   }
 
@@ -18003,7 +18008,7 @@
     field public static final int HYPHENATION_FREQUENCY_FULL = 2; // 0x2
     field public static final int HYPHENATION_FREQUENCY_NONE = 0; // 0x0
     field public static final int HYPHENATION_FREQUENCY_NORMAL = 1; // 0x1
-    field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
+    field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
     field public static final int JUSTIFICATION_MODE_INTER_WORD = 1; // 0x1
     field public static final int JUSTIFICATION_MODE_NONE = 0; // 0x0
   }
@@ -24706,6 +24711,7 @@
   }
 
   public class Ringtone {
+    method protected void finalize();
     method public android.media.AudioAttributes getAudioAttributes();
     method @Deprecated public int getStreamType();
     method public String getTitle(android.content.Context);
@@ -26632,6 +26638,8 @@
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.BroadcastInfoRequest> CREATOR;
     field public static final int REQUEST_OPTION_AUTO_UPDATE = 1; // 0x1
+    field @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public static final int REQUEST_OPTION_ONESHOT = 3; // 0x3
+    field @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public static final int REQUEST_OPTION_ONEWAY = 2; // 0x2
     field public static final int REQUEST_OPTION_REPEAT = 0; // 0x0
   }
 
@@ -27438,6 +27446,7 @@
     method public void notifyTuned(@NonNull android.net.Uri);
     method public void notifyTvMessage(int, @NonNull android.os.Bundle);
     method public void notifyVideoAvailable();
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void notifyVideoFreezeUpdated(boolean);
     method public void notifyVideoUnavailable(int);
     method public void onAdBufferReady(@NonNull android.media.tv.AdBuffer);
     method public void onAppPrivateCommand(@NonNull String, android.os.Bundle);
@@ -27608,6 +27617,7 @@
     method public void setStreamVolume(@FloatRange(from=0.0, to=1.0) float);
     method public void setTimeShiftPositionCallback(@Nullable android.media.tv.TvView.TimeShiftPositionCallback);
     method public void setTvMessageEnabled(int, boolean);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void setVideoFrozen(boolean);
     method public void setZOrderMediaOverlay(boolean);
     method public void setZOrderOnTop(boolean);
     method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void stopPlayback(int);
@@ -27651,6 +27661,7 @@
     method public void onTuned(@NonNull String, @NonNull android.net.Uri);
     method public void onTvMessage(@NonNull String, int, @NonNull android.os.Bundle);
     method public void onVideoAvailable(String);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onVideoFreezeUpdated(@NonNull String, boolean);
     method public void onVideoSizeChanged(String, int, int);
     method public void onVideoUnavailable(String, int);
   }
@@ -27662,7 +27673,18 @@
   @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public class TvAdManager {
     method @NonNull public java.util.List<android.media.tv.ad.TvAdServiceInfo> getTvAdServiceList();
     method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdManager.TvAdServiceCallback);
+    method public void sendAppLinkCommand(@NonNull String, @NonNull android.os.Bundle);
     method public void unregisterCallback(@NonNull android.media.tv.ad.TvAdManager.TvAdServiceCallback);
+    field public static final String ACTION_APP_LINK_COMMAND = "android.media.tv.ad.action.APP_LINK_COMMAND";
+    field public static final String APP_LINK_KEY_BACK_URI = "back_uri";
+    field public static final String APP_LINK_KEY_CLASS_NAME = "class_name";
+    field public static final String APP_LINK_KEY_COMMAND_TYPE = "command_type";
+    field public static final String APP_LINK_KEY_PACKAGE_NAME = "package_name";
+    field public static final String APP_LINK_KEY_SERVICE_ID = "service_id";
+    field public static final String INTENT_KEY_AD_SERVICE_ID = "ad_service_id";
+    field public static final String INTENT_KEY_CHANNEL_URI = "channel_uri";
+    field public static final String INTENT_KEY_COMMAND_TYPE = "command_type";
+    field public static final String INTENT_KEY_TV_INPUT_ID = "tv_input_id";
     field public static final String SESSION_DATA_KEY_AD_BUFFER = "ad_buffer";
     field public static final String SESSION_DATA_KEY_AD_REQUEST = "ad_request";
     field public static final String SESSION_DATA_KEY_BROADCAST_INFO_REQUEST = "broadcast_info_request";
@@ -27682,6 +27704,7 @@
 
   @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public abstract class TvAdService extends android.app.Service {
     ctor public TvAdService();
+    method public void onAppLinkCommand(@NonNull android.os.Bundle);
     method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
     method @Nullable public abstract android.media.tv.ad.TvAdService.Session onCreateSession(@NonNull String, @NonNull String);
     field public static final String SERVICE_INTERFACE = "android.media.tv.ad.TvAdService";
@@ -27858,6 +27881,7 @@
     method public void onAdResponse(@NonNull android.media.tv.AdResponse);
     method public void onAvailableSpeeds(@NonNull float[]);
     method public void onBroadcastInfoResponse(@NonNull android.media.tv.BroadcastInfoResponse);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onCertificate(@NonNull String, int, @NonNull android.net.http.SslCertificate);
     method public void onContentAllowed();
     method public void onContentBlocked(@NonNull android.media.tv.TvContentRating);
     method public void onCreateBiInteractiveAppRequest(@NonNull android.net.Uri, @Nullable android.os.Bundle);
@@ -27907,11 +27931,13 @@
     method public void onTvRecordingInfo(@Nullable android.media.tv.TvRecordingInfo);
     method public void onTvRecordingInfoList(@NonNull java.util.List<android.media.tv.TvRecordingInfo>);
     method public void onVideoAvailable();
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onVideoFreezeUpdated(boolean);
     method public void onVideoUnavailable(int);
     method @CallSuper public void removeBroadcastInfo(int);
     method @CallSuper public void requestAd(@NonNull android.media.tv.AdRequest);
     method @CallSuper public void requestAvailableSpeeds();
     method @CallSuper public void requestBroadcastInfo(@NonNull android.media.tv.BroadcastInfoRequest);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") @CallSuper public void requestCertificate(@NonNull String, int);
     method @CallSuper public void requestCurrentChannelLcn();
     method @CallSuper public void requestCurrentChannelUri();
     method @CallSuper public void requestCurrentTvInputId();
@@ -27920,6 +27946,7 @@
     method @CallSuper public void requestScheduleRecording(@NonNull String, @NonNull String, @NonNull android.net.Uri, long, long, int, @NonNull android.os.Bundle);
     method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") @CallSuper public void requestSelectedTrackInfo();
     method @CallSuper public void requestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") @CallSuper public void requestSigning(@NonNull String, @NonNull String, @NonNull String, int, @NonNull byte[]);
     method @CallSuper public void requestStartRecording(@NonNull String, @Nullable android.net.Uri);
     method @CallSuper public void requestStopRecording(@NonNull String);
     method @CallSuper public void requestStreamVolume();
@@ -27969,6 +27996,7 @@
     method public void notifyTimeShiftStartPositionChanged(@NonNull String, long);
     method public void notifyTimeShiftStatusChanged(@NonNull String, int);
     method public void notifyTvMessage(@NonNull int, @NonNull android.os.Bundle);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void notifyVideoFreezeUpdated(boolean);
     method public void onAttachedToWindow();
     method public void onDetachedFromWindow();
     method public void onLayout(boolean, int, int, int, int);
@@ -27979,6 +28007,7 @@
     method public void reset();
     method public void resetInteractiveApp();
     method public void sendAvailableSpeeds(@NonNull float[]);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void sendCertificate(@NonNull String, int, @NonNull android.net.http.SslCertificate);
     method public void sendCurrentChannelLcn(int);
     method public void sendCurrentChannelUri(@Nullable android.net.Uri);
     method public void sendCurrentTvInputId(@Nullable String);
@@ -28013,6 +28042,7 @@
     method public void onBiInteractiveAppCreated(@NonNull String, @NonNull android.net.Uri, @Nullable String);
     method public void onPlaybackCommandRequest(@NonNull String, @NonNull String, @NonNull android.os.Bundle);
     method public void onRequestAvailableSpeeds(@NonNull String);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onRequestCertificate(@NonNull String, @NonNull String, int);
     method public void onRequestCurrentChannelLcn(@NonNull String);
     method public void onRequestCurrentChannelUri(@NonNull String);
     method public void onRequestCurrentTvInputId(@NonNull String);
@@ -28021,6 +28051,7 @@
     method public void onRequestScheduleRecording(@NonNull String, @NonNull String, @NonNull String, @NonNull android.net.Uri, long, long, int, @NonNull android.os.Bundle);
     method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onRequestSelectedTrackInfo(@NonNull String);
     method public void onRequestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void onRequestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int, @NonNull byte[]);
     method public void onRequestStartRecording(@NonNull String, @NonNull String, @Nullable android.net.Uri);
     method public void onRequestStopRecording(@NonNull String, @NonNull String);
     method public void onRequestStreamVolume(@NonNull String);
@@ -46273,6 +46304,7 @@
     method @NonNull public android.telephony.euicc.EuiccManager createForCardId(int);
     method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void deleteSubscription(int, android.app.PendingIntent);
     method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void downloadSubscription(android.telephony.euicc.DownloadableSubscription, boolean, android.app.PendingIntent);
+    method @FlaggedApi("com.android.internal.telephony.flags.esim_available_memory") @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE", "carrier privileges"}) public long getAvailableMemoryInBytes();
     method @Nullable public String getEid();
     method @Nullable public android.telephony.euicc.EuiccInfo getEuiccInfo();
     method public boolean isEnabled();
@@ -46305,6 +46337,7 @@
     field public static final int ERROR_SIM_MISSING = 10008; // 0x2718
     field public static final int ERROR_TIME_OUT = 10005; // 0x2715
     field public static final int ERROR_UNSUPPORTED_VERSION = 10007; // 0x2717
+    field @FlaggedApi("com.android.internal.telephony.flags.esim_available_memory") public static final long EUICC_MEMORY_FIELD_UNAVAILABLE = -1L; // 0xffffffffffffffffL
     field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DETAILED_CODE";
     field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION";
     field public static final String EXTRA_EMBEDDED_SUBSCRIPTION_ERROR_CODE = "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_ERROR_CODE";
@@ -47278,7 +47311,7 @@
     method public int getLineForOffset(int);
     method public int getLineForVertical(int);
     method public float getLineLeft(int);
-    method @FlaggedApi("com.android.text.flags.inter_character_justification") @IntRange(from=0) public int getLineLetterSpacingUnitCount(@IntRange(from=0) int, boolean);
+    method @FlaggedApi("com.android.text.flags.letter_spacing_justification") @IntRange(from=0) public int getLineLetterSpacingUnitCount(@IntRange(from=0) int, boolean);
     method public float getLineMax(int);
     method public float getLineRight(int);
     method @FlaggedApi("com.android.text.flags.use_bounds_for_width") public final float getLineSpacingAmount();
@@ -47329,7 +47362,7 @@
     field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_ANY_OVERLAP;
     field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_ALL;
     field @NonNull public static final android.text.Layout.TextInclusionStrategy INCLUSION_STRATEGY_CONTAINS_CENTER;
-    field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
+    field @FlaggedApi("com.android.text.flags.letter_spacing_justification") public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2; // 0x2
     field public static final int JUSTIFICATION_MODE_INTER_WORD = 1; // 0x1
     field public static final int JUSTIFICATION_MODE_NONE = 0; // 0x0
   }
@@ -53593,8 +53626,8 @@
     method @NonNull public abstract android.view.LayoutInflater getLayoutInflater();
     method protected final int getLocalFeatures();
     method public android.media.session.MediaController getMediaController();
-    method @ColorInt public abstract int getNavigationBarColor();
-    method @ColorInt public int getNavigationBarDividerColor();
+    method @Deprecated @ColorInt public abstract int getNavigationBarColor();
+    method @Deprecated @ColorInt public int getNavigationBarDividerColor();
     method @NonNull public android.window.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
     method public android.transition.Transition getReenterTransition();
     method public android.transition.Transition getReturnTransition();
@@ -53604,7 +53637,7 @@
     method public android.transition.Transition getSharedElementReenterTransition();
     method public android.transition.Transition getSharedElementReturnTransition();
     method public boolean getSharedElementsUseOverlay();
-    method @ColorInt public abstract int getStatusBarColor();
+    method @Deprecated @ColorInt public abstract int getStatusBarColor();
     method @NonNull public java.util.List<android.graphics.Rect> getSystemGestureExclusionRects();
     method public long getTransitionBackgroundFadeDuration();
     method public android.transition.TransitionManager getTransitionManager();
@@ -53620,7 +53653,7 @@
     method public abstract boolean isFloating();
     method public boolean isNavigationBarContrastEnforced();
     method public abstract boolean isShortcutKey(int, android.view.KeyEvent);
-    method public boolean isStatusBarContrastEnforced();
+    method @Deprecated public boolean isStatusBarContrastEnforced();
     method public boolean isWideColorGamut();
     method public final void makeActive();
     method protected abstract void onActive();
@@ -53652,7 +53685,7 @@
     method public abstract void setContentView(android.view.View);
     method public abstract void setContentView(android.view.View, android.view.ViewGroup.LayoutParams);
     method public abstract void setDecorCaptionShade(int);
-    method public void setDecorFitsSystemWindows(boolean);
+    method @Deprecated public void setDecorFitsSystemWindows(boolean);
     method protected void setDefaultWindowFormat(int);
     method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public void setDesiredHdrHeadroom(@FloatRange(from=0.0f, to=10000.0) float);
     method public void setDimAmount(float);
@@ -53674,9 +53707,9 @@
     method public void setLocalFocus(boolean, boolean);
     method public void setLogo(@DrawableRes int);
     method public void setMediaController(android.media.session.MediaController);
-    method public abstract void setNavigationBarColor(@ColorInt int);
+    method @Deprecated public abstract void setNavigationBarColor(@ColorInt int);
     method public void setNavigationBarContrastEnforced(boolean);
-    method public void setNavigationBarDividerColor(@ColorInt int);
+    method @Deprecated public void setNavigationBarDividerColor(@ColorInt int);
     method public void setPreferMinimalPostProcessing(boolean);
     method public void setReenterTransition(android.transition.Transition);
     method public abstract void setResizingCaptionDrawable(android.graphics.drawable.Drawable);
@@ -53688,8 +53721,8 @@
     method public void setSharedElementReturnTransition(android.transition.Transition);
     method public void setSharedElementsUseOverlay(boolean);
     method public void setSoftInputMode(int);
-    method public abstract void setStatusBarColor(@ColorInt int);
-    method public void setStatusBarContrastEnforced(boolean);
+    method @Deprecated public abstract void setStatusBarColor(@ColorInt int);
+    method @Deprecated public void setStatusBarContrastEnforced(boolean);
     method public void setSustainedPerformanceMode(boolean);
     method public void setSystemGestureExclusionRects(@NonNull java.util.List<android.graphics.Rect>);
     method public abstract void setTitle(CharSequence);
diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt
index 162f54c..e901f00 100644
--- a/core/api/lint-baseline.txt
+++ b/core/api/lint-baseline.txt
@@ -389,6 +389,12 @@
     Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
 
 
+KotlinOperator: android.graphics.Matrix44#get(int, int):
+    Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
+KotlinOperator: android.graphics.Matrix44#set(int, int, float):
+    Method can be invoked with an indexing operator from Kotlin: `set` (this is usually desirable; just make sure it makes sense for this type of object)
+
+
 RequiresPermission: android.accounts.AccountManager#getAccountsByTypeAndFeatures(String, String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler):
     Method 'getAccountsByTypeAndFeatures' documentation mentions permissions without declaring @RequiresPermission
 RequiresPermission: android.accounts.AccountManager#hasFeatures(android.accounts.Account, String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler):
@@ -477,6 +483,8 @@
     Method 'clearResetPasswordToken' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#generateKeyPair(android.content.ComponentName, String, android.security.keystore.KeyGenParameterSpec, int):
     Method 'generateKeyPair' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.app.admin.DevicePolicyManager#getContentProtectionPolicy(android.content.ComponentName):
+    Method 'getContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#getCrossProfileWidgetProviders(android.content.ComponentName):
     Method 'getCrossProfileWidgetProviders' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#getLockTaskFeatures(android.content.ComponentName):
@@ -513,6 +521,8 @@
     Method 'removeCrossProfileWidgetProvider' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#setAlwaysOnVpnPackage(android.content.ComponentName, String, boolean):
     Method 'setAlwaysOnVpnPackage' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.app.admin.DevicePolicyManager#setContentProtectionPolicy(android.content.ComponentName, int):
+    Method 'setContentProtectionPolicy' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#setLockTaskFeatures(android.content.ComponentName, int):
     Method 'setLockTaskFeatures' documentation mentions permissions already declared by @RequiresPermission
 RequiresPermission: android.app.admin.DevicePolicyManager#setLockTaskPackages(android.content.ComponentName, String[]):
diff --git a/core/api/module-lib-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 f36d560..d6a0185 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -11,6 +11,7 @@
     field public static final String ACCESS_DRM_CERTIFICATES = "android.permission.ACCESS_DRM_CERTIFICATES";
     field @Deprecated public static final String ACCESS_FM_RADIO = "android.permission.ACCESS_FM_RADIO";
     field public static final String ACCESS_FPS_COUNTER = "android.permission.ACCESS_FPS_COUNTER";
+    field @FlaggedApi("android.multiuser.flags.enable_permission_to_access_hidden_profiles") public static final String ACCESS_HIDDEN_PROFILES_FULL = "android.permission.ACCESS_HIDDEN_PROFILES_FULL";
     field public static final String ACCESS_INSTANT_APPS = "android.permission.ACCESS_INSTANT_APPS";
     field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ACCESS_LAST_KNOWN_CELL_ID = "android.permission.ACCESS_LAST_KNOWN_CELL_ID";
     field public static final String ACCESS_LOCUS_ID_USAGE_STATS = "android.permission.ACCESS_LOCUS_ID_USAGE_STATS";
@@ -3153,7 +3154,9 @@
   public class WearableSensingManager {
     method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideData(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideDataStream(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideWearableConnection(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     field public static final int STATUS_ACCESS_DENIED = 5; // 0x5
+    field @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") public static final int STATUS_CHANNEL_ERROR = 7; // 0x7
     field public static final int STATUS_SERVICE_UNAVAILABLE = 3; // 0x3
     field public static final int STATUS_SUCCESS = 1; // 0x1
     field public static final int STATUS_UNKNOWN = 0; // 0x0
@@ -4249,7 +4252,6 @@
   public final class UserProperties implements android.os.Parcelable {
     method public int describeContents();
     method public int getCrossProfileContentSharingStrategy();
-    method @FlaggedApi("android.multiuser.support_hiding_profiles") @NonNull public int getProfileApiVisibility();
     method public int getShowInQuietMode();
     method public int getShowInSharingSurfaces();
     method public boolean isCredentialShareableWithParent();
@@ -4259,9 +4261,6 @@
     field public static final int CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT = 1; // 0x1
     field public static final int CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION = 0; // 0x0
     field public static final int CROSS_PROFILE_CONTENT_SHARING_UNKNOWN = -1; // 0xffffffff
-    field @FlaggedApi("android.multiuser.support_hiding_profiles") public static final int PROFILE_API_VISIBILITY_HIDDEN = 1; // 0x1
-    field @FlaggedApi("android.multiuser.support_hiding_profiles") public static final int PROFILE_API_VISIBILITY_UNKNOWN = -1; // 0xffffffff
-    field @FlaggedApi("android.multiuser.support_hiding_profiles") public static final int PROFILE_API_VISIBILITY_VISIBLE = 0; // 0x0
     field public static final int SHOW_IN_QUIET_MODE_DEFAULT = 2; // 0x2
     field public static final int SHOW_IN_QUIET_MODE_HIDDEN = 1; // 0x1
     field public static final int SHOW_IN_QUIET_MODE_PAUSED = 0; // 0x0
@@ -12404,6 +12403,7 @@
     method @Deprecated public int onDownloadSubscription(int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean);
     method @Deprecated public abstract int onEraseSubscriptions(int);
     method public int onEraseSubscriptions(int, int);
+    method @FlaggedApi("com.android.internal.telephony.flags.esim_available_memory") public long onGetAvailableMemoryInBytes(int);
     method public abstract android.service.euicc.GetDefaultDownloadableSubscriptionListResult onGetDefaultDownloadableSubscriptionList(int, boolean);
     method public abstract android.service.euicc.GetDownloadableSubscriptionMetadataResult onGetDownloadableSubscriptionMetadata(int, android.telephony.euicc.DownloadableSubscription, boolean);
     method @NonNull public android.service.euicc.GetDownloadableSubscriptionMetadataResult onGetDownloadableSubscriptionMetadata(int, int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean);
@@ -13433,6 +13433,7 @@
     method @BinderThread public abstract void onDataProvided(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @BinderThread public abstract void onDataStreamProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @BinderThread public abstract void onQueryServiceStatus(@NonNull java.util.Set<java.lang.Integer>, @NonNull String, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionServiceStatus>);
+    method @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") @BinderThread public void onSecureWearableConnectionProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @BinderThread public abstract void onStartDetection(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull String, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionServiceStatus>, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionResult>);
     method public abstract void onStopDetection(@NonNull String);
     field public static final String SERVICE_INTERFACE = "android.service.wearable.WearableSensingService";
@@ -14757,6 +14758,7 @@
     method public boolean areUiccApplicationsEnabled();
     method @Nullable public java.util.List<android.telephony.UiccAccessRule> getAccessRules();
     method public int getProfileClass();
+    method @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") public int getTransferStatus();
     method public boolean isGroupDisabled();
   }
 
@@ -14779,6 +14781,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultVoiceSubscriptionId(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPreferredDataSubscriptionId(int, boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setSubscriptionEnabled(int, boolean);
+    method @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setTransferStatus(int, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUiccApplicationsEnabled(int, boolean);
     field @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_PLANS) public static final String ACTION_SUBSCRIPTION_PLANS_CHANGED = "android.telephony.action.SUBSCRIPTION_PLANS_CHANGED";
     field @NonNull public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI;
@@ -14788,6 +14791,9 @@
     field public static final int PROFILE_CLASS_PROVISIONING = 1; // 0x1
     field public static final int PROFILE_CLASS_TESTING = 0; // 0x0
     field public static final int PROFILE_CLASS_UNSET = -1; // 0xffffffff
+    field @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") public static final int TRANSFER_STATUS_CONVERTED = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") public static final int TRANSFER_STATUS_NONE = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") public static final int TRANSFER_STATUS_TRANSFERRED_OUT = 1; // 0x1
     field @NonNull public static final android.net.Uri VT_ENABLED_CONTENT_URI;
     field @NonNull public static final android.net.Uri WFC_ENABLED_CONTENT_URI;
     field @NonNull public static final android.net.Uri WFC_MODE_CONTENT_URI;
@@ -15653,7 +15659,9 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public int getOtaStatus();
     method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public java.util.List<java.lang.String> getSupportedCountries();
     method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public java.util.List<java.lang.String> getUnsupportedCountries();
+    method @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public boolean isPsimConversionSupported(int);
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public boolean isSupportedCountry(@NonNull String);
+    method @FlaggedApi("com.android.internal.telephony.flags.support_psim_to_esim_conversion") @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setPsimConversionSupportedCarriers(@NonNull java.util.Set<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setSupportedCountries(@NonNull java.util.List<java.lang.String>);
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setUnsupportedCountries(@NonNull java.util.List<java.lang.String>);
     field @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public static final String ACTION_CONVERT_TO_EMBEDDED_SUBSCRIPTION = "android.telephony.euicc.action.CONVERT_TO_EMBEDDED_SUBSCRIPTION";
@@ -17377,6 +17385,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();
@@ -17442,10 +17463,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>);
@@ -17507,6 +17529,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..bbd6bde 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -624,6 +624,7 @@
     field public static final int OPERATION_SET_APPLICATION_HIDDEN = 15; // 0xf
     field public static final int OPERATION_SET_APPLICATION_RESTRICTIONS = 16; // 0x10
     field public static final int OPERATION_SET_CAMERA_DISABLED = 31; // 0x1f
+    field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final int OPERATION_SET_CONTENT_PROTECTION_POLICY = 41; // 0x29
     field public static final int OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY = 32; // 0x20
     field public static final int OPERATION_SET_GLOBAL_PRIVATE_DNS = 33; // 0x21
     field public static final int OPERATION_SET_KEEP_UNINSTALLED_PACKAGES = 17; // 0x11
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/Notification.java b/core/java/android/app/Notification.java
index 0a34d36..aa9de81 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -23,6 +23,7 @@
 import static android.app.admin.DevicePolicyResources.UNDEFINED;
 import static android.graphics.drawable.Icon.TYPE_URI;
 import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
+import static android.app.Flags.evenlyDividedCallStyleActionLayout;
 
 import static java.util.Objects.requireNonNull;
 
@@ -3540,15 +3541,12 @@
      * Sets the token used for background operations for the pending intents associated with this
      * notification.
      *
-     * This token is automatically set during deserialization for you, you usually won't need to
-     * call this unless you want to change the existing token, if any.
-     *
      * @hide
      */
-    public void clearAllowlistToken() {
-        mAllowlistToken = null;
+    public void overrideAllowlistToken(IBinder token) {
+        mAllowlistToken = token;
         if (publicVersion != null) {
-            publicVersion.clearAllowlistToken();
+            publicVersion.overrideAllowlistToken(token);
         }
     }
 
@@ -5957,7 +5955,7 @@
                 // there is enough space to do so (and fall back to the left edge if not).
                 big.setInt(R.id.actions, "setCollapsibleIndentDimen",
                         R.dimen.call_notification_collapsible_indent);
-                if (CallStyle.USE_NEW_ACTION_LAYOUT) {
+                if (evenlyDividedCallStyleActionLayout()) {
                     if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
                         Log.d(TAG, "setting evenly divided mode on action list");
                     }
@@ -6439,7 +6437,7 @@
                     title = ContrastColorUtil.ensureColorSpanContrast(title, buttonFillColor);
                 }
                 final CharSequence label = ensureColorSpanContrast(title, p);
-                if (p.mCallStyleActions && CallStyle.USE_NEW_ACTION_LAYOUT) {
+                if (p.mCallStyleActions && evenlyDividedCallStyleActionLayout()) {
                     if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
                         Log.d(TAG, "new action layout enabled, gluing instead of setting text");
                     }
@@ -6463,7 +6461,7 @@
                 button.setColorStateList(R.id.action0, "setButtonBackground",
                         ColorStateList.valueOf(buttonFillColor));
                 if (p.mCallStyleActions) {
-                    if (CallStyle.USE_NEW_ACTION_LAYOUT) {
+                    if (evenlyDividedCallStyleActionLayout()) {
                         if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) {
                             Log.d(TAG, "new action layout enabled, gluing instead of setting icon");
                         }
@@ -9600,11 +9598,6 @@
         /**
          * @hide
          */
-        public static final boolean USE_NEW_ACTION_LAYOUT = false;
-
-        /**
-         * @hide
-         */
         public static final boolean DEBUG_NEW_ACTION_LAYOUT = true;
 
         /**
diff --git a/core/java/android/app/QueuedWork.java b/core/java/android/app/QueuedWork.java
index 6a114f9..60b61f3 100644
--- a/core/java/android/app/QueuedWork.java
+++ b/core/java/android/app/QueuedWork.java
@@ -27,6 +27,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ExponentiallyBucketedHistogram;
 
 import java.util.LinkedList;
@@ -114,6 +115,20 @@
     }
 
     /**
+     * Tear down the handler.
+     */
+    @VisibleForTesting
+    public static void resetHandler() {
+        synchronized (sLock) {
+            if (sHandler == null) {
+                return;
+            }
+            sHandler.getLooper().quitSafely();
+            sHandler = null;
+        }
+    }
+
+    /**
      * Remove all Messages from the Handler with the given code.
      *
      * This method intentionally avoids creating the Handler if it doesn't
diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java
index b0bec78..d7aafa0 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -22,7 +22,6 @@
 import android.app.admin.flags.Flags;
 import android.os.UserManager;
 
-
 import java.util.Objects;
 
 /**
@@ -163,6 +162,12 @@
     public static final String CROSS_PROFILE_WIDGET_PROVIDER_POLICY = "crossProfileWidgetProvider";
 
     /**
+     * String identifier for {@link DevicePolicyManager#setContentProtectionPolicy}.
+     */
+    @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+    public static final String CONTENT_PROTECTION_POLICY = "contentProtection";
+
+    /**
      * String identifier for {@link DevicePolicyManager#setUsbDataSignalingEnabled}.
      */
     @FlaggedApi(Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED)
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 86d0125..1ef4346 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -24,6 +24,7 @@
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CAMERA;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CERTIFICATES;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_DEFAULT_SMS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_FACTORY_RESET;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_INPUT_METHODS;
@@ -53,7 +54,6 @@
 import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
 import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
-import static android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED;
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
@@ -3825,6 +3825,10 @@
     /** @hide */
     @TestApi
     public static final int OPERATION_UNINSTALL_CA_CERT = 40;
+    /** @hide */
+    @TestApi
+    @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+    public static final int OPERATION_SET_CONTENT_PROTECTION_POLICY = 41;
 
     private static final String PREFIX_OPERATION = "OPERATION_";
 
@@ -3869,7 +3873,8 @@
             OPERATION_SET_PERMISSION_GRANT_STATE,
             OPERATION_SET_PERMISSION_POLICY,
             OPERATION_SET_RESTRICTIONS_PROVIDER,
-            OPERATION_UNINSTALL_CA_CERT
+            OPERATION_UNINSTALL_CA_CERT,
+            OPERATION_SET_CONTENT_PROTECTION_POLICY
     })
     @Retention(RetentionPolicy.SOURCE)
     public static @interface DevicePolicyOperation {
@@ -4095,15 +4100,15 @@
     }
 
     /** Indicates that content protection is not controlled by policy, allowing user to choose. */
-    @FlaggedApi(FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+    @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
     public static final int CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY = 0;
 
-    /** Indicates that content protection is controlled and disabled by a policy. */
-    @FlaggedApi(FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+    /** Indicates that content protection is controlled and disabled by a policy (default). */
+    @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
     public static final int CONTENT_PROTECTION_DISABLED = 1;
 
     /** Indicates that content protection is controlled and enabled by a policy. */
-    @FlaggedApi(FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+    @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
     public static final int CONTENT_PROTECTION_ENABLED = 2;
 
     /** @hide */
@@ -4118,6 +4123,86 @@
     public @interface ContentProtectionPolicy {}
 
     /**
+     * Sets the content protection policy which controls scanning for deceptive apps.
+     * <p>
+     * This function can only be called by the device owner, a profile owner of an affiliated user
+     * or profile, or the profile owner when no device owner is set or holders of the permission
+     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CONTENT_PROTECTION}. See
+     * {@link #isAffiliatedUser}.
+     * Any policy set via this method will be cleared if the user becomes unaffiliated.
+     * <p>
+     * After the content protection policy has been set,
+     * {@link PolicyUpdateReceiver#onPolicySetResult(Context, String, Bundle, TargetUser,
+     * PolicyUpdateResult)} will notify the admin on whether the policy was successfully set or not.
+     * This callback will contain:
+     * <ul>
+     * <li> The policy identifier {@link DevicePolicyIdentifiers#CONTENT_PROTECTION_POLICY}
+     * <li> The {@link TargetUser} that this policy relates to
+     * <li> The {@link PolicyUpdateResult}, which will be
+     * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the
+     * reason the policy failed to be set
+     * (e.g. {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY})
+     * </ul>
+     * If there has been a change to the policy,
+     * {@link PolicyUpdateReceiver#onPolicyChanged(Context, String, Bundle, TargetUser,
+     * PolicyUpdateResult)} will notify the admin of this change. This callback will contain the
+     * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult}
+     * will contain the reason why the policy changed.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
+     *               caller is not a device admin.
+     * @param policy The content protection policy to set. One of {@link
+     *               #CONTENT_PROTECTION_NOT_CONTROLLED_BY_POLICY},
+     *               {@link #CONTENT_PROTECTION_DISABLED} or {@link #CONTENT_PROTECTION_ENABLED}.
+     * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an
+     * affiliated user or profile, or the profile owner when no device owner is set or holder of the
+     * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CONTENT_PROTECTION}.
+     * @see #isAffiliatedUser
+     */
+    @RequiresPermission(value = MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, conditional = true)
+    @SupportsCoexistence
+    @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+    public void setContentProtectionPolicy(
+            @Nullable ComponentName admin, @ContentProtectionPolicy int policy) {
+        throwIfParentInstance("setContentProtectionPolicy");
+        if (mService != null) {
+            try {
+                mService.setContentProtectionPolicy(admin, mContext.getPackageName(), policy);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Returns the current content protection policy.
+     * <p>
+     * The returned policy will be the current resolved policy rather than the policy set by the
+     * calling admin.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
+     *              caller is not a device admin.
+     * @throws SecurityException if {@code admin} is not the device owner, the profile owner of an
+     * affiliated user or profile, or the profile owner when no device owner is set or holder of the
+     * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CONTENT_PROTECTION}.
+     * @see #isAffiliatedUser
+     * @see #setContentProtectionPolicy
+     */
+    @RequiresPermission(value = MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, conditional = true)
+    @FlaggedApi(android.view.contentprotection.flags.Flags.FLAG_MANAGE_DEVICE_POLICY_ENABLED)
+    public @ContentProtectionPolicy int getContentProtectionPolicy(@Nullable ComponentName admin) {
+        throwIfParentInstance("getContentProtectionPolicy");
+        if (mService != null) {
+            try {
+                return mService.getContentProtectionPolicy(admin, mContext.getPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return CONTENT_PROTECTION_DISABLED;
+    }
+
+    /**
      * This object is a single place to tack on invalidation and disable calls.  All
      * binder caches in this class derive from this Config, so all can be invalidated or
      * disabled through this Config.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 575fa4c..efcf563 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -610,4 +610,7 @@
     String getFinancedDeviceKioskRoleHolder(String callerPackageName);
 
     void calculateHasIncompatibleAccounts();
+
+    void setContentProtectionPolicy(in ComponentName who, String callerPackageName, int policy);
+    int getContentProtectionPolicy(in ComponentName who, String callerPackageName);
 }
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index ca2e97e..ed1b8ca 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -17,12 +17,14 @@
 package android.app.admin;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.os.Build;
@@ -99,6 +101,7 @@
             TAG_PACKAGE_INSTALLED,
             TAG_PACKAGE_UPDATED,
             TAG_PACKAGE_UNINSTALLED,
+            TAG_BACKUP_SERVICE_TOGGLED,
     })
     public @interface SecurityLogTag {}
 
@@ -599,6 +602,18 @@
     public static final int TAG_PACKAGE_UNINSTALLED = SecurityLogTags.SECURITY_PACKAGE_UNINSTALLED;
 
     /**
+     * Indicates that an admin has enabled or disabled backup service. The log entry contains the
+     * following information about the event encapsulated in an {@link Object} array, accessible
+     * via {@link SecurityEvent#getData()}:
+     * <li> [0] admin package name ({@code String})
+     * <li> [1] admin user ID ({@code Integer})
+     * <li> [2] backup service state ({@code Integer}, 1 for enabled, 0 for disabled)
+     * @see DevicePolicyManager#setBackupServiceEnabled(ComponentName, boolean)
+     */
+    @FlaggedApi(Flags.FLAG_BACKUP_SERVICE_SECURITY_LOG_EVENT_ENABLED)
+    public static final int TAG_BACKUP_SERVICE_TOGGLED =
+            SecurityLogTags.SECURITY_BACKUP_SERVICE_TOGGLED;
+    /**
      * Event severity level indicating that the event corresponds to normal workflow.
      */
     public static final int LEVEL_INFO = 1;
diff --git a/core/java/android/app/admin/SecurityLogTags.logtags b/core/java/android/app/admin/SecurityLogTags.logtags
index e4af8dd..7b3aa7b 100644
--- a/core/java/android/app/admin/SecurityLogTags.logtags
+++ b/core/java/android/app/admin/SecurityLogTags.logtags
@@ -47,4 +47,5 @@
 210040 security_bluetooth_disconnection         (addr|3),(reason|3)
 210041 security_package_installed               (package_name|3),(version_code|1),(user_id|1)
 210042 security_package_updated                 (package_name|3),(version_code|1),(user_id|1)
-210043 security_package_uninstalled             (package_name|3),(version_code|1),(user_id|1)
\ No newline at end of file
+210043 security_package_uninstalled             (package_name|3),(version_code|1),(user_id|1)
+210044 security_backup_service_toggled          (package|3),(admin_user|1),(enabled|1)
\ No newline at end of file
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index b3ecd92..561eb00 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -62,3 +62,10 @@
     description: "Exempt the default sms app of the context user for suspension when calling setPersonalAppsSuspended"
     bug: "309183330"
 }
+
+flag {
+  name: "backup_service_security_log_event_enabled"
+  namespace: "enterprise"
+  description: "Emit a security log event when DPM.setBackupServiceEnabled is called"
+  bug: "304999634"
+}
diff --git a/core/java/android/app/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..9d55ce28 100644
--- a/core/java/android/app/wearable/IWearableSensingManager.aidl
+++ b/core/java/android/app/wearable/IWearableSensingManager.aidl
@@ -28,6 +28,8 @@
  */
 interface IWearableSensingManager {
      @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
+     void provideWearableConnection(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
+     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
      void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
      @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
      void provideData(in PersistableBundle data, in SharedMemory sharedMemory, in RemoteCallback callback);
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
index eca0039..401d0b7 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -26,6 +26,7 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.app.ambientcontext.AmbientContextEvent;
+import android.companion.CompanionDeviceManager;
 import android.content.Context;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
@@ -36,6 +37,8 @@
 import android.service.wearable.WearableSensingService;
 import android.system.OsConstants;
 
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.Executor;
@@ -107,6 +110,14 @@
     @FlaggedApi(Flags.FLAG_ENABLE_UNSUPPORTED_OPERATION_STATUS_CODE)
     public static final int STATUS_UNSUPPORTED_OPERATION = 6;
 
+    /**
+     * The value of the status code that indicates an error occurred in the encrypted channel backed
+     * by the provided connection. See {@link #provideWearableConnection(ParcelFileDescriptor,
+     * Executor, Consumer)}.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API)
+    public static final int STATUS_CHANNEL_ERROR = 7;
+
     /** @hide */
     @IntDef(prefix = { "STATUS_" }, value = {
             STATUS_UNKNOWN,
@@ -115,7 +126,8 @@
             STATUS_SERVICE_UNAVAILABLE,
             STATUS_WEARABLE_UNAVAILABLE,
             STATUS_ACCESS_DENIED,
-            STATUS_UNSUPPORTED_OPERATION
+            STATUS_UNSUPPORTED_OPERATION,
+            STATUS_CHANNEL_ERROR
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface StatusCode {}
@@ -132,6 +144,60 @@
     }
 
     /**
+     * Provides a remote wearable device connection to the WearableSensingService and sends the
+     * resulting status to the {@code statusConsumer} after the call.
+     *
+     * <p>This is used by applications that will also provide an implementation of the isolated
+     * WearableSensingService.
+     *
+     * <p>The provided {@code wearableConnection} is expected to be a connection to a remotely
+     * connected wearable device. This {@code wearableConnection} will be attached to
+     * CompanionDeviceManager via {@link CompanionDeviceManager#attachSystemDataTransport(int,
+     * InputStream, OutputStream)}, which will create an encrypted channel using {@code
+     * wearableConnection} as the raw underlying connection. The wearable device is expected to
+     * attach its side of the raw connection to its CompanionDeviceManager via the same method so
+     * that the two CompanionDeviceManagers on the two devices can perform attestation and set up
+     * the encrypted channel. Attestation requirements are listed in
+     * com.android.server.security.AttestationVerificationPeerDeviceVerifier
+     *
+     * <p>A proxy to the encrypted channel will be provided to the WearableSensingService, which is
+     * referred to as the secureWearableConnection in WearableSensingService. Any data written to
+     * secureWearableConnection will be encrypted by CompanionDeviceManager and sent over the raw
+     * {@code wearableConnection} to the remote wearable device, which is expected to use its
+     * CompanionDeviceManager to decrypt the data. Encrypted data arriving at the raw {@code
+     * wearableConnection} will be decrypted by CompanionDeviceManager and be readable as plain text
+     * from secureWearableConnection. The raw {@code wearableConnection} provided to this method
+     * will not be directly available to the WearableSensingService.
+     *
+     * <p>If an error occurred in the encrypted channel (such as the underlying stream closed), the
+     * system will send a status code of {@link STATUS_CHANNEL_ERROR} to the {@code statusConsumer}
+     * and kill the WearableSensingService process.
+     *
+     * <p>Before providing the secureWearableConnection, the system will restart the
+     * WearableSensingService process. Other method calls into WearableSensingService may be dropped
+     * during the restart. The caller is responsible for ensuring other method calls are queued
+     * until a success status is returned from the {@code statusConsumer}.
+     *
+     * @param wearableConnection The connection to provide
+     * @param executor Executor on which to run the consumer callback
+     * @param statusConsumer A consumer that handles the status codes for providing the connection
+     *     and errors in the encrypted channel.
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
+    @FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API)
+    public void provideWearableConnection(
+            @NonNull ParcelFileDescriptor wearableConnection,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull @StatusCode Consumer<Integer> statusConsumer) {
+        try {
+            RemoteCallback callback = createStatusCallback(executor, statusConsumer);
+            mService.provideWearableConnection(wearableConnection, callback);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Provides a data stream to the WearableSensingService that's backed by the
      * parcelFileDescriptor, and sends the result to the {@link Consumer} right after the call.
      * This is used by applications that will also provide an implementation of
@@ -149,15 +215,7 @@
             @NonNull @CallbackExecutor Executor executor,
             @NonNull @StatusCode Consumer<Integer> statusConsumer) {
         try {
-            RemoteCallback callback = new RemoteCallback(result -> {
-                int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
-                final long identity = Binder.clearCallingIdentity();
-                try {
-                    executor.execute(() -> statusConsumer.accept(status));
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
-                }
-            });
+            RemoteCallback callback = createStatusCallback(executor, statusConsumer);
             mService.provideDataStream(parcelFileDescriptor, callback);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -191,19 +249,24 @@
             @NonNull @CallbackExecutor Executor executor,
             @NonNull @StatusCode Consumer<Integer> statusConsumer) {
         try {
-            RemoteCallback callback = new RemoteCallback(result -> {
-                int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
-                final long identity = Binder.clearCallingIdentity();
-                try {
-                    executor.execute(() -> statusConsumer.accept(status));
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
-                }
-            });
+            RemoteCallback callback = createStatusCallback(executor, statusConsumer);
             mService.provideData(data, sharedMemory, callback);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
+    private static RemoteCallback createStatusCallback(
+            Executor executor, Consumer<Integer> statusConsumer) {
+        return new RemoteCallback(
+                result -> {
+                    int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
+                    final long identity = Binder.clearCallingIdentity();
+                    try {
+                        executor.execute(() -> statusConsumer.accept(status));
+                    } finally {
+                        Binder.restoreCallingIdentity(identity);
+                    }
+                });
+    }
 }
diff --git a/core/java/android/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/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/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/Telephony.java b/core/java/android/provider/Telephony.java
index 2841dc0..658cec8 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4943,6 +4943,13 @@
         public static final String COLUMN_IS_NTN = "is_ntn";
 
         /**
+         * TelephonyProvider column name for transferred status
+         *
+         * @hide
+         */
+        public static final String COLUMN_TRANSFER_STATUS = "transfer_status";
+
+        /**
          * TelephonyProvider column name to indicate the service capability bitmasks.
          *
          * @hide
@@ -5021,7 +5028,8 @@
                 COLUMN_SATELLITE_ENABLED,
                 COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
                 COLUMN_IS_NTN,
-                COLUMN_SERVICE_CAPABILITIES
+                COLUMN_SERVICE_CAPABILITIES,
+                COLUMN_TRANSFER_STATUS
         );
 
         /**
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index 1afe8d9..da8817a 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -26,8 +26,8 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.content.ClipData;
+import android.content.Intent;
 import android.content.IntentSender;
-import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArrayMap;
@@ -190,7 +190,7 @@
     @Nullable private final InlinePresentation mInlineTooltipPresentation;
     private final IntentSender mAuthentication;
 
-    @Nullable private final Bundle mAuthenticationExtras;
+    @Nullable private Intent mCredentialFillInIntent;
 
     @Nullable String mId;
 
@@ -229,7 +229,7 @@
         mInlinePresentation = inlinePresentation;
         mInlineTooltipPresentation = inlineTooltipPresentation;
         mAuthentication = authentication;
-        mAuthenticationExtras = null;
+        mCredentialFillInIntent = null;
         mId = id;
     }
 
@@ -252,7 +252,7 @@
         mInlinePresentation = dataset.mInlinePresentation;
         mInlineTooltipPresentation = dataset.mInlineTooltipPresentation;
         mAuthentication = dataset.mAuthentication;
-        mAuthenticationExtras = dataset.mAuthenticationExtras;
+        mCredentialFillInIntent = dataset.mCredentialFillInIntent;
         mId = dataset.mId;
         mAutofillDatatypes = dataset.mAutofillDatatypes;
     }
@@ -271,7 +271,7 @@
         mInlinePresentation = builder.mInlinePresentation;
         mInlineTooltipPresentation = builder.mInlineTooltipPresentation;
         mAuthentication = builder.mAuthentication;
-        mAuthenticationExtras = builder.mAuthenticationExtras;
+        mCredentialFillInIntent = builder.mCredentialFillInIntent;
         mId = builder.mId;
         mAutofillDatatypes = builder.mAutofillDatatypes;
     }
@@ -354,8 +354,14 @@
 
     /** @hide */
     @Hide
-    public @Nullable Bundle getAuthenticationExtras() {
-        return mAuthenticationExtras;
+    public @Nullable Intent getCredentialFillInIntent() {
+        return mCredentialFillInIntent;
+    }
+
+    /** @hide */
+    @Hide
+    public void setCredentialFillInIntent(Intent intent) {
+        mCredentialFillInIntent = intent;
     }
 
     /** @hide */
@@ -415,7 +421,7 @@
         if (mAuthentication != null) {
             builder.append(", hasAuthentication");
         }
-        if (mAuthenticationExtras != null) {
+        if (mCredentialFillInIntent != null) {
             builder.append(", hasAuthenticationExtras");
         }
         if (mAutofillDatatypes != null) {
@@ -472,7 +478,7 @@
         @Nullable private InlinePresentation mInlineTooltipPresentation;
         private IntentSender mAuthentication;
 
-        private Bundle mAuthenticationExtras;
+        private Intent mCredentialFillInIntent;
         private boolean mDestroyed;
         @Nullable private String mId;
 
@@ -655,9 +661,9 @@
          * @hide
          */
         @Hide
-        public @NonNull Builder setAuthenticationExtras(@Nullable Bundle authenticationExtra) {
+        public @NonNull Builder setCredentialFillInIntent(@Nullable Intent credentialFillInIntent) {
             throwIfDestroyed();
-            mAuthenticationExtras = authenticationExtra;
+            mCredentialFillInIntent = credentialFillInIntent;
             return this;
         }
 
@@ -1401,7 +1407,7 @@
         parcel.writeParcelable(mAuthentication, flags);
         parcel.writeString(mId);
         parcel.writeInt(mEligibleReason);
-        parcel.writeTypedObject(mAuthenticationExtras, flags);
+        parcel.writeTypedObject(mCredentialFillInIntent, flags);
     }
 
     public static final @NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() {
@@ -1437,7 +1443,7 @@
                     android.content.IntentSender.class);
             final String datasetId = parcel.readString();
             final int eligibleReason = parcel.readInt();
-            final Bundle authenticationExtras = parcel.readTypedObject(Bundle.CREATOR);
+            final Intent credentialFillInIntent = parcel.readTypedObject(Intent.CREATOR);
 
             // Always go through the builder to ensure the data ingested by
             // the system obeys the contract of the builder to avoid attacks
@@ -1482,7 +1488,7 @@
                         fieldDialogPresentation);
             }
             builder.setAuthentication(authentication);
-            builder.setAuthenticationExtras(authenticationExtras);
+            builder.setCredentialFillInIntent(credentialFillInIntent);
             builder.setId(datasetId);
             Dataset dataset = builder.build();
             dataset.mEligibleReason = eligibleReason;
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 09ec933..c43ba6c 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -56,6 +56,7 @@
  * <p>See the main {@link AutofillService} documentation for more details and examples.
  */
 public final class FillResponse implements Parcelable {
+    // common_typos_disable
 
     /**
      * Flag used to generate {@link FillEventHistory.Event events} of type
@@ -82,11 +83,17 @@
      */
     public static final int FLAG_DELAY_FILL = 0x4;
 
+    /**
+     * @hide
+     */
+    public static final int FLAG_CREDENTIAL_MANAGER_RESPONSE = 0x8;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "FLAG_" }, value = {
             FLAG_TRACK_CONTEXT_COMMITED,
             FLAG_DISABLE_ACTIVITY_ONLY,
-            FLAG_DELAY_FILL
+            FLAG_DELAY_FILL,
+            FLAG_CREDENTIAL_MANAGER_RESPONSE
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface FillResponseFlags {}
@@ -834,7 +841,9 @@
         public Builder setFlags(@FillResponseFlags int flags) {
             throwIfDestroyed();
             mFlags = Preconditions.checkFlagsArgument(flags,
-                    FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY | FLAG_DELAY_FILL);
+                    FLAG_TRACK_CONTEXT_COMMITED
+                            | FLAG_DISABLE_ACTIVITY_ONLY | FLAG_DELAY_FILL
+                            | FLAG_CREDENTIAL_MANAGER_RESPONSE);
             return this;
         }
 
diff --git a/core/java/android/service/wearable/IWearableSensingService.aidl b/core/java/android/service/wearable/IWearableSensingService.aidl
index 44a13c4..8fdb2c2 100644
--- a/core/java/android/service/wearable/IWearableSensingService.aidl
+++ b/core/java/android/service/wearable/IWearableSensingService.aidl
@@ -28,6 +28,7 @@
  * @hide
  */
 oneway interface IWearableSensingService {
+    void provideSecureWearableConnection(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
     void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
     void provideData(in PersistableBundle data, in SharedMemory sharedMemory, in RemoteCallback callback);
     void startDetection(in AmbientContextEventRequest request, in String packageName,
diff --git a/core/java/android/service/wearable/WearableSensingService.java b/core/java/android/service/wearable/WearableSensingService.java
index e7e44a5..d21115b 100644
--- a/core/java/android/service/wearable/WearableSensingService.java
+++ b/core/java/android/service/wearable/WearableSensingService.java
@@ -17,12 +17,14 @@
 package android.service.wearable;
 
 import android.annotation.BinderThread;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.app.Service;
 import android.app.ambientcontext.AmbientContextEvent;
 import android.app.ambientcontext.AmbientContextEventRequest;
+import android.app.wearable.Flags;
 import android.app.wearable.WearableSensingManager;
 import android.content.Intent;
 import android.os.Bundle;
@@ -39,6 +41,7 @@
 import java.util.HashSet;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /**
@@ -94,17 +97,20 @@
             return new IWearableSensingService.Stub() {
                 /** {@inheritDoc} */
                 @Override
+                public void provideSecureWearableConnection(
+                        ParcelFileDescriptor secureWearableConnection, RemoteCallback callback) {
+                    Objects.requireNonNull(secureWearableConnection);
+                    Consumer<Integer> consumer = createWearableStatusConsumer(callback);
+                    WearableSensingService.this.onSecureWearableConnectionProvided(
+                            secureWearableConnection, consumer);
+                }
+
+                /** {@inheritDoc} */
+                @Override
                 public void provideDataStream(
-                        ParcelFileDescriptor parcelFileDescriptor,
-                        RemoteCallback callback) {
+                        ParcelFileDescriptor parcelFileDescriptor, RemoteCallback callback) {
                     Objects.requireNonNull(parcelFileDescriptor);
-                    Consumer<Integer> consumer = response -> {
-                        Bundle bundle = new Bundle();
-                        bundle.putInt(
-                                STATUS_RESPONSE_BUNDLE_KEY,
-                                response);
-                        callback.sendResult(bundle);
-                    };
+                    Consumer<Integer> consumer = createWearableStatusConsumer(callback);
                     WearableSensingService.this.onDataStreamProvided(
                             parcelFileDescriptor, consumer);
                 }
@@ -116,38 +122,38 @@
                         SharedMemory sharedMemory,
                         RemoteCallback callback) {
                     Objects.requireNonNull(data);
-                    Consumer<Integer> consumer = response -> {
-                        Bundle bundle = new Bundle();
-                        bundle.putInt(
-                                STATUS_RESPONSE_BUNDLE_KEY,
-                                response);
-                        callback.sendResult(bundle);
-                    };
+                    Consumer<Integer> consumer = createWearableStatusConsumer(callback);
                     WearableSensingService.this.onDataProvided(data, sharedMemory, consumer);
                 }
 
                 /** {@inheritDoc} */
                 @Override
-                public void startDetection(@NonNull AmbientContextEventRequest request,
-                        String packageName, RemoteCallback detectionResultCallback,
+                public void startDetection(
+                        @NonNull AmbientContextEventRequest request,
+                        String packageName,
+                        RemoteCallback detectionResultCallback,
                         RemoteCallback statusCallback) {
                     Objects.requireNonNull(request);
                     Objects.requireNonNull(packageName);
                     Objects.requireNonNull(detectionResultCallback);
                     Objects.requireNonNull(statusCallback);
-                    Consumer<AmbientContextDetectionResult> detectionResultConsumer = result -> {
-                        Bundle bundle = new Bundle();
-                        bundle.putParcelable(
-                                AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY, result);
-                        detectionResultCallback.sendResult(bundle);
-                    };
-                    Consumer<AmbientContextDetectionServiceStatus> statusConsumer = status -> {
-                        Bundle bundle = new Bundle();
-                        bundle.putParcelable(
-                                AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY,
-                                status);
-                        statusCallback.sendResult(bundle);
-                    };
+                    Consumer<AmbientContextDetectionResult> detectionResultConsumer =
+                            result -> {
+                                Bundle bundle = new Bundle();
+                                bundle.putParcelable(
+                                        AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY,
+                                        result);
+                                detectionResultCallback.sendResult(bundle);
+                            };
+                    Consumer<AmbientContextDetectionServiceStatus> statusConsumer =
+                            status -> {
+                                Bundle bundle = new Bundle();
+                                bundle.putParcelable(
+                                        AmbientContextDetectionServiceStatus
+                                                .STATUS_RESPONSE_BUNDLE_KEY,
+                                        status);
+                                statusCallback.sendResult(bundle);
+                            };
                     WearableSensingService.this.onStartDetection(
                             request, packageName, statusConsumer, detectionResultConsumer);
                     Slog.d(TAG, "startDetection " + request);
@@ -162,23 +168,26 @@
 
                 /** {@inheritDoc} */
                 @Override
-                public void queryServiceStatus(@AmbientContextEvent.EventCode int[] eventTypes,
-                        String packageName, RemoteCallback callback) {
+                public void queryServiceStatus(
+                        @AmbientContextEvent.EventCode int[] eventTypes,
+                        String packageName,
+                        RemoteCallback callback) {
                     Objects.requireNonNull(eventTypes);
                     Objects.requireNonNull(packageName);
                     Objects.requireNonNull(callback);
-                    Consumer<AmbientContextDetectionServiceStatus> consumer = response -> {
-                        Bundle bundle = new Bundle();
-                        bundle.putParcelable(
-                                AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY,
-                                response);
-                        callback.sendResult(bundle);
-                    };
+                    Consumer<AmbientContextDetectionServiceStatus> consumer =
+                            response -> {
+                                Bundle bundle = new Bundle();
+                                bundle.putParcelable(
+                                        AmbientContextDetectionServiceStatus
+                                                .STATUS_RESPONSE_BUNDLE_KEY,
+                                        response);
+                                callback.sendResult(bundle);
+                            };
                     Integer[] events = intArrayToIntegerArray(eventTypes);
                     WearableSensingService.this.onQueryServiceStatus(
                             new HashSet<>(Arrays.asList(events)), packageName, consumer);
                 }
-
             };
         }
         Slog.w(TAG, "Incorrect service interface, returning null.");
@@ -186,6 +195,30 @@
     }
 
     /**
+     * Called when a secure connection to the wearable is available. See {@link
+     * WearableSensingManager#provideWearableConnection(ParcelFileDescriptor, Executor, Consumer)}
+     * for details about the secure connection.
+     *
+     * <p>When the {@code secureWearableConnection} is closed, the system will send a {@link
+     * WearableSensingManager#STATUS_CHANNEL_ERROR} status code to the status consumer provided by
+     * the caller of {@link WearableSensingManager#provideWearableConnection(ParcelFileDescriptor,
+     * Executor, Consumer)}.
+     *
+     * <p>The implementing class should override this method. It should return an appropriate status
+     * code via {@code statusConsumer} after receiving the {@code secureWearableConnection}.
+     *
+     * @param secureWearableConnection The secure connection to the wearable.
+     * @param statusConsumer The consumer for the service status.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API)
+    @BinderThread
+    public void onSecureWearableConnectionProvided(
+            @NonNull ParcelFileDescriptor secureWearableConnection,
+            @NonNull Consumer<Integer> statusConsumer) {
+        statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION);
+    }
+
+    /**
      * Called when a data stream to the wearable is provided. This data stream can be used to obtain
      * data from a wearable device. It is up to the implementation to maintain the data stream and
      * close the data stream when it is finished.
@@ -275,4 +308,13 @@
         }
         return intArray;
     }
+
+    @NonNull
+    private static Consumer<Integer> createWearableStatusConsumer(RemoteCallback statusCallback) {
+        return response -> {
+            Bundle bundle = new Bundle();
+            bundle.putInt(STATUS_RESPONSE_BUNDLE_KEY, response);
+            statusCallback.sendResult(bundle);
+        };
+    }
 }
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 1ea80f1..8ddb42d 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -18,7 +18,7 @@
 
 import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
 import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
-import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
+import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION;
 
 import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
@@ -172,7 +172,7 @@
     /**
      * Value for justification mode indicating the text is justified by stretching letter spacing.
      */
-    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
     public static final int JUSTIFICATION_MODE_INTER_CHARACTER =
             LineBreaker.JUSTIFICATION_MODE_INTER_CHARACTER;
 
@@ -1831,7 +1831,7 @@
      * @return the number of cluster count in the line.
      */
     @IntRange(from = 0)
-    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
     public int getLineLetterSpacingUnitCount(@IntRange(from = 0) int line,
             boolean includeTrailingWhitespace) {
         final int start = getLineStart(line);
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 224e5d8..bde9c77 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -305,7 +305,7 @@
             }
             mAddedWordSpacingInPx = (justifyWidth - width) / spaces;
             mAddedLetterSpacingInPx = 0;
-        } else {  // justificationMode == Layout.JUSTIFICATION_MODE_INTER_CHARACTER
+        } else {  // justificationMode == Layout.JUSTIFICATION_MODE_LETTER_SPACING
             LineInfo lineInfo = new LineInfo();
             float width = Math.abs(measure(end, false, null, null, lineInfo));
 
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index a49aee1..f37c4c2a 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -77,7 +77,7 @@
 }
 
 flag {
-  name: "inter_character_justification"
+  name: "letter_spacing_justification"
   namespace: "text"
   description: "A feature flag that implement inter character justification."
   bug: "283193133"
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 7bae7ec..4ba4ee3 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1497,13 +1497,21 @@
      * {@link View#SYSTEM_UI_LAYOUT_FLAGS} as well the
      * {@link WindowManager.LayoutParams#SOFT_INPUT_ADJUST_RESIZE} flag and fits content according
      * to these flags.
-     * </p>
+     *
      * <p>
      * If set to {@code false}, the framework will not fit the content view to the insets and will
      * just pass through the {@link WindowInsets} to the content view.
-     * </p>
+     *
+     * <p>
+     * If the app targets
+     * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
+     * the behavior will be like setting this to {@code false}, and cannot be changed.
+     *
      * @param decorFitsSystemWindows Whether the decor view should fit root-level content views for
      *                               insets.
+     * @deprecated Make space in the container views to prevent the critical elements from getting
+     *             obscured by {@link WindowInsets.Type#systemBars()} or
+     *             {@link WindowInsets.Type#displayCutout()} instead.
      */
     public void setDecorFitsSystemWindows(boolean decorFitsSystemWindows) {
     }
@@ -2597,7 +2605,9 @@
 
     /**
      * @return the color of the status bar.
+     * @deprecated This is no longer needed since the setter is deprecated.
      */
+    @Deprecated
     @ColorInt
     public abstract int getStatusBarColor();
 
@@ -2614,13 +2624,29 @@
      * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.
      * <p>
      * The transitionName for the view background will be "android:status:background".
-     * </p>
+     *
+     * <p>
+     * If the color is transparent and the window enforces the status bar contrast, the system
+     * will determine whether a scrim is necessary and draw one on behalf of the app to ensure
+     * that the status bar has enough contrast with the contents of this app, and set an appropriate
+     * effective bar background accordingly.
+     *
+     * <p>
+     * If the app targets
+     * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
+     * the color will be transparent and cannot be changed.
+     *
+     * @see #setNavigationBarContrastEnforced
+     * @deprecated Draw proper background behind {@link WindowInsets.Type#statusBars()}} instead.
      */
+    @Deprecated
     public abstract void setStatusBarColor(@ColorInt int color);
 
     /**
      * @return the color of the navigation bar.
+     * @deprecated This is no longer needed since the setter is deprecated.
      */
+    @Deprecated
     @ColorInt
     public abstract int getNavigationBarColor();
 
@@ -2637,9 +2663,24 @@
      * {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}.
      * <p>
      * The transitionName for the view background will be "android:navigation:background".
-     * </p>
+     *
+     * <p>
+     * If the color is transparent and the window enforces the navigation bar contrast, the system
+     * will determine whether a scrim is necessary and draw one on behalf of the app to ensure that
+     * the navigation bar has enough contrast with the contents of this app, and set an appropriate
+     * effective bar background accordingly.
+     *
+     * <p>
+     * If the app targets
+     * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
+     * the color will be transparent and cannot be changed.
+     *
      * @attr ref android.R.styleable#Window_navigationBarColor
+     * @see #setNavigationBarContrastEnforced
+     * @deprecated Draw proper background behind {@link WindowInsets.Type#navigationBars()} or
+     *             {@link WindowInsets.Type#tappableElement()} instead.
      */
+    @Deprecated
     public abstract void setNavigationBarColor(@ColorInt int color);
 
     /**
@@ -2651,9 +2692,17 @@
      * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} and
      * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_NAVIGATION} must not be set.
      *
+     * <p>
+     * If the app targets
+     * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM VANILLA_ICE_CREAM} or above,
+     * the color will be transparent and cannot be changed.
+     *
      * @param dividerColor The color of the thin line.
      * @attr ref android.R.styleable#Window_navigationBarDividerColor
+     * @deprecated Draw proper background behind {@link WindowInsets.Type#navigationBars()} or
+     *             {@link WindowInsets.Type#tappableElement()} instead.
      */
+    @Deprecated
     public void setNavigationBarDividerColor(@ColorInt int dividerColor) {
     }
 
@@ -2663,7 +2712,9 @@
      * @return The color of the navigation bar divider color.
      * @see #setNavigationBarColor(int)
      * @attr ref android.R.styleable#Window_navigationBarDividerColor
+     * @deprecated This is no longer needed since the setter is deprecated.
      */
+    @Deprecated
     public @ColorInt int getNavigationBarDividerColor() {
         return 0;
     }
@@ -2682,7 +2733,9 @@
      * @see android.R.attr#enforceStatusBarContrast
      * @see #isStatusBarContrastEnforced
      * @see #setStatusBarColor
+     * @deprecated Draw proper background behind {@link WindowInsets.Type#statusBars()}} instead.
      */
+    @Deprecated
     public void setStatusBarContrastEnforced(boolean ensureContrast) {
     }
 
@@ -2697,7 +2750,9 @@
      * @see android.R.attr#enforceStatusBarContrast
      * @see #setStatusBarContrastEnforced
      * @see #setStatusBarColor
+     * @deprecated This is not needed since the setter is deprecated.
      */
+    @Deprecated
     public boolean isStatusBarContrastEnforced() {
         return false;
     }
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index c49fce5..848261d 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -179,9 +179,29 @@
         }
     }
 
+    /**
+     * Sets {@link com.android.server.wm.WindowManagerService} for the system process.
+     * <p>
+     * It is needed to prevent possible deadlock. A possible scenario is:
+     * In system process, WMS holds {@link com.android.server.wm.WindowManagerGlobalLock} to call
+     * {@code WindowManagerGlobal} APIs and wait to lock {@code WindowManagerGlobal} itself
+     * (i.e. call {@link #getWindowManagerService()} in the global lock), while
+     * another component may lock {@code WindowManagerGlobal} and wait to lock
+     * {@link com.android.server.wm.WindowManagerGlobalLock}(i.e call {@link #addView} in the
+     * system process, which calls to {@link com.android.server.wm.WindowManagerService} API
+     * directly).
+     */
+    public static void setWindowManagerServiceForSystemProcess(@NonNull IWindowManager wms) {
+        sWindowManagerService = wms;
+    }
+
     @Nullable
     @UnsupportedAppUsage
     public static IWindowManager getWindowManagerService() {
+        if (sWindowManagerService != null) {
+            // Use WMS directly without locking WMGlobal to prevent deadlock.
+            return sWindowManagerService;
+        }
         synchronized (WindowManagerGlobal.class) {
             if (sWindowManagerService == null) {
                 sWindowManagerService = IWindowManager.Stub.asInterface(
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 559ccfea7..7ebabee 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -64,6 +64,7 @@
 import android.service.autofill.FillEventHistory;
 import android.service.autofill.Flags;
 import android.service.autofill.UserData;
+import android.service.credentials.CredentialProviderService;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -2382,7 +2383,18 @@
                 return;
             }
 
-            final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT);
+            final Parcelable result;
+            if (data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT) != null) {
+                result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT);
+            } else if (data.getParcelableExtra(
+                    CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE) != null
+                    && Flags.autofillCredmanIntegration()) {
+                result = data.getParcelableExtra(
+                        CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE);
+            } else {
+                result = null;
+            }
+
             final Bundle responseData = new Bundle();
             responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result);
             final Bundle newClientState = data.getBundleExtra(EXTRA_CLIENT_STATE);
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index 1dd99ba..6a4408b 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -1,13 +1,6 @@
 package: "android.view.flags"
 
 flag {
-     name: "enable_surface_native_alloc_registration"
-     namespace: "toolkit"
-     description: "Feature flag for registering surfaces with the VM for faster cleanup"
-     bug: "306193257"
-}
-
-flag {
     name: "enable_surface_native_alloc_registration_ro"
     namespace: "toolkit"
     description: "Feature flag for registering surfaces with the VM for faster"
diff --git a/core/java/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/proto/OWNERS b/core/proto/OWNERS
index 381580b..c65794e 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -19,6 +19,7 @@
 per-file android/hardware/sensorprivacy.proto = ntmyren@google.com,evanseverson@google.com
 per-file background_install_control.proto = wenhaowang@google.com,georgechan@google.com,billylau@google.com
 per-file android/content/intent.proto = file:/PACKAGE_MANAGER_OWNERS
+per-file android/hardware/location/context_hub_info.proto = file:/services/core/java/com/android/server/location/contexthub/OWNERS
 
 # Biometrics
 jaggies@google.com
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c71a842..5c764e2 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3216,6 +3216,13 @@
         android:description="@string/permdesc_accessHiddenProfile"
         android:protectionLevel="normal" />
 
+    <!-- @SystemApi @hide Allows privileged applications to get details about hidden profile
+        users.
+        @FlaggedApi("android.multiuser.flags.enable_permission_to_access_hidden_profiles") -->
+    <permission
+        android:name="android.permission.ACCESS_HIDDEN_PROFILES_FULL"
+        android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi @hide Allows starting activities across profiles in the same profile group. -->
     <permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES"
                 android:protectionLevel="signature|role" />
diff --git a/core/res/res/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/public-staging.xml b/core/res/res/values/public-staging.xml
index 81a8908..4799c37 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -147,6 +147,8 @@
     <public name="useBoundsForWidth"/>
     <!-- @FlaggedApi("android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP") -->
     <public name="autoTransact"/>
+    <!-- @FlaggedApi("com.android.window.flags.enforce_edge_to_edge") -->
+    <public name="windowOptOutEdgeToEdgeEnforcement"/>
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/res/res/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/text/TextLineJustificationTest.kt b/core/tests/coretests/src/android/text/TextLineJustificationTest.kt
index a525615..c18f35f 100644
--- a/core/tests/coretests/src/android/text/TextLineJustificationTest.kt
+++ b/core/tests/coretests/src/android/text/TextLineJustificationTest.kt
@@ -126,4 +126,4 @@
 
         TextLine.recycle(tl)
     }
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
index 27869bb..71980c1 100644
--- a/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
+++ b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
@@ -21,7 +21,7 @@
 import android.platform.test.flag.junit.DeviceFlagsValueProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION
+import com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
@@ -40,7 +40,7 @@
     @JvmField
     val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
 
-    @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @RequiresFlagsEnabled(FLAG_LETTER_SPACING_JUSTIFICATION)
     @Test
     fun calculateRunFlagTest() {
         // Only one Bidi run
@@ -84,7 +84,7 @@
                 .isEqualTo(LEFT_EDGE)
     }
 
-    @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @RequiresFlagsEnabled(FLAG_LETTER_SPACING_JUSTIFICATION)
     @Test
     fun resolveRunFlagForSubSequenceTest() {
         val runStart = 5
@@ -221,4 +221,4 @@
                 MIDDLE_OF_LINE, true, runStart, runEnd, runStart + 1, runEnd - 1))
                 .isEqualTo(MIDDLE_OF_LINE)
     }
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
index de55b07..3df3b9d2 100644
--- a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
+++ b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
@@ -20,6 +20,7 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
 
 import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertThat;
@@ -63,7 +64,8 @@
         createPhoneWindowWithTheme(R.style.LayoutInDisplayCutoutModeUnset);
         installDecor();
 
-        if (mPhoneWindow.mEdgeToEdgeEnforced && !mPhoneWindow.isFloating()) {
+        if ((mPhoneWindow.getAttributes().privateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0
+                && !mPhoneWindow.isFloating()) {
             assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode,
                     is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS));
         } else {
diff --git a/data/etc/enhanced-confirmation.xml b/data/etc/enhanced-confirmation.xml
index 4a9dd2f..3b1867c 100644
--- a/data/etc/enhanced-confirmation.xml
+++ b/data/etc/enhanced-confirmation.xml
@@ -21,12 +21,36 @@
 
 Example usage:
 
-    <enhanced-confirmation-trusted-installer
+    <enhanced-confirmation-trusted-package
          package="com.example.app"
-         signature="E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0"/>
+         sha256-cert-digest="E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0"/>
 
-This indicates that "com.example.app" should be exempt from ECM, and that, if "com.example.app" is
-an installer, all packages installed via "com.example.app" will also be exempt from ECM.
+    ...
+
+    <enhanced-confirmation-trusted-installer
+         package="com.example.installer"
+         sha256-cert-digest="E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0"/>
+
+    ...
+
+The "enhanced-confirmation-trusted-package" entry shown above indicates that "com.example.app"
+should be considered a "trusted package". A "trusted package" will be exempt from ECM restrictions.
+
+The "enhanced-confirmation-trusted-installer" entry shown above indicates that
+"com.example.installer" should be considered a "trusted installer". A "trusted installer", and all
+packages that it installs, will be exempt from ECM restrictions. (There are some exceptions to this.
+For example, a trusted installer, at the time of installing an app, can opt the app back in to ECM
+restrictions by setting the app's package source to PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
+or PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE.)
+
+In either case:
+
+- The "package" XML attribute refers to the app's package name.
+- The "sha256-cert-digest" XML attribute refers to the SHA-256 hash of an app signing certificate.
+
+For any entry to successfully apply to a package, both XML attributes must be present, and must
+match the package. That is, the package name must match the "package" attribute, and the app must be
+signed by the signing certificate identified by the "sha256-cert-digest" attribute..
 -->
 
 <config></config>
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index ae61a2d..df95a91 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -17,7 +17,7 @@
 package android.graphics;
 
 import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
-import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
+import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION;
 
 import android.annotation.ColorInt;
 import android.annotation.ColorLong;
@@ -293,7 +293,7 @@
      * {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both
      * {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified.
      */
-    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
     public static final int TEXT_RUN_FLAG_LEFT_EDGE = 0x2000;
 
 
@@ -324,7 +324,7 @@
      * {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both
      * {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified.
      */
-    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
     public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 0x4000;
 
     // These flags are always set on a new/reset paint, even if flags 0 is passed.
diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java
index 9707126..0e9f29d 100644
--- a/graphics/java/android/graphics/text/LineBreaker.java
+++ b/graphics/java/android/graphics/text/LineBreaker.java
@@ -17,7 +17,7 @@
 package android.graphics.text;
 
 import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
-import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
+import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION;
 
 import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
@@ -183,7 +183,7 @@
     /**
      * Value for justification mode indicating the text is justified by stretching letter spacing.
      */
-    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @FlaggedApi(FLAG_LETTER_SPACING_JUSTIFICATION)
     public static final int JUSTIFICATION_MODE_INTER_CHARACTER = 2;
 
     /**
diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
index 2beb434..2430e8d 100644
--- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java
+++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
 import android.os.StrictMode;
@@ -218,4 +219,28 @@
             return SYSTEM_ERROR;
         }
     }
+
+    /**
+     * Returns the list of Application UIDs that have auth-bound keys that are bound to
+     * the given SID. This enables warning the user when they are about to invalidate
+     * a SID (for example, removing the LSKF).
+     *
+     * @param userId - The ID of the user the SID is associated with.
+     * @param userSecureId - The SID in question.
+     *
+     * @return A list of app UIDs.
+     */
+    public static long[] getAllAppUidsAffectedBySid(int userId, long userSecureId)
+            throws KeyStoreException {
+        StrictMode.noteDiskWrite();
+        try {
+            return getService().getAppUidsAffectedBySid(userId, userSecureId);
+        } catch (RemoteException | NullPointerException e) {
+            throw new KeyStoreException(SYSTEM_ERROR,
+                    "Failure to connect to Keystore while trying to get apps affected by SID.");
+        } catch (ServiceSpecificException e) {
+            throw new KeyStoreException(e.errorCode,
+                    "Keystore error while trying to get apps affected by SID.");
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 9854e58..a80afe2 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -45,9 +45,6 @@
     <!-- Allow PIP to resize to a slightly bigger state upon touch/showing the menu -->
     <bool name="config_pipEnableResizeForMenu">true</bool>
 
-    <!-- Allow PIP to resize via dragging the corner of PiP. -->
-    <bool name="config_pipEnableDragCornerResize">false</bool>
-
     <!-- PiP minimum size, which is a % based off the shorter side of display width and height -->
     <fraction name="config_pipShortestEdgePercent">40%</fraction>
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index a2a2914..7a4ad0a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -912,9 +912,8 @@
         if (uid != -1) {
             intent.putExtra(Settings.EXTRA_APP_UID, uid);
         }
-        intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
         return intent;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/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/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 9719ba8..cc726cb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -121,7 +121,7 @@
         mPipResizeGestureHandler = new PipResizeGestureHandler(mContext, pipBoundsAlgorithm,
                 mPipBoundsState, motionHelper, mPipTouchState, mPipTaskOrganizer,
                 mPipDismissTargetHandler,
-                (Rect bounds) -> new Rect(), () -> {}, mPipUiEventLogger, mPhonePipMenuController,
+                () -> {}, mPipUiEventLogger, mPhonePipMenuController,
                 mMainExecutor) {
             @Override
             public void pilferPointers() {
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index 6ebfc63..ac75c07 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -41,9 +41,9 @@
 #endif  // __ANDROID__
 }
 
-inline bool inter_character_justification() {
+inline bool letter_spacing_justification() {
 #ifdef __ANDROID__
-    return com_android_text_flags_inter_character_justification();
+    return com_android_text_flags_letter_spacing_justification();
 #else
     return true;
 #endif  // __ANDROID__
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index 5613369..d66d7f8 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -72,7 +72,7 @@
     const minikin::Range contextRange(contextStart, contextStart + contextCount);
     const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
     const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
-    const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification()
+    const minikin::RunFlag minikinRunFlag = text_feature::letter_spacing_justification()
                                                     ? paint->getRunFlag()
                                                     : minikin::RunFlag::NONE;
 
@@ -105,7 +105,7 @@
     const minikin::Range range(start, start + count);
     const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
     const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
-    const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification()
+    const minikin::RunFlag minikinRunFlag = text_feature::letter_spacing_justification()
                                                     ? paint->getRunFlag()
                                                     : minikin::RunFlag::NONE;
 
diff --git a/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/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/browse/MediaBrowserUtils.java b/media/java/android/media/browse/MediaBrowserUtils.java
index 19d9f00..8c008bc 100644
--- a/media/java/android/media/browse/MediaBrowserUtils.java
+++ b/media/java/android/media/browse/MediaBrowserUtils.java
@@ -18,6 +18,9 @@
 
 import android.os.Bundle;
 
+import java.util.Collections;
+import java.util.List;
+
 /**
  * @hide
  */
@@ -75,4 +78,29 @@
         }
         return false;
     }
+
+    /**
+     * Returns a paged version of the given {@code list}, using the paging parameters in {@code
+     * options}.
+     */
+    public static List<MediaBrowser.MediaItem> applyPagingOptions(
+            List<MediaBrowser.MediaItem> list, final Bundle options) {
+        if (list == null) {
+            return null;
+        }
+        int page = options.getInt(MediaBrowser.EXTRA_PAGE, -1);
+        int pageSize = options.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1);
+        if (page == -1 && pageSize == -1) {
+            return list;
+        }
+        int fromIndex = pageSize * page;
+        int toIndex = fromIndex + pageSize;
+        if (page < 0 || pageSize < 1 || fromIndex >= list.size()) {
+            return Collections.EMPTY_LIST;
+        }
+        if (toIndex > list.size()) {
+            toIndex = list.size();
+        }
+        return list.subList(fromIndex, toIndex);
+    }
 }
diff --git a/media/java/android/media/tv/BroadcastInfoRequest.java b/media/java/android/media/tv/BroadcastInfoRequest.java
index 694756c..eb980a3 100644
--- a/media/java/android/media/tv/BroadcastInfoRequest.java
+++ b/media/java/android/media/tv/BroadcastInfoRequest.java
@@ -16,9 +16,11 @@
 
 package android.media.tv;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
+import android.media.tv.flags.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -51,14 +53,14 @@
     /**
      * Request option: one-way
      * <p> With this option, no response is expected after sending the request.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
     public static final int REQUEST_OPTION_ONEWAY = 2;
     /**
      * Request option: one-shot
      * <p> With this option, only one response will be given per request.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
     public static final int REQUEST_OPTION_ONESHOT = 3;
 
     public static final @NonNull Parcelable.Creator<BroadcastInfoRequest> CREATOR =
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 432e109..6658918 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -768,12 +768,12 @@
         /**
          * Informs the application that the video freeze state has been updated.
          *
-         * When {@code true}, the video is frozen on the last frame but audio playback remains
+         * <p>When {@code true}, the video is frozen on the last frame but audio playback remains
          * active.
          *
          * @param isFrozen Whether or not the video is frozen
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
         public void notifyVideoFreezeUpdated(boolean isFrozen) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index ffc121e..e604cb7 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -683,13 +683,15 @@
      * Sets whether or not the video is frozen. While the video is frozen, audio playback will
      * continue.
      *
-     * <p> This should be invoked after a {@link TvInteractiveAppService.Session#requestCommand} is
+     * <p>This should be invoked after a {@link TvInteractiveAppService.Session#requestCommand} is
      * received with the command to freeze the video.
      *
-     * <p> This will freeze the video to the last frame when the state is set to {@code true}.
+     * <p>This will freeze the video to the last frame when the state is set to {@code true}.
+     *
+     * @see TvView.TvInputCallback#setVideoFrozen(boolean)
      * @param isFrozen whether or not the video is frozen.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
     public void setVideoFrozen(boolean isFrozen) {
         if (mSession != null) {
             mSession.setVideoFrozen(isFrozen);
@@ -1325,6 +1327,16 @@
         public void onTvMessage(@NonNull String inputId,
                 @TvInputManager.TvMessageType int type, @NonNull Bundle data) {
         }
+
+        /**
+         * This is called when the video freeze status is updated.
+         *
+         * @see #setVideoFrozen(boolean)
+         * @param inputId The ID of the TV input bound to this view.
+         * @param isFrozen Whether or not the video is currently frozen on the las
+         */
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+        public void onVideoFreezeUpdated(@NonNull String inputId, boolean isFrozen) {}
     }
 
     /**
@@ -1753,5 +1765,19 @@
                 mCallback.onTvMessage(mInputId, type, data);
             }
         }
+
+        @Override
+        public void onVideoFreezeUpdated(Session session, boolean isFrozen) {
+            if (DEBUG) {
+                Log.d(TAG, "onVideoFreezeUpdated(isFrozen=" + isFrozen + ")");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onVideoFreezeUpdated - session not created");
+                return;
+            }
+            if (mCallback != null) {
+                mCallback.onVideoFreezeUpdated(mInputId, isFrozen);
+            }
+        }
     }
 }
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index 15ce9fd..59b10c6 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -68,7 +68,6 @@
      * <p>Type: String
      *
      * @see #sendAppLinkCommand(String, Bundle)
-     * @hide
      */
     public static final String APP_LINK_KEY_PACKAGE_NAME = "package_name";
 
@@ -77,7 +76,6 @@
      * <p>Type: String
      *
      * @see #sendAppLinkCommand(String, Bundle)
-     * @hide
      */
     public static final String APP_LINK_KEY_CLASS_NAME = "class_name";
 
@@ -86,7 +84,6 @@
      * <p>Type: String
      *
      * @see #sendAppLinkCommand(String, Bundle)
-     * @hide
      */
     public static final String APP_LINK_KEY_COMMAND_TYPE = "command_type";
 
@@ -95,7 +92,6 @@
      * <p>Type: String
      *
      * @see #sendAppLinkCommand(String, Bundle)
-     * @hide
      */
     public static final String APP_LINK_KEY_SERVICE_ID = "service_id";
 
@@ -104,7 +100,6 @@
      * <p>Type: String
      *
      * @see #sendAppLinkCommand(String, Bundle)
-     * @hide
      */
     public static final String APP_LINK_KEY_BACK_URI = "back_uri";
 
@@ -112,7 +107,6 @@
      * Broadcast intent action to send app command to TV app.
      *
      * @see #sendAppLinkCommand(String, Bundle)
-     * @hide
      */
     public static final String ACTION_APP_LINK_COMMAND =
             "android.media.tv.ad.action.APP_LINK_COMMAND";
@@ -123,7 +117,6 @@
      *
      * @see #sendAppLinkCommand(String, Bundle)
      * @see #ACTION_APP_LINK_COMMAND
-     * @hide
      */
     public static final String INTENT_KEY_TV_INPUT_ID = "tv_input_id";
 
@@ -134,7 +127,6 @@
      * @see #sendAppLinkCommand(String, Bundle)
      * @see #ACTION_APP_LINK_COMMAND
      * @see TvAdServiceInfo#getId()
-     * @hide
      */
     public static final String INTENT_KEY_AD_SERVICE_ID = "ad_service_id";
 
@@ -144,7 +136,6 @@
      *
      * @see #sendAppLinkCommand(String, Bundle)
      * @see #ACTION_APP_LINK_COMMAND
-     * @hide
      */
     public static final String INTENT_KEY_CHANNEL_URI = "channel_uri";
 
@@ -155,7 +146,6 @@
      *
      * @see #sendAppLinkCommand(String, Bundle)
      * @see #ACTION_APP_LINK_COMMAND
-     * @hide
      */
     public static final String INTENT_KEY_COMMAND_TYPE = "command_type";
 
@@ -486,8 +476,17 @@
      *
      * @param serviceId The ID of TV AD service which the command to be sent to. The ID can be found
      *                  in {@link TvAdServiceInfo#getId()}.
-     * @param command The command to be sent.
-     * @hide
+     * @param command The command to be sent. The command is a bundle with the following keys:
+     *                <ul>
+     *                <li>{@link #APP_LINK_KEY_PACKAGE_NAME}: The package name of the app to be
+     *                launched.
+     *                <li>{@link #APP_LINK_KEY_CLASS_NAME}: The class name of the app to be
+     *                launched.
+     *                <li>{@link #APP_LINK_KEY_COMMAND_TYPE}: The command type.
+     *                <li>{@link #APP_LINK_KEY_SERVICE_ID}: The ID of the TV AD service.
+     *                <li>{@link #APP_LINK_KEY_BACK_URI}: The URI to be used to return to the
+     *                previous app.
+     *                </ul>
      */
     public void sendAppLinkCommand(@NonNull String serviceId, @NonNull Bundle command) {
         try {
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
index d7b29d3..6c8a8fd 100644
--- a/media/java/android/media/tv/ad/TvAdService.java
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -136,7 +136,6 @@
      * Called when app link command is received.
      *
      * @see TvAdManager#sendAppLinkCommand(String, Bundle)
-     * @hide
      */
     public void onAppLinkCommand(@NonNull Bundle command) {
     }
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 498eec6..7cf32ec 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -2634,8 +2634,8 @@
 
         /**
          * This is called when
-         * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, int, byte[])} is
-         * called.
+         * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, int, byte[])}
+         * is called.
          *
          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
          * @param signingId the ID to identify the request.
@@ -2644,7 +2644,6 @@
          * @param host The host of the SSL CLient Authentication Server
          * @param port The port of the SSL Client Authentication Server
          * @param data the original bytes to be signed.
-         * @hide
          */
         public void onRequestSigning(
                 Session session, String signingId, String algorithm, String host,
@@ -2657,7 +2656,6 @@
          * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
          * @param host the host name of the SSL authentication server.
          * @param port the port of the SSL authentication server. E.g., 443
-         * @hide
          */
         public void onRequestCertificate(Session session, String host, int port) {
         }
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 6b0620c..eba26d4 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -742,8 +742,8 @@
          * @param host the host name of the SSL authentication server.
          * @param port the port of the SSL authentication server. E.g., 443
          * @param cert the SSL certificate received.
-         * @hide
          */
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
         public void onCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) {
         }
 
@@ -896,13 +896,13 @@
         }
 
         /**
-         * Called when video becomes frozen or unfrozen. Audio playback will continue while
-         * video will be frozen to the last frame if {@code true}.
+         * Called when video becomes frozen or unfrozen. Audio playback will continue while video
+         * will be frozen to the last frame if {@code true}.
+         *
          * @param isFrozen Whether or not the video is frozen.
-         * @hide
          */
-        public void onVideoFreezeUpdated(boolean isFrozen) {
-        }
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+        public void onVideoFreezeUpdated(boolean isFrozen) {}
 
         /**
          * Called when content is allowed.
@@ -1666,9 +1666,9 @@
          * @see #onSigningResult(String, byte[])
          * @see TvInteractiveAppView#createBiInteractiveApp(Uri, Bundle)
          * @see TvInteractiveAppView#BI_INTERACTIVE_APP_KEY_ALIAS
-         * @hide
          */
         @CallSuper
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
         public void requestSigning(@NonNull String signingId, @NonNull String algorithm,
                 @NonNull String host, int port, @NonNull byte[] data) {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -1695,8 +1695,9 @@
          *
          * @param host the host name of the SSL authentication server.
          * @param port the port of the SSL authentication server. E.g., 443
-         * @hide
          */
+        @CallSuper
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
         public void requestCertificate(@NonNull String host, int port) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 584ea84..29a3b98 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -723,12 +723,12 @@
     }
 
     /**
-     * Alerts the TV Interactive app that the video freeze state has been updated.
-     * If {@code true}, the video is frozen on the last frame while audio playback continues.
+     * Alerts the TV Interactive app that the video freeze state has been updated. If {@code true},
+     * the video is frozen on the last frame while audio playback continues.
      *
      * @param isFrozen Whether the video is frozen.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
     public void notifyVideoFreezeUpdated(boolean isFrozen) {
         if (DEBUG) {
             Log.d(TAG, "notifyVideoFreezeUpdated");
@@ -760,12 +760,12 @@
     }
 
     /**
-     * Send the requested SSL certificate to the TV Interactive App
+     * Sends the requested SSL certificate to the TV Interactive App
      * @param host the host name of the SSL authentication server.
      * @param port the port of the SSL authentication server. E.g., 443
      * @param cert the SSL certificate requested
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
     public void sendCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) {
         if (DEBUG) {
             Log.d(TAG, "sendCertificate");
@@ -1390,6 +1390,37 @@
         }
 
         /**
+         * This is called when
+         * {@link TvInteractiveAppService.Session#requestSigning(String, String, String, int, byte[])}
+         * is called.
+         *
+         * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @param signingId the ID to identify the request.
+         * @param algorithm the standard name of the signature algorithm requested, such as
+         *                  MD5withRSA, SHA256withDSA, etc.
+         * @param host The hostname of the SSL authentication server.
+         * @param port The port of the SSL authentication server.
+         * @param data the original bytes to be signed.
+         */
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+        public void onRequestSigning(@NonNull String iAppServiceId, @NonNull String signingId,
+                @NonNull String algorithm, @NonNull String host, int port, @NonNull byte[] data) {
+        }
+
+        /**
+         * This is called when
+         * {@link TvInteractiveAppService.Session#requestCertificate(String, int)} is called.
+         *
+         * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @param host The hostname of the SSL authentication server.
+         * @param port The port of the SSL authentication server.
+         */
+        @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+        public void onRequestCertificate(@NonNull String iAppServiceId, @NonNull String host,
+                int port) {
+        }
+
+        /**
          * This is called when {@link TvInteractiveAppService.Session#setTvRecordingInfo(String,
          * TvRecordingInfo)} is called.
          *
@@ -1957,5 +1988,34 @@
                 mCallback.onRequestSigning(mIAppServiceId, id, algorithm, alias, data);
             }
         }
+
+        @Override
+        public void onRequestSigning(
+                Session session, String id, String algorithm, String host, int port, byte[] data) {
+            if (DEBUG) {
+                Log.d(TAG, "onRequestSigning");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onRequestSigning - session not created");
+                return;
+            }
+            if (mCallback != null && Flags.tiafVApis()) {
+                mCallback.onRequestSigning(mIAppServiceId, id, algorithm, host, port, data);
+            }
+        }
+
+        @Override
+        public void onRequestCertificate(Session session, String host, int port) {
+            if (DEBUG) {
+                Log.d(TAG, "onRequestCertificate");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onRequestCertificate - session not created");
+                return;
+            }
+            if (mCallback != null && Flags.tiafVApis()) {
+                mCallback.onRequestCertificate(mIAppServiceId, host, port);
+            }
+        }
     }
 }
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index e8ef464..ba7ab9a 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -48,7 +48,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -728,7 +727,7 @@
 
                 List<MediaBrowser.MediaItem> filteredList =
                         (flag & RESULT_FLAG_OPTION_NOT_HANDLED) != 0
-                                ? applyOptions(list, options) : list;
+                                ? MediaBrowserUtils.applyPagingOptions(list, options) : list;
                 final ParceledListSlice<MediaBrowser.MediaItem> pls;
                 if (filteredList == null) {
                     pls = null;
@@ -762,27 +761,6 @@
         }
     }
 
-    private List<MediaBrowser.MediaItem> applyOptions(List<MediaBrowser.MediaItem> list,
-            final Bundle options) {
-        if (list == null) {
-            return null;
-        }
-        int page = options.getInt(MediaBrowser.EXTRA_PAGE, -1);
-        int pageSize = options.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1);
-        if (page == -1 && pageSize == -1) {
-            return list;
-        }
-        int fromIndex = pageSize * page;
-        int toIndex = fromIndex + pageSize;
-        if (page < 0 || pageSize < 1 || fromIndex >= list.size()) {
-            return Collections.EMPTY_LIST;
-        }
-        if (toIndex > list.size()) {
-            toIndex = list.size();
-        }
-        return list.subList(fromIndex, toIndex);
-    }
-
     private void performLoadItem(String itemId, final ConnectionRecord connection,
             final ResultReceiver receiver) {
         final Result<MediaBrowser.MediaItem> result =
diff --git a/media/tests/MediaFrameworkTest/Android.bp b/media/tests/MediaFrameworkTest/Android.bp
index 7a329bc..1325fc1 100644
--- a/media/tests/MediaFrameworkTest/Android.bp
+++ b/media/tests/MediaFrameworkTest/Android.bp
@@ -22,7 +22,6 @@
         "android-ex-camera2",
         "android.media.playback.flags-aconfig-java",
         "flag-junit",
-        "testables",
         "testng",
         "truth",
     ],
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/OWNERS b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/OWNERS
deleted file mode 100644
index 6d5f82c..0000000
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Haptics team also works on Ringtone
-per-file *Ringtone* = file:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java
deleted file mode 100644
index 3c0c684..0000000
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java
+++ /dev/null
@@ -1,840 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.mediaframeworktest.unit;
-
-import static android.media.Ringtone.MEDIA_SOUND;
-import static android.media.Ringtone.MEDIA_SOUND_AND_VIBRATION;
-import static android.media.Ringtone.MEDIA_VIBRATION;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.doCallRealMethod;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.AssetFileDescriptor;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.IRingtonePlayer;
-import android.media.MediaPlayer;
-import android.media.Ringtone;
-import android.media.audiofx.HapticGenerator;
-import android.net.Uri;
-import android.os.IBinder;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.testing.TestableContext;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.mediaframeworktest.R;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runner.RunWith;
-import org.junit.runners.model.Statement;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.io.FileNotFoundException;
-import java.util.ArrayDeque;
-import java.util.Map;
-import java.util.Queue;
-
-@RunWith(AndroidJUnit4.class)
-public class RingtoneTest {
-
-    private static final Uri SOUND_URI = Uri.parse("content://fake-sound-uri");
-
-    private static final AudioAttributes RINGTONE_ATTRIBUTES =
-            audioAttributes(AudioAttributes.USAGE_NOTIFICATION_RINGTONE);
-    private static final AudioAttributes RINGTONE_ATTRIBUTES_WITH_HC =
-            new AudioAttributes.Builder(RINGTONE_ATTRIBUTES).setHapticChannelsMuted(false).build();
-    private static final VibrationAttributes RINGTONE_VIB_ATTRIBUTES =
-            new VibrationAttributes.Builder(RINGTONE_ATTRIBUTES).build();
-
-    private static final VibrationEffect VIBRATION_EFFECT =
-            VibrationEffect.createWaveform(new long[] { 0, 100, 50, 100}, -1);
-    private static final VibrationEffect VIBRATION_EFFECT_REPEATING =
-            VibrationEffect.createWaveform(new long[] { 0, 100, 50, 100, 50}, 1);
-
-    @Rule
-    public final RingtoneInjectablesTrackingTestRule
-            mMediaPlayerRule = new RingtoneInjectablesTrackingTestRule();
-
-    @Captor private ArgumentCaptor<IBinder> mIBinderCaptor;
-    @Mock private IRingtonePlayer mMockRemotePlayer;
-    @Mock private Vibrator mMockVibrator;
-    private AudioManager mSpyAudioManager;
-    private TestableContext mContext;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        TestableContext testContext =
-                new TestableContext(InstrumentationRegistry.getTargetContext(), null);
-        testContext.getTestablePermissions().setPermission(Manifest.permission.VIBRATE,
-                PackageManager.PERMISSION_GRANTED);
-        AudioManager realAudioManager = testContext.getSystemService(AudioManager.class);
-        mSpyAudioManager = spy(realAudioManager);
-        when(mSpyAudioManager.getRingtonePlayer()).thenReturn(mMockRemotePlayer);
-        testContext.addMockSystemService(AudioManager.class, mSpyAudioManager);
-        testContext.addMockSystemService(Vibrator.class, mMockVibrator);
-
-        mContext = spy(testContext);
-    }
-
-    @Test
-    public void testRingtone_fullLifecycleUsingLocalMediaPlayer() throws Exception {
-        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
-        Ringtone ringtone =
-                newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES).setUri(SOUND_URI).build();
-        assertThat(ringtone).isNotNull();
-        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
-
-        // Verify all the properties.
-        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND);
-        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
-        assertThat(ringtone.getAudioAttributes()).isEqualTo(RINGTONE_ATTRIBUTES);
-        assertThat(ringtone.getVolume()).isEqualTo(1.0f);
-        assertThat(ringtone.isLooping()).isEqualTo(false);
-        assertThat(ringtone.isHapticGeneratorEnabled()).isEqualTo(false);
-        assertThat(ringtone.getPreferBuiltinDevice()).isFalse();
-        assertThat(ringtone.getVolumeShaperConfig()).isNull();
-        assertThat(ringtone.isLocalOnly()).isFalse();
-
-        // Prepare
-        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES);
-        verify(mockMediaPlayer).setVolume(1.0f);
-        verify(mockMediaPlayer).setLooping(false);
-        verify(mockMediaPlayer).prepare();
-
-        // Play
-        ringtone.play();
-        verifyLocalPlay(mockMediaPlayer);
-
-        // Verify dynamic controls.
-        ringtone.setVolume(0.8f);
-        verify(mockMediaPlayer).setVolume(0.8f);
-        when(mockMediaPlayer.isLooping()).thenReturn(false);
-        ringtone.setLooping(true);
-        verify(mockMediaPlayer).isLooping();
-        verify(mockMediaPlayer).setLooping(true);
-        HapticGenerator mockHapticGenerator =
-                mMediaPlayerRule.expectHapticGenerator(mockMediaPlayer);
-        ringtone.setHapticGeneratorEnabled(true);
-        verify(mockHapticGenerator).setEnabled(true);
-
-        // Release
-        ringtone.stop();
-        verifyLocalStop(mockMediaPlayer);
-
-        // This test is intended to strictly verify all interactions with MediaPlayer in a local
-        // playback case. This shouldn't be necessary in other tests that have the same basic
-        // setup.
-        verifyNoMoreInteractions(mockMediaPlayer);
-        verify(mockHapticGenerator).release();
-        verifyNoMoreInteractions(mockHapticGenerator);
-        verifyZeroInteractions(mMockRemotePlayer);
-        verifyZeroInteractions(mMockVibrator);
-    }
-
-    @Test
-    public void testRingtone_localMediaPlayerWithAudioCoupledOverride() throws Exception {
-        // Audio coupled playback is enabled in the incoming attributes, plus an instruction
-        // to leave the attributes alone. This test verifies that the attributes reach the
-        // media player without changing.
-        final AudioAttributes audioAttributes = RINGTONE_ATTRIBUTES_WITH_HC;
-        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
-        mMediaPlayerRule.setHasHapticChannels(mockMediaPlayer, true);
-        Ringtone ringtone =
-                newBuilder(MEDIA_SOUND, audioAttributes)
-                        .setUri(SOUND_URI)
-                        .setUseExactAudioAttributes(true)
-                        .build();
-        assertThat(ringtone).isNotNull();
-        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
-
-        // Verify all the properties.
-        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND);
-        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
-        assertThat(ringtone.getAudioAttributes()).isEqualTo(audioAttributes);
-
-        // Prepare
-        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, audioAttributes);
-        verify(mockMediaPlayer).prepare();
-
-        // Play
-        ringtone.play();
-        verifyLocalPlay(mockMediaPlayer);
-
-        // Release
-        ringtone.stop();
-        verifyLocalStop(mockMediaPlayer);
-
-        verifyZeroInteractions(mMockRemotePlayer);
-        verifyZeroInteractions(mMockVibrator);
-    }
-
-    @Test
-    public void testRingtone_fullLifecycleUsingRemoteMediaPlayer() throws Exception {
-        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
-        setupFileNotFound(mockMediaPlayer, SOUND_URI);
-        Ringtone ringtone =
-                newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
-                .setUri(SOUND_URI)
-                .build();
-        assertThat(ringtone).isNotNull();
-        assertThat(ringtone.isUsingRemotePlayer()).isTrue();
-
-        // Verify all the properties.
-        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND);
-        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
-        assertThat(ringtone.getAudioAttributes()).isEqualTo(RINGTONE_ATTRIBUTES);
-        assertThat(ringtone.getVolume()).isEqualTo(1.0f);
-        assertThat(ringtone.isLooping()).isEqualTo(false);
-        assertThat(ringtone.isHapticGeneratorEnabled()).isEqualTo(false);
-        assertThat(ringtone.getPreferBuiltinDevice()).isFalse();
-        assertThat(ringtone.getVolumeShaperConfig()).isNull();
-        assertThat(ringtone.isLocalOnly()).isFalse();
-
-        // Initialization did try to create a local media player.
-        verify(mockMediaPlayer).setDataSource(mContext, SOUND_URI);
-        // setDataSource throws file not found, so nothing else will happen on the local player.
-        verify(mockMediaPlayer).release();
-
-        // Delegates to remote media player.
-        ringtone.play();
-        verify(mMockRemotePlayer).playRemoteRingtone(mIBinderCaptor.capture(), eq(SOUND_URI),
-                eq(RINGTONE_ATTRIBUTES), eq(false), eq(MEDIA_SOUND), isNull(),
-                eq(1.0f), eq(false), eq(false), isNull());
-        IBinder remoteToken = mIBinderCaptor.getValue();
-
-        // Verify dynamic controls.
-        ringtone.setVolume(0.8f);
-        verify(mMockRemotePlayer).setVolume(remoteToken, 0.8f);
-        ringtone.setLooping(true);
-        verify(mMockRemotePlayer).setLooping(remoteToken, true);
-        ringtone.setHapticGeneratorEnabled(true);
-        verify(mMockRemotePlayer).setHapticGeneratorEnabled(remoteToken, true);
-
-        ringtone.stop();
-        verify(mMockRemotePlayer).stop(remoteToken);
-        verifyNoMoreInteractions(mMockRemotePlayer);
-        verifyNoMoreInteractions(mockMediaPlayer);
-        verifyZeroInteractions(mMockVibrator);
-    }
-
-    @Test
-    public void testRingtone_localMediaWithVibration() throws Exception {
-        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
-        when(mMockVibrator.hasVibrator()).thenReturn(true);
-        Ringtone ringtone =
-                newBuilder(MEDIA_SOUND_AND_VIBRATION, RINGTONE_ATTRIBUTES)
-                        .setUri(SOUND_URI)
-                        .setVibrationEffect(VIBRATION_EFFECT)
-                        .build();
-        assertThat(ringtone).isNotNull();
-        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
-        verify(mMockVibrator).hasVibrator();
-
-        // Verify all the properties.
-        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND_AND_VIBRATION);
-        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
-        assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
-
-        // Prepare
-        // Uses attributes with haptic channels enabled, but will use the effect when there aren't
-        // any present.
-        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
-        verify(mockMediaPlayer).setVolume(1.0f);
-        verify(mockMediaPlayer).setLooping(false);
-        verify(mockMediaPlayer).prepare();
-
-        // Play
-        ringtone.play();
-
-        verifyLocalPlay(mockMediaPlayer);
-        verify(mMockVibrator).vibrate(VIBRATION_EFFECT, RINGTONE_VIB_ATTRIBUTES);
-
-        // Verify dynamic controls.
-        ringtone.setVolume(0.8f);
-        verify(mockMediaPlayer).setVolume(0.8f);
-
-        // Set looping doesn't affect an already-started vibration.
-        when(mockMediaPlayer.isLooping()).thenReturn(false);  // Checks original
-        ringtone.setLooping(true);
-        verify(mockMediaPlayer).isLooping();
-        verify(mockMediaPlayer).setLooping(true);
-
-        // This is ignored because there's a vibration effect being used.
-        ringtone.setHapticGeneratorEnabled(true);
-
-        // Release
-        ringtone.stop();
-        verifyLocalStop(mockMediaPlayer);
-        verify(mMockVibrator).cancel(VibrationAttributes.USAGE_RINGTONE);
-
-        // This test is intended to strictly verify all interactions with MediaPlayer in a local
-        // playback case. This shouldn't be necessary in other tests that have the same basic
-        // setup.
-        verifyNoMoreInteractions(mockMediaPlayer);
-        verifyZeroInteractions(mMockRemotePlayer);
-        verifyNoMoreInteractions(mMockVibrator);
-    }
-
-    @Test
-    public void testRingtone_localMediaWithVibrationOnly() throws Exception {
-        when(mMockVibrator.hasVibrator()).thenReturn(true);
-        Ringtone ringtone =
-                newBuilder(MEDIA_VIBRATION, RINGTONE_ATTRIBUTES)
-                        // TODO: set sound uri too in diff test
-                        .setVibrationEffect(VIBRATION_EFFECT)
-                        .build();
-        assertThat(ringtone).isNotNull();
-        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
-        verify(mMockVibrator).hasVibrator();
-
-        // Verify all the properties.
-        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_VIBRATION);
-        assertThat(ringtone.getUri()).isNull();
-        assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
-
-        // Play
-        ringtone.play();
-
-        verify(mMockVibrator).vibrate(VIBRATION_EFFECT, RINGTONE_VIB_ATTRIBUTES);
-
-        // Verify dynamic controls (no-op without sound)
-        ringtone.setVolume(0.8f);
-
-        // Set looping doesn't affect an already-started vibration.
-        ringtone.setLooping(true);
-
-        // This is ignored because there's a vibration effect being used and no sound.
-        ringtone.setHapticGeneratorEnabled(true);
-
-        // Release
-        ringtone.stop();
-        verify(mMockVibrator).cancel(VibrationAttributes.USAGE_RINGTONE);
-
-        // This test is intended to strictly verify all interactions with MediaPlayer in a local
-        // playback case. This shouldn't be necessary in other tests that have the same basic
-        // setup.
-        verifyZeroInteractions(mMockRemotePlayer);
-        verifyNoMoreInteractions(mMockVibrator);
-    }
-
-    @Test
-    public void testRingtone_localMediaWithVibrationOnlyAndSoundUriNoHapticChannels()
-            throws Exception {
-        // A media player will still be created for vibration-only because the vibration can come
-        // from haptic channels on the sound file (although in this case it doesn't).
-        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
-        mMediaPlayerRule.setHasHapticChannels(mockMediaPlayer, false);
-        when(mMockVibrator.hasVibrator()).thenReturn(true);
-        Ringtone ringtone =
-                newBuilder(MEDIA_VIBRATION, RINGTONE_ATTRIBUTES)
-                        .setUri(SOUND_URI)
-                        .setVibrationEffect(VIBRATION_EFFECT)
-                        .build();
-        assertThat(ringtone).isNotNull();
-        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
-        verify(mMockVibrator).hasVibrator();
-
-        // Verify all the properties.
-        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_VIBRATION);
-        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
-        assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
-
-        // Prepare
-        // Uses attributes with haptic channels enabled, but will abandon the MediaPlayer when it
-        // knows there aren't any.
-        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
-        verify(mockMediaPlayer).setVolume(0.0f);  // Vibration-only: sound muted.
-        verify(mockMediaPlayer).setLooping(false);
-        verify(mockMediaPlayer).prepare();
-        verify(mockMediaPlayer).release();  // abandoned: no haptic channels.
-
-        // Play
-        ringtone.play();
-
-        verify(mMockVibrator).vibrate(VIBRATION_EFFECT, RINGTONE_VIB_ATTRIBUTES);
-
-        // Verify dynamic controls (no-op without sound)
-        ringtone.setVolume(0.8f);
-
-        // Set looping doesn't affect an already-started vibration.
-        ringtone.setLooping(true);
-
-        // This is ignored because there's a vibration effect being used and no sound.
-        ringtone.setHapticGeneratorEnabled(true);
-
-        // Release
-        ringtone.stop();
-        verify(mMockVibrator).cancel(VibrationAttributes.USAGE_RINGTONE);
-
-        // This test is intended to strictly verify all interactions with MediaPlayer in a local
-        // playback case. This shouldn't be necessary in other tests that have the same basic
-        // setup.
-        verifyZeroInteractions(mMockRemotePlayer);
-        verifyNoMoreInteractions(mMockVibrator);
-        verifyNoMoreInteractions(mockMediaPlayer);
-    }
-
-    @Test
-    public void testRingtone_localMediaWithVibrationOnlyAndSoundUriWithHapticChannels()
-            throws Exception {
-        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
-        when(mMockVibrator.hasVibrator()).thenReturn(true);
-        mMediaPlayerRule.setHasHapticChannels(mockMediaPlayer, true);
-        Ringtone ringtone =
-                newBuilder(MEDIA_VIBRATION, RINGTONE_ATTRIBUTES)
-                        .setUri(SOUND_URI)
-                        .setVibrationEffect(VIBRATION_EFFECT)
-                        .build();
-        assertThat(ringtone).isNotNull();
-        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
-        verify(mMockVibrator).hasVibrator();
-
-        // Verify all the properties.
-        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_VIBRATION);
-        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
-        assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
-
-        // Prepare
-        // Uses attributes with haptic channels enabled, but will use the effect when there aren't
-        // any present.
-        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
-        verify(mockMediaPlayer).setVolume(0.0f);  // Vibration-only: sound muted.
-        verify(mockMediaPlayer).setLooping(false);
-        verify(mockMediaPlayer).prepare();
-
-        // Play
-        ringtone.play();
-        // Vibrator.vibrate isn't called because the vibration comes from the sound.
-        verifyLocalPlay(mockMediaPlayer);
-
-        // Verify dynamic controls (no-op without sound)
-        ringtone.setVolume(0.8f);
-
-        when(mockMediaPlayer.isLooping()).thenReturn(false);  // Checks original
-        ringtone.setLooping(true);
-        verify(mockMediaPlayer).isLooping();
-        verify(mockMediaPlayer).setLooping(true);
-
-        // This is ignored because it's using haptic channels.
-        ringtone.setHapticGeneratorEnabled(true);
-
-        // Release
-        ringtone.stop();
-        verifyLocalStop(mockMediaPlayer);
-
-        // This test is intended to strictly verify all interactions with MediaPlayer in a local
-        // playback case. This shouldn't be necessary in other tests that have the same basic
-        // setup.
-        verifyZeroInteractions(mMockRemotePlayer);
-        verifyZeroInteractions(mMockVibrator);
-    }
-
-    @Test
-    public void testRingtone_localMediaWithVibrationPrefersHapticChannels() throws Exception {
-        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
-        mMediaPlayerRule.setHasHapticChannels(mockMediaPlayer, true);
-        when(mMockVibrator.hasVibrator()).thenReturn(true);
-        Ringtone ringtone =
-                newBuilder(MEDIA_SOUND_AND_VIBRATION, RINGTONE_ATTRIBUTES)
-                        .setUri(SOUND_URI)
-                        .setVibrationEffect(VIBRATION_EFFECT)
-                        .build();
-        assertThat(ringtone).isNotNull();
-        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
-        verify(mMockVibrator).hasVibrator();
-
-        // Verify all the properties.
-        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND_AND_VIBRATION);
-        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
-        assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
-
-        // Prepare
-        // The attributes here have haptic channels enabled (unlike above)
-        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
-        verify(mockMediaPlayer).prepare();
-
-        // Play
-        ringtone.play();
-        when(mockMediaPlayer.isPlaying()).thenReturn(true);
-        verifyLocalPlay(mockMediaPlayer);
-
-        // Release
-        ringtone.stop();
-        verifyLocalStop(mockMediaPlayer);
-
-        verifyZeroInteractions(mMockRemotePlayer);
-        // Nothing after the initial hasVibrator - it uses audio-coupled.
-        verifyNoMoreInteractions(mMockVibrator);
-    }
-
-    @Test
-    public void testRingtone_localMediaWithVibrationButSoundMuted() throws Exception {
-        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
-        mMediaPlayerRule.setHasHapticChannels(mockMediaPlayer, false);
-        doReturn(0).when(mSpyAudioManager)
-                .getStreamVolume(AudioAttributes.toLegacyStreamType(RINGTONE_ATTRIBUTES));
-        when(mMockVibrator.hasVibrator()).thenReturn(true);
-        Ringtone ringtone =
-                newBuilder(MEDIA_SOUND_AND_VIBRATION, RINGTONE_ATTRIBUTES)
-                        .setUri(SOUND_URI)
-                        .setVibrationEffect(VIBRATION_EFFECT)
-                        .build();
-        assertThat(ringtone).isNotNull();
-        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
-        verify(mMockVibrator).hasVibrator();
-
-        // Verify all the properties.
-        assertThat(ringtone.getEnabledMedia()).isEqualTo(MEDIA_SOUND_AND_VIBRATION);
-        assertThat(ringtone.getUri()).isEqualTo(SOUND_URI);
-        assertThat(ringtone.getVibrationEffect()).isEqualTo(VIBRATION_EFFECT);
-
-        // Prepare
-        // The attributes here have haptic channels enabled (unlike above)
-        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
-        verify(mockMediaPlayer).prepare();
-
-        // Play
-        ringtone.play();
-        // The media player is never played, because sound is muted.
-        verify(mockMediaPlayer, never()).start();
-        when(mockMediaPlayer.isPlaying()).thenReturn(true);
-        verify(mMockVibrator).vibrate(VIBRATION_EFFECT, RINGTONE_VIB_ATTRIBUTES);
-
-        // Release
-        ringtone.stop();
-        verify(mockMediaPlayer).release();
-        verify(mMockVibrator).cancel(VibrationAttributes.USAGE_RINGTONE);
-
-        verifyZeroInteractions(mMockRemotePlayer);
-        // Nothing after the initial hasVibrator - it uses audio-coupled.
-        verifyNoMoreInteractions(mMockVibrator);
-    }
-
-    @Test
-    public void testRingtone_nullMediaOnBuilderUsesFallback() throws Exception {
-        AssetFileDescriptor testResourceFd =
-                mContext.getResources().openRawResourceFd(R.raw.shortmp3);
-        // Ensure it will flow as expected.
-        assertThat(testResourceFd).isNotNull();
-        assertThat(testResourceFd.getDeclaredLength()).isAtLeast(0);
-        mContext.getOrCreateTestableResources()
-                .addOverride(com.android.internal.R.raw.fallbackring, testResourceFd);
-
-        MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
-        Ringtone ringtone = newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
-                .setUri(null)
-                .build();
-        assertThat(ringtone).isNotNull();
-        assertThat(ringtone.isUsingRemotePlayer()).isFalse();
-
-        // Delegates straight to fallback in local player.
-        // Prepare
-        verifyLocalPlayerFallbackSetup(mockMediaPlayer, testResourceFd, RINGTONE_ATTRIBUTES);
-        verify(mockMediaPlayer).setVolume(1.0f);
-        verify(mockMediaPlayer).setLooping(false);
-        verify(mockMediaPlayer).prepare();
-
-        // Play
-        ringtone.play();
-        verifyLocalPlay(mockMediaPlayer);
-
-        // Release
-        ringtone.stop();
-        verifyLocalStop(mockMediaPlayer);
-
-        verifyNoMoreInteractions(mockMediaPlayer);
-        verifyNoMoreInteractions(mMockRemotePlayer);
-    }
-
-    @Test
-    public void testRingtone_nullMediaOnBuilderUsesFallbackViaRemote() throws Exception {
-        mContext.getOrCreateTestableResources()
-                .addOverride(com.android.internal.R.raw.fallbackring, null);
-        Ringtone ringtone = newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
-                .setUri(null)
-                .setLooping(true) // distinct from haptic generator, to match plumbing
-                .build();
-        assertThat(ringtone).isNotNull();
-        // Local player fallback fails as the resource isn't found (no media player creation is
-        // attempted), and then goes on to create the remote player.
-        assertThat(ringtone.isUsingRemotePlayer()).isTrue();
-
-        ringtone.play();
-        verify(mMockRemotePlayer).playRemoteRingtone(mIBinderCaptor.capture(), isNull(),
-                eq(RINGTONE_ATTRIBUTES), eq(false),
-                eq(MEDIA_SOUND), isNull(),
-                eq(1.0f), eq(true), eq(false), isNull());
-        ringtone.stop();
-        verify(mMockRemotePlayer).stop(mIBinderCaptor.getValue());
-        verifyNoMoreInteractions(mMockRemotePlayer);
-    }
-
-    @Test
-    public void testRingtone_noMediaSetOnBuilderFallbackFailsAndNoRemote() throws Exception {
-        mContext.getOrCreateTestableResources()
-                .addOverride(com.android.internal.R.raw.fallbackring, null);
-        Ringtone ringtone = newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
-                .setUri(null)
-                .setLocalOnly()
-                .build();
-        // Local player fallback fails as the resource isn't found (no media player creation is
-        // attempted), and since there is no local player, the ringtone ends up having nothing to
-        // do.
-        assertThat(ringtone).isNull();
-    }
-
-    private Ringtone.Builder newBuilder(@Ringtone.RingtoneMedia int ringtoneMedia,
-            AudioAttributes audioAttributes) {
-        return new Ringtone.Builder(mContext, ringtoneMedia, audioAttributes)
-                .setInjectables(mMediaPlayerRule.injectables);
-    }
-
-    private static AudioAttributes audioAttributes(int audioUsage) {
-        return new AudioAttributes.Builder()
-                .setUsage(audioUsage)
-                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-                .build();
-    }
-
-    /** Makes the mock get some sort of file access problem. */
-    private void setupFileNotFound(MediaPlayer mockMediaPlayer, Uri uri) throws Exception {
-        doThrow(new FileNotFoundException("Fake file not found"))
-                .when(mockMediaPlayer).setDataSource(any(Context.class), eq(uri));
-    }
-
-    private void verifyLocalPlayerSetup(MediaPlayer mockPlayer, Uri expectedUri,
-            AudioAttributes expectedAudioAttributes) throws Exception {
-        verify(mockPlayer).setDataSource(mContext, expectedUri);
-        verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
-        verify(mockPlayer).setPreferredDevice(null);
-        verify(mockPlayer).prepare();
-    }
-
-    private void verifyLocalPlayerFallbackSetup(MediaPlayer mockPlayer, AssetFileDescriptor afd,
-            AudioAttributes expectedAudioAttributes) throws Exception {
-        // This is very specific but it's a simple way to test that the test resource matches.
-        if (afd.getDeclaredLength() < 0) {
-            verify(mockPlayer).setDataSource(afd.getFileDescriptor());
-        } else {
-            verify(mockPlayer).setDataSource(afd.getFileDescriptor(),
-                    afd.getStartOffset(),
-                    afd.getDeclaredLength());
-        }
-        verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
-        verify(mockPlayer).setPreferredDevice(null);
-        verify(mockPlayer).prepare();
-    }
-
-    private void verifyLocalPlay(MediaPlayer mockMediaPlayer) {
-        verify(mockMediaPlayer).setOnCompletionListener(any());
-        verify(mockMediaPlayer).start();
-    }
-
-    private void verifyLocalStop(MediaPlayer mockMediaPlayer) {
-        verify(mockMediaPlayer).stop();
-        verify(mockMediaPlayer).setOnCompletionListener(isNull());
-        verify(mockMediaPlayer).reset();
-        verify(mockMediaPlayer).release();
-    }
-
-    /**
-     * This rule ensures that all expected media player creations from the factory do actually
-     * occur. The reason for this level of control is that creating a media player is fairly
-     * expensive and blocking, so we do want unit tests of this class to "declare" interactions
-     * of all created media players.
-     *
-     * This needs to be a TestRule so that the teardown assertions can be skipped if the test has
-     * failed (and media player assertions may just be a distracting side effect). Otherwise, the
-     * teardown failures hide the real test ones.
-     */
-    public static class RingtoneInjectablesTrackingTestRule implements TestRule {
-        public Ringtone.Injectables injectables = new TestInjectables();
-        public boolean hapticGeneratorAvailable = true;
-
-        // Queue of (local) media players, in order of expected creation. Enqueue using
-        // expectNewMediaPlayer(), dequeued by the media player factory passed to Ringtone.
-        // This queue is asserted to be empty at the end of the test.
-        private Queue<MediaPlayer> mMockMediaPlayerQueue = new ArrayDeque<>();
-
-        // Similar to media players, but for haptic generator, which also needs releasing.
-        private Map<MediaPlayer, HapticGenerator> mMockHapticGeneratorMap = new ArrayMap<>();
-
-        // Media players with haptic channels.
-        private ArraySet<MediaPlayer> mHapticChannels = new ArraySet<>();
-
-        @Override
-        public Statement apply(Statement base, Description description) {
-            return new Statement() {
-                @Override
-                public void evaluate() throws Throwable {
-                    base.evaluate();
-                    // Only assert if the test didn't fail (base.evaluate() would throw).
-                    assertWithMessage("Test setup an expectLocalMediaPlayer but it wasn't consumed")
-                            .that(mMockMediaPlayerQueue).isEmpty();
-                    // Only assert if the test didn't fail (base.evaluate() would throw).
-                    assertWithMessage(
-                            "Test setup an expectLocalHapticGenerator but it wasn't consumed")
-                            .that(mMockHapticGeneratorMap).isEmpty();
-                }
-            };
-        }
-
-        private TestMediaPlayer expectLocalMediaPlayer() {
-            TestMediaPlayer mockMediaPlayer = Mockito.mock(TestMediaPlayer.class);
-            // Delegate to simulated methods. This means they can be verified but also reflect
-            // realistic transitions from the TestMediaPlayer.
-            doCallRealMethod().when(mockMediaPlayer).start();
-            doCallRealMethod().when(mockMediaPlayer).stop();
-            doCallRealMethod().when(mockMediaPlayer).setLooping(anyBoolean());
-            when(mockMediaPlayer.isLooping()).thenCallRealMethod();
-            when(mockMediaPlayer.isLooping()).thenCallRealMethod();
-            mMockMediaPlayerQueue.add(mockMediaPlayer);
-            return mockMediaPlayer;
-        }
-
-        private HapticGenerator expectHapticGenerator(MediaPlayer mockMediaPlayer) {
-            HapticGenerator mockHapticGenerator = Mockito.mock(HapticGenerator.class);
-            // A test should never want this.
-            assertWithMessage("Can't expect a second haptic generator created "
-                    + "for one media player")
-                    .that(mMockHapticGeneratorMap.put(mockMediaPlayer, mockHapticGenerator))
-                    .isNull();
-            return mockHapticGenerator;
-        }
-
-        private void setHasHapticChannels(MediaPlayer mp, boolean hasHapticChannels) {
-            if (hasHapticChannels) {
-                mHapticChannels.add(mp);
-            } else {
-                mHapticChannels.remove(mp);
-            }
-        }
-
-        private class TestInjectables extends Ringtone.Injectables {
-            @Override
-            public MediaPlayer newMediaPlayer() {
-                assertWithMessage(
-                        "Unexpected MediaPlayer creation. Bug or need expectNewMediaPlayer")
-                        .that(mMockMediaPlayerQueue)
-                        .isNotEmpty();
-                return mMockMediaPlayerQueue.remove();
-            }
-
-            @Override
-            public boolean isHapticGeneratorAvailable() {
-                return hapticGeneratorAvailable;
-            }
-
-            @Override
-            public HapticGenerator createHapticGenerator(MediaPlayer mediaPlayer) {
-                HapticGenerator mockHapticGenerator = mMockHapticGeneratorMap.remove(mediaPlayer);
-                assertWithMessage("Unexpected HapticGenerator creation. "
-                        + "Bug or need expectHapticGenerator")
-                        .that(mockHapticGenerator)
-                        .isNotNull();
-                return mockHapticGenerator;
-            }
-
-            @Override
-            public boolean isHapticPlaybackSupported() {
-                return true;
-            }
-
-            @Override
-            public boolean hasHapticChannels(MediaPlayer mp) {
-                return mHapticChannels.contains(mp);
-            }
-        }
-    }
-
-    /**
-     * MediaPlayer relies on a native backend and so its necessary to intercept calls from
-     * fake usage hitting them.
-     *
-     * Mocks don't work directly on native calls, but if they're overridden then it does work.
-     * Some basic state faking is also done to make the mocks more realistic.
-     */
-    private static class TestMediaPlayer extends MediaPlayer {
-        private boolean mIsPlaying = false;
-        private boolean mIsLooping = false;
-
-        @Override
-        public void start() {
-            mIsPlaying = true;
-        }
-
-        @Override
-        public void stop() {
-            mIsPlaying = false;
-        }
-
-        @Override
-        public void setLooping(boolean value) {
-            mIsLooping = value;
-        }
-
-        @Override
-        public boolean isLooping() {
-            return mIsLooping;
-        }
-
-        @Override
-        public boolean isPlaying() {
-            return mIsPlaying;
-        }
-
-        void simulatePlayingFinished() {
-            if (!mIsPlaying) {
-                throw new IllegalStateException(
-                        "Attempted to pretend playing finished when not playing");
-            }
-            mIsPlaying = false;
-        }
-    }
-}
diff --git a/media/tests/ringtone/Android.bp b/media/tests/ringtone/Android.bp
deleted file mode 100644
index 55b98c4..0000000
--- a/media/tests/ringtone/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-package {
-    // See: http://go/android-license-faq
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test {
-    name: "MediaRingtoneTests",
-
-    srcs: ["src/**/*.java"],
-
-    libs: [
-        "android.test.runner",
-        "android.test.base",
-    ],
-
-    static_libs: [
-        "androidx.test.rules",
-        "testng",
-        "androidx.test.ext.truth",
-        "frameworks-base-testutils",
-    ],
-
-    test_suites: [
-        "device-tests",
-        "automotive-tests",
-    ],
-
-    platform_apis: true,
-    certificate: "platform",
-}
diff --git a/media/tests/ringtone/AndroidManifest.xml b/media/tests/ringtone/AndroidManifest.xml
deleted file mode 100644
index 27eda07..0000000
--- a/media/tests/ringtone/AndroidManifest.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2023 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.framework.base.media.ringtone.tests">
-
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.MANAGE_USERS" />
-
-    <application android:debuggable="true">
-        <uses-library android:name="android.test.runner" />
-
-        <activity android:name="MediaRingtoneTests"
-                  android:label="Media Ringtone Tests"
-                  android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.framework.base.media.ringtone.tests"
-                     android:label="Media Ringtone Tests"/>
-</manifest>
diff --git a/media/tests/ringtone/TEST_MAPPING b/media/tests/ringtone/TEST_MAPPING
deleted file mode 100644
index 6f25c14..0000000
--- a/media/tests/ringtone/TEST_MAPPING
+++ /dev/null
@@ -1,20 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "MediaRingtoneTests",
-      "options": [
-        {"exclude-annotation": "androidx.test.filters.LargeTest"},
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-        {"exclude-annotation": "org.junit.Ignore"}
-      ]
-    }
-  ],
-  "postsubmit": [
-    {
-      "name": "MediaRingtoneTests",
-      "options": [
-        {"exclude-annotation": "org.junit.Ignore"}
-      ]
-    }
-  ]
-}
\ No newline at end of file
diff --git a/media/tests/ringtone/res/raw/test_haptic_file.ahv b/media/tests/ringtone/res/raw/test_haptic_file.ahv
deleted file mode 100644
index d6eba1a..0000000
--- a/media/tests/ringtone/res/raw/test_haptic_file.ahv
+++ /dev/null
@@ -1,17 +0,0 @@
-<vibration-effect>
-  <waveform-effect>
-    <waveform-entry durationMs="63" amplitude="255"/>
-    <waveform-entry durationMs="63" amplitude="231"/>
-    <waveform-entry durationMs="63" amplitude="208"/>
-    <waveform-entry durationMs="63" amplitude="185"/>
-    <waveform-entry durationMs="63" amplitude="162"/>
-    <waveform-entry durationMs="63" amplitude="139"/>
-    <waveform-entry durationMs="63" amplitude="115"/>
-    <waveform-entry durationMs="63" amplitude="92"/>
-    <waveform-entry durationMs="63" amplitude="69"/>
-    <waveform-entry durationMs="63" amplitude="46"/>
-    <waveform-entry durationMs="63" amplitude="23"/>
-    <waveform-entry durationMs="63" amplitude="0"/>
-    <waveform-entry durationMs="1250" amplitude="0"/>
-  </waveform-effect>
-</vibration-effect>
diff --git a/media/tests/ringtone/res/raw/test_sound_file.mp3 b/media/tests/ringtone/res/raw/test_sound_file.mp3
deleted file mode 100644
index c1b2fdf..0000000
--- a/media/tests/ringtone/res/raw/test_sound_file.mp3
+++ /dev/null
Binary files differ
diff --git a/media/tests/ringtone/src/com/android/media/RingtoneManagerTest.java b/media/tests/ringtone/src/com/android/media/RingtoneManagerTest.java
deleted file mode 100644
index a92b298..0000000
--- a/media/tests/ringtone/src/com/android/media/RingtoneManagerTest.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.media;
-
-import static com.google.android.mms.ContentType.AUDIO_MP3;
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assume.assumeTrue;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Environment;
-import android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
-import android.os.vibrator.persistence.VibrationXmlParser;
-import android.provider.MediaStore;
-import android.text.TextUtils;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.framework.base.media.ringtone.tests.R;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-@RunWith(Parameterized.class)
-public class RingtoneManagerTest {
-    @RingtoneManager.MediaType
-    private final int mMediaType;
-    private final List<Uri> mAddedFilesUri;
-    private Context mContext;
-    private RingtoneManager mRingtoneManager;
-    private long mTimestamp;
-
-    @Parameterized.Parameters(name = "media = {0}")
-    public static Iterable<?> data() {
-        return Arrays.asList(Ringtone.MEDIA_SOUND, Ringtone.MEDIA_VIBRATION);
-    }
-
-    public RingtoneManagerTest(@RingtoneManager.MediaType int mediaType) {
-        mMediaType = mediaType;
-        mAddedFilesUri = new ArrayList<>();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
-        mTimestamp = SystemClock.uptimeMillis();
-        mRingtoneManager = new RingtoneManager(mContext);
-        mRingtoneManager.setMediaType(mMediaType);
-    }
-
-    @After
-    public void tearDown() {
-        // Clean up media store
-        for (Uri fileUri : mAddedFilesUri) {
-            mContext.getContentResolver().delete(fileUri, null);
-        }
-    }
-
-    @Test
-    public void testSetMediaType_withValidValue_setsMediaCorrectly() {
-        mRingtoneManager.setMediaType(mMediaType);
-        assertThat(mRingtoneManager.getMediaType()).isEqualTo(mMediaType);
-    }
-
-    @Test
-    public void testSetMediaType_withInvalidValue_throwsException() {
-        assertThrows(IllegalArgumentException.class, () -> mRingtoneManager.setMediaType(999));
-    }
-
-    @Test
-    public void testSetMediaType_afterCallingGetCursor_throwsException() {
-        mRingtoneManager.getCursor();
-        assertThrows(IllegalStateException.class, () -> mRingtoneManager.setMediaType(mMediaType));
-    }
-
-    @Test
-    public void testGetRingtone_ringtoneHasCorrectTitle() throws Exception {
-        String fileName = generateUniqueFileName("new_file");
-        Ringtone ringtone = addNewRingtoneToMediaStore(mRingtoneManager, fileName);
-
-        assertThat(ringtone.getTitle(mContext)).isEqualTo(fileName);
-    }
-
-    @Test
-    public void testGetRingtone_ringtoneCanBePlayedAndStopped() throws Exception {
-        //TODO(b/261571543) Remove this assumption once we support playing vibrations.
-        assumeTrue(mMediaType == Ringtone.MEDIA_SOUND);
-        String fileName = generateUniqueFileName("new_file");
-        Ringtone ringtone = addNewRingtoneToMediaStore(mRingtoneManager, fileName);
-
-        ringtone.play();
-        assertThat(ringtone.isPlaying()).isTrue();
-
-        ringtone.stop();
-        assertThat(ringtone.isPlaying()).isFalse();
-    }
-
-    @Test
-    public void testGetCursor_withDifferentMedia_returnsCorrectCursor() throws Exception {
-        RingtoneManager audioRingtoneManager = new RingtoneManager(mContext);
-        String audioFileName = generateUniqueFileName("ringtone");
-        addNewRingtoneToMediaStore(audioRingtoneManager, audioFileName);
-
-        RingtoneManager vibrationRingtoneManager = new RingtoneManager(mContext);
-        vibrationRingtoneManager.setMediaType(Ringtone.MEDIA_VIBRATION);
-        String vibrationFileName = generateUniqueFileName("vibration");
-        addNewRingtoneToMediaStore(vibrationRingtoneManager, vibrationFileName);
-
-        Cursor audioCursor = audioRingtoneManager.getCursor();
-        Cursor vibrationCursor = vibrationRingtoneManager.getCursor();
-
-        List<String> audioTitles = extractRecordTitles(audioCursor);
-        List<String> vibrationTitles = extractRecordTitles(vibrationCursor);
-
-        assertThat(audioTitles).contains(audioFileName);
-        assertThat(audioTitles).doesNotContain(vibrationFileName);
-
-        assertThat(vibrationTitles).contains(vibrationFileName);
-        assertThat(vibrationTitles).doesNotContain(audioFileName);
-    }
-
-    private List<String> extractRecordTitles(Cursor cursor) {
-        List<String> titles = new ArrayList<>();
-
-        if (cursor.moveToFirst()) {
-            do {
-                String title = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX);
-                titles.add(title);
-            } while (cursor.moveToNext());
-        }
-
-        return titles;
-    }
-
-    private Ringtone addNewRingtoneToMediaStore(RingtoneManager ringtoneManager, String fileName)
-            throws Exception {
-        Uri fileUri = ringtoneManager.getMediaType() == Ringtone.MEDIA_SOUND ? addAudioFile(
-                fileName) : addVibrationFile(fileName);
-        mAddedFilesUri.add(fileUri);
-
-        int ringtonePosition = ringtoneManager.getRingtonePosition(fileUri);
-        Ringtone ringtone = ringtoneManager.getRingtone(ringtonePosition);
-        // Validate this is the expected ringtone.
-        assertThat(ringtone.getUri()).isEqualTo(fileUri);
-        return ringtone;
-    }
-
-    private Uri addAudioFile(String fileName) throws Exception {
-        ContentResolver resolver = mContext.getContentResolver();
-        ContentValues contentValues = new ContentValues();
-        contentValues.put(MediaStore.Audio.Media.DISPLAY_NAME, fileName + ".mp3");
-        contentValues.put(MediaStore.Audio.Media.RELATIVE_PATH, Environment.DIRECTORY_RINGTONES);
-        contentValues.put(MediaStore.Audio.Media.MIME_TYPE, AUDIO_MP3);
-        contentValues.put(MediaStore.Audio.Media.TITLE, fileName);
-        contentValues.put(MediaStore.Audio.Media.IS_RINGTONE, 1);
-
-        Uri contentUri = resolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
-                contentValues);
-        writeRawDataToFile(resolver, contentUri, R.raw.test_sound_file);
-
-        return resolver.canonicalizeOrElse(contentUri);
-    }
-
-    private Uri addVibrationFile(String fileName) throws Exception {
-        ContentResolver resolver = mContext.getContentResolver();
-        ContentValues contentValues = new ContentValues();
-        contentValues.put(MediaStore.Files.FileColumns.DISPLAY_NAME, fileName + ".ahv");
-        contentValues.put(MediaStore.Files.FileColumns.RELATIVE_PATH,
-                Environment.DIRECTORY_DOWNLOADS);
-        contentValues.put(MediaStore.Files.FileColumns.MIME_TYPE,
-                VibrationXmlParser.APPLICATION_VIBRATION_XML_MIME_TYPE);
-        contentValues.put(MediaStore.Files.FileColumns.TITLE, fileName);
-
-        Uri contentUri = resolver.insert(MediaStore.Files.getContentUri(MediaStore
-                .VOLUME_EXTERNAL), contentValues);
-        writeRawDataToFile(resolver, contentUri, R.raw.test_haptic_file);
-
-        return resolver.canonicalizeOrElse(contentUri);
-    }
-
-    private void writeRawDataToFile(ContentResolver resolver, Uri contentUri, int rawResource)
-            throws Exception {
-        try (ParcelFileDescriptor pfd =
-                     resolver.openFileDescriptor(contentUri, "w", null)) {
-            InputStream inputStream = mContext.getResources().openRawResource(rawResource);
-            FileOutputStream outputStream = new FileOutputStream(pfd.getFileDescriptor());
-            outputStream.write(inputStream.readAllBytes());
-
-            inputStream.close();
-            outputStream.flush();
-            outputStream.close();
-
-        } catch (Exception e) {
-            throw new Exception("Failed to write data to file", e);
-        }
-    }
-
-    private String generateUniqueFileName(String prefix) {
-        return TextUtils.formatSimple("%s_%d", prefix, mTimestamp);
-    }
-
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 0ccb07a..b13df61 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -58,6 +58,7 @@
     private val providerEnabledList: List<ProviderData>
     private val providerDisabledList: List<DisabledProviderData>?
     val resultReceiver: ResultReceiver?
+    val finalResponseReceiver: ResultReceiver?
 
     var initialUiState: UiState
 
@@ -105,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
@@ -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..26a97cd 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -216,13 +216,18 @@
             android.credentials.selection.Constants.EXTRA_RESULT_RECEIVER,
             ResultReceiver::class.java
         )
+        val finalResponseResultReceiver = intent.getParcelableExtra(
+                android.credentials.selection.Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
+                ResultReceiver::class.java
+        )
+
         val requestInfo = intent.extras?.getParcelable(
             RequestInfo.EXTRA_REQUEST_INFO,
             RequestInfo::class.java
         )
         CredentialManagerRepo.sendCancellationCode(
             BaseDialogResult.RESULT_CODE_DATA_PARSING_FAILURE,
-            requestInfo?.token, resultReceiver
+            requestInfo?.token, resultReceiver, finalResponseResultReceiver
         )
         this.finish()
     }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 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/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/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index 0a98791..988afd7 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -17,10 +17,11 @@
 package com.android.settingslib.spaprivileged.model.app
 
 import android.content.Context
-import android.content.pm.FeatureFlags
-import android.content.pm.FeatureFlagsImpl
 import android.content.Intent
 import android.content.pm.ApplicationInfo
+import android.content.pm.FeatureFlags
+import android.content.pm.FeatureFlagsImpl
+import android.content.pm.Flags
 import android.content.pm.PackageManager
 import android.content.pm.PackageManager.ApplicationInfoFlags
 import android.content.pm.ResolveInfo
@@ -85,13 +86,7 @@
         loadInstantApps: Boolean,
         matchAnyUserForAdmin: Boolean,
     ): List<ApplicationInfo> = coroutineScope {
-        val hiddenSystemModulesDeferred = async {
-            packageManager.getInstalledModules(0)
-                .filter { it.isHidden }
-                .map { it.packageName }
-                .filterNotNull()
-                .toSet()
-        }
+        val hiddenSystemModulesDeferred = async { packageManager.getHiddenSystemModules() }
         val hideWhenDisabledPackagesDeferred = async {
             context.resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)
         }
@@ -205,6 +200,15 @@
     private fun isSystemApp(app: ApplicationInfo, homeOrLauncherPackages: Set<String>): Boolean =
         app.isSystemApp && !app.isUpdatedSystemApp && app.packageName !in homeOrLauncherPackages
 
+    private fun PackageManager.getHiddenSystemModules(): Set<String> {
+        val moduleInfos = getInstalledModules(0).filter { it.isHidden }
+        val hiddenApps = moduleInfos.mapNotNull { it.packageName }.toMutableSet()
+        if (Flags.provideInfoOfApkInApex()) {
+            hiddenApps += moduleInfos.flatMap { it.apkInApexPackageNames }
+        }
+        return hiddenApps
+    }
+
     companion object {
         private fun ApplicationInfo.isInAppList(
             showInstantApps: Boolean,
diff --git a/packages/SettingsLib/SpaPrivileged/tests/Android.bp b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
index a28ebc6..458fcc9 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
@@ -35,5 +35,6 @@
         "androidx.test.ext.junit",
         "androidx.test.runner",
         "mockito-target-minus-junit4",
+        "flag-junit",
     ],
 }
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index f292231..efd53a4 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -21,6 +21,7 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.FakeFeatureFlagsImpl
 import android.content.pm.Flags
+import android.content.pm.ModuleInfo
 import android.content.pm.PackageManager
 import android.content.pm.PackageManager.ApplicationInfoFlags
 import android.content.pm.PackageManager.ResolveInfoFlags
@@ -28,6 +29,7 @@
 import android.content.pm.UserInfo
 import android.content.res.Resources
 import android.os.UserManager
+import android.platform.test.flag.junit.SetFlagsRule
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.internal.R
@@ -35,6 +37,7 @@
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runTest
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.any
@@ -50,6 +53,9 @@
 
 @RunWith(AndroidJUnit4::class)
 class AppListRepositoryTest {
+    @get:Rule
+    val mSetFlagsRule = SetFlagsRule()
+
     private val resources = mock<Resources> {
         on { getStringArray(R.array.config_hideWhenDisabled_packageNames) } doReturn emptyArray()
     }
@@ -273,6 +279,38 @@
     }
 
     @Test
+    fun loadApps_hasApkInApexInfo_shouldNotIncludeAllHiddenApps() = runTest {
+        mSetFlagsRule.enableFlags(Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX)
+        packageManager.stub {
+            on { getInstalledModules(any()) } doReturn listOf(HIDDEN_MODULE)
+        }
+        mockInstalledApplications(
+            listOf(NORMAL_APP, HIDDEN_APEX_APP, HIDDEN_MODULE_APP),
+            ADMIN_USER_ID
+        )
+
+        val appList = repository.loadApps(userId = ADMIN_USER_ID)
+
+        assertThat(appList).containsExactly(NORMAL_APP)
+    }
+
+    @Test
+    fun loadApps_noApkInApexInfo_shouldNotIncludeHiddenSystemModule() = runTest {
+        mSetFlagsRule.disableFlags(Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX)
+        packageManager.stub {
+            on { getInstalledModules(any()) } doReturn listOf(HIDDEN_MODULE)
+        }
+        mockInstalledApplications(
+            listOf(NORMAL_APP, HIDDEN_APEX_APP, HIDDEN_MODULE_APP),
+            ADMIN_USER_ID
+        )
+
+        val appList = repository.loadApps(userId = ADMIN_USER_ID)
+
+        assertThat(appList).containsExactly(NORMAL_APP, HIDDEN_APEX_APP)
+    }
+
+    @Test
     fun showSystemPredicate_showSystem() = runTest {
         val app = SYSTEM_APP
 
@@ -402,6 +440,20 @@
             isArchived = true
         }
 
+        val HIDDEN_APEX_APP = ApplicationInfo().apply {
+            packageName = "hidden.apex.package"
+        }
+
+        val HIDDEN_MODULE_APP = ApplicationInfo().apply {
+            packageName = "hidden.module.package"
+        }
+
+        val HIDDEN_MODULE = ModuleInfo().apply {
+            packageName = "hidden.module.package"
+            apkInApexPackageNames = listOf("hidden.apex.package")
+            isHidden = true
+        }
+
         fun resolveInfoOf(packageName: String) = ResolveInfo().apply {
             activityInfo = ActivityInfo().apply {
                 this.packageName = packageName
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
index 07de7fd..c482995 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
@@ -22,6 +22,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -164,12 +165,16 @@
 
         try {
             final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */);
-            // Check if the package is contained in an APEX. There is no public API to properly
-            // check whether a given APK package comes from an APEX registered as module.
-            // Therefore we conservatively assume that any package scanned from an /apex path is
-            // a system package.
-            return pkg.applicationInfo.sourceDir.startsWith(
-                    Environment.getApexDirectory().getAbsolutePath());
+            if (Flags.provideInfoOfApkInApex()) {
+                return pkg.getApexPackageName() != null;
+            } else {
+                // Check if the package is contained in an APEX. There is no public API to properly
+                // check whether a given APK package comes from an APEX registered as module.
+                // Therefore we conservatively assume that any package scanned from an /apex path is
+                // a system package.
+                return pkg.applicationInfo.sourceDir.startsWith(
+                        Environment.getApexDirectory().getAbsolutePath());
+            }
         } catch (PackageManager.NameNotFoundException e) {
             return false;
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 8e1067f..e3012cd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -27,6 +27,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
 import android.content.pm.IPackageManager;
 import android.content.pm.IPackageStatsObserver;
 import android.content.pm.ModuleInfo;
@@ -226,6 +227,11 @@
         final List<ModuleInfo> moduleInfos = mPm.getInstalledModules(0 /* flags */);
         for (ModuleInfo info : moduleInfos) {
             mSystemModules.put(info.getPackageName(), info.isHidden());
+            if (Flags.provideInfoOfApkInApex()) {
+                for (String apkInApexPackageName : info.getApkInApexPackageNames()) {
+                    mSystemModules.put(apkInApexPackageName, info.isHidden());
+                }
+            }
         }
 
         /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/model/RoutingSession.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/model/RoutingSession.kt
new file mode 100644
index 0000000..a98f3e2
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/model/RoutingSession.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.data.model
+
+import android.media.RoutingSessionInfo
+
+/** Models a routing session which is created when a media route is selected. */
+data class RoutingSession(
+    val routingSessionInfo: RoutingSessionInfo,
+    val isVolumeSeekBarEnabled: Boolean,
+    val isMediaOutputDisabled: Boolean,
+)
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 6761aa7..f729c04 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -16,15 +16,12 @@
 
 package com.android.settingslib.volume.data.repository
 
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
 import android.media.AudioDeviceInfo
 import android.media.AudioManager
 import android.media.AudioManager.OnCommunicationDeviceChangedListener
 import androidx.concurrent.futures.DirectExecutor
 import com.android.internal.util.ConcurrentUtils
+import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
 import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.settingslib.volume.shared.model.AudioStreamModel
 import com.android.settingslib.volume.shared.model.RingerMode
@@ -32,7 +29,6 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.callbackFlow
@@ -40,7 +36,6 @@
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
@@ -77,7 +72,7 @@
 }
 
 class AudioRepositoryImpl(
-    private val context: Context,
+    private val audioManagerIntentsReceiver: AudioManagerIntentsReceiver,
     private val audioManager: AudioManager,
     private val backgroundCoroutineContext: CoroutineContext,
     private val coroutineScope: CoroutineScope,
@@ -93,30 +88,9 @@
             .flowOn(backgroundCoroutineContext)
             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), audioManager.mode)
 
-    private val audioManagerIntents: SharedFlow<String> =
-        callbackFlow {
-                val receiver =
-                    object : BroadcastReceiver() {
-                        override fun onReceive(context: Context?, intent: Intent) {
-                            intent.action?.let { action -> launch { send(action) } }
-                        }
-                    }
-                context.registerReceiver(
-                    receiver,
-                    IntentFilter().apply {
-                        for (action in allActions) {
-                            addAction(action)
-                        }
-                    }
-                )
-
-                awaitClose { context.unregisterReceiver(receiver) }
-            }
-            .shareIn(coroutineScope, SharingStarted.WhileSubscribed())
-
     override val ringerMode: StateFlow<RingerMode> =
-        audioManagerIntents
-            .filter { ringerActions.contains(it) }
+        audioManagerIntentsReceiver.intents
+            .filter { AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION == it.action }
             .map { RingerMode(audioManager.ringerModeInternal) }
             .flowOn(backgroundCoroutineContext)
             .stateIn(
@@ -146,8 +120,7 @@
                 )
 
     override suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> {
-        return audioManagerIntents
-            .filter { modelActions.contains(it) }
+        return audioManagerIntentsReceiver.intents
             .map { getCurrentAudioStream(audioStream) }
             .flowOn(backgroundCoroutineContext)
     }
@@ -189,20 +162,4 @@
             // return STREAM_VOICE_CALL in getAudioStream
             audioManager.getStreamMinVolume(AudioManager.STREAM_VOICE_CALL)
         }
-
-    private companion object {
-        val modelActions =
-            setOf(
-                AudioManager.STREAM_MUTE_CHANGED_ACTION,
-                AudioManager.MASTER_MUTE_CHANGED_ACTION,
-                AudioManager.VOLUME_CHANGED_ACTION,
-                AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
-                AudioManager.STREAM_DEVICES_CHANGED_ACTION,
-            )
-        val ringerActions =
-            setOf(
-                AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
-            )
-        val allActions = ringerActions + modelActions
-    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
index 1597b77..aa9ae76 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
@@ -15,8 +15,13 @@
  */
 package com.android.settingslib.volume.data.repository
 
+import android.media.AudioManager
+import android.media.MediaRouter2Manager
+import android.media.RoutingSessionInfo
 import com.android.settingslib.media.LocalMediaManager
 import com.android.settingslib.media.MediaDevice
+import com.android.settingslib.volume.data.model.RoutingSession
+import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
@@ -24,10 +29,15 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
 
 /** Repository providing data about connected media devices. */
 interface LocalMediaRepository {
@@ -37,43 +47,55 @@
 
     /** Currently connected media device */
     val currentConnectedDevice: StateFlow<MediaDevice?>
+
+    val remoteRoutingSessions: StateFlow<Collection<RoutingSession>>
+
+    suspend fun adjustSessionVolume(sessionId: String?, volume: Int)
 }
 
 class LocalMediaRepositoryImpl(
+    audioManagerIntentsReceiver: AudioManagerIntentsReceiver,
     private val localMediaManager: LocalMediaManager,
+    private val mediaRouter2Manager: MediaRouter2Manager,
     coroutineScope: CoroutineScope,
-    backgroundContext: CoroutineContext,
+    private val backgroundContext: CoroutineContext,
 ) : LocalMediaRepository {
 
-    private val deviceUpdates: Flow<DevicesUpdate> = callbackFlow {
-        val callback =
-            object : LocalMediaManager.DeviceCallback {
-                override fun onDeviceListUpdate(newDevices: List<MediaDevice>?) {
-                    trySend(DevicesUpdate.DeviceListUpdate(newDevices ?: emptyList()))
-                }
+    private val devicesChanges =
+        audioManagerIntentsReceiver.intents.filter {
+            AudioManager.STREAM_DEVICES_CHANGED_ACTION == it.action
+        }
+    private val mediaDevicesUpdates: Flow<DevicesUpdate> =
+        callbackFlow {
+                val callback =
+                    object : LocalMediaManager.DeviceCallback {
+                        override fun onDeviceListUpdate(newDevices: List<MediaDevice>?) {
+                            trySend(DevicesUpdate.DeviceListUpdate(newDevices ?: emptyList()))
+                        }
 
-                override fun onSelectedDeviceStateChanged(
-                    device: MediaDevice?,
-                    state: Int,
-                ) {
-                    trySend(DevicesUpdate.SelectedDeviceStateChanged)
-                }
+                        override fun onSelectedDeviceStateChanged(
+                            device: MediaDevice?,
+                            state: Int,
+                        ) {
+                            trySend(DevicesUpdate.SelectedDeviceStateChanged)
+                        }
 
-                override fun onDeviceAttributesChanged() {
-                    trySend(DevicesUpdate.DeviceAttributesChanged)
+                        override fun onDeviceAttributesChanged() {
+                            trySend(DevicesUpdate.DeviceAttributesChanged)
+                        }
+                    }
+                localMediaManager.registerCallback(callback)
+                localMediaManager.startScan()
+
+                awaitClose {
+                    localMediaManager.stopScan()
+                    localMediaManager.unregisterCallback(callback)
                 }
             }
-        localMediaManager.registerCallback(callback)
-        localMediaManager.startScan()
-
-        awaitClose {
-            localMediaManager.stopScan()
-            localMediaManager.unregisterCallback(callback)
-        }
-    }
+            .shareIn(coroutineScope, SharingStarted.WhileSubscribed(), replay = 0)
 
     override val mediaDevices: StateFlow<Collection<MediaDevice>> =
-        deviceUpdates
+        mediaDevicesUpdates
             .mapNotNull {
                 if (it is DevicesUpdate.DeviceListUpdate) {
                     it.newDevices ?: emptyList()
@@ -85,7 +107,7 @@
             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyList())
 
     override val currentConnectedDevice: StateFlow<MediaDevice?> =
-        deviceUpdates
+        merge(devicesChanges, mediaDevicesUpdates)
             .map { localMediaManager.currentConnectedDevice }
             .stateIn(
                 coroutineScope,
@@ -93,6 +115,30 @@
                 localMediaManager.currentConnectedDevice
             )
 
+    override val remoteRoutingSessions: StateFlow<Collection<RoutingSession>> =
+        merge(devicesChanges, mediaDevicesUpdates)
+            .onStart { emit(Unit) }
+            .map { localMediaManager.remoteRoutingSessions.map(::toRoutingSession) }
+            .flowOn(backgroundContext)
+            .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyList())
+
+    override suspend fun adjustSessionVolume(sessionId: String?, volume: Int) {
+        withContext(backgroundContext) {
+            if (sessionId == null) {
+                localMediaManager.adjustSessionVolume(volume)
+            } else {
+                localMediaManager.adjustSessionVolume(sessionId, volume)
+            }
+        }
+    }
+
+    private fun toRoutingSession(info: RoutingSessionInfo): RoutingSession =
+        RoutingSession(
+            info,
+            isMediaOutputDisabled = mediaRouter2Manager.getTransferableRoutes(info).isEmpty(),
+            isVolumeSeekBarEnabled = localMediaManager.shouldEnableVolumeSeekBar(info)
+        )
+
     private sealed interface DevicesUpdate {
 
         data class DeviceListUpdate(val newDevices: List<MediaDevice>?) : DevicesUpdate
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt
index 93aa90d..ab8c6b8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt
@@ -16,30 +16,23 @@
 
 package com.android.settingslib.volume.data.repository
 
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
 import android.media.AudioManager
 import android.media.session.MediaController
 import android.media.session.MediaSessionManager
 import android.media.session.PlaybackState
 import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.settingslib.bluetooth.headsetAudioModeChanges
+import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
 
 /** Provides controllers for currently active device media sessions. */
 interface MediaControllerRepository {
@@ -49,40 +42,25 @@
 }
 
 class MediaControllerRepositoryImpl(
-    private val context: Context,
+    audioManagerIntentsReceiver: AudioManagerIntentsReceiver,
     private val mediaSessionManager: MediaSessionManager,
     localBluetoothManager: LocalBluetoothManager?,
     coroutineScope: CoroutineScope,
     backgroundContext: CoroutineContext,
 ) : MediaControllerRepository {
 
-    private val devicesChanges: Flow<Unit> =
-        callbackFlow {
-                val receiver =
-                    object : BroadcastReceiver() {
-                        override fun onReceive(context: Context?, intent: Intent?) {
-                            if (AudioManager.STREAM_DEVICES_CHANGED_ACTION == intent?.action) {
-                                launch { send(Unit) }
-                            }
-                        }
-                    }
-                context.registerReceiver(
-                    receiver,
-                    IntentFilter(AudioManager.STREAM_DEVICES_CHANGED_ACTION)
-                )
-
-                awaitClose { context.unregisterReceiver(receiver) }
-            }
-            .shareIn(coroutineScope, SharingStarted.WhileSubscribed(), replay = 0)
-
+    private val devicesChanges =
+        audioManagerIntentsReceiver.intents.filter {
+            AudioManager.STREAM_DEVICES_CHANGED_ACTION == it.action
+        }
     override val activeMediaController: StateFlow<MediaController?> =
-        combine(
-                localBluetoothManager?.headsetAudioModeChanges?.onStart { emit(Unit) }
-                    ?: emptyFlow(),
-                devicesChanges.onStart { emit(Unit) },
-            ) { _, _ ->
-                getActiveLocalMediaController()
+        buildList {
+                localBluetoothManager?.headsetAudioModeChanges?.let { add(it) }
+                add(devicesChanges)
             }
+            .merge()
+            .onStart { emit(Unit) }
+            .map { getActiveLocalMediaController() }
             .flowOn(backgroundContext)
             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/LocalMediaInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/LocalMediaInteractor.kt
new file mode 100644
index 0000000..f621335
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/LocalMediaInteractor.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.settingslib.volume.domain.interactor
+
+import com.android.settingslib.media.MediaDevice
+import com.android.settingslib.volume.data.repository.LocalMediaRepository
+import com.android.settingslib.volume.domain.model.RoutingSession
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+class LocalMediaInteractor(
+    private val repository: LocalMediaRepository,
+    coroutineScope: CoroutineScope,
+) {
+
+    /** Available devices list */
+    val mediaDevices: StateFlow<Collection<MediaDevice>>
+        get() = repository.mediaDevices
+
+    /** Currently connected media device */
+    val currentConnectedDevice: StateFlow<MediaDevice?>
+        get() = repository.currentConnectedDevice
+
+    val remoteRoutingSessions: StateFlow<List<RoutingSession>> =
+        repository.remoteRoutingSessions
+            .map { sessions ->
+                sessions.map {
+                    RoutingSession(
+                        routingSessionInfo = it.routingSessionInfo,
+                        isMediaOutputDisabled = it.isMediaOutputDisabled,
+                        isVolumeSeekBarEnabled =
+                            it.isVolumeSeekBarEnabled && it.routingSessionInfo.volumeMax > 0
+                    )
+                }
+            }
+            .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyList())
+
+    suspend fun adjustSessionVolume(sessionId: String?, volume: Int) =
+        repository.adjustSessionVolume(sessionId, volume)
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/model/RoutingSession.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/model/RoutingSession.kt
new file mode 100644
index 0000000..dfc4703
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/model/RoutingSession.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.domain.model
+
+import android.media.RoutingSessionInfo
+
+/** Models a routing session which is created when a media route is selected. */
+data class RoutingSession(
+    val routingSessionInfo: RoutingSessionInfo,
+    val isMediaOutputDisabled: Boolean,
+    val isVolumeSeekBarEnabled: Boolean,
+)
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerIntentsReceiver.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerIntentsReceiver.kt
new file mode 100644
index 0000000..9fa4c86
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerIntentsReceiver.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.settingslib.volume.shared
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.media.AudioManager
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.launch
+
+/** Exposes [AudioManager] intents as a observable shared flow. */
+interface AudioManagerIntentsReceiver {
+
+    val intents: SharedFlow<Intent>
+}
+
+class AudioManagerIntentsReceiverImpl(
+    private val context: Context,
+    coroutineScope: CoroutineScope,
+) : AudioManagerIntentsReceiver {
+
+    private val allActions: Collection<String>
+        get() =
+            setOf(
+                AudioManager.STREAM_MUTE_CHANGED_ACTION,
+                AudioManager.MASTER_MUTE_CHANGED_ACTION,
+                AudioManager.VOLUME_CHANGED_ACTION,
+                AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
+                AudioManager.STREAM_DEVICES_CHANGED_ACTION,
+            )
+
+    override val intents: SharedFlow<Intent> =
+        callbackFlow {
+                val receiver =
+                    object : BroadcastReceiver() {
+                        override fun onReceive(context: Context?, intent: Intent?) {
+                            launch { send(intent) }
+                        }
+                    }
+                context.registerReceiver(
+                    receiver,
+                    IntentFilter().apply {
+                        for (action in allActions) {
+                            addAction(action)
+                        }
+                    }
+                )
+
+                awaitClose { context.unregisterReceiver(receiver) }
+            }
+            .filterNotNull()
+            .filter { intent -> allActions.contains(intent.action) }
+            .shareIn(coroutineScope, SharingStarted.WhileSubscribed())
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
index 7b70c64..48b04db 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -16,13 +16,11 @@
 
 package com.android.settingslib.volume.data.repository
 
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
 import android.media.AudioDeviceInfo
 import android.media.AudioManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.settingslib.volume.shared.FakeAudioManagerIntentsReceiver
 import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.settingslib.volume.shared.model.AudioStreamModel
 import com.android.settingslib.volume.shared.model.RingerMode
@@ -30,6 +28,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -51,17 +50,16 @@
 @RunWith(AndroidJUnit4::class)
 class AudioRepositoryTest {
 
-    @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
     @Captor
     private lateinit var modeListenerCaptor: ArgumentCaptor<AudioManager.OnModeChangedListener>
     @Captor
     private lateinit var communicationDeviceListenerCaptor:
         ArgumentCaptor<AudioManager.OnCommunicationDeviceChangedListener>
 
-    @Mock private lateinit var context: Context
     @Mock private lateinit var audioManager: AudioManager
     @Mock private lateinit var communicationDevice: AudioDeviceInfo
 
+    private val intentsReceiver = FakeAudioManagerIntentsReceiver()
     private val volumeByStream: MutableMap<Int, Int> = mutableMapOf()
     private val isAffectedByRingerModeByStream: MutableMap<Int, Boolean> = mutableMapOf()
     private val isMuteByStream: MutableMap<Int, Boolean> = mutableMapOf()
@@ -98,7 +96,7 @@
 
         underTest =
             AudioRepositoryImpl(
-                context,
+                intentsReceiver,
                 audioManager,
                 testScope.testScheduler,
                 testScope.backgroundScope,
@@ -270,8 +268,7 @@
     }
 
     private fun triggerIntent(action: String) {
-        verify(context).registerReceiver(receiverCaptor.capture(), any())
-        receiverCaptor.value.onReceive(context, Intent(action))
+        testScope.launch { intentsReceiver.triggerIntent(action) }
     }
 
     private companion object {
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeLocalMediaRepository.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeLocalMediaRepository.kt
new file mode 100644
index 0000000..642b72c
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeLocalMediaRepository.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.settingslib.volume.data.repository
+
+import com.android.settingslib.media.MediaDevice
+import com.android.settingslib.volume.data.model.RoutingSession
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeLocalMediaRepository : LocalMediaRepository {
+
+    private val volumeBySession: MutableMap<String?, Int> = mutableMapOf()
+
+    private val mutableMediaDevices = MutableStateFlow<Collection<MediaDevice>>(emptyList())
+    override val mediaDevices: StateFlow<Collection<MediaDevice>>
+        get() = mutableMediaDevices.asStateFlow()
+
+    private val mutableCurrentConnectedDevice = MutableStateFlow<MediaDevice?>(null)
+    override val currentConnectedDevice: StateFlow<MediaDevice?>
+        get() = mutableCurrentConnectedDevice.asStateFlow()
+
+    private val mutableRemoteRoutingSessions =
+        MutableStateFlow<Collection<RoutingSession>>(emptyList())
+    override val remoteRoutingSessions: StateFlow<Collection<RoutingSession>>
+        get() = mutableRemoteRoutingSessions.asStateFlow()
+
+    fun updateMediaDevices(devices: Collection<MediaDevice>) {
+        mutableMediaDevices.value = devices
+    }
+
+    fun updateCurrentConnectedDevice(device: MediaDevice?) {
+        mutableCurrentConnectedDevice.value = device
+    }
+
+    fun updateRemoteRoutingSessions(sessions: List<RoutingSession>) {
+        mutableRemoteRoutingSessions.value = sessions
+    }
+
+    fun getSessionVolume(sessionId: String?): Int = volumeBySession.getOrDefault(sessionId, 0)
+
+    override suspend fun adjustSessionVolume(sessionId: String?, volume: Int) {
+        volumeBySession[sessionId] = volume
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt
index d106bce..dc9ea10 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt
@@ -15,10 +15,15 @@
  */
 package com.android.settingslib.volume.data.repository
 
+import android.media.MediaRoute2Info
+import android.media.MediaRouter2Manager
+import android.media.RoutingSessionInfo
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.media.LocalMediaManager
 import com.android.settingslib.media.MediaDevice
+import com.android.settingslib.volume.data.model.RoutingSession
+import com.android.settingslib.volume.shared.FakeAudioManagerIntentsReceiver
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
@@ -32,6 +37,10 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
@@ -44,10 +53,12 @@
     @Mock private lateinit var localMediaManager: LocalMediaManager
     @Mock private lateinit var mediaDevice1: MediaDevice
     @Mock private lateinit var mediaDevice2: MediaDevice
+    @Mock private lateinit var mediaRouter2Manager: MediaRouter2Manager
 
     @Captor
     private lateinit var deviceCallbackCaptor: ArgumentCaptor<LocalMediaManager.DeviceCallback>
 
+    private val intentsReceiver = FakeAudioManagerIntentsReceiver()
     private val testScope = TestScope()
 
     private lateinit var underTest: LocalMediaRepository
@@ -58,7 +69,9 @@
 
         underTest =
             LocalMediaRepositoryImpl(
+                intentsReceiver,
                 localMediaManager,
+                mediaRouter2Manager,
                 testScope.backgroundScope,
                 testScope.testScheduler,
             )
@@ -97,4 +110,78 @@
             assertThat(currentConnectedDevice).isEqualTo(mediaDevice1)
         }
     }
+
+    @Test
+    fun kek() {
+        testScope.runTest {
+            `when`(localMediaManager.remoteRoutingSessions)
+                .thenReturn(
+                    listOf(
+                        testRoutingSessionInfo1,
+                        testRoutingSessionInfo2,
+                        testRoutingSessionInfo3,
+                    )
+                )
+            `when`(localMediaManager.shouldEnableVolumeSeekBar(any())).then {
+                (it.arguments[0] as RoutingSessionInfo) == testRoutingSessionInfo1
+            }
+            `when`(mediaRouter2Manager.getTransferableRoutes(any<RoutingSessionInfo>())).then {
+                if ((it.arguments[0] as RoutingSessionInfo) == testRoutingSessionInfo2) {
+                    return@then listOf(mock(MediaRoute2Info::class.java))
+                }
+                emptyList<MediaRoute2Info>()
+            }
+            var remoteRoutingSessions: Collection<RoutingSession>? = null
+            underTest.remoteRoutingSessions
+                .onEach { remoteRoutingSessions = it }
+                .launchIn(backgroundScope)
+
+            runCurrent()
+
+            assertThat(remoteRoutingSessions)
+                .containsExactlyElementsIn(
+                    listOf(
+                        RoutingSession(
+                            routingSessionInfo = testRoutingSessionInfo1,
+                            isVolumeSeekBarEnabled = true,
+                            isMediaOutputDisabled = true,
+                        ),
+                        RoutingSession(
+                            routingSessionInfo = testRoutingSessionInfo2,
+                            isVolumeSeekBarEnabled = false,
+                            isMediaOutputDisabled = false,
+                        ),
+                        RoutingSession(
+                            routingSessionInfo = testRoutingSessionInfo3,
+                            isVolumeSeekBarEnabled = false,
+                            isMediaOutputDisabled = true,
+                        )
+                    )
+                )
+        }
+    }
+
+    @Test
+    fun adjustSessionVolume_adjusts() {
+        testScope.runTest {
+            var volume = 0
+            `when`(localMediaManager.adjustSessionVolume(anyString(), anyInt())).then {
+                volume = it.arguments[1] as Int
+                Unit
+            }
+
+            underTest.adjustSessionVolume("test_session", 10)
+
+            assertThat(volume).isEqualTo(10)
+        }
+    }
+
+    private companion object {
+        val testRoutingSessionInfo1 =
+            RoutingSessionInfo.Builder("id_1", "test.pkg.1").addSelectedRoute("route_1").build()
+        val testRoutingSessionInfo2 =
+            RoutingSessionInfo.Builder("id_2", "test.pkg.2").addSelectedRoute("route_2").build()
+        val testRoutingSessionInfo3 =
+            RoutingSessionInfo.Builder("id_3", "test.pkg.3").addSelectedRoute("route_3").build()
+    }
 }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt
index f07b1bff..430d733 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt
@@ -16,9 +16,6 @@
 
 package com.android.settingslib.volume.data.repository
 
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
 import android.media.AudioManager
 import android.media.session.MediaController
 import android.media.session.MediaController.PlaybackInfo
@@ -29,6 +26,7 @@
 import com.android.settingslib.bluetooth.BluetoothCallback
 import com.android.settingslib.bluetooth.BluetoothEventManager
 import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.volume.shared.FakeAudioManagerIntentsReceiver
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
@@ -52,10 +50,8 @@
 @SmallTest
 class MediaControllerRepositoryImplTest {
 
-    @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
     @Captor private lateinit var callbackCaptor: ArgumentCaptor<BluetoothCallback>
 
-    @Mock private lateinit var context: Context
     @Mock private lateinit var mediaSessionManager: MediaSessionManager
     @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
     @Mock private lateinit var eventManager: BluetoothEventManager
@@ -70,6 +66,7 @@
     @Mock private lateinit var localPlaybackInfo: PlaybackInfo
 
     private val testScope = TestScope()
+    private val intentsReceiver = FakeAudioManagerIntentsReceiver()
 
     private lateinit var underTest: MediaControllerRepository
 
@@ -97,7 +94,7 @@
 
         underTest =
             MediaControllerRepositoryImpl(
-                context,
+                intentsReceiver,
                 mediaSessionManager,
                 localBluetoothManager,
                 testScope.backgroundScope,
@@ -124,7 +121,7 @@
                 .launchIn(backgroundScope)
             runCurrent()
 
-            triggerDevicesChange()
+            intentsReceiver.triggerIntent(AudioManager.STREAM_DEVICES_CHANGED_ACTION)
             triggerOnAudioModeChanged()
             runCurrent()
 
@@ -149,7 +146,7 @@
                 .launchIn(backgroundScope)
             runCurrent()
 
-            triggerDevicesChange()
+            intentsReceiver.triggerIntent(AudioManager.STREAM_DEVICES_CHANGED_ACTION)
             triggerOnAudioModeChanged()
             runCurrent()
 
@@ -157,22 +154,19 @@
         }
     }
 
-    private fun triggerDevicesChange() {
-        verify(context).registerReceiver(receiverCaptor.capture(), any())
-        receiverCaptor.value.onReceive(context, Intent(AudioManager.STREAM_DEVICES_CHANGED_ACTION))
-    }
-
     private fun triggerOnAudioModeChanged() {
         verify(eventManager).registerCallback(callbackCaptor.capture())
         callbackCaptor.value.onAudioModeChanged()
     }
 
     private companion object {
-        val statePlaying =
+        val statePlaying: PlaybackState =
             PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0, 0f).build()
-        val stateError = PlaybackState.Builder().setState(PlaybackState.STATE_ERROR, 0, 0f).build()
-        val stateStopped =
+        val stateError: PlaybackState =
+            PlaybackState.Builder().setState(PlaybackState.STATE_ERROR, 0, 0f).build()
+        val stateStopped: PlaybackState =
             PlaybackState.Builder().setState(PlaybackState.STATE_STOPPED, 0, 0f).build()
-        val stateNone = PlaybackState.Builder().setState(PlaybackState.STATE_NONE, 0, 0f).build()
+        val stateNone: PlaybackState =
+            PlaybackState.Builder().setState(PlaybackState.STATE_NONE, 0, 0f).build()
     }
 }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/FakeAudioManagerIntentsReceiver.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/FakeAudioManagerIntentsReceiver.kt
new file mode 100644
index 0000000..530690a
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/FakeAudioManagerIntentsReceiver.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.settingslib.volume.shared
+
+import android.content.Intent
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+
+class FakeAudioManagerIntentsReceiver : AudioManagerIntentsReceiver {
+
+    private val mutableIntents = MutableSharedFlow<Intent>()
+    override val intents: SharedFlow<Intent> = mutableIntents.asSharedFlow()
+
+    suspend fun triggerIntent(intent: Intent) {
+        mutableIntents.emit(intent)
+    }
+
+    suspend fun triggerIntent(action: String) {
+        triggerIntent(Intent(action))
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
index 994c1ee..e32d880 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java
@@ -16,6 +16,8 @@
 
 package com.android.settingslib.applications;
 
+import static android.content.pm.Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.fail;
@@ -24,12 +26,16 @@
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.graphics.drawable.Drawable;
+import android.os.Environment;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import com.android.settingslib.Utils;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -39,6 +45,8 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowPackageManager;
 
 import java.io.File;
 import java.lang.reflect.Field;
@@ -60,6 +68,10 @@
     private ApplicationInfo mAppInfo;
     private ApplicationsState.AppEntry mAppEntry;
     private ArrayList<ApplicationsState.AppEntry> mAppEntries;
+    private ShadowPackageManager mShadowPackageManager;
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Before
     public void setUp() {
@@ -70,6 +82,7 @@
         mAppEntry = createAppEntry(mAppInfo, /* id= */ 1);
         mAppEntries = new ArrayList<>(Arrays.asList(mAppEntry));
         doReturn(mIcon).when(mIcon).mutate();
+        mShadowPackageManager = Shadow.extract(mContext.getPackageManager());
     }
 
     @After
@@ -151,6 +164,32 @@
         assertThat(AppUtils.isAppInstalled(appEntry)).isFalse();
     }
 
+    @Test
+    public void isMainlineModule_hasApexPackageName_shouldCheckByPackageInfo() {
+        mSetFlagsRule.enableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = APP_PACKAGE_NAME;
+        packageInfo.setApexPackageName("com.test.apex.package");
+        mShadowPackageManager.installPackage(packageInfo);
+
+        assertThat(
+                AppUtils.isMainlineModule(mContext.getPackageManager(), APP_PACKAGE_NAME)).isTrue();
+    }
+
+    @Test
+    public void isMainlineModule_noApexPackageName_shouldCheckBySourceDirPath() {
+        mSetFlagsRule.disableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.sourceDir = Environment.getApexDirectory().getAbsolutePath();
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = APP_PACKAGE_NAME;
+        packageInfo.applicationInfo = applicationInfo;
+        mShadowPackageManager.installPackage(packageInfo);
+
+        assertThat(
+                AppUtils.isMainlineModule(mContext.getPackageManager(), APP_PACKAGE_NAME)).isTrue();
+    }
+
     private ApplicationsState.AppEntry createAppEntry(ApplicationInfo appInfo, int id) {
         ApplicationsState.AppEntry appEntry = new ApplicationsState.AppEntry(mContext, appInfo, id);
         appEntry.label = "label";
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index 34d8148..827d8fa 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.applications;
 
+import static android.content.pm.Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX;
 import static android.os.UserHandle.MU_ENABLED;
 import static android.os.UserHandle.USER_SYSTEM;
 
@@ -58,6 +59,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.text.TextUtils;
 import android.util.IconDrawableFactory;
 
@@ -70,6 +72,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -89,6 +92,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.UUID;
@@ -137,6 +141,9 @@
     @Mock
     private IPackageManager mPackageManagerService;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Implements(value = IconDrawableFactory.class)
     public static class ShadowIconDrawableFactory {
 
@@ -169,7 +176,9 @@
         public List<ModuleInfo> getInstalledModules(int flags) {
             if (mInstalledModules.isEmpty()) {
                 for (String moduleName : mModuleNames) {
-                    mInstalledModules.add(createModuleInfo(moduleName));
+                    mInstalledModules.add(
+                            createModuleInfo(moduleName,
+                                    TextUtils.concat(moduleName, ".apex").toString()));
                 }
             }
             return mInstalledModules;
@@ -188,10 +197,11 @@
             return resolveInfos;
         }
 
-        private ModuleInfo createModuleInfo(String packageName) {
+        private ModuleInfo createModuleInfo(String packageName, String apexPackageName) {
             final ModuleInfo info = new ModuleInfo();
             info.setName(packageName);
             info.setPackageName(packageName);
+            info.setApkInApexPackageNames(Collections.singletonList(apexPackageName));
             // will treat any app with package name that contains "hidden" as hidden module
             info.setHidden(!TextUtils.isEmpty(packageName) && packageName.contains("hidden"));
             return info;
@@ -822,4 +832,32 @@
         assertThat(mApplicationsState.getEntry(PKG_1, /* userId= */ 0).info.packageName)
                 .isEqualTo(PKG_1);
     }
+
+    @Test
+    public void isHiddenModule_hasApkInApexInfo_shouldSupportHiddenApexPackage() {
+        mSetFlagsRule.enableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
+        ApplicationsState.sInstance = null;
+        mApplicationsState = ApplicationsState.getInstance(mApplication, mPackageManagerService);
+        String normalModulePackage = "test.module.1";
+        String hiddenModulePackage = "test.hidden.module.2";
+        String hiddenApexPackage = "test.hidden.module.2.apex";
+
+        assertThat(mApplicationsState.isHiddenModule(normalModulePackage)).isFalse();
+        assertThat(mApplicationsState.isHiddenModule(hiddenModulePackage)).isTrue();
+        assertThat(mApplicationsState.isHiddenModule(hiddenApexPackage)).isTrue();
+    }
+
+    @Test
+    public void isHiddenModule_noApkInApexInfo_onlySupportHiddenModule() {
+        mSetFlagsRule.disableFlags(FLAG_PROVIDE_INFO_OF_APK_IN_APEX);
+        ApplicationsState.sInstance = null;
+        mApplicationsState = ApplicationsState.getInstance(mApplication, mPackageManagerService);
+        String normalModulePackage = "test.module.1";
+        String hiddenModulePackage = "test.hidden.module.2";
+        String hiddenApexPackage = "test.hidden.module.2.apex";
+
+        assertThat(mApplicationsState.isHiddenModule(normalModulePackage)).isFalse();
+        assertThat(mApplicationsState.isHiddenModule(hiddenModulePackage)).isTrue();
+        assertThat(mApplicationsState.isHiddenModule(hiddenApexPackage)).isFalse();
+    }
 }
diff --git a/packages/SoundPicker/OWNERS b/packages/SoundPicker/OWNERS
deleted file mode 100644
index 5bf46e0..0000000
--- a/packages/SoundPicker/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Haptics team works on the SoundPicker
-include platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/packages/SoundPicker2/Android.bp b/packages/SoundPicker2/Android.bp
deleted file mode 100644
index f4d8bf2..0000000
--- a/packages/SoundPicker2/Android.bp
+++ /dev/null
@@ -1,46 +0,0 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_library {
-    name: "SoundPicker2Lib",
-    srcs: [
-        "src/**/*.java",
-    ],
-    resource_dirs: [
-        "res",
-    ],
-    static_libs: [
-        "androidx.appcompat_appcompat",
-        "hilt_android",
-        "guava",
-        "androidx.recyclerview_recyclerview",
-        "androidx-constraintlayout_constraintlayout",
-        "androidx.viewpager2_viewpager2",
-        "com.google.android.material_material",
-    ],
-}
-
-android_app {
-    name: "SoundPicker2",
-    defaults: ["platform_app_defaults"],
-    manifest: "AndroidManifest.xml",
-    static_libs: ["SoundPicker2Lib"],
-    platform_apis: true,
-    certificate: "media",
-    privileged: true,
-
-    optimize: {
-        enabled: true,
-        optimize: true,
-        shrink: true,
-        shrink_resources: true,
-        obfuscate: false,
-        proguard_compatibility: false,
-    },
-}
diff --git a/packages/SoundPicker2/AndroidManifest.xml b/packages/SoundPicker2/AndroidManifest.xml
deleted file mode 100644
index 934b003..0000000
--- a/packages/SoundPicker2/AndroidManifest.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.soundpicker"
-        android:sharedUserId="android.media">
-
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
-    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
-    <uses-permission android:name="android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY" />
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
-
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
-
-    <application
-            android:name=".RingtonePickerApplication"
-            android:allowBackup="false"
-            android:label="@string/app_label"
-            android:theme="@style/Theme.AppCompat"
-            android:supportsRtl="true">
-        <receiver android:name="RingtoneReceiver"
-                android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY"/>
-            </intent-filter>
-        </receiver>
-
-        <service android:name="RingtoneOverlayService" />
-
-        <activity android:name="RingtonePickerActivity"
-                android:theme="@style/Theme.AppCompat.Dialog"
-                android:enabled="@*android:bool/config_defaultRingtonePickerEnabled"
-                android:excludeFromRecents="true"
-                android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.RINGTONE_PICKER" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.RINGTONE_PICKER_SOUND" />
-                <category android:name="android.intent.category.RINGTONE_PICKER_VIBRATION" />
-                <category android:name="android.intent.category.RINGTONE_PICKER_RINGTONE" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/packages/SoundPicker2/OWNERS b/packages/SoundPicker2/OWNERS
deleted file mode 100644
index 5bf46e0..0000000
--- a/packages/SoundPicker2/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Haptics team works on the SoundPicker
-include platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/packages/SoundPicker2/res/drawable/ic_add.xml b/packages/SoundPicker2/res/drawable/ic_add.xml
deleted file mode 100644
index 22b3fe9..0000000
--- a/packages/SoundPicker2/res/drawable/ic_add.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-    Copyright (C) 2016 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="48.0"
-        android:viewportHeight="48.0">
-    <path
-        android:fillColor="?android:attr/colorAccent"
-        android:pathData="M38.0,26.0L26.0,26.0l0.0,12.0l-4.0,0.0L22.0,26.0L10.0,26.0l0.0,-4.0l12.0,0.0L22.0,10.0l4.0,0.0l0.0,12.0l12.0,0.0l0.0,4.0z"/>
-</vector>
\ No newline at end of file
diff --git a/packages/SoundPicker2/res/drawable/ic_add_padded.xml b/packages/SoundPicker2/res/drawable/ic_add_padded.xml
deleted file mode 100644
index c376867..0000000
--- a/packages/SoundPicker2/res/drawable/ic_add_padded.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!--
-    Copyright (C) 2017 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
-        android:drawable="@drawable/ic_add"
-        android:insetTop="4dp"
-        android:insetRight="4dp"
-        android:insetBottom="4dp"
-        android:insetLeft="4dp"/>
diff --git a/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml b/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml
deleted file mode 100644
index edfc0ab..0000000
--- a/packages/SoundPicker2/res/layout-watch/add_new_sound_item.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<!--
-     Currently, no file manager app on watch could handle ACTION_GET_CONTENT intent.
-     Make the visibility to "gone" to prevent failures.
- -->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/add_new_sound_text"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:minHeight="?android:attr/listPreferredItemHeightSmall"
-        android:textAppearance="?android:attr/textAppearanceMedium"
-        android:text="@null"
-        android:textColor="?android:attr/colorAccent"
-        android:gravity="center_vertical"
-        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-        android:drawableStart="@drawable/ic_add_padded"
-        android:drawablePadding="8dp"
-        android:ellipsize="marquee"
-        android:visibility="gone" />
diff --git a/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml b/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml
deleted file mode 100644
index ee29a37..0000000
--- a/packages/SoundPicker2/res/layout-watch/radio_with_work_badge.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<com.android.soundpicker.CheckedListItem xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="wrap_content"
-    android:gravity="center_vertical"
-    android:background="?android:attr/selectableItemBackground"
-    >
-
-    <CheckedTextView
-        android:id="@+id/checked_text_view"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:minHeight="?android:attr/listPreferredItemHeightSmall"
-        android:textAppearance="?android:attr/textAppearanceMedium"
-        android:textColor="?android:attr/textColorAlertDialogListItem"
-        android:gravity="center_vertical"
-        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
-        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
-        android:drawableStart="?android:attr/listChoiceIndicatorSingle"
-        android:drawablePadding="8dp"
-        android:ellipsize="marquee"
-        android:layout_toLeftOf="@+id/work_icon"
-        android:maxLines="3" />
-
-    <ImageView
-        android:id="@id/work_icon"
-        android:layout_width="18dp"
-        android:layout_height="18dp"
-        android:layout_alignParentRight="true"
-        android:layout_centerVertical="true"
-        android:scaleType="centerCrop"
-        android:layout_marginRight="20dp" />
-</com.android.soundpicker.CheckedListItem>
diff --git a/packages/SoundPicker2/res/layout/activity_ringtone_picker.xml b/packages/SoundPicker2/res/layout/activity_ringtone_picker.xml
deleted file mode 100644
index 6fc6080..0000000
--- a/packages/SoundPicker2/res/layout/activity_ringtone_picker.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2023 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:orientation="vertical"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"/>
\ No newline at end of file
diff --git a/packages/SoundPicker2/res/layout/add_new_sound_item.xml b/packages/SoundPicker2/res/layout/add_new_sound_item.xml
deleted file mode 100644
index 024b97e..0000000
--- a/packages/SoundPicker2/res/layout/add_new_sound_item.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2016 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:layout_width="fill_parent"
-              android:layout_height="wrap_content"
-              android:gravity="center_vertical"
-              android:background="?android:attr/selectableItemBackground"
-              android:focusable="true"
-              android:clickable="true">
-
-    <ImageView
-        android:layout_width="24dp"
-        android:layout_height="24dp"
-        android:layout_alignParentRight="true"
-        android:layout_centerVertical="true"
-        android:scaleType="centerCrop"
-        android:layout_marginRight="24dp"
-        android:layout_marginLeft="24dp"
-        android:src="@drawable/ic_add"/>
-
-    <TextView
-        android:id="@+id/add_new_sound_text"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:minHeight="?android:attr/listPreferredItemHeightSmall"
-        android:text="@null"
-        android:textColor="?android:attr/colorAccent"
-        android:textAppearance="?android:attr/textAppearanceMedium"
-        android:maxLines="3"
-        android:gravity="center_vertical"
-        android:paddingEnd="?android:attr/dialogPreferredPadding"
-        android:drawablePadding="20dp"
-        android:ellipsize="marquee"/>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SoundPicker2/res/layout/fragment_ringtone_picker.xml b/packages/SoundPicker2/res/layout/fragment_ringtone_picker.xml
deleted file mode 100644
index 787f92e..0000000
--- a/packages/SoundPicker2/res/layout/fragment_ringtone_picker.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2023 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<androidx.recyclerview.widget.RecyclerView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/recycler_view"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-/>
\ No newline at end of file
diff --git a/packages/SoundPicker2/res/layout/fragment_tabbed_dialog.xml b/packages/SoundPicker2/res/layout/fragment_tabbed_dialog.xml
deleted file mode 100644
index 7efd911..0000000
--- a/packages/SoundPicker2/res/layout/fragment_tabbed_dialog.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2023 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:orientation="vertical"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent">
-    <com.google.android.material.tabs.TabLayout
-            android:id="@+id/tabLayout"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"/>
-    <androidx.viewpager2.widget.ViewPager2
-            android:id="@+id/masterViewPager"
-            android:paddingTop="12dp"
-            android:paddingBottom="12dp"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"/>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SoundPicker2/res/layout/radio_with_work_badge.xml b/packages/SoundPicker2/res/layout/radio_with_work_badge.xml
deleted file mode 100644
index 36ac93e..0000000
--- a/packages/SoundPicker2/res/layout/radio_with_work_badge.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<com.android.soundpicker.CheckedListItem
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="wrap_content"
-    android:gravity="center_vertical"
-    android:background="?android:attr/selectableItemBackground"
-    android:focusable="true"
-    android:clickable="true">
-
-    <CheckedTextView
-        android:id="@+id/checked_text_view"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:minHeight="?android:attr/listPreferredItemHeightSmall"
-        android:textAppearance="?android:attr/textAppearanceMedium"
-        android:textColor="?android:attr/textColorAlertDialogListItem"
-        android:gravity="center_vertical"
-        android:paddingStart="20dp"
-        android:paddingEnd="?android:attr/dialogPreferredPadding"
-        android:drawableStart="?android:attr/listChoiceIndicatorSingle"
-        android:drawablePadding="20dp"
-        android:ellipsize="marquee"
-        android:layout_toLeftOf="@+id/work_icon"
-        android:maxLines="3"/>
-
-    <ImageView
-        android:id="@id/work_icon"
-        android:layout_width="18dp"
-        android:layout_height="18dp"
-        android:layout_alignParentRight="true"
-        android:layout_centerVertical="true"
-        android:scaleType="centerCrop"
-        android:layout_marginRight="20dp"/>
-</com.android.soundpicker.CheckedListItem>
diff --git a/packages/SoundPicker2/res/raw/default_alarm_alert.ogg b/packages/SoundPicker2/res/raw/default_alarm_alert.ogg
deleted file mode 100644
index e69de29..0000000
--- a/packages/SoundPicker2/res/raw/default_alarm_alert.ogg
+++ /dev/null
diff --git a/packages/SoundPicker2/res/raw/default_notification_sound.ogg b/packages/SoundPicker2/res/raw/default_notification_sound.ogg
deleted file mode 100644
index e69de29..0000000
--- a/packages/SoundPicker2/res/raw/default_notification_sound.ogg
+++ /dev/null
diff --git a/packages/SoundPicker2/res/raw/default_ringtone.ogg b/packages/SoundPicker2/res/raw/default_ringtone.ogg
deleted file mode 100644
index e69de29..0000000
--- a/packages/SoundPicker2/res/raw/default_ringtone.ogg
+++ /dev/null
diff --git a/packages/SoundPicker2/res/values/config.xml b/packages/SoundPicker2/res/values/config.xml
deleted file mode 100644
index 4e237a2..0000000
--- a/packages/SoundPicker2/res/values/config.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<!-- These resources are around just to allow their values to be customized
-     for different hardware and product builds.  Do not translate.
-
-     NOTE: The naming convention is "config_camelCaseValue".  -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-    <!-- True if the ringtone picker should show the ok/cancel buttons. If it is not shown, the
-    ringtone will be automatically selected when the picker is closed. -->
-    <bool name="config_showOkCancelButtons">true</bool>
-</resources>
diff --git a/packages/SoundPicker2/res/values/strings.xml b/packages/SoundPicker2/res/values/strings.xml
deleted file mode 100644
index ab7b95a..0000000
--- a/packages/SoundPicker2/res/values/strings.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Choice in the ringtone picker.  If chosen, the default ringtone will be used. -->
-    <string name="ringtone_default">Default ringtone</string>
-
-    <!-- Choice in the notification sound picker.  If chosen, the default notification sound will be
-         used. -->
-    <string name="notification_sound_default">Default notification sound</string>
-
-    <!-- Choice in the alarm sound picker.  If chosen, the default alarm sound will be used. -->
-    <string name="alarm_sound_default">Default alarm sound</string>
-
-    <!-- Text for the RingtonePicker item that allows adding a new ringtone. -->
-    <string name="add_ringtone_text">Add ringtone</string>
-    <!-- Text for the RingtonePicker item that allows adding a new alarm. -->
-    <string name="add_alarm_text">Add alarm</string>
-    <!-- Text for the RingtonePicker item that allows adding a new notification. -->
-    <string name="add_notification_text">Add notification</string>
-    <!-- Text for the RingtonePicker item ContextMenu that allows deleting a custom ringtone. -->
-    <string name="delete_ringtone_text">Delete</string>
-    <!-- Text for the Toast displayed when adding a custom ringtone fails. -->
-    <string name="unable_to_add_ringtone">Unable to add custom ringtone</string>
-    <!-- Text for the Toast displayed when deleting a custom ringtone fails. -->
-    <string name="unable_to_delete_ringtone">Unable to delete custom ringtone</string>
-
-    <!-- Text for the name of the app. [CHAR LIMIT=12] -->
-    <string name="app_label">Sounds</string>
-
-    <string name="empty_list">The list is empty</string>
-    <string name="sound_page_title">Sound</string>
-    <string name="vibration_page_title">Vibration</string>
-</resources>
diff --git a/packages/SoundPicker2/res/values/styles.xml b/packages/SoundPicker2/res/values/styles.xml
deleted file mode 100644
index d22d9c4..0000000
--- a/packages/SoundPicker2/res/values/styles.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <style name="PickerDialogTheme" parent="@*android:style/Theme.DeviceDefault.Settings.Dialog">
-    </style>
-
-</resources>
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/BasePickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/BasePickerFragment.java
deleted file mode 100644
index 4fc2a86..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/BasePickerFragment.java
+++ /dev/null
@@ -1,312 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import android.app.Activity;
-import android.content.ContentProvider;
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.MediaStore;
-import android.util.Log;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.fragment.app.Fragment;
-import androidx.lifecycle.ViewModelProvider;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import dagger.hilt.android.AndroidEntryPoint;
-
-import java.util.Objects;
-
-/**
- * Base class for generic picker fragments.
- *
- * <p>This fragment displays a recycler view that is populated by a {@link RingtoneListViewAdapter}
- * with data provided by a {@link RingtoneListHandler}. Each item can be selected on click,
- * which also triggers a ringtone preview performed by the shared {@link RingtonePickerViewModel}.
- * The ringtone preview uses the selection state of all picker fragments (e.g. sound selected by
- * one fragment and vibration selected by another).
- */
-@AndroidEntryPoint(Fragment.class)
-public abstract class BasePickerFragment extends Hilt_BasePickerFragment implements
-        RingtoneListViewAdapter.Callbacks {
-
-    private static final String TAG = "BasePickerFragment";
-    private static final String COLUMN_LABEL = MediaStore.Audio.Media.TITLE;
-    private boolean mIsManagedProfile;
-    private Drawable mWorkIconDrawable;
-
-    protected RingtoneListViewAdapter mRingtoneListViewAdapter;
-    protected RecyclerView mRecyclerView;
-    protected RingtonePickerViewModel.Config mPickerConfig;
-    protected RingtonePickerViewModel mRingtonePickerViewModel;
-    protected RingtoneListHandler.Config mRingtoneListConfig;
-    protected RingtoneListHandler mRingtoneListHandler;
-
-    public BasePickerFragment() {
-        super(R.layout.fragment_ringtone_picker);
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-        mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
-                RingtonePickerViewModel.class);
-        mRingtoneListHandler = getRingtoneListHandler();
-        mRecyclerView = view.requireViewById(R.id.recycler_view);
-
-        mPickerConfig = mRingtonePickerViewModel.getPickerConfig();
-        mRingtoneListConfig = mRingtoneListHandler.getRingtoneListConfig();
-
-        mIsManagedProfile = UserManager.get(requireActivity()).isManagedProfile(
-                mPickerConfig.userId);
-
-        mRingtoneListViewAdapter = createRingtoneListViewAdapter();
-        mRecyclerView.setHasFixedSize(true);
-        mRecyclerView.setAdapter(mRingtoneListViewAdapter);
-        mRecyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
-        setSelectedItem(mRingtoneListHandler.getSelectedItemPosition());
-        prepareRecyclerView(mRecyclerView);
-    }
-
-    @Override
-    public boolean isWorkRingtone(int position) {
-        if (!mIsManagedProfile) {
-            return false;
-        }
-
-        /*
-         * Display the work icon if the ringtone belongs to a work profile. We
-         * can tell that a ringtone belongs to a work profile if the picker user
-         * is a managed profile, the ringtone Uri is in external storage, and
-         * either the uri has no user id or has the id of the picker user
-         */
-        Uri currentUri = mRingtoneListHandler.getRingtoneUri(position);
-        int uriUserId = ContentProvider.getUserIdFromUri(currentUri,
-                mPickerConfig.userId);
-        Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(currentUri);
-
-        return uriUserId == mPickerConfig.userId
-                && uriWithoutUserId.toString().startsWith(
-                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString());
-    }
-
-    @Override
-    public Drawable getWorkIconDrawable() {
-        if (mWorkIconDrawable == null) {
-            mWorkIconDrawable = requireActivity().getPackageManager()
-                    .getUserBadgeForDensityNoBackground(
-                            UserHandle.of(mPickerConfig.userId), /* density= */ -1);
-        }
-
-        return mWorkIconDrawable;
-    }
-
-    @Override
-    public void onRingtoneSelected(int position) {
-        setSelectedItem(position);
-
-        // In the buttonless (watch-only) version, preemptively set our result since
-        // we won't have another chance to do so before the activity closes.
-        if (!mPickerConfig.showOkCancelButtons) {
-            setSuccessResultWithSelectedRingtone();
-        }
-
-        // Play clip
-        mRingtonePickerViewModel.playRingtone();
-    }
-
-    @Override
-    public void onAddRingtoneSelected() {
-        addRingtoneAsync();
-    }
-
-    /**
-     * Sets up the list by adding fixed items to the top and bottom, if required. And sets the
-     * selected item in the list.
-     * @param recyclerView The recyclerview that contains the list of displayed items.
-     */
-    protected void prepareRecyclerView(@NonNull RecyclerView recyclerView) {
-        // Reset the static item count, as this method can be called multiple times
-        mRingtoneListHandler.resetFixedItems();
-
-        if (mRingtoneListConfig.hasDefaultItem) {
-            int defaultItemPos = addDefaultRingtoneItem();
-
-            if (getSelectedItem() < 0
-                    && RingtoneManager.isDefault(mRingtoneListConfig.initialSelectedUri)) {
-                setSelectedItem(defaultItemPos);
-            }
-        }
-
-        if (mRingtoneListConfig.hasSilentItem) {
-            int silentItemPos = addSilentItem();
-
-            // The 'Silent' item should use a null Uri
-            if (getSelectedItem() < 0
-                    && mRingtoneListConfig.initialSelectedUri == null) {
-                setSelectedItem(silentItemPos);
-            }
-        }
-
-        if (getSelectedItem() < 0) {
-            setSelectedItem(mRingtoneListHandler.getRingtonePosition(
-                    mRingtoneListConfig.initialSelectedUri));
-        }
-
-        // In the buttonless (watch-only) version, preemptively set our result since we won't
-        // have another chance to do so before the activity closes.
-        if (!mPickerConfig.showOkCancelButtons) {
-            setSuccessResultWithSelectedRingtone();
-        }
-
-        addNewRingtoneItem();
-
-        // Enable context menu in ringtone items
-        registerForContextMenu(recyclerView);
-    }
-
-    /**
-     * Returns the fragment's sound/vibration list handler.
-     * @return The ringtone list handler.
-     */
-    protected abstract RingtoneListHandler getRingtoneListHandler();
-
-    /**
-     * Starts the process to add a new ringtone to the list of ringtones asynchronously.
-     * Currently, only works for adding sound files.
-     */
-    protected abstract void addRingtoneAsync();
-
-    /**
-     * Adds an item to the end of the list that can be used to add new ringtones to the list.
-     * Currently, only works for adding sound files.
-     */
-    protected abstract void addNewRingtoneItem();
-
-    protected int getSelectedItem() {
-        return mRingtoneListHandler.getSelectedItemPosition();
-    }
-
-    /**
-     * Returns the selected URI to the caller activity.
-     */
-    protected void setSuccessResultWithSelectedRingtone() {
-        requireActivity().setResult(Activity.RESULT_OK,
-                new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI,
-                        mRingtonePickerViewModel.getSelectedRingtoneUri()));
-    }
-
-    /**
-     * Creates a ringtone recyclerview adapter using the ringtone manager cursor.
-     * @return The created RingtoneListViewAdapter.
-     */
-    protected RingtoneListViewAdapter createRingtoneListViewAdapter() {
-        LocalizedCursor cursor = new LocalizedCursor(
-                mRingtoneListHandler.getRingtoneCursor(), getResources(), COLUMN_LABEL);
-        return new RingtoneListViewAdapter(cursor, /* RingtoneListViewAdapterCallbacks= */ this);
-    }
-
-    /**
-     * Sets the selected item in the list and scroll to the position in the recyclerview.
-     * @param pos the position of the selected item in the list.
-     */
-    protected void setSelectedItem(int pos) {
-        Objects.requireNonNull(mRingtoneListViewAdapter);
-        mRingtoneListHandler.setSelectedItemPosition(pos);
-        mRingtoneListViewAdapter.setSelectedItem(pos);
-        mRingtoneListHandler.setSelectedItemId(mRingtoneListViewAdapter.getItemId(pos));
-        mRecyclerView.scrollToPosition(pos);
-    }
-
-    /**
-     * Adds a fixed item to the fixed items list . A fixed item is one that is not from
-     * the RingtoneManager.
-     *
-     * @param textResId The resource ID of the text for the item.
-     * @return The index of the inserted fixed item in the adapter.
-     */
-    protected int addFixedItem(int textResId) {
-        return mRingtoneListViewAdapter.addTitleForFixedItem(textResId);
-    }
-
-    /**
-     * Re-query RingtoneManager for the most recent set of installed ringtones. May move the
-     * selected item position to match the new position of the chosen ringtone.
-     * <p>
-     * This should only need to happen after adding or removing a ringtone.
-     */
-    protected void requeryForAdapter() {
-        mRingtonePickerViewModel.reinit();
-        // Refresh and set a new cursor, and closing the old one.
-        mRingtoneListViewAdapter = createRingtoneListViewAdapter();
-        mRecyclerView.setAdapter(mRingtoneListViewAdapter);
-        prepareRecyclerView(mRecyclerView);
-
-        // Update selected item location.
-        for (int i = 0; i < mRingtoneListViewAdapter.getItemCount(); i++) {
-            if (mRingtoneListViewAdapter.getItemId(i)
-                    == mRingtoneListHandler.getSelectedItemId()) {
-                setSelectedItem(i);
-                return;
-            }
-        }
-
-        // If selected item is still unknown, then set it to the default item, if available.
-        // If it's not available, then attempt to set it to the silent item in the list.
-        int selectedPosition = mRingtoneListHandler.getDefaultItemPosition();
-
-        if (selectedPosition < 0) {
-            selectedPosition = mRingtoneListHandler.getSilentItemPosition();
-        }
-
-        setSelectedItem(selectedPosition);
-    }
-
-    private int addDefaultRingtoneItem() {
-        int defaultItemPosInAdapter = addFixedItem(
-                RingtonePickerViewModel.getDefaultRingtoneItemTextByType(
-                        mPickerConfig.ringtoneType));
-        int defaultItemPosInListHandler = mRingtoneListHandler.addDefaultItem();
-
-        if (defaultItemPosInAdapter != defaultItemPosInListHandler) {
-            Log.wtf(TAG, "Default item position in adapter and list handler must match.");
-            return RingtoneListHandler.ITEM_POSITION_UNKNOWN;
-        }
-
-        return defaultItemPosInListHandler;
-    }
-
-    private int addSilentItem() {
-        int silentItemPosInAdapter = addFixedItem(com.android.internal.R.string.ringtone_silent);
-        int silentItemPosInListHandler = mRingtoneListHandler.addSilentItem();
-
-        if (silentItemPosInAdapter != silentItemPosInListHandler) {
-            Log.wtf(TAG, "Silent item position in adapter and list handler must match.");
-            return RingtoneListHandler.ITEM_POSITION_UNKNOWN;
-        }
-
-        return silentItemPosInListHandler;
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java b/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java
deleted file mode 100644
index 819ae98..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/CheckedListItem.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.Checkable;
-import android.widget.CheckedTextView;
-import android.widget.RelativeLayout;
-
-/**
- * The {@link CheckedListItem} is a layout item that represents a ringtone, and is used in
- * {@link RingtonePickerActivity}. It contains the ringtone's name, and a work badge to right of the
- * name if the ringtone belongs to a work profile.
- */
-public class CheckedListItem extends RelativeLayout implements Checkable {
-
-    public CheckedListItem(Context context) {
-        super(context);
-    }
-
-    public CheckedListItem(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public CheckedListItem(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    public CheckedListItem(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
-    @Override
-    public void setChecked(boolean checked) {
-        getCheckedTextView().setChecked(checked);
-    }
-
-    @Override
-    public boolean isChecked() {
-        return getCheckedTextView().isChecked();
-    }
-
-    @Override
-    public void toggle() {
-        getCheckedTextView().toggle();
-    }
-
-    private CheckedTextView getCheckedTextView() {
-        return (CheckedTextView) findViewById(R.id.checked_text_view);
-    }
-
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/ListeningExecutorServiceFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/ListeningExecutorServiceFactory.java
deleted file mode 100644
index afdbf05..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/ListeningExecutorServiceFactory.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
-
-import java.util.concurrent.Executors;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * A factory class used to create {@link ListeningExecutorService}.
- */
-@Singleton
-public class ListeningExecutorServiceFactory {
-
-    @Inject
-    ListeningExecutorServiceFactory() {
-    }
-
-    /**
-     * Returns a single thread {@link ListeningExecutorService}.
-     *
-     */
-    public ListeningExecutorService createSingleThreadExecutor() {
-        return MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/LocalizedCursor.java b/packages/SoundPicker2/src/com/android/soundpicker/LocalizedCursor.java
deleted file mode 100644
index 83d04a3..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/LocalizedCursor.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.CursorWrapper;
-import android.util.Log;
-import android.util.TypedValue;
-
-import androidx.annotation.Nullable;
-
-import java.util.Locale;
-import java.util.regex.Pattern;
-
-/**
- * A cursor wrapper class mainly used to guarantee getting a ringtone title
- */
-final class LocalizedCursor extends CursorWrapper {
-
-    private static final String TAG = "LocalizedCursor";
-    private static final String SOUND_NAME_RES_PREFIX = "sound_name_";
-
-    private final int mTitleIndex;
-    private final Resources mResources;
-    private final Pattern mSanitizePattern;
-    private final String mNamePrefix;
-
-    LocalizedCursor(Cursor cursor, Resources resources, String columnLabel) {
-        super(cursor);
-        mTitleIndex = mCursor.getColumnIndex(columnLabel);
-        mResources = resources;
-        mSanitizePattern = Pattern.compile("[^a-zA-Z0-9]");
-        if (mTitleIndex == -1) {
-            Log.e(TAG, "No index for column " + columnLabel);
-            mNamePrefix = null;
-        } else {
-            mNamePrefix = buildNamePrefix(mResources);
-        }
-    }
-
-    /**
-     * Builds the prefix for the name of the resource to look up.
-     * The format is: "ResourcePackageName::ResourceTypeName/" (the type name is expected to be
-     * "string" but let's not hardcode it).
-     * Here we use an existing resource "notification_sound_default" which is always expected to be
-     * found.
-     *
-     * @param resources Application's resources
-     * @return the built name prefix, or null if failed to build.
-     */
-    @Nullable
-    private static String buildNamePrefix(Resources resources) {
-        try {
-            return String.format("%s:%s/%s",
-                    resources.getResourcePackageName(R.string.notification_sound_default),
-                    resources.getResourceTypeName(R.string.notification_sound_default),
-                    SOUND_NAME_RES_PREFIX);
-        } catch (Resources.NotFoundException e) {
-            Log.e(TAG, "Failed to build the prefix for the name of the resource.", e);
-        }
-
-        return null;
-    }
-
-    /**
-     * Process resource name to generate a valid resource name.
-     *
-     * @return a non-null String
-     */
-    private String sanitize(String input) {
-        if (input == null) {
-            return "";
-        }
-        return mSanitizePattern.matcher(input).replaceAll("_").toLowerCase(Locale.ROOT);
-    }
-
-    @Override
-    public String getString(int columnIndex) {
-        final String defaultName = mCursor.getString(columnIndex);
-        if ((columnIndex != mTitleIndex) || (mNamePrefix == null)) {
-            return defaultName;
-        }
-        TypedValue value = new TypedValue();
-        try {
-            // the name currently in the database is used to derive a name to match
-            // against resource names in this package
-            mResources.getValue(mNamePrefix + sanitize(defaultName), value,
-                    /* resolveRefs= */ false);
-        } catch (Resources.NotFoundException e) {
-            Log.d(TAG, "Failed to get localized string. Using default string instead.", e);
-            return defaultName;
-        }
-        if ((value != null) && (value.type == TypedValue.TYPE_STRING)) {
-            Log.d(TAG, String.format("Replacing name %s with %s",
-                    defaultName, value.string.toString()));
-            return value.string.toString();
-        } else {
-            Log.e(TAG, "Invalid value when looking up localized name, using " + defaultName);
-            return defaultName;
-        }
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneFactory.java
deleted file mode 100644
index 6817f53..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneFactory.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import android.content.Context;
-import android.media.AudioAttributes;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-
-import dagger.hilt.android.qualifiers.ApplicationContext;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * A factory class used to create {@link Ringtone}.
- */
-@Singleton
-public class RingtoneFactory {
-
-    private final Context mApplicationContext;
-
-    @Inject
-    RingtoneFactory(@ApplicationContext Context applicationContext) {
-        mApplicationContext = applicationContext;
-    }
-
-    /**
-     * Returns a {@link Ringtone} built from the provided URI and audio attributes flags.
-     *
-     * @param uri The URI used to build the {@link Ringtone}.
-     * @param audioAttributesFlags A combination of audio attribute flags that affect the volume
-     *                             and settings when playing the ringtone.
-     * @return the built {@link Ringtone}.
-     */
-    public Ringtone create(Uri uri, int audioAttributesFlags) {
-        AudioAttributes audioAttributes = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
-                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-                .setFlags(audioAttributesFlags)
-                .build();
-        return RingtoneManager.getRingtone(mApplicationContext, uri,
-                /* volumeShaperConfig= */ null, audioAttributes);
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListHandler.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListHandler.java
deleted file mode 100644
index bb38e0e..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListHandler.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.Nullable;
-import android.database.Cursor;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import javax.inject.Inject;
-
-/**
- * Handles ringtone list state and actions. This includes keeping track of the selected item,
- * ringtone manager cursor and added items to the list.
- */
-public class RingtoneListHandler {
-
-    // TODO: We're using an empty URI instead of null, because null URIs still produce a sound,
-    //  while empty ones don't (Potentially this might be due to empty URIs being perceived as
-    //  malformed ones). We will switch to using the official silent URIs (SOUND_OFF, VIBRATION_OFF)
-    //  once they become available.
-    static final Uri SILENT_URI = Uri.EMPTY;
-    static final int ITEM_POSITION_UNKNOWN = -1;
-
-    private static final String TAG = "RingtoneListHandler";
-
-    /** The position in the list of the 'Silent' item. */
-    private int mSilentItemPosition = ITEM_POSITION_UNKNOWN;
-    /** The position in the list of the 'Default' item. */
-    private int mDefaultItemPosition = ITEM_POSITION_UNKNOWN;
-    /** The number of fixed items in the list. */
-    private int mFixedItemCount;
-    /**
-     * Stable ID for the ringtone that is currently selected (may be -1 if no ringtone is selected).
-     */
-    private long mSelectedItemId = -1;
-    private int mSelectedItemPosition = ITEM_POSITION_UNKNOWN;
-
-    private RingtoneManager mRingtoneManager;
-    private Config mRingtoneListConfig;
-    private Cursor mRingtoneCursor;
-
-    /**
-     * Holds immutable info on the ringtone list that is displayed.
-     */
-    static final class Config {
-        /**
-         * Whether this list has the 'Default' item.
-         */
-        public final boolean hasDefaultItem;
-        /**
-         * The Uri to play when the 'Default' item is clicked.
-         */
-        public final Uri uriForDefaultItem;
-        /**
-         * Whether this list has the 'Silent' item.
-         */
-        public final boolean hasSilentItem;
-        /**
-         * The initially selected uri in the list.
-         */
-        public final Uri initialSelectedUri;
-
-        Config(boolean hasDefaultItem, Uri uriForDefaultItem, boolean hasSilentItem,
-                Uri initialSelectedUri) {
-            this.hasDefaultItem = hasDefaultItem;
-            this.uriForDefaultItem = uriForDefaultItem;
-            this.hasSilentItem = hasSilentItem;
-            this.initialSelectedUri = initialSelectedUri;
-        }
-    }
-
-    @Inject
-    RingtoneListHandler() {
-    }
-
-    void init(@NonNull Config ringtoneListConfig,
-            @NonNull RingtoneManager ringtoneManager, @NonNull Cursor ringtoneCursor) {
-        mRingtoneManager = requireNonNull(ringtoneManager);
-        mRingtoneListConfig = requireNonNull(ringtoneListConfig);
-        mRingtoneCursor = requireNonNull(ringtoneCursor);
-    }
-
-    Config getRingtoneListConfig() {
-        return mRingtoneListConfig;
-    }
-
-    Cursor getRingtoneCursor() {
-        requireInitCalled();
-        return mRingtoneCursor;
-    }
-
-    Uri getRingtoneUri(int position) {
-        if (position < 0) {
-            Log.w(TAG, "Selected item position is unknown.");
-            // When the selected item is ITEM_POSITION_UNKNOWN, it is not the case we expected.
-            // We return SILENT_URI for this case.
-            return SILENT_URI;
-        } else if (position == mDefaultItemPosition) {
-            // Use the default Uri that they originally gave us.
-            return mRingtoneListConfig.uriForDefaultItem;
-        } else if (position == mSilentItemPosition) {
-            // Use SILENT_URI for the 'Silent' item.
-            return SILENT_URI;
-        } else {
-            requireInitCalled();
-            return mRingtoneManager.getRingtoneUri(mapListPositionToRingtonePosition(position));
-        }
-    }
-
-    int getRingtonePosition(Uri uri) {
-        requireInitCalled();
-        return mapRingtonePositionToListPosition(mRingtoneManager.getRingtonePosition(uri));
-    }
-
-    void resetFixedItems() {
-        mFixedItemCount = 0;
-        mDefaultItemPosition = ITEM_POSITION_UNKNOWN;
-        mSilentItemPosition = ITEM_POSITION_UNKNOWN;
-    }
-
-    int addDefaultItem() {
-        if (mDefaultItemPosition < 0) {
-            mDefaultItemPosition = addFixedItem();
-        }
-        return mDefaultItemPosition;
-    }
-
-    int getDefaultItemPosition() {
-        return mDefaultItemPosition;
-    }
-
-    int addSilentItem() {
-        if (mSilentItemPosition < 0) {
-            mSilentItemPosition = addFixedItem();
-        }
-        return mSilentItemPosition;
-    }
-
-    public int getSilentItemPosition() {
-        return mSilentItemPosition;
-    }
-
-    int getSelectedItemPosition() {
-        return mSelectedItemPosition;
-    }
-
-    void setSelectedItemPosition(int selectedItemPosition) {
-        mSelectedItemPosition = selectedItemPosition;
-    }
-
-    void setSelectedItemId(long selectedItemId) {
-        mSelectedItemId = selectedItemId;
-    }
-
-    long getSelectedItemId() {
-        return mSelectedItemId;
-    }
-
-    @Nullable
-    Uri getSelectedRingtoneUri() {
-        return getRingtoneUri(mSelectedItemPosition);
-    }
-
-    /**
-     * Maps the item position in the list, to its equivalent position in the RingtoneManager.
-     *
-     * @param itemPosition the position of item in the list.
-     * @return position of the item in the RingtoneManager.
-     */
-    private int mapListPositionToRingtonePosition(int itemPosition) {
-        // If the manager position is less than add items, then return that.
-        if (itemPosition < mFixedItemCount) return itemPosition;
-
-        return itemPosition - mFixedItemCount;
-    }
-
-    /**
-     * Maps the item position in the RingtoneManager, to its equivalent position in the list.
-     *
-     * @param itemPosition the position of the item in the RingtoneManager.
-     * @return position of the item in the list.
-     */
-    private int mapRingtonePositionToListPosition(int itemPosition) {
-        // If the manager position is less than add items, then return that.
-        if (itemPosition < 0) return itemPosition;
-
-        return itemPosition + mFixedItemCount;
-    }
-
-    /**
-     * Increments the number of added fixed items and returns the index of the newest added item.
-     * @return index of the newest added fixed item.
-     */
-    private int addFixedItem() {
-        return mFixedItemCount++;
-    }
-
-    private void requireInitCalled() {
-        requireNonNull(mRingtoneManager);
-        requireNonNull(mRingtoneCursor);
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListViewAdapter.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListViewAdapter.java
deleted file mode 100644
index 4ca8943..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneListViewAdapter.java
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import static com.android.internal.widget.RecyclerView.NO_ID;
-
-import android.database.Cursor;
-import android.graphics.drawable.Drawable;
-import android.media.RingtoneManager;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CheckedTextView;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.StringRes;
-import androidx.recyclerview.widget.RecyclerView;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * The adapter presents a list of ringtones which may include fixed item in the list and an action
- * button at the end.
- *
- * The adapter handles three different types of items:
- * <ul>
- * <li>FIXED: Fixed items are items added to the top of the list. These items can not be modified
- * and their position will never change.
- * <li>DYNAMIC: Dynamic items are items from the ringtone manager. These items can be modified
- * and their position can change.
- * <li>FOOTER: A footer item is an added button to the end of the list. This item can be clicked
- * but not selected and its position will never change.
- * </ul>
- */
-final class RingtoneListViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
-
-    private static final int VIEW_TYPE_FIXED_ITEM = 0;
-    private static final int VIEW_TYPE_DYNAMIC_ITEM = 1;
-    private static final int VIEW_TYPE_ADD_RINGTONE_ITEM = 2;
-    private final Cursor mCursor;
-    private final List<Integer> mFixedItemTitles;
-    private final Callbacks mCallbacks;
-    private final int mRowIDColumn;
-    private int mSelectedItem = -1;
-    @StringRes private Integer mAddRingtoneItemTitle;
-
-    /** Provides callbacks for the adapter. */
-    interface Callbacks {
-        void onRingtoneSelected(int position);
-        void onAddRingtoneSelected();
-        boolean isWorkRingtone(int position);
-        Drawable getWorkIconDrawable();
-    }
-
-    RingtoneListViewAdapter(Cursor cursor,
-            Callbacks callbacks) {
-        mCursor = cursor;
-        mCallbacks = callbacks;
-        mFixedItemTitles = new ArrayList<>();
-        mRowIDColumn = mCursor != null ? mCursor.getColumnIndex("_id") : -1;
-        setHasStableIds(true);
-    }
-
-    void setSelectedItem(int position) {
-        notifyItemChanged(mSelectedItem);
-        mSelectedItem = position;
-        notifyItemChanged(mSelectedItem);
-    }
-
-    /**
-     * Adds title to the fixed items list and returns the index of the newest added item.
-     * @param textResId the title to add to the fixed items list.
-     * @return The index of the newest added item in the fixed items list.
-     */
-    int addTitleForFixedItem(@StringRes int textResId) {
-        mFixedItemTitles.add(textResId);
-        notifyItemInserted(mFixedItemTitles.size() - 1);
-        return mFixedItemTitles.size() - 1;
-    }
-
-    void addTitleForAddRingtoneItem(@StringRes int textResId) {
-        mAddRingtoneItemTitle = textResId;
-        notifyItemInserted(getItemCount() - 1);
-    }
-
-    @NotNull
-    @Override
-    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
-
-        if (viewType == VIEW_TYPE_FIXED_ITEM) {
-            View fixedItemView = inflater.inflate(
-                    com.android.internal.R.layout.select_dialog_singlechoice_material, parent,
-                    false);
-
-            return new FixedItemViewHolder(fixedItemView, mCallbacks);
-        }
-
-        if (viewType == VIEW_TYPE_ADD_RINGTONE_ITEM) {
-            View addRingtoneItemView = inflater.inflate(R.layout.add_new_sound_item, parent, false);
-
-            return new AddRingtoneItemViewHolder(addRingtoneItemView,
-                    mCallbacks);
-        }
-
-        View view = inflater.inflate(R.layout.radio_with_work_badge, parent, false);
-
-        return new DynamicItemViewHolder(view, mCallbacks);
-    }
-
-    @Override
-    public void onBindViewHolder(@NotNull RecyclerView.ViewHolder holder, int position) {
-        if (holder instanceof FixedItemViewHolder) {
-            FixedItemViewHolder viewHolder = (FixedItemViewHolder) holder;
-
-            viewHolder.onBind(mFixedItemTitles.get(position),
-                    /* isChecked= */ position == mSelectedItem);
-            return;
-        }
-        if (holder instanceof AddRingtoneItemViewHolder) {
-            AddRingtoneItemViewHolder viewHolder = (AddRingtoneItemViewHolder) holder;
-
-            viewHolder.onBind(mAddRingtoneItemTitle);
-            return;
-        }
-
-        if (!(holder instanceof DynamicItemViewHolder)) {
-            throw new IllegalArgumentException("holder type is not supported");
-        }
-
-        DynamicItemViewHolder viewHolder = (DynamicItemViewHolder) holder;
-        int pos = position - mFixedItemTitles.size();
-        if (!mCursor.moveToPosition(pos)) {
-            throw new IllegalStateException("Could not move cursor to position: " + pos);
-        }
-
-        Drawable workIcon = (mCallbacks != null)
-                && mCallbacks.isWorkRingtone(position)
-                ? mCallbacks.getWorkIconDrawable() : null;
-
-        viewHolder.onBind(mCursor.getString(RingtoneManager.TITLE_COLUMN_INDEX),
-                /* isChecked= */ position == mSelectedItem, workIcon);
-    }
-
-    @Override
-    public int getItemViewType(int position) {
-        if (!mFixedItemTitles.isEmpty() && position < mFixedItemTitles.size()) {
-            return VIEW_TYPE_FIXED_ITEM;
-        }
-        if (mAddRingtoneItemTitle != null && position == getItemCount() - 1) {
-            return VIEW_TYPE_ADD_RINGTONE_ITEM;
-        }
-
-        return VIEW_TYPE_DYNAMIC_ITEM;
-    }
-
-    @Override
-    public int getItemCount() {
-        int itemCount = mFixedItemTitles.size() + mCursor.getCount();
-
-        if (mAddRingtoneItemTitle != null) {
-            itemCount++;
-        }
-
-        return itemCount;
-    }
-
-    @Override
-    public long getItemId(int position) {
-        int itemViewType = getItemViewType(position);
-        if (itemViewType == VIEW_TYPE_FIXED_ITEM) {
-            // Since the item is a fixed item, then we can use the position as a stable ID
-            // since the order of the fixed items should never change.
-            return position;
-        }
-        if (itemViewType == VIEW_TYPE_DYNAMIC_ITEM && mCursor != null
-                && mCursor.moveToPosition(position - mFixedItemTitles.size())
-                && mRowIDColumn != -1) {
-            return mCursor.getLong(mRowIDColumn) + mFixedItemTitles.size();
-        }
-
-        // The position is either invalid or the item is the add ringtone item view, so no stable
-        // ID is returned. Add ringtone item view cannot be selected and only include an action
-        // buttons.
-        return NO_ID;
-    }
-
-    private static class DynamicItemViewHolder extends RecyclerView.ViewHolder {
-        private final CheckedTextView mTitleTextView;
-        private final ImageView mWorkIcon;
-
-        DynamicItemViewHolder(View itemView, Callbacks listener) {
-            super(itemView);
-
-            mTitleTextView = itemView.requireViewById(R.id.checked_text_view);
-            mWorkIcon = itemView.requireViewById(R.id.work_icon);
-            itemView.setOnClickListener(v -> listener.onRingtoneSelected(this.getLayoutPosition()));
-        }
-
-        void onBind(String title, boolean isChecked, Drawable workIcon) {
-            mTitleTextView.setText(title);
-            mTitleTextView.setChecked(isChecked);
-
-            if (workIcon == null) {
-                mWorkIcon.setVisibility(View.GONE);
-            } else {
-                mWorkIcon.setImageDrawable(workIcon);
-                mWorkIcon.setVisibility(View.VISIBLE);
-            }
-        }
-    }
-
-    private static class FixedItemViewHolder extends RecyclerView.ViewHolder {
-        private final CheckedTextView mTitleTextView;
-
-        FixedItemViewHolder(View itemView, Callbacks listener) {
-            super(itemView);
-
-            mTitleTextView = (CheckedTextView) itemView;
-            itemView.setOnClickListener(v -> listener.onRingtoneSelected(this.getLayoutPosition()));
-        }
-
-        void onBind(@StringRes int title, boolean isChecked) {
-            Objects.requireNonNull(mTitleTextView);
-
-            mTitleTextView.setText(title);
-            mTitleTextView.setChecked(isChecked);
-        }
-    }
-
-    private static class AddRingtoneItemViewHolder extends RecyclerView.ViewHolder {
-        private final TextView mTitleTextView;
-
-        AddRingtoneItemViewHolder(View itemView, Callbacks listener) {
-            super(itemView);
-
-            mTitleTextView = itemView.requireViewById(R.id.add_new_sound_text);
-            itemView.setOnClickListener(v -> listener.onAddRingtoneSelected());
-        }
-
-        void onBind(@StringRes int title) {
-            mTitleTextView.setText(title);
-        }
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneManagerFactory.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneManagerFactory.java
deleted file mode 100644
index f08eb24..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneManagerFactory.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import android.content.Context;
-import android.media.RingtoneManager;
-
-import dagger.hilt.android.qualifiers.ApplicationContext;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * A factory class used to create {@link RingtoneManager}.
- */
-@Singleton
-public class RingtoneManagerFactory {
-
-    private final Context mApplicationContext;
-
-    @Inject
-    RingtoneManagerFactory(@ApplicationContext Context applicationContext) {
-        mApplicationContext = applicationContext;
-    }
-
-    /**
-     * Creates a new {@link RingtoneManager} and returns it.
-     *
-     * @return a {@link RingtoneManager}
-     */
-    public RingtoneManager create() {
-        return new RingtoneManager(mApplicationContext, /* includeParentRingtones */ true);
-    }
-}
-
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java
deleted file mode 100644
index b94ebeb..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneOverlayService.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import android.app.Service;
-import android.content.Intent;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Environment;
-import android.os.FileUtils;
-import android.os.IBinder;
-import android.provider.MediaStore;
-import android.provider.Settings.System;
-import android.util.Log;
-
-import androidx.annotation.IdRes;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Service to copy and set customization of default sounds
- */
-public class RingtoneOverlayService extends Service {
-    private static final String TAG = "RingtoneOverlayService";
-    private static final boolean DEBUG = false;
-
-    @Override
-    public int onStartCommand(@Nullable final Intent intent, final int flags, final int startId) {
-        AsyncTask.execute(() -> {
-            updateRingtones();
-            stopSelf();
-        });
-
-        // Try again later if we are killed before we finish.
-        return Service.START_REDELIVER_INTENT;
-    }
-
-    @Override
-    public IBinder onBind(@Nullable final Intent intent) {
-        return null;
-    }
-
-    private void updateRingtones() {
-        copyResourceAndSetAsSound(R.raw.default_ringtone,
-                System.RINGTONE, Environment.DIRECTORY_RINGTONES);
-        copyResourceAndSetAsSound(R.raw.default_notification_sound,
-                System.NOTIFICATION_SOUND, Environment.DIRECTORY_NOTIFICATIONS);
-        copyResourceAndSetAsSound(R.raw.default_alarm_alert,
-                System.ALARM_ALERT, Environment.DIRECTORY_ALARMS);
-    }
-
-    /* If the resource contains any data, copy a resource to the file system, scan it, and set the
-     * file URI as the default for a sound. */
-    private void copyResourceAndSetAsSound(@IdRes final int id, @NonNull final String name,
-            @NonNull final String subPath) {
-        final File destDir = Environment.getExternalStoragePublicDirectory(subPath);
-        if (!destDir.exists() && !destDir.mkdirs()) {
-            Log.e(TAG, "can't create " + destDir.getAbsolutePath());
-            return;
-        }
-
-        final File dest = new File(destDir, "default_" + name + ".ogg");
-        try (
-                InputStream is = getResources().openRawResource(id);
-                FileOutputStream os = new FileOutputStream(dest);
-        ) {
-            if (is.available() > 0) {
-                FileUtils.copy(is, os);
-                final Uri uri = scanFile(dest);
-                if (uri != null) {
-                    set(name, uri);
-                }
-            } else {
-                // TODO Shall we remove any former copied resource in this case and unset
-                // the defaults if we use this event a second time to clear the data?
-                if (DEBUG) Log.d(TAG, "Resource for " + name + " has no overlay");
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "Unable to open resource for " + name + ": " + e);
-        }
-    }
-
-    private Uri scanFile(@NonNull final File file) {
-        return MediaStore.scanFile(getContentResolver(), file);
-    }
-
-    private void set(@NonNull final String name, @NonNull final Uri uri) {
-        final Uri settingUri = System.getUriFor(name);
-        RingtoneManager.setActualDefaultRingtoneUri(this,
-                RingtoneManager.getDefaultType(settingUri), uri);
-        System.putInt(getContentResolver(), name + "_set", 1);
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java
deleted file mode 100644
index 90a14f9..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerActivity.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import android.content.Intent;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.util.Log;
-
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentTransaction;
-import androidx.lifecycle.ViewModelProvider;
-
-import dagger.hilt.android.AndroidEntryPoint;
-
-/**
- * The {@link RingtonePickerActivity} allows the user to choose one from all of the
- * available ringtones. The chosen ringtone's URI will be persisted as a string.
- *
- * @see RingtoneManager#ACTION_RINGTONE_PICKER
- */
-@AndroidEntryPoint(AppCompatActivity.class)
-public final class RingtonePickerActivity extends Hilt_RingtonePickerActivity {
-
-    private static final String TAG = "RingtonePickerActivity";
-    // TODO: Use the extra keys from RingtoneManager once they're added.
-    private static final String EXTRA_RINGTONE_PICKER_CATEGORY = "EXTRA_RINGTONE_PICKER_CATEGORY";
-    private static final String EXTRA_VIBRATION_SHOW_DEFAULT = "EXTRA_VIBRATION_SHOW_DEFAULT";
-    private static final String EXTRA_VIBRATION_DEFAULT_URI = "EXTRA_VIBRATION_DEFAULT_URI";
-    private static final String EXTRA_VIBRATION_SHOW_SILENT = "EXTRA_VIBRATION_SHOW_SILENT";
-    private static final String EXTRA_VIBRATION_EXISTING_URI = "EXTRA_VIBRATION_EXISTING_URI";
-    private static final boolean RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED = false;
-
-    private RingtonePickerViewModel mRingtonePickerViewModel;
-    private int mAttributesFlags;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_ringtone_picker);
-
-        mRingtonePickerViewModel = new ViewModelProvider(this).get(RingtonePickerViewModel.class);
-
-        Intent intent = getIntent();
-        /**
-         * Id of the user to which the ringtone picker should list the ringtones
-         */
-        int pickerUserId = UserHandle.myUserId();
-
-        // Get the types of ringtones to show
-        int ringtoneType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE,
-                RingtonePickerViewModel.RINGTONE_TYPE_UNKNOWN);
-
-        // AudioAttributes flags
-        mAttributesFlags |= intent.getIntExtra(
-                RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
-                0 /*defaultValue == no flags*/);
-
-        boolean showOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons);
-
-        String title = intent.getStringExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
-        if (title == null) {
-            title = getString(RingtonePickerViewModel.getTitleByType(ringtoneType));
-        }
-        String ringtonePickerCategory = intent.getStringExtra(EXTRA_RINGTONE_PICKER_CATEGORY);
-        RingtonePickerViewModel.PickerType pickerType = mapCategoryToPickerType(
-                ringtonePickerCategory);
-
-        RingtoneListHandler.Config soundListConfig = getSoundListConfig(pickerType, intent,
-                ringtoneType);
-        RingtoneListHandler.Config vibrationListConfig = getVibrationListConfig(pickerType, intent);
-
-        RingtonePickerViewModel.Config pickerConfig =
-                new RingtonePickerViewModel.Config(title, pickerUserId, ringtoneType,
-                        showOkCancelButtons, mAttributesFlags, pickerType);
-
-        mRingtonePickerViewModel.init(pickerConfig, soundListConfig, vibrationListConfig);
-
-        if (savedInstanceState == null) {
-            TabbedDialogFragment dialogFragment = new TabbedDialogFragment();
-
-            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
-            Fragment prev = getSupportFragmentManager().findFragmentByTag(TabbedDialogFragment.TAG);
-            if (prev != null) {
-                ft.remove(prev);
-            }
-            ft.addToBackStack(null);
-            dialogFragment.show(ft, TabbedDialogFragment.TAG);
-        }
-
-        // The volume keys will control the stream that we are choosing a ringtone for
-        setVolumeControlStream(mRingtonePickerViewModel.getRingtoneStreamType());
-    }
-
-    private RingtoneListHandler.Config getSoundListConfig(
-            RingtonePickerViewModel.PickerType pickerType, Intent intent, int ringtoneType) {
-        if (pickerType != RingtonePickerViewModel.PickerType.SOUND_PICKER
-                && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
-            // This ringtone picker does not require a sound picker.
-            return null;
-        }
-
-        // Get whether to show the 'Default' sound item, and the URI to play when it's clicked
-        boolean hasDefaultSoundItem =
-                intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
-
-        // The Uri to play when the 'Default' sound item is clicked.
-        Uri uriForDefaultSoundItem =
-                intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
-        if (uriForDefaultSoundItem == null) {
-            uriForDefaultSoundItem = RingtonePickerViewModel.getDefaultItemUriByType(ringtoneType);
-        }
-
-        // Get whether this list has the 'Silent' sound item.
-        boolean hasSilentSoundItem =
-                intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
-
-        // AudioAttributes flags
-        mAttributesFlags |= intent.getIntExtra(
-                RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
-                0 /*defaultValue == no flags*/);
-
-        // Get the sound URI whose list item should have a checkmark
-        Uri existingSoundUri = intent
-                .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
-
-        return new RingtoneListHandler.Config(hasDefaultSoundItem,
-                uriForDefaultSoundItem, hasSilentSoundItem, existingSoundUri);
-    }
-
-    private RingtoneListHandler.Config getVibrationListConfig(
-            RingtonePickerViewModel.PickerType pickerType, Intent intent) {
-        if (pickerType != RingtonePickerViewModel.PickerType.VIBRATION_PICKER
-                && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
-            // This ringtone picker does not require a vibration picker.
-            return null;
-        }
-
-        // Get whether to show the 'Default' vibration item, and the URI to play when it's clicked
-        boolean hasDefaultVibrationItem =
-                intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_DEFAULT, false);
-
-        // The Uri to play when the 'Default' vibration item is clicked.
-        Uri uriForDefaultVibrationItem = intent.getParcelableExtra(EXTRA_VIBRATION_DEFAULT_URI);
-
-        // Get whether this list has the 'Silent' vibration item.
-        boolean hasSilentVibrationItem =
-                intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_SILENT, true);
-
-        // Get the vibration URI whose list item should have a checkmark
-        Uri existingVibrationUri = intent.getParcelableExtra(EXTRA_VIBRATION_EXISTING_URI);
-
-        return new RingtoneListHandler.Config(
-                hasDefaultVibrationItem, uriForDefaultVibrationItem, hasSilentVibrationItem,
-                existingVibrationUri);
-    }
-
-    @Override
-    public void onDestroy() {
-        mRingtonePickerViewModel.cancelPendingAsyncTasks();
-        super.onDestroy();
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-        mRingtonePickerViewModel.onStop(isChangingConfigurations());
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-        mRingtonePickerViewModel.onPause(isChangingConfigurations());
-    }
-
-    /**
-     * Maps the ringtone picker category to the appropriate PickerType.
-     * If the category is null or the feature is still not released, then it defaults to sound
-     * picker.
-     *
-     * @param category the ringtone picker category.
-     * @return the corresponding picker type.
-     */
-    private static RingtonePickerViewModel.PickerType mapCategoryToPickerType(String category) {
-        if (category == null || !RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED) {
-            return RingtonePickerViewModel.PickerType.SOUND_PICKER;
-        }
-
-        switch (category) {
-            case "android.intent.category.RINGTONE_PICKER_RINGTONE":
-                return RingtonePickerViewModel.PickerType.RINGTONE_PICKER;
-            case "android.intent.category.RINGTONE_PICKER_SOUND":
-                return RingtonePickerViewModel.PickerType.SOUND_PICKER;
-            case "android.intent.category.RINGTONE_PICKER_VIBRATION":
-                return RingtonePickerViewModel.PickerType.VIBRATION_PICKER;
-            default:
-                Log.w(TAG, "Unrecognized category: " + category + ". Defaulting to sound picker.");
-                return RingtonePickerViewModel.PickerType.SOUND_PICKER;
-        }
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerViewModel.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerViewModel.java
deleted file mode 100644
index 2c09711..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerViewModel.java
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.Nullable;
-import android.annotation.StringRes;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.provider.Settings;
-
-import androidx.annotation.NonNull;
-import androidx.lifecycle.ViewModel;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-
-import dagger.hilt.android.lifecycle.HiltViewModel;
-
-import java.io.IOException;
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-
-/**
- * A view model which holds immutable info about the picker state and means to retrieve and play
- * currently selected ringtones.
- */
-@HiltViewModel
-public final class RingtonePickerViewModel extends ViewModel {
-
-    static final int RINGTONE_TYPE_UNKNOWN = -1;
-
-    /**
-     * Keep the currently playing ringtone around when changing orientation, so that it
-     * can be stopped later, after the activity is recreated.
-     */
-    @VisibleForTesting
-    static Ringtone sPlayingRingtone;
-
-    private static final String TAG = "RingtonePickerViewModel";
-
-    private final RingtoneManagerFactory mRingtoneManagerFactory;
-    private final RingtoneFactory mRingtoneFactory;
-    private final RingtoneListHandler mSoundListHandler;
-    private final RingtoneListHandler mVibrationListHandler;
-    private final ListeningExecutorService mListeningExecutorService;
-
-    private RingtoneManager mRingtoneManager;
-
-    /**
-     * The ringtone that's currently playing.
-     */
-    private Ringtone mCurrentRingtone;
-
-    private Config mPickerConfig;
-
-    private ListenableFuture<Uri> mAddCustomRingtoneFuture;
-
-    public enum PickerType {
-        RINGTONE_PICKER,
-        SOUND_PICKER,
-        VIBRATION_PICKER
-    }
-
-    /**
-     * Holds immutable info on the picker that should be displayed.
-     */
-    static final class Config {
-        public final String title;
-        /**
-         * Id of the user to which the ringtone picker should list the ringtones.
-         */
-        public final int userId;
-        /**
-         * Ringtone type.
-         */
-        public final int ringtoneType;
-        /**
-         * AudioAttributes flags.
-         */
-        public final int audioAttributesFlags;
-        /**
-         * In the buttonless (watch-only) version we don't show the OK/Cancel buttons.
-         */
-        public final boolean showOkCancelButtons;
-
-        public final PickerType mPickerType;
-
-        Config(String title, int userId, int ringtoneType, boolean showOkCancelButtons,
-                int audioAttributesFlags, PickerType pickerType) {
-            this.title = title;
-            this.userId = userId;
-            this.ringtoneType = ringtoneType;
-            this.showOkCancelButtons = showOkCancelButtons;
-            this.audioAttributesFlags = audioAttributesFlags;
-            this.mPickerType = pickerType;
-        }
-    }
-
-    @Inject
-    RingtonePickerViewModel(RingtoneManagerFactory ringtoneManagerFactory,
-            RingtoneFactory ringtoneFactory,
-            ListeningExecutorServiceFactory listeningExecutorServiceFactory,
-            RingtoneListHandler soundListHandler,
-            RingtoneListHandler vibrationListHandler) {
-        mRingtoneManagerFactory = ringtoneManagerFactory;
-        mRingtoneFactory = ringtoneFactory;
-        mListeningExecutorService = listeningExecutorServiceFactory.createSingleThreadExecutor();
-        mSoundListHandler = soundListHandler;
-        mVibrationListHandler = vibrationListHandler;
-    }
-
-    @StringRes
-    static int getTitleByType(int ringtoneType) {
-        switch (ringtoneType) {
-            case RingtoneManager.TYPE_ALARM:
-                return com.android.internal.R.string.ringtone_picker_title_alarm;
-            case RingtoneManager.TYPE_NOTIFICATION:
-                return com.android.internal.R.string.ringtone_picker_title_notification;
-            default:
-                return com.android.internal.R.string.ringtone_picker_title;
-        }
-    }
-
-    static Uri getDefaultItemUriByType(int ringtoneType) {
-        switch (ringtoneType) {
-            case RingtoneManager.TYPE_ALARM:
-                return Settings.System.DEFAULT_ALARM_ALERT_URI;
-            case RingtoneManager.TYPE_NOTIFICATION:
-                return Settings.System.DEFAULT_NOTIFICATION_URI;
-            default:
-                return Settings.System.DEFAULT_RINGTONE_URI;
-        }
-    }
-
-    @StringRes
-    static int getAddNewItemTextByType(int ringtoneType) {
-        switch (ringtoneType) {
-            case RingtoneManager.TYPE_ALARM:
-                return R.string.add_alarm_text;
-            case RingtoneManager.TYPE_NOTIFICATION:
-                return R.string.add_notification_text;
-            default:
-                return R.string.add_ringtone_text;
-        }
-    }
-
-    @StringRes
-    static int getDefaultRingtoneItemTextByType(int ringtoneType) {
-        switch (ringtoneType) {
-            case RingtoneManager.TYPE_ALARM:
-                return R.string.alarm_sound_default;
-            case RingtoneManager.TYPE_NOTIFICATION:
-                return R.string.notification_sound_default;
-            default:
-                return R.string.ringtone_default;
-        }
-    }
-
-    void init(@NonNull Config pickerConfig,
-            RingtoneListHandler.Config soundListConfig,
-            RingtoneListHandler.Config vibrationListConfig) {
-        mRingtoneManager = mRingtoneManagerFactory.create();
-        mPickerConfig = pickerConfig;
-        if (mPickerConfig.ringtoneType != RINGTONE_TYPE_UNKNOWN) {
-            mRingtoneManager.setType(mPickerConfig.ringtoneType);
-        }
-        if (soundListConfig != null) {
-            mSoundListHandler.init(soundListConfig, mRingtoneManager,
-                    mRingtoneManager.getCursor());
-        }
-        if (vibrationListConfig != null) {
-            // TODO: Switch to the vibration cursor, once the API is made available.
-            mVibrationListHandler.init(vibrationListConfig, mRingtoneManager,
-                    mRingtoneManager.getCursor());
-        }
-    }
-
-    /**
-     * Re-initializes the view model which is required after updating any of the picker lists.
-     * This could happen when adding a custom ringtone.
-     */
-    void reinit() {
-        init(mPickerConfig, mSoundListHandler.getRingtoneListConfig(),
-                mVibrationListHandler.getRingtoneListConfig());
-    }
-
-    @NonNull
-    Config getPickerConfig() {
-        requireInitCalled();
-        return mPickerConfig;
-    }
-
-    @NonNull
-    RingtoneListHandler getSoundListHandler() {
-        return mSoundListHandler;
-    }
-
-    @NonNull
-    RingtoneListHandler getVibrationListHandler() {
-        return mVibrationListHandler;
-    }
-
-    /**
-     * Combined the currently selected sound and vibration URIs and returns a unified URI. If the
-     * picker does not show either sound or vibration, that portion of the URI will be null.
-     *
-     * Currently only the sound URI is returned, since we don't have the API to retrieve vibrations
-     * yet.
-     * @return Combined sound and vibration URI.
-     */
-    Uri getSelectedRingtoneUri() {
-        // TODO: Combine sound and vibration URIs before returning.
-        return mSoundListHandler.getSelectedRingtoneUri();
-    }
-
-    int getRingtoneStreamType() {
-        requireInitCalled();
-        return mRingtoneManager.inferStreamType();
-    }
-
-    void onPause(boolean isChangingConfigurations) {
-        if (!isChangingConfigurations) {
-            stopAnyPlayingRingtone();
-        }
-    }
-
-    void onStop(boolean isChangingConfigurations) {
-        if (isChangingConfigurations) {
-            saveAnyPlayingRingtone();
-        } else {
-            stopAnyPlayingRingtone();
-        }
-    }
-
-    /**
-     * Plays a ringtone which is created using the currently selected sound and vibration URIs. If
-     * this is a sound or vibration only picker, then the other portion of the URI will be empty
-     * and should not affect the played ringtone.
-     *
-     * Currently, we only use the sound URI to create the ringtone, since we still don't have the
-     * API to retrieve the available vibrations list.
-     */
-    void playRingtone() {
-        requireInitCalled();
-        stopAnyPlayingRingtone();
-
-        mCurrentRingtone = mRingtoneFactory.create(getSelectedRingtoneUri(),
-                mPickerConfig.audioAttributesFlags);
-
-        if (mCurrentRingtone != null) {
-            mCurrentRingtone.play();
-        }
-    }
-
-    /**
-     * Cancels all pending async tasks.
-     */
-    void cancelPendingAsyncTasks() {
-        if (mAddCustomRingtoneFuture != null && !mAddCustomRingtoneFuture.isDone()) {
-            mAddCustomRingtoneFuture.cancel(/* mayInterruptIfRunning= */ true);
-        }
-    }
-
-    /**
-     * Adds an audio file to the list of ringtones asynchronously.
-     * Any previous async tasks are canceled before start the new one.
-     *
-     * @param uri      Uri of the file to be added as ringtone. Must be a media file.
-     * @param type     The type of the ringtone to be added.
-     * @param callback The callback to invoke when the task is completed.
-     * @param executor The executor to run the callback on when the task completes.
-     */
-    void addSoundRingtoneAsync(Uri uri, int type, FutureCallback<Uri> callback, Executor executor) {
-        // Cancel any currently running add ringtone tasks before starting a new one
-        cancelPendingAsyncTasks();
-        mAddCustomRingtoneFuture = mListeningExecutorService.submit(
-                () -> addRingtone(uri, type));
-        Futures.addCallback(mAddCustomRingtoneFuture, callback, executor);
-    }
-
-    /**
-     * Adds an audio file to the list of ringtones.
-     *
-     * @param uri  Uri of the file to be added as ringtone. Must be a media file.
-     * @param type The type of the ringtone to be added.
-     * @return The Uri of the installed ringtone, which may be the {@code uri} if it
-     * is already in ringtone storage. Or null if it failed to add the audio file.
-     */
-    @Nullable
-    private Uri addRingtone(Uri uri, int type) throws IOException {
-        requireInitCalled();
-        return mRingtoneManager.addCustomExternalRingtone(uri, type);
-    }
-
-    private void saveAnyPlayingRingtone() {
-        if (mCurrentRingtone != null && mCurrentRingtone.isPlaying()) {
-            sPlayingRingtone = mCurrentRingtone;
-        }
-        mCurrentRingtone = null;
-    }
-
-    private void stopAnyPlayingRingtone() {
-        if (sPlayingRingtone != null && sPlayingRingtone.isPlaying()) {
-            sPlayingRingtone.stop();
-        }
-        sPlayingRingtone = null;
-
-        if (mCurrentRingtone != null && mCurrentRingtone.isPlaying()) {
-            mCurrentRingtone.stop();
-        }
-        mCurrentRingtone = null;
-    }
-
-    private void requireInitCalled() {
-        requireNonNull(mRingtoneManager);
-        requireNonNull(mPickerConfig);
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java b/packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java
deleted file mode 100644
index 6a34936..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtoneReceiver.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-public class RingtoneReceiver extends BroadcastReceiver {
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        final String action = intent.getAction();
-        if (Intent.ACTION_DEVICE_CUSTOMIZATION_READY.equals(action)) {
-            initResourceRingtones(context);
-        }
-    }
-
-    private void initResourceRingtones(Context context) {
-        context.startService(
-                new Intent(context, RingtoneOverlayService.class));
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/SoundPickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/SoundPickerFragment.java
deleted file mode 100644
index a37191f..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/SoundPickerFragment.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Environment;
-import android.util.Log;
-import android.view.View;
-import android.widget.Toast;
-
-import androidx.activity.result.ActivityResult;
-import androidx.activity.result.ActivityResultCallback;
-import androidx.activity.result.ActivityResultLauncher;
-import androidx.activity.result.contract.ActivityResultContracts;
-import androidx.core.content.ContextCompat;
-import androidx.lifecycle.ViewModelProvider;
-
-import com.google.common.util.concurrent.FutureCallback;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * A fragment that displays a picker used to select sound or silent. It also includes the
- * ability to add custom sounds.
- */
-public class SoundPickerFragment extends BasePickerFragment {
-
-    private static final String TAG = "SoundPickerFragment";
-
-    private final FutureCallback<Uri> mAddCustomRingtoneCallback = new FutureCallback<>() {
-        @Override
-        public void onSuccess(Uri ringtoneUri) {
-            requeryForAdapter();
-        }
-
-        @Override
-        public void onFailure(Throwable throwable) {
-            Log.e(TAG, "Failed to add custom ringtone.", throwable);
-            // Ringtone was not added, display error Toast
-            Toast.makeText(requireActivity().getApplicationContext(),
-                    R.string.unable_to_add_ringtone, Toast.LENGTH_SHORT).show();
-        }
-    };
-
-    ActivityResultLauncher<Intent> mActivityResultLauncher = registerForActivityResult(
-            new ActivityResultContracts.StartActivityForResult(),
-            new ActivityResultCallback<ActivityResult>() {
-                @Override
-                public void onActivityResult(ActivityResult result) {
-                    if (result.getResultCode() == Activity.RESULT_OK) {
-                        // There are no request codes
-                        Intent data = result.getData();
-                        mRingtonePickerViewModel.addSoundRingtoneAsync(data.getData(),
-                                mPickerConfig.ringtoneType,
-                                mAddCustomRingtoneCallback,
-                                // Causes the callback to be executed on the main thread.
-                                ContextCompat.getMainExecutor(
-                                        requireActivity().getApplicationContext()));
-                    }
-                }
-            });
-
-    @Override
-    public void onViewCreated(@NotNull View view, Bundle savedInstanceState) {
-        mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
-                RingtonePickerViewModel.class);
-        super.onViewCreated(view, savedInstanceState);
-    }
-
-    @Override
-    protected RingtoneListHandler getRingtoneListHandler() {
-        return mRingtonePickerViewModel.getSoundListHandler();
-    }
-
-    @Override
-    protected void addRingtoneAsync() {
-        // The "Add new ringtone" item was clicked. Start a file picker intent to
-        // select only audio files (MIME type "audio/*")
-        final Intent chooseFile = getMediaFilePickerIntent();
-        mActivityResultLauncher.launch(chooseFile);
-    }
-
-    @Override
-    protected void addNewRingtoneItem() {
-        // If external storage is available, add a button to install sounds from storage.
-        if (resolvesMediaFilePicker()
-                && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
-            mRingtoneListViewAdapter.addTitleForAddRingtoneItem(
-                    RingtonePickerViewModel.getAddNewItemTextByType(mPickerConfig.ringtoneType));
-        }
-    }
-
-    private boolean resolvesMediaFilePicker() {
-        return getMediaFilePickerIntent().resolveActivity(requireActivity().getPackageManager())
-                != null;
-    }
-
-    private Intent getMediaFilePickerIntent() {
-        final Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
-        chooseFile.setType("audio/*");
-        chooseFile.putExtra(Intent.EXTRA_MIME_TYPES,
-                new String[]{"audio/*", "application/ogg"});
-        return chooseFile;
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/TabbedDialogFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/TabbedDialogFragment.java
deleted file mode 100644
index 50ea9d7..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/TabbedDialogFragment.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import static android.app.Activity.RESULT_CANCELED;
-
-import android.app.Activity;
-import android.app.Dialog;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.media.RingtoneManager;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.DialogFragment;
-import androidx.lifecycle.ViewModelProvider;
-import androidx.viewpager2.widget.ViewPager2;
-
-import com.google.android.material.tabs.TabLayout;
-import com.google.android.material.tabs.TabLayoutMediator;
-
-import dagger.hilt.android.AndroidEntryPoint;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * A dialog fragment with a sound and/or vibration tab based on the picker type.
- * <ul>
- * <li> Ringtone Pickers will display both sound and vibration tabs.
- * <li> Sound Pickers will only display the sound tab.
- * <li> Vibration Pickers will only display the vibration tab.
- * </ul>
- */
-@AndroidEntryPoint(DialogFragment.class)
-public class TabbedDialogFragment extends Hilt_TabbedDialogFragment {
-
-    static final String TAG = "TabbedDialogFragment";
-
-    private RingtonePickerViewModel mRingtonePickerViewModel;
-
-    private final ViewPager2.OnPageChangeCallback mOnPageChangeCallback =
-            new ViewPager2.OnPageChangeCallback() {
-                @Override
-                public void onPageScrollStateChanged(int state) {
-                    super.onPageScrollStateChanged(state);
-                    if (state == ViewPager2.SCROLL_STATE_IDLE) {
-                        mRingtonePickerViewModel.onStop(/* isChangingConfigurations= */ false);
-                    }
-                }
-            };
-
-    @Override
-    public void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
-                RingtonePickerViewModel.class);
-    }
-
-    @NonNull
-    @Override
-    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
-        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity(),
-                android.R.style.ThemeOverlay_Material_Dialog)
-                .setTitle(mRingtonePickerViewModel.getPickerConfig().title);
-        // Do not show OK/Cancel buttons in the buttonless (watch-only) version.
-        if (mRingtonePickerViewModel.getPickerConfig().showOkCancelButtons) {
-            dialogBuilder
-                    .setPositiveButton(getString(com.android.internal.R.string.ok),
-                            (dialog, whichButton) -> {
-                                setSuccessResultWithSelectedRingtone();
-                                requireActivity().finish();
-                            })
-                    .setNegativeButton(getString(com.android.internal.R.string.cancel),
-                            (dialog, whichButton) -> {
-                                requireActivity().setResult(RESULT_CANCELED);
-                                requireActivity().finish();
-                            });
-        }
-
-        View view = buildTabbedView(requireActivity().getLayoutInflater());
-        dialogBuilder.setView(view);
-
-        return dialogBuilder.create();
-    }
-
-    @Override
-    public void onCancel(@NonNull @NotNull DialogInterface dialog) {
-        super.onCancel(dialog);
-        if (!requireActivity().isChangingConfigurations()) {
-            requireActivity().finish();
-        }
-    }
-
-    @Override
-    public void onDismiss(@NonNull @NotNull DialogInterface dialog) {
-        super.onDismiss(dialog);
-        if (!requireActivity().isChangingConfigurations()) {
-            requireActivity().finish();
-        }
-    }
-
-    private void setSuccessResultWithSelectedRingtone() {
-        requireActivity().setResult(Activity.RESULT_OK,
-                new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI,
-                        mRingtonePickerViewModel.getSelectedRingtoneUri()));
-    }
-
-    /**
-     * Inflates the tabbed layout view and adds the required fragments. If there's only one
-     * fragment to display, then the tab area is hidden.
-     * @param inflater The LayoutInflater that is used to inflate the tabbed view.
-     * @return The tabbed view.
-     */
-    private View buildTabbedView(@NonNull LayoutInflater inflater) {
-        View view = inflater.inflate(R.layout.fragment_tabbed_dialog, null, false);
-        TabLayout tabLayout = view.requireViewById(R.id.tabLayout);
-        ViewPager2 viewPager = view.requireViewById(R.id.masterViewPager);
-
-        ViewPagerAdapter adapter = new ViewPagerAdapter(requireActivity());
-        addFragments(adapter);
-
-        if (adapter.getItemCount() == 1) {
-            // Hide the tab area since there's only one fragment to display.
-            tabLayout.setVisibility(View.GONE);
-        }
-
-        viewPager.setAdapter(adapter);
-        viewPager.registerOnPageChangeCallback(mOnPageChangeCallback);
-        new TabLayoutMediator(tabLayout, viewPager,
-                (tab, position) -> tab.setText(adapter.getTitle(position))).attach();
-
-        return view;
-    }
-
-    /**
-     * Adds the appropriate fragments to the adapter based on the PickerType.
-     *
-     * @param adapter The adapter to add the fragments to.
-     */
-    private void addFragments(ViewPagerAdapter adapter) {
-        switch (mRingtonePickerViewModel.getPickerConfig().mPickerType) {
-            case RINGTONE_PICKER:
-                adapter.addFragment(getString(R.string.sound_page_title),
-                        new SoundPickerFragment());
-                adapter.addFragment(getString(R.string.vibration_page_title),
-                        new VibrationPickerFragment());
-                break;
-            case SOUND_PICKER:
-                adapter.addFragment(getString(R.string.sound_page_title),
-                        new SoundPickerFragment());
-                break;
-            case VIBRATION_PICKER:
-                adapter.addFragment(getString(R.string.vibration_page_title),
-                        new VibrationPickerFragment());
-                break;
-            default:
-                adapter.addFragment(getString(R.string.sound_page_title),
-                        new SoundPickerFragment());
-                break;
-        }
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/VibrationPickerFragment.java b/packages/SoundPicker2/src/com/android/soundpicker/VibrationPickerFragment.java
deleted file mode 100644
index 7412c19..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/VibrationPickerFragment.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import android.os.Bundle;
-import android.view.View;
-
-import androidx.lifecycle.ViewModelProvider;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * A fragment that displays a picker used to select vibration or silent (no vibration).
- */
-public class VibrationPickerFragment extends BasePickerFragment {
-
-    @Override
-    public void onViewCreated(@NotNull View view, Bundle savedInstanceState) {
-        mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
-                RingtonePickerViewModel.class);
-        super.onViewCreated(view, savedInstanceState);
-    }
-
-    @Override
-    protected RingtoneListHandler getRingtoneListHandler() {
-        return mRingtonePickerViewModel.getVibrationListHandler();
-    }
-
-    @Override
-    protected void addRingtoneAsync() {
-        // no-op
-    }
-
-    @Override
-    protected void addNewRingtoneItem() {
-        // no-op
-    }
-}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/ViewPagerAdapter.java b/packages/SoundPicker2/src/com/android/soundpicker/ViewPagerAdapter.java
deleted file mode 100644
index 179068e..0000000
--- a/packages/SoundPicker2/src/com/android/soundpicker/ViewPagerAdapter.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import androidx.annotation.NonNull;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentActivity;
-import androidx.viewpager2.adapter.FragmentStateAdapter;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * An adapter used to populate pages inside a ViewPager.
- */
-public class ViewPagerAdapter extends FragmentStateAdapter {
-
-    private final List<Fragment> mFragments = new ArrayList<>();
-    private final List<String> mTitles = new ArrayList<>();
-
-    public ViewPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
-        super(fragmentActivity);
-    }
-
-    /**
-     * Adds a fragment and page title to the adapter.
-     * @param title the title of the page in the ViewPager.
-     * @param fragment the fragment that will be inflated on this page.
-     */
-    public void addFragment(String title, Fragment fragment) {
-        mTitles.add(title);
-        mFragments.add(fragment);
-    }
-
-    /**
-     * Returns the title of the requested page.
-     * @param position the position of the page in the Viewpager.
-     * @return The title of the requested page.
-     */
-    public String getTitle(int position) {
-        return mTitles.get(position);
-    }
-
-    @NonNull
-    @Override
-    public Fragment createFragment(int position) {
-        return Objects.requireNonNull(mFragments.get(position),
-                "Could not find a fragment using position: " + position);
-    }
-
-    @Override
-    public int getItemCount() {
-        return mFragments.size();
-    }
-}
diff --git a/packages/SoundPicker2/tests/Android.bp b/packages/SoundPicker2/tests/Android.bp
deleted file mode 100644
index d88d442..0000000
--- a/packages/SoundPicker2/tests/Android.bp
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2023, The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test {
-    name: "SoundPicker2Tests",
-    certificate: "platform",
-    libs: [
-        "android.test.runner",
-        "android.test.base",
-    ],
-    static_libs: [
-        "androidx.test.core",
-        "androidx.test.rules",
-        "androidx.test.ext.junit",
-        "androidx.test.ext.truth",
-        "mockito-target-minus-junit4",
-        "guava-android-testlib",
-        "SoundPicker2Lib",
-    ],
-    srcs: [
-        "src/**/*.java",
-    ],
-}
diff --git a/packages/SoundPicker2/tests/AndroidManifest.xml b/packages/SoundPicker2/tests/AndroidManifest.xml
deleted file mode 100644
index 295aeb1..0000000
--- a/packages/SoundPicker2/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.soundpicker.tests">
-
-    <application android:debuggable="true">
-        <uses-library android:name="android.test.runner" />
-    </application>
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.soundpicker.tests"
-        android:label="Sound picker tests">
-    </instrumentation>
-</manifest>
diff --git a/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java b/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java
deleted file mode 100644
index 80e71e200..0000000
--- a/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.when;
-
-import android.database.Cursor;
-import android.media.RingtoneManager;
-import android.net.Uri;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-public class RingtoneListHandlerTest {
-
-    private static final Uri DEFAULT_URI = Uri.parse("media://custom/ringtone/default_uri");
-    private static final Uri RINGTONE_URI = Uri.parse("media://custom/ringtone/uri");
-    private static final int SILENT_RINGTONE_POSITION = 0;
-    private static final int DEFAULT_RINGTONE_POSITION = 1;
-    private static final int RINGTONE_POSITION = 2;
-
-    @Mock
-    private RingtoneManager mMockRingtoneManager;
-    @Mock
-    private Cursor mMockCursor;
-
-    private RingtoneListHandler mRingtoneListHandler;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        RingtoneListHandler.Config mRingtoneListConfig = createRingtoneListConfig();
-
-        mRingtoneListHandler = new RingtoneListHandler();
-
-        // Add silent and default options to the list.
-        mRingtoneListHandler.addSilentItem();
-        mRingtoneListHandler.addDefaultItem();
-
-        mRingtoneListHandler.init(mRingtoneListConfig, mMockRingtoneManager, mMockCursor);
-    }
-
-    @Test
-    public void testGetRingtoneCursor_returnsTheCorrectRingtoneCursor() {
-        assertThat(mRingtoneListHandler.getRingtoneCursor()).isEqualTo(mMockCursor);
-    }
-
-    @Test
-    public void testGetRingtoneUri_returnsTheCorrectRingtoneUri() {
-        Uri expectedUri = RINGTONE_URI;
-        when(mMockRingtoneManager.getRingtoneUri(eq(0))).thenReturn(expectedUri);
-
-        // Request 3rd item from list.
-        Uri actualUri = mRingtoneListHandler.getRingtoneUri(RINGTONE_POSITION);
-        assertThat(actualUri).isEqualTo(expectedUri);
-    }
-
-    @Test
-    public void testGetRingtoneUri_withSelectedItemUnknown_returnsTheCorrectRingtoneUri() {
-        Uri uri = mRingtoneListHandler.getRingtoneUri(RingtoneListHandler.ITEM_POSITION_UNKNOWN);
-        assertThat(uri).isEqualTo(RingtoneListHandler.SILENT_URI);
-    }
-
-    @Test
-    public void testGetRingtoneUri_withSelectedItemDefaultPosition_returnsTheCorrectRingtoneUri() {
-        Uri actualUri = mRingtoneListHandler.getRingtoneUri(DEFAULT_RINGTONE_POSITION);
-        assertThat(actualUri).isEqualTo(DEFAULT_URI);
-    }
-
-    @Test
-    public void testGetRingtoneUri_withSelectedItemSilentPosition_returnsTheCorrectRingtoneUri() {
-        Uri uri = mRingtoneListHandler.getRingtoneUri(SILENT_RINGTONE_POSITION);
-        assertThat(uri).isEqualTo(RingtoneListHandler.SILENT_URI);
-    }
-
-    @Test
-    public void testGetCurrentlySelectedRingtoneUri_returnsTheCorrectRingtoneUri() {
-        mRingtoneListHandler.setSelectedItemPosition(RingtoneListHandler.ITEM_POSITION_UNKNOWN);
-        Uri actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
-        assertThat(actualUri).isEqualTo(RingtoneListHandler.SILENT_URI);
-
-        mRingtoneListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
-        actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
-        assertThat(actualUri).isEqualTo(DEFAULT_URI);
-
-        mRingtoneListHandler.setSelectedItemPosition(SILENT_RINGTONE_POSITION);
-        actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
-        assertThat(actualUri).isEqualTo(RingtoneListHandler.SILENT_URI);
-
-        when(mMockRingtoneManager.getRingtoneUri(eq(0))).thenReturn(RINGTONE_URI);
-        mRingtoneListHandler.setSelectedItemPosition(RINGTONE_POSITION);
-        actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
-        assertThat(actualUri).isEqualTo(RINGTONE_URI);
-    }
-
-    @Test
-    public void testGetRingtonePosition_returnsTheCorrectRingtonePosition() {
-        when(mMockRingtoneManager.getRingtonePosition(RINGTONE_URI)).thenReturn(0);
-
-        int actualPosition = mRingtoneListHandler.getRingtonePosition(RINGTONE_URI);
-
-        assertThat(actualPosition).isEqualTo(RINGTONE_POSITION);
-
-    }
-
-    @Test
-    public void testFixedItems_onlyAddsItemsOnceAndInOrder() {
-        // Clear fixed items before testing the add methods.
-        mRingtoneListHandler.resetFixedItems();
-
-        assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
-                RingtoneListHandler.ITEM_POSITION_UNKNOWN);
-        assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
-                RingtoneListHandler.ITEM_POSITION_UNKNOWN);
-
-        mRingtoneListHandler.addSilentItem();
-        mRingtoneListHandler.addDefaultItem();
-        mRingtoneListHandler.addSilentItem();
-        mRingtoneListHandler.addDefaultItem();
-
-        assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
-                SILENT_RINGTONE_POSITION);
-        assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
-                DEFAULT_RINGTONE_POSITION);
-    }
-
-    @Test
-    public void testResetFixedItems_resetsSilentAndDefaultItemPositions() {
-        assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
-                SILENT_RINGTONE_POSITION);
-        assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
-                DEFAULT_RINGTONE_POSITION);
-
-        mRingtoneListHandler.resetFixedItems();
-
-        assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
-                RingtoneListHandler.ITEM_POSITION_UNKNOWN);
-        assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
-                RingtoneListHandler.ITEM_POSITION_UNKNOWN);
-    }
-
-    private RingtoneListHandler.Config createRingtoneListConfig() {
-        return new RingtoneListHandler.Config(/* hasDefaultItem= */ true,
-                /* uriForDefaultItem= */ DEFAULT_URI, /* hasSilentItem= */ true,
-                /* existingUri= */ DEFAULT_URI);
-    }
-}
diff --git a/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java b/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
deleted file mode 100644
index cde6c76..0000000
--- a/packages/SoundPicker2/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
+++ /dev/null
@@ -1,534 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.soundpicker;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.database.Cursor;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.provider.Settings;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.testing.TestingExecutors;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.IOException;
-import java.util.concurrent.ExecutorService;
-
-@RunWith(AndroidJUnit4.class)
-public class RingtonePickerViewModelTest {
-
-    private static final Uri DEFAULT_URI = Uri.parse("media://custom/ringtone/default_uri");
-    private static final Uri RINGTONE_URI = Uri.parse("media://custom/ringtone/uri");
-    private static final int RINGTONE_TYPE_UNKNOWN = -1;
-    private static final int DEFAULT_RINGTONE_POSITION = 1;
-
-    @Mock
-    private RingtoneManagerFactory mMockRingtoneManagerFactory;
-    @Mock
-    private RingtoneFactory mMockRingtoneFactory;
-    @Mock
-    private RingtoneManager mMockRingtoneManager;
-    @Mock
-    private ListeningExecutorServiceFactory mMockListeningExecutorServiceFactory;
-    @Mock
-    private Cursor mMockCursor;
-
-    private RingtoneListHandler mSoundListHandler;
-    private RingtoneListHandler mVibrationListHandler;
-    private ExecutorService mMainThreadExecutor;
-    private ListeningExecutorService mBackgroundThreadExecutor;
-    private Ringtone mMockDefaultRingtone;
-    private Ringtone mMockRingtone;
-    private RingtonePickerViewModel mViewModel;
-    private RingtoneListHandler.Config mSoundListConfig;
-    private RingtoneListHandler.Config mVibrationListConfig;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mSoundListHandler = new RingtoneListHandler();
-        mVibrationListHandler = new RingtoneListHandler();
-        mSoundListConfig = createRingtoneListConfig();
-        mVibrationListConfig = createRingtoneListConfig();
-        mMockDefaultRingtone = createMockRingtone();
-        mMockRingtone = createMockRingtone();
-        when(mMockRingtoneManagerFactory.create()).thenReturn(mMockRingtoneManager);
-        when(mMockRingtoneFactory.create(DEFAULT_URI,
-                AudioAttributes.FLAG_AUDIBILITY_ENFORCED)).thenReturn(mMockDefaultRingtone);
-        when(mMockRingtoneManager.getRingtoneUri(anyInt())).thenReturn(RINGTONE_URI);
-        when(mMockRingtoneManager.getCursor()).thenReturn(mMockCursor);
-        mMainThreadExecutor = TestingExecutors.sameThreadScheduledExecutor();
-        mBackgroundThreadExecutor = TestingExecutors.sameThreadScheduledExecutor();
-        when(mMockListeningExecutorServiceFactory.createSingleThreadExecutor()).thenReturn(
-                mBackgroundThreadExecutor);
-
-        mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
-                mMockListeningExecutorServiceFactory, mSoundListHandler,
-                mVibrationListHandler);
-
-        // Add silent and default options to the sound list.
-        mSoundListHandler.addSilentItem();
-        mSoundListHandler.addDefaultItem();
-
-        // Add silent and default options to the vibration list.
-        mVibrationListHandler.addSilentItem();
-        mVibrationListHandler.addDefaultItem();
-
-        mSoundListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
-        mVibrationListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
-    }
-
-    @After
-    public void teardown() {
-        if (mMainThreadExecutor != null && !mMainThreadExecutor.isShutdown()) {
-            mMainThreadExecutor.shutdown();
-        }
-        if (mBackgroundThreadExecutor != null && !mBackgroundThreadExecutor.isShutdown()) {
-            mBackgroundThreadExecutor.shutdown();
-        }
-    }
-
-    @Test
-    public void testInitRingtoneManager_whenTypeIsUnknown_createManagerButDoNotSetType() {
-        mViewModel.init(createPickerConfig(RINGTONE_TYPE_UNKNOWN), mSoundListConfig,
-                mVibrationListConfig);
-
-        verify(mMockRingtoneManagerFactory).create();
-        verify(mMockRingtoneManager, never()).setType(anyInt());
-        assertNotNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
-        assertNotNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
-    }
-
-    @Test
-    public void testInitRingtoneManager_whenTypeIsNotUnknown_createManagerAndSetType() {
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_NOTIFICATION), mSoundListConfig,
-                mVibrationListConfig);
-
-        verify(mMockRingtoneManagerFactory).create();
-        verify(mMockRingtoneManager).setType(RingtoneManager.TYPE_NOTIFICATION);
-        assertNotNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
-        assertNotNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
-    }
-
-    @Test
-    public void testInitRingtoneManager_bothListConfigsAreNull_onlyRecreateRingtoneManager() {
-        mViewModel.init(
-                createPickerConfig(RingtoneManager.TYPE_NOTIFICATION),
-                /* soundListConfig= */ null, /* vibrationListConfig= */ null);
-
-        verify(mMockRingtoneManagerFactory).create();
-        verify(mMockRingtoneManager).setType(RingtoneManager.TYPE_NOTIFICATION);
-        assertNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
-        assertNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
-    }
-
-    @Test
-    public void testReinitialize_bothListConfigsInitialized_recreateManagerAndReinitHandlers() {
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_NOTIFICATION), mSoundListConfig,
-                mVibrationListConfig);
-        mViewModel.reinit();
-
-        verify(mMockRingtoneManagerFactory, times(2)).create();
-        verify(mMockRingtoneManager, times(2)).setType(RingtoneManager.TYPE_NOTIFICATION);
-        assertNotNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
-        assertNotNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
-    }
-
-    @Test
-    public void testReinitialize_bothListConfigsAlreadyNull_onlyRecreateRingtoneManager() {
-        mViewModel.init(
-                createPickerConfig(RingtoneManager.TYPE_NOTIFICATION),
-                /* soundListConfig= */ null, /* vibrationListConfig= */ null);
-        mViewModel.reinit();
-
-        verify(mMockRingtoneManagerFactory, times(2)).create();
-        verify(mMockRingtoneManager, times(2)).setType(RingtoneManager.TYPE_NOTIFICATION);
-        assertNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
-        assertNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
-    }
-
-    @Test
-    public void testGetStreamType_returnsTheCorrectStreamType() {
-        when(mMockRingtoneManager.inferStreamType()).thenReturn(AudioManager.STREAM_ALARM);
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-        assertEquals(mViewModel.getRingtoneStreamType(), AudioManager.STREAM_ALARM);
-    }
-
-    @Test
-    public void testOnPause_withChangingConfigurationTrue_doNotStopPlayingRingtone() {
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-        mViewModel.playRingtone();
-        verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
-        mViewModel.onPause(/* isChangingConfigurations= */ true);
-        verify(mMockDefaultRingtone, never()).stop();
-    }
-
-    @Test
-    public void testOnPause_withChangingConfigurationFalse_stopPlayingRingtone() {
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-        mViewModel.playRingtone();
-        verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
-        mViewModel.onPause(/* isChangingConfigurations= */ false);
-        verify(mMockDefaultRingtone).stop();
-    }
-
-    @Test
-    public void testOnViewModelRecreated_previousRingtoneCanStillBeStopped() {
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-        Ringtone mockRingtone1 = createMockRingtone();
-        Ringtone mockRingtone2 = createMockRingtone();
-
-        when(mMockRingtoneFactory.create(any(), anyInt())).thenReturn(mockRingtone1, mockRingtone2);
-        mViewModel.playRingtone();
-        verifyRingtonePlayCalledAndMockPlayingState(mockRingtone1);
-        // Fake a scenario where the activity is destroyed and recreated due to a config change.
-        // This will result in a new view model getting created.
-        mViewModel.onStop(/* isChangingConfigurations= */ true);
-        verify(mockRingtone1, never()).stop();
-        mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
-                mMockListeningExecutorServiceFactory, mSoundListHandler,
-                mVibrationListHandler);
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-        mViewModel.playRingtone();
-        verifyRingtonePlayCalledAndMockPlayingState(mockRingtone2);
-        verify(mockRingtone1).stop();
-        verify(mockRingtone2, never()).stop();
-    }
-
-    @Test
-    public void testOnStop_withChangingConfigurationTrueAndDefaultRingtonePlaying_saveRingtone() {
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-        mViewModel.playRingtone();
-        verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
-        mViewModel.onStop(/* isChangingConfigurations= */ true);
-        assertEquals(RingtonePickerViewModel.sPlayingRingtone, mMockDefaultRingtone);
-    }
-
-    @Test
-    public void testOnStop_withChangingConfigurationTrueAndCurrentRingtonePlaying_saveRingtone() {
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-        mViewModel.playRingtone();
-        verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
-        mViewModel.onStop(/* isChangingConfigurations= */ true);
-        assertEquals(RingtonePickerViewModel.sPlayingRingtone, mMockDefaultRingtone);
-    }
-
-    @Test
-    public void testOnStop_withChangingConfigurationTrueAndNoPlayingRingtone_saveNothing() {
-        mViewModel.onStop(/* isChangingConfigurations= */ true);
-        assertNull(RingtonePickerViewModel.sPlayingRingtone);
-    }
-
-    @Test
-    public void testOnStop_withChangingConfigurationFalse_stopPlayingRingtone() {
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-
-        mViewModel.playRingtone();
-        verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
-        mViewModel.onStop(/* isChangingConfigurations= */ false);
-        verify(mMockDefaultRingtone).stop();
-    }
-
-    @Test
-    public void testGetCurrentlySelectedRingtoneUri_returnsTheCorrectRingtoneUri() {
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-
-        assertEquals(DEFAULT_URI, mViewModel.getSelectedRingtoneUri());
-    }
-
-    @Test
-    public void testPlayRingtone_playTheCorrectRingtone() {
-        mSoundListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-
-        mViewModel.playRingtone();
-        verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
-    }
-
-    @Test
-    public void testPlayRingtone_stopsPreviouslyRunningRingtone() {
-        // Start playing the first ringtone
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-        mViewModel.playRingtone();
-        verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
-        // Start playing the second ringtone
-        when(mMockRingtoneFactory.create(DEFAULT_URI,
-                AudioAttributes.FLAG_AUDIBILITY_ENFORCED)).thenReturn(mMockRingtone);
-        mViewModel.playRingtone();
-        verifyRingtonePlayCalledAndMockPlayingState(mMockRingtone);
-
-        verify(mMockDefaultRingtone).stop();
-    }
-
-    @Test
-    public void testDefaultItemUri_withNotificationIntent_returnDefaultNotificationUri() {
-        Uri uri = RingtonePickerViewModel.getDefaultItemUriByType(
-                RingtoneManager.TYPE_NOTIFICATION);
-        assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, uri);
-    }
-
-    @Test
-    public void testDefaultItemUri_withAlarmIntent_returnDefaultAlarmUri() {
-        Uri uri = RingtonePickerViewModel.getDefaultItemUriByType(RingtoneManager.TYPE_ALARM);
-        assertEquals(Settings.System.DEFAULT_ALARM_ALERT_URI, uri);
-    }
-
-    @Test
-    public void testDefaultItemUri_withRingtoneIntent_returnDefaultRingtoneUri() {
-        Uri uri = RingtonePickerViewModel.getDefaultItemUriByType(RingtoneManager.TYPE_RINGTONE);
-        assertEquals(Settings.System.DEFAULT_RINGTONE_URI, uri);
-    }
-
-    @Test
-    public void testDefaultItemUri_withInvalidRingtoneType_returnDefaultRingtoneUri() {
-        Uri uri = RingtonePickerViewModel.getDefaultItemUriByType(-1);
-        assertEquals(Settings.System.DEFAULT_RINGTONE_URI, uri);
-    }
-
-    @Test
-    public void testTitle_withNotificationRingtoneType_returnRingtoneNotificationTitle() {
-        int title = RingtonePickerViewModel.getTitleByType(RingtoneManager.TYPE_NOTIFICATION);
-        assertEquals(com.android.internal.R.string.ringtone_picker_title_notification, title);
-    }
-
-    @Test
-    public void testTitle_withAlarmRingtoneType_returnRingtoneAlarmTitle() {
-        int title = RingtonePickerViewModel.getTitleByType(RingtoneManager.TYPE_ALARM);
-        assertEquals(com.android.internal.R.string.ringtone_picker_title_alarm, title);
-    }
-
-    @Test
-    public void testTitle_withInvalidRingtoneType_returnDefaultRingtoneTitle() {
-        int title = RingtonePickerViewModel.getTitleByType(/*ringtoneType= */ -1);
-        assertEquals(com.android.internal.R.string.ringtone_picker_title, title);
-    }
-
-    @Test
-    public void testAddNewItemText_withAlarmType_returnAlarmAddItemText() {
-        int addNewItemTextResId = RingtonePickerViewModel.getAddNewItemTextByType(
-                RingtoneManager.TYPE_ALARM);
-        assertEquals(R.string.add_alarm_text, addNewItemTextResId);
-    }
-
-    @Test
-    public void testAddNewItemText_withNotificationType_returnNotificationAddItemText() {
-        int addNewItemTextResId = RingtonePickerViewModel.getAddNewItemTextByType(
-                RingtoneManager.TYPE_NOTIFICATION);
-        assertEquals(R.string.add_notification_text, addNewItemTextResId);
-    }
-
-    @Test
-    public void testAddNewItemText_withRingtoneType_returnRingtoneAddItemText() {
-        int addNewItemTextResId = RingtonePickerViewModel.getAddNewItemTextByType(
-                RingtoneManager.TYPE_RINGTONE);
-        assertEquals(R.string.add_ringtone_text, addNewItemTextResId);
-    }
-
-    @Test
-    public void testAddNewItemText_withInvalidType_returnRingtoneAddItemText() {
-        int addNewItemTextResId = RingtonePickerViewModel.getAddNewItemTextByType(-1);
-        assertEquals(R.string.add_ringtone_text, addNewItemTextResId);
-    }
-
-    @Test
-    public void testDefaultItemText_withNotificationType_returnNotificationDefaultItemText() {
-        int defaultRingtoneItemText = RingtonePickerViewModel.getDefaultRingtoneItemTextByType(
-                RingtoneManager.TYPE_NOTIFICATION);
-        assertEquals(R.string.notification_sound_default, defaultRingtoneItemText);
-    }
-
-    @Test
-    public void testDefaultItemText_withAlarmType_returnAlarmDefaultItemText() {
-        int defaultRingtoneItemText = RingtonePickerViewModel.getDefaultRingtoneItemTextByType(
-                RingtoneManager.TYPE_NOTIFICATION);
-        assertEquals(R.string.notification_sound_default, defaultRingtoneItemText);
-    }
-
-    @Test
-    public void testDefaultItemText_withRingtoneType_returnRingtoneDefaultItemText() {
-        int defaultRingtoneItemText = RingtonePickerViewModel.getDefaultRingtoneItemTextByType(
-                RingtoneManager.TYPE_RINGTONE);
-        assertEquals(R.string.ringtone_default, defaultRingtoneItemText);
-    }
-
-    @Test
-    public void testDefaultItemText_withInvalidType_returnRingtoneDefaultItemText() {
-        int defaultRingtoneItemText = RingtonePickerViewModel.getDefaultRingtoneItemTextByType(-1);
-        assertEquals(R.string.ringtone_default, defaultRingtoneItemText);
-    }
-
-    @Test
-    public void testCancelPendingAsyncTasks_correctlyCancelsPendingTasks()
-            throws IOException {
-        FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
-
-        when(mMockListeningExecutorServiceFactory.createSingleThreadExecutor()).thenReturn(
-                TestingExecutors.noOpScheduledExecutor());
-
-        mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
-                mMockListeningExecutorServiceFactory, mSoundListHandler,
-                mVibrationListHandler);
-        mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
-                mockCallback, mMainThreadExecutor);
-        verify(mockCallback, never()).onFailure(any());
-        // Calling cancelPendingAsyncTasks should cancel the pending task. Cancelling an async
-        // task invokes the onFailure method in the callable.
-        mViewModel.cancelPendingAsyncTasks();
-        verify(mockCallback).onFailure(any());
-        verify(mockCallback, never()).onSuccess(any());
-
-    }
-
-    @Test
-    public void testAddRingtoneAsync_cancelPreviousTaskBeforeStartingNewOne()
-            throws IOException {
-        FutureCallback<Uri> mockCallback1 = mock(FutureCallback.class);
-        FutureCallback<Uri> mockCallback2 = mock(FutureCallback.class);
-
-        when(mMockListeningExecutorServiceFactory.createSingleThreadExecutor()).thenReturn(
-                TestingExecutors.noOpScheduledExecutor());
-
-        mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
-                mMockListeningExecutorServiceFactory, mSoundListHandler,
-                mVibrationListHandler);
-        mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
-                mockCallback1, mMainThreadExecutor);
-        verify(mockCallback1, never()).onFailure(any());
-        // We call addRingtoneAsync again to cancel the previous task and start a new one.
-        // Cancelling an async task invokes the onFailure method in the callable.
-        mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
-                mockCallback2, mMainThreadExecutor);
-        verify(mockCallback1).onFailure(any());
-        verify(mockCallback1, never()).onSuccess(any());
-        verifyNoMoreInteractions(mockCallback2);
-    }
-
-    @Test
-    public void testAddRingtoneAsync_whenAddRingtoneIsSuccessful_successCallbackIsInvoked()
-            throws IOException {
-        Uri expectedUri = DEFAULT_URI;
-        FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
-
-        when(mMockRingtoneManager.addCustomExternalRingtone(DEFAULT_URI,
-                RingtoneManager.TYPE_NOTIFICATION)).thenReturn(expectedUri);
-
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-
-        mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
-                mockCallback, mMainThreadExecutor);
-
-        verify(mockCallback).onSuccess(expectedUri);
-        verify(mockCallback, never()).onFailure(any());
-    }
-
-    @Test
-    public void testAddRingtoneAsync_whenAddRingtoneFailed_failureCallbackIsInvoked()
-            throws IOException {
-        FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
-
-        when(mMockRingtoneManager.addCustomExternalRingtone(any(), anyInt())).thenThrow(
-                IOException.class);
-
-        mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
-                mVibrationListConfig);
-
-        mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
-                mockCallback, mMainThreadExecutor);
-
-        verify(mockCallback).onFailure(any(IOException.class));
-        verify(mockCallback, never()).onSuccess(any());
-    }
-
-    private Ringtone createMockRingtone() {
-        Ringtone mockRingtone = mock(Ringtone.class);
-        when(mockRingtone.getAudioAttributes()).thenReturn(
-                audioAttributes(AudioAttributes.USAGE_NOTIFICATION_RINGTONE, 0));
-
-        return mockRingtone;
-    }
-
-    private void verifyRingtonePlayCalledAndMockPlayingState(Ringtone ringtone) {
-        verify(ringtone).play();
-        when(ringtone.isPlaying()).thenReturn(true);
-    }
-
-    private static AudioAttributes audioAttributes(int audioUsage, int flags) {
-        return new AudioAttributes.Builder()
-                .setUsage(audioUsage)
-                .setFlags(flags)
-                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-                .build();
-    }
-
-    private RingtonePickerViewModel.Config createPickerConfig(int ringtoneType,
-            int audioAttributes) {
-        return new RingtonePickerViewModel.Config("Phone ringtone", /* userId= */ 1,
-                ringtoneType, /* showOkCancelButtons= */ true,
-                audioAttributes, RingtonePickerViewModel.PickerType.RINGTONE_PICKER);
-    }
-
-    private RingtonePickerViewModel.Config createPickerConfig(int ringtoneType) {
-        return new RingtonePickerViewModel.Config("Phone ringtone", /* userId= */ 1,
-                ringtoneType, /* showOkCancelButtons= */ true,
-                AudioAttributes.FLAG_AUDIBILITY_ENFORCED,
-                RingtonePickerViewModel.PickerType.RINGTONE_PICKER);
-    }
-
-    private RingtoneListHandler.Config createRingtoneListConfig() {
-        return new RingtoneListHandler.Config(/* hasDefaultItem= */ true,
-                /* uriForDefaultItem= */ DEFAULT_URI, /* hasSilentItem= */ true,
-                /* existingUri= */ Uri.parse(""));
-    }
-}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 8194055..4973caf 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -53,12 +53,12 @@
  */
 class ActivityLaunchAnimator(
     /** The animator used when animating a View into an app. */
-    private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR,
+    private val transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
 
     /** The animator used when animating a Dialog into an app. */
     // TODO(b/218989950): Remove this animator and instead set the duration of the dim fade out to
     // TIMINGS.contentBeforeFadeOutDuration.
-    private val dialogToAppAnimator: LaunchAnimator = DEFAULT_DIALOG_TO_APP_ANIMATOR,
+    private val dialogToAppAnimator: TransitionAnimator = DEFAULT_DIALOG_TO_APP_ANIMATOR,
 
     /**
      * Whether we should disable the WindowManager timeout. This should be set to true in tests
@@ -71,7 +71,7 @@
         /** The timings when animating a View into an app. */
         @JvmField
         val TIMINGS =
-            LaunchAnimator.Timings(
+            TransitionAnimator.Timings(
                 totalDuration = 500L,
                 contentBeforeFadeOutDelay = 0L,
                 contentBeforeFadeOutDuration = 150L,
@@ -89,7 +89,7 @@
 
         /** The interpolators when animating a View or a dialog into an app. */
         val INTERPOLATORS =
-            LaunchAnimator.Interpolators(
+            TransitionAnimator.Interpolators(
                 positionInterpolator = Interpolators.EMPHASIZED,
                 positionXInterpolator = Interpolators.EMPHASIZED_COMPLEMENT,
                 contentBeforeFadeOutInterpolator = Interpolators.LINEAR_OUT_SLOW_IN,
@@ -99,8 +99,9 @@
         // TODO(b/288507023): Remove this flag.
         @JvmField val DEBUG_LAUNCH_ANIMATION = Build.IS_DEBUGGABLE
 
-        private val DEFAULT_LAUNCH_ANIMATOR = LaunchAnimator(TIMINGS, INTERPOLATORS)
-        private val DEFAULT_DIALOG_TO_APP_ANIMATOR = LaunchAnimator(DIALOG_TIMINGS, INTERPOLATORS)
+        private val DEFAULT_TRANSITION_ANIMATOR = TransitionAnimator(TIMINGS, INTERPOLATORS)
+        private val DEFAULT_DIALOG_TO_APP_ANIMATOR =
+            TransitionAnimator(DIALOG_TIMINGS, INTERPOLATORS)
 
         /** Durations & interpolators for the navigation bar fading in & out. */
         private const val ANIMATION_DURATION_NAV_FADE_IN = 266L
@@ -154,7 +155,7 @@
      * Start an intent and animate the opening window. The intent will be started by running
      * [intentStarter], which should use the provided [RemoteAnimationAdapter] and return the launch
      * result. [controller] is responsible from animating the view from which the intent was started
-     * in [Controller.onLaunchAnimationProgress]. No animation will start if there is no window
+     * in [Controller.onTransitionAnimationProgress]. No animation will start if there is no window
      * opening.
      *
      * If [controller] is null or [animate] is false, then the intent will be started and no
@@ -255,7 +256,7 @@
 
     private fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) {
         if (Looper.myLooper() != Looper.getMainLooper()) {
-            this.launchContainer.context.mainExecutor.execute {
+            this.transitionContainer.context.mainExecutor.execute {
                 callOnIntentStartedOnMainThread(willAnimate)
             }
         } else {
@@ -306,14 +307,14 @@
     @VisibleForTesting
     fun createRunner(controller: Controller): Runner {
         // Make sure we use the modified timings when animating a dialog into an app.
-        val launchAnimator =
+        val transitionAnimator =
             if (controller.isDialogLaunch) {
                 dialogToAppAnimator
             } else {
-                launchAnimator
+                transitionAnimator
             }
 
-        return Runner(controller, callback!!, launchAnimator, lifecycleListener)
+        return Runner(controller, callback!!, transitionAnimator, lifecycleListener)
     }
 
     interface PendingIntentStarter {
@@ -364,7 +365,7 @@
      *
      * Note that all callbacks (onXXX methods) are all called on the main thread.
      */
-    interface Controller : LaunchAnimator.Controller {
+    interface Controller : TransitionAnimator.Controller {
         companion object {
             /**
              * Return a [Controller] that will animate and expand [view] into the opening window.
@@ -427,9 +428,9 @@
         fun onIntentStarted(willAnimate: Boolean) {}
 
         /**
-         * The animation was cancelled. Note that [onLaunchAnimationEnd] will still be called after
-         * this if the animation was already started, i.e. if [onLaunchAnimationStart] was called
-         * before the cancellation.
+         * The animation was cancelled. Note that [onTransitionAnimationEnd] will still be called
+         * after this if the animation was already started, i.e. if [onTransitionAnimationStart] was
+         * called before the cancellation.
          *
          * If this launch animation affected the occlusion state of the keyguard, WM will provide us
          * with [newKeyguardOccludedState] so that we can set the occluded state appropriately.
@@ -475,11 +476,11 @@
         controller: Controller,
         callback: Callback,
         /** The animator to use to animate the window launch. */
-        launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR,
+        transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
         /** Listener for animation lifecycle events. */
         listener: Listener? = null
     ) : IRemoteAnimationRunner.Stub() {
-        private val context = controller.launchContainer.context
+        private val context = controller.transitionContainer.context
 
         // This is being passed across IPC boundaries and cycles (through PendingIntentRecords,
         // etc.) are possible. So we need to make sure we drop any references that might
@@ -492,7 +493,7 @@
                     controller,
                     callback,
                     DelegatingAnimationCompletionListener(listener, this::dispose),
-                    launchAnimator,
+                    transitionAnimator,
                     disableWmTimeout
                 )
         }
@@ -543,7 +544,7 @@
         /** Listener for animation lifecycle events. */
         private val listener: Listener? = null,
         /** The animator to use to animate the window launch. */
-        private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR,
+        private val transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR,
 
         /**
          * Whether we should disable the WindowManager timeout. This should be set to true in tests
@@ -552,10 +553,10 @@
         // TODO(b/301385865): Remove this flag.
         disableWmTimeout: Boolean = false,
     ) : RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> {
-        private val launchContainer = controller.launchContainer
-        private val context = launchContainer.context
+        private val transitionContainer = controller.transitionContainer
+        private val context = transitionContainer.context
         private val transactionApplierView =
-            controller.openingWindowSyncView ?: controller.launchContainer
+            controller.openingWindowSyncView ?: controller.transitionContainer
         private val transactionApplier = SyncRtSurfaceTransactionApplier(transactionApplierView)
         private val timeoutHandler =
             if (!disableWmTimeout) {
@@ -570,7 +571,7 @@
         private var windowCropF = RectF()
         private var timedOut = false
         private var cancelled = false
-        private var animation: LaunchAnimator.Animation? = null
+        private var animation: TransitionAnimator.Animation? = null
 
         /**
          * A timeout to cancel the launch animation if the remote animation is not started or
@@ -660,7 +661,7 @@
             nonApps: Array<out RemoteAnimationTarget>?,
             iCallback: IRemoteAnimationFinishedCallback?
         ) {
-            if (LaunchAnimator.DEBUG) {
+            if (TransitionAnimator.DEBUG) {
                 Log.d(TAG, "Remote animation started")
             }
 
@@ -687,7 +688,7 @@
 
             val windowBounds = window.screenSpaceBounds
             val endState =
-                LaunchAnimator.State(
+                TransitionAnimator.State(
                     top = windowBounds.top,
                     bottom = windowBounds.bottom,
                     left = windowBounds.left,
@@ -699,7 +700,7 @@
             // TODO(b/184121838): We should somehow get the top and bottom radius of the window
             // instead of recomputing isExpandingFullyAbove here.
             val isExpandingFullyAbove =
-                launchAnimator.isExpandingFullyAbove(controller.launchContainer, endState)
+                transitionAnimator.isExpandingFullyAbove(controller.transitionContainer, endState)
             val endRadius =
                 if (isExpandingFullyAbove) {
                     // Most of the time, expanding fully above the root view means expanding in full
@@ -718,7 +719,7 @@
             val delegate = this.controller
             val controller =
                 object : Controller by delegate {
-                    override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+                    override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
                         listener?.onLaunchAnimationStart()
 
                         if (DEBUG_LAUNCH_ANIMATION) {
@@ -728,10 +729,10 @@
                                     "$isExpandingFullyAbove) [controller=$delegate]"
                             )
                         }
-                        delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+                        delegate.onTransitionAnimationStart(isExpandingFullyAbove)
                     }
 
-                    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+                    override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
                         listener?.onLaunchAnimationEnd()
                         iCallback?.invoke()
 
@@ -742,11 +743,11 @@
                                     "$isExpandingFullyAbove) [controller=$delegate]"
                             )
                         }
-                        delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+                        delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
                     }
 
-                    override fun onLaunchAnimationProgress(
-                        state: LaunchAnimator.State,
+                    override fun onTransitionAnimationProgress(
+                        state: TransitionAnimator.State,
                         progress: Float,
                         linearProgress: Float
                     ) {
@@ -758,12 +759,12 @@
                         navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
 
                         listener?.onLaunchAnimationProgress(linearProgress)
-                        delegate.onLaunchAnimationProgress(state, progress, linearProgress)
+                        delegate.onTransitionAnimationProgress(state, progress, linearProgress)
                     }
                 }
 
             animation =
-                launchAnimator.startAnimation(
+                transitionAnimator.startAnimation(
                     controller,
                     endState,
                     windowBackgroundColor,
@@ -774,7 +775,7 @@
 
         private fun applyStateToWindow(
             window: RemoteAnimationTarget,
-            state: LaunchAnimator.State,
+            state: TransitionAnimator.State,
             linearProgress: Float,
         ) {
             if (transactionApplierView.viewRootImpl == null || !window.leash.isValid) {
@@ -825,7 +826,7 @@
             val alpha =
                 if (controller.isBelowAnimatingWindow) {
                     val windowProgress =
-                        LaunchAnimator.getProgress(
+                        TransitionAnimator.getProgress(
                             TIMINGS,
                             linearProgress,
                             TIMINGS.contentAfterFadeInDelay,
@@ -857,7 +858,7 @@
 
         private fun applyStateToNavigationBar(
             navigationBar: RemoteAnimationTarget,
-            state: LaunchAnimator.State,
+            state: TransitionAnimator.State,
             linearProgress: Float
         ) {
             if (transactionApplierView.viewRootImpl == null || !navigationBar.leash.isValid) {
@@ -868,7 +869,7 @@
             }
 
             val fadeInProgress =
-                LaunchAnimator.getProgress(
+                TransitionAnimator.getProgress(
                     TIMINGS,
                     linearProgress,
                     ANIMATION_DELAY_NAV_FADE_IN,
@@ -890,7 +891,7 @@
                     .withVisibility(true)
             } else {
                 val fadeOutProgress =
-                    LaunchAnimator.getProgress(
+                    TransitionAnimator.getProgress(
                         TIMINGS,
                         linearProgress,
                         0,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 168039e..9a36960 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -58,7 +58,7 @@
     private val callback: Callback,
     private val interactionJankMonitor: InteractionJankMonitor,
     private val featureFlags: AnimationFeatureFlags,
-    private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS),
+    private val transitionAnimator: TransitionAnimator = TransitionAnimator(TIMINGS, INTERPOLATORS),
     private val isForTesting: Boolean = false,
 ) {
     private companion object {
@@ -108,21 +108,21 @@
         fun stopDrawingInOverlay()
 
         /**
-         * Create the [LaunchAnimator.Controller] that will be called to animate the source
+         * Create the [TransitionAnimator.Controller] that will be called to animate the source
          * controlled by this [Controller] during the dialog launch animation.
          *
          * At the end of this animation, the source should *not* be visible anymore (until the
          * dialog is closed and is animated back into the source).
          */
-        fun createLaunchController(): LaunchAnimator.Controller
+        fun createTransitionController(): TransitionAnimator.Controller
 
         /**
-         * Create the [LaunchAnimator.Controller] that will be called to animate the source
+         * Create the [TransitionAnimator.Controller] that will be called to animate the source
          * controlled by this [Controller] during the dialog exit animation.
          *
          * At the end of this animation, the source should be visible again.
          */
-        fun createExitController(): LaunchAnimator.Controller
+        fun createExitController(): TransitionAnimator.Controller
 
         /**
          * Whether we should animate the dialog back into the source when it is dismissed. If this
@@ -270,7 +270,7 @@
 
         val animatedDialog =
             AnimatedDialog(
-                launchAnimator = launchAnimator,
+                transitionAnimator = transitionAnimator,
                 callback = callback,
                 interactionJankMonitor = interactionJankMonitor,
                 controller = controller,
@@ -406,8 +406,8 @@
                 dialog.dismiss()
             }
 
-            override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
-                controller.onLaunchAnimationStart(isExpandingFullyAbove)
+            override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
+                controller.onTransitionAnimationStart(isExpandingFullyAbove)
 
                 // Make sure the dialog is not dismissed during the animation.
                 disableDialogDismiss()
@@ -420,8 +420,8 @@
                 dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
             }
 
-            override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
-                controller.onLaunchAnimationEnd(isExpandingFullyAbove)
+            override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+                controller.onTransitionAnimationEnd(isExpandingFullyAbove)
 
                 // Hide the dialog then dismiss it to instantly dismiss it without playing the
                 // animation.
@@ -492,7 +492,7 @@
 data class DialogCuj(@CujType val cujType: Int, val tag: String? = null)
 
 private class AnimatedDialog(
-    private val launchAnimator: LaunchAnimator,
+    private val transitionAnimator: TransitionAnimator,
     private val callback: DialogLaunchAnimator.Callback,
     private val interactionJankMonitor: InteractionJankMonitor,
 
@@ -892,7 +892,7 @@
         // Create 2 controllers to animate both the dialog and the source.
         val startController =
             if (isLaunching) {
-                controller.createLaunchController()
+                controller.createTransitionController()
             } else {
                 GhostedViewLaunchAnimatorController(dialogContentWithBackground!!)
             }
@@ -902,34 +902,34 @@
             } else {
                 controller.createExitController()
             }
-        startController.launchContainer = decorView
-        endController.launchContainer = decorView
+        startController.transitionContainer = decorView
+        endController.transitionContainer = decorView
 
         val endState = endController.createAnimatorState()
         val controller =
-            object : LaunchAnimator.Controller {
-                override var launchContainer: ViewGroup
-                    get() = startController.launchContainer
+            object : TransitionAnimator.Controller {
+                override var transitionContainer: ViewGroup
+                    get() = startController.transitionContainer
                     set(value) {
-                        startController.launchContainer = value
-                        endController.launchContainer = value
+                        startController.transitionContainer = value
+                        endController.transitionContainer = value
                     }
 
-                override fun createAnimatorState(): LaunchAnimator.State {
+                override fun createAnimatorState(): TransitionAnimator.State {
                     return startController.createAnimatorState()
                 }
 
-                override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+                override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
                     // During launch, onLaunchAnimationStart will be used to remove the temporary
                     // touch surface ghost so it is important to call this before calling
                     // onLaunchAnimationStart on the controller (which will create its own ghost).
                     onLaunchAnimationStart()
 
-                    startController.onLaunchAnimationStart(isExpandingFullyAbove)
-                    endController.onLaunchAnimationStart(isExpandingFullyAbove)
+                    startController.onTransitionAnimationStart(isExpandingFullyAbove)
+                    endController.onTransitionAnimationStart(isExpandingFullyAbove)
                 }
 
-                override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+                override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
                     // onLaunchAnimationEnd is called by an Animator at the end of the animation,
                     // on a Choreographer animation tick. The following calls will move the animated
                     // content from the dialog overlay back to its original position, and this
@@ -943,23 +943,23 @@
                     // that the move of the content back to its original window will be reflected in
                     // the next frame right after [onLaunchAnimationEnd] is called.
                     dialog.context.mainExecutor.execute {
-                        startController.onLaunchAnimationEnd(isExpandingFullyAbove)
-                        endController.onLaunchAnimationEnd(isExpandingFullyAbove)
+                        startController.onTransitionAnimationEnd(isExpandingFullyAbove)
+                        endController.onTransitionAnimationEnd(isExpandingFullyAbove)
 
                         onLaunchAnimationEnd()
                     }
                 }
 
-                override fun onLaunchAnimationProgress(
-                    state: LaunchAnimator.State,
+                override fun onTransitionAnimationProgress(
+                    state: TransitionAnimator.State,
                     progress: Float,
                     linearProgress: Float
                 ) {
-                    startController.onLaunchAnimationProgress(state, progress, linearProgress)
+                    startController.onTransitionAnimationProgress(state, progress, linearProgress)
 
                     // The end view is visible only iff the starting view is not visible.
                     state.visible = !state.visible
-                    endController.onLaunchAnimationProgress(state, progress, linearProgress)
+                    endController.onTransitionAnimationProgress(state, progress, linearProgress)
 
                     // If the dialog content is complex, its dimension might change during the
                     // launch animation. The animation end position might also change during the
@@ -973,7 +973,7 @@
                 }
             }
 
-        launchAnimator.startAnimation(controller, endState, originalDialogBackgroundColor)
+        transitionAnimator.startAnimation(controller, endState, originalDialogBackgroundColor)
     }
 
     private fun shouldAnimateDialogIntoSource(): Boolean {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 055252b..03f10f9 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -66,11 +66,11 @@
 ) : ActivityLaunchAnimator.Controller {
 
     /** The container to which we will add the ghost view and expanding background. */
-    override var launchContainer = ghostedView.rootView as ViewGroup
-    private val launchContainerOverlay: ViewGroupOverlay
-        get() = launchContainer.overlay
+    override var transitionContainer = ghostedView.rootView as ViewGroup
+    private val transitionContainerOverlay: ViewGroupOverlay
+        get() = transitionContainer.overlay
 
-    private val launchContainerLocation = IntArray(2)
+    private val transitionContainerLocation = IntArray(2)
 
     /** The ghost view that is drawn and animated instead of the ghosted view. */
     private var ghostView: GhostView? = null
@@ -78,8 +78,8 @@
     private val ghostViewMatrix = Matrix()
 
     /**
-     * The expanding background view that will be added to [launchContainer] (below [ghostView]) and
-     * animate.
+     * The expanding background view that will be added to [transitionContainer] (below [ghostView])
+     * and animate.
      */
     private var backgroundView: FrameLayout? = null
 
@@ -92,7 +92,7 @@
     private var startBackgroundAlpha: Int = 0xFF
 
     private val ghostedViewLocation = IntArray(2)
-    private val ghostedViewState = LaunchAnimator.State()
+    private val ghostedViewState = TransitionAnimator.State()
 
     /**
      * The background of the [ghostedView]. This background will be used to draw the background of
@@ -175,9 +175,9 @@
         return radius * ghostedView.scaleX
     }
 
-    override fun createAnimatorState(): LaunchAnimator.State {
+    override fun createAnimatorState(): TransitionAnimator.State {
         val state =
-            LaunchAnimator.State(
+            TransitionAnimator.State(
                 topCornerRadius = getCurrentTopCornerRadius(),
                 bottomCornerRadius = getCurrentBottomCornerRadius()
             )
@@ -185,7 +185,7 @@
         return state
     }
 
-    fun fillGhostedViewState(state: LaunchAnimator.State) {
+    fun fillGhostedViewState(state: TransitionAnimator.State) {
         // For the animation we are interested in the area that has a non transparent background,
         // so we have to take the optical insets into account.
         ghostedView.getLocationOnScreen(ghostedViewLocation)
@@ -200,7 +200,7 @@
                 insets.right
     }
 
-    override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+    override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
         if (ghostedView.parent !is ViewGroup) {
             // This should usually not happen, but let's make sure we don't crash if the view was
             // detached right before we started the animation.
@@ -209,7 +209,7 @@
         }
 
         backgroundView =
-            FrameLayout(launchContainer.context).also { launchContainerOverlay.add(it) }
+            FrameLayout(transitionContainer.context).also { transitionContainerOverlay.add(it) }
 
         // We wrap the ghosted view background and use it to draw the expandable background. Its
         // alpha will be set to 0 as soon as we start drawing the expanding background.
@@ -225,7 +225,7 @@
 
         // Create a ghost of the view that will be moving and fading out. This allows to fade out
         // the content before fading out the background.
-        ghostView = GhostView.addGhost(ghostedView, launchContainer)
+        ghostView = GhostView.addGhost(ghostedView, transitionContainer)
 
         // [GhostView.addGhost], the result of which is our [ghostView], creates a [GhostView], and
         // adds it first to a [FrameLayout] container. It then adds _that_ container to an
@@ -244,8 +244,8 @@
         cujType?.let { interactionJankMonitor.begin(ghostedView, it) }
     }
 
-    override fun onLaunchAnimationProgress(
-        state: LaunchAnimator.State,
+    override fun onTransitionAnimationProgress(
+        state: TransitionAnimator.State,
         progress: Float,
         linearProgress: Float
     ) {
@@ -287,15 +287,15 @@
         if (ghostedView.parent is ViewGroup) {
             // Recalculate the matrix in case the ghosted view moved. We ensure that the ghosted
             // view is still attached to a ViewGroup, otherwise calculateMatrix will throw.
-            GhostView.calculateMatrix(ghostedView, launchContainer, ghostViewMatrix)
+            GhostView.calculateMatrix(ghostedView, transitionContainer, ghostViewMatrix)
         }
 
-        launchContainer.getLocationOnScreen(launchContainerLocation)
+        transitionContainer.getLocationOnScreen(transitionContainerLocation)
         ghostViewMatrix.postScale(
             scale,
             scale,
-            ghostedViewState.centerX - launchContainerLocation[0],
-            ghostedViewState.centerY - launchContainerLocation[1]
+            ghostedViewState.centerX - transitionContainerLocation[0],
+            ghostedViewState.centerY - transitionContainerLocation[1]
         )
         ghostViewMatrix.postTranslate(
             (leftChange + rightChange) / 2f,
@@ -310,10 +310,10 @@
         val rightWithInsets = state.right + insets.right
         val bottomWithInsets = state.bottom + insets.bottom
 
-        backgroundView.top = topWithInsets - launchContainerLocation[1]
-        backgroundView.bottom = bottomWithInsets - launchContainerLocation[1]
-        backgroundView.left = leftWithInsets - launchContainerLocation[0]
-        backgroundView.right = rightWithInsets - launchContainerLocation[0]
+        backgroundView.top = topWithInsets - transitionContainerLocation[1]
+        backgroundView.bottom = bottomWithInsets - transitionContainerLocation[1]
+        backgroundView.left = leftWithInsets - transitionContainerLocation[0]
+        backgroundView.right = rightWithInsets - transitionContainerLocation[0]
 
         val backgroundDrawable = backgroundDrawable!!
         backgroundDrawable.wrapped?.let {
@@ -321,7 +321,7 @@
         }
     }
 
-    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+    override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
         if (ghostView == null) {
             // We didn't actually run the animation.
             return
@@ -332,7 +332,7 @@
         backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
 
         GhostView.removeGhost(ghostedView)
-        backgroundView?.let { launchContainerOverlay.remove(it) }
+        backgroundView?.let { transitionContainerOverlay.remove(it) }
 
         if (ghostedView is LaunchableView) {
             // Restore the ghosted view visibility.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
similarity index 79%
rename from packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
rename to packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index d6eba2e..5e4276c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -31,17 +31,18 @@
 import com.android.app.animation.Interpolators.LINEAR
 import kotlin.math.roundToInt
 
-private const val TAG = "LaunchAnimator"
+private const val TAG = "TransitionAnimator"
 
-/** A base class to animate a window launch (activity or dialog) from a view . */
-class LaunchAnimator(private val timings: Timings, private val interpolators: Interpolators) {
+/** A base class to animate a window (activity or dialog) launch to or return from a view . */
+class TransitionAnimator(private val timings: Timings, private val interpolators: Interpolators) {
     companion object {
         internal const val DEBUG = false
         private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
 
         /**
-         * Given the [linearProgress] of a launch animation, return the linear progress of the
-         * sub-animation starting [delay] ms after the launch animation and that lasts [duration].
+         * Given the [linearProgress] of a transition animation, return the linear progress of the
+         * sub-animation starting [delay] ms after the transition animation and that lasts
+         * [duration].
          */
         @JvmStatic
         fun getProgress(
@@ -58,7 +59,7 @@
         }
     }
 
-    private val launchContainerLocation = IntArray(2)
+    private val transitionContainerLocation = IntArray(2)
     private val cornerRadii = FloatArray(8)
 
     /**
@@ -73,7 +74,7 @@
          *
          * This will be used to:
          * - Get the associated [Context].
-         * - Compute whether we are expanding fully above the launch container.
+         * - Compute whether we are expanding fully above the transition container.
          * - Get to overlay to which we initially put the window background layer, until the opening
          *   window is made visible (see [openingWindowSyncView]).
          *
@@ -81,7 +82,7 @@
          * inside a different location, for instance to ensure correct layering during the
          * animation.
          */
-        var launchContainer: ViewGroup
+        var transitionContainer: ViewGroup
 
         /**
          * The [View] with which the opening app window should be synchronized with once it starts
@@ -90,7 +91,7 @@
          * We will also move the window background layer to this view's overlay once the opening
          * window is visible.
          *
-         * If null, this will default to [launchContainer].
+         * If null, this will default to [transitionContainer].
          */
         val openingWindowSyncView: View?
             get() = null
@@ -99,7 +100,7 @@
          * Return the [State] of the view that will be animated. We will animate from this state to
          * the final window state.
          *
-         * Note: This state will be mutated and passed to [onLaunchAnimationProgress] during the
+         * Note: This state will be mutated and passed to [onTransitionAnimationProgress] during the
          * animation.
          */
         fun createAnimatorState(): State
@@ -107,22 +108,22 @@
         /**
          * The animation started. This is typically used to initialize any additional resource
          * needed for the animation. [isExpandingFullyAbove] will be true if the window is expanding
-         * fully above the [launchContainer].
+         * fully above the [transitionContainer].
          */
-        fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {}
+        fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {}
 
         /** The animation made progress and the expandable view [state] should be updated. */
-        fun onLaunchAnimationProgress(state: State, progress: Float, linearProgress: Float) {}
+        fun onTransitionAnimationProgress(state: State, progress: Float, linearProgress: Float) {}
 
         /**
-         * The animation ended. This will be called *if and only if* [onLaunchAnimationStart] was
-         * called previously. This is typically used to clean up the resources initialized when the
-         * animation was started.
+         * The animation ended. This will be called *if and only if* [onTransitionAnimationStart]
+         * was called previously. This is typically used to clean up the resources initialized when
+         * the animation was started.
          */
-        fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {}
+        fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {}
     }
 
-    /** The state of an expandable view during a [LaunchAnimator] animation. */
+    /** The state of an expandable view during a [TransitionAnimator] animation. */
     open class State(
         /** The position of the view in screen space coordinates. */
         var top: Int = 0,
@@ -198,13 +199,13 @@
     )
 
     /**
-     * Start a launch animation controlled by [controller] towards [endState]. An intermediary layer
-     * with [windowBackgroundColor] will fade in then (optionally) fade out above the expanding
-     * view, and should be the same background color as the opening (or closing) window.
+     * Start a transition animation controlled by [controller] towards [endState]. An intermediary
+     * layer with [windowBackgroundColor] will fade in then (optionally) fade out above the
+     * expanding view, and should be the same background color as the opening (or closing) window.
      *
      * If [fadeOutWindowBackgroundLayer] is true, then this intermediary layer will fade out during
      * the second half of the animation, and will have SRC blending mode (ultimately punching a hole
-     * in the [launch container][Controller.launchContainer]) iff [drawHole] is true.
+     * in the [transition container][Controller.transitionContainer]) iff [drawHole] is true.
      */
     fun startAnimation(
         controller: Controller,
@@ -251,13 +252,13 @@
             }
         }
 
-        val launchContainer = controller.launchContainer
-        val isExpandingFullyAbove = isExpandingFullyAbove(launchContainer, endState)
+        val transitionContainer = controller.transitionContainer
+        val isExpandingFullyAbove = isExpandingFullyAbove(transitionContainer, endState)
 
         // We add an extra layer with the same color as the dialog/app splash screen background
         // color, which is usually the same color of the app background. We first fade in this layer
         // to hide the expanding view, then we fade it out with SRC mode to draw a hole in the
-        // launch container and reveal the opening window.
+        // transition container and reveal the opening window.
         val windowBackgroundLayer =
             GradientDrawable().apply {
                 setColor(windowBackgroundColor)
@@ -275,9 +276,9 @@
         val openingWindowSyncViewOverlay = openingWindowSyncView?.overlay
         val moveBackgroundLayerWhenAppIsVisible =
             openingWindowSyncView != null &&
-                openingWindowSyncView.viewRootImpl != controller.launchContainer.viewRootImpl
+                openingWindowSyncView.viewRootImpl != controller.transitionContainer.viewRootImpl
 
-        val launchContainerOverlay = launchContainer.overlay
+        val transitionContainerOverlay = transitionContainer.overlay
         var cancelled = false
         var movedBackgroundLayer = false
 
@@ -287,20 +288,20 @@
                     if (DEBUG) {
                         Log.d(TAG, "Animation started")
                     }
-                    controller.onLaunchAnimationStart(isExpandingFullyAbove)
+                    controller.onTransitionAnimationStart(isExpandingFullyAbove)
 
-                    // Add the drawable to the launch container overlay. Overlays always draw
+                    // Add the drawable to the transition container overlay. Overlays always draw
                     // drawables after views, so we know that it will be drawn above any view added
                     // by the controller.
-                    launchContainerOverlay.add(windowBackgroundLayer)
+                    transitionContainerOverlay.add(windowBackgroundLayer)
                 }
 
                 override fun onAnimationEnd(animation: Animator) {
                     if (DEBUG) {
                         Log.d(TAG, "Animation ended")
                     }
-                    controller.onLaunchAnimationEnd(isExpandingFullyAbove)
-                    launchContainerOverlay.remove(windowBackgroundLayer)
+                    controller.onTransitionAnimationEnd(isExpandingFullyAbove)
+                    transitionContainerOverlay.remove(windowBackgroundLayer)
 
                     if (moveBackgroundLayerWhenAppIsVisible) {
                         openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
@@ -353,17 +354,21 @@
                 // in its new container.
                 movedBackgroundLayer = true
 
-                launchContainerOverlay.remove(windowBackgroundLayer)
+                transitionContainerOverlay.remove(windowBackgroundLayer)
                 openingWindowSyncViewOverlay!!.add(windowBackgroundLayer)
 
-                ViewRootSync.synchronizeNextDraw(launchContainer, openingWindowSyncView, then = {})
+                ViewRootSync.synchronizeNextDraw(
+                    transitionContainer,
+                    openingWindowSyncView,
+                    then = {}
+                )
             }
 
             val container =
                 if (movedBackgroundLayer) {
                     openingWindowSyncView!!
                 } else {
-                    controller.launchContainer
+                    controller.transitionContainer
                 }
 
             applyStateToWindowBackgroundLayer(
@@ -374,7 +379,7 @@
                 fadeOutWindowBackgroundLayer,
                 drawHole
             )
-            controller.onLaunchAnimationProgress(state, progress, linearProgress)
+            controller.onTransitionAnimationProgress(state, progress, linearProgress)
         }
 
         animator.start()
@@ -386,30 +391,30 @@
         }
     }
 
-    /** Return whether we are expanding fully above the [launchContainer]. */
-    internal fun isExpandingFullyAbove(launchContainer: View, endState: State): Boolean {
-        launchContainer.getLocationOnScreen(launchContainerLocation)
-        return endState.top <= launchContainerLocation[1] &&
-            endState.bottom >= launchContainerLocation[1] + launchContainer.height &&
-            endState.left <= launchContainerLocation[0] &&
-            endState.right >= launchContainerLocation[0] + launchContainer.width
+    /** Return whether we are expanding fully above the [transitionContainer]. */
+    internal fun isExpandingFullyAbove(transitionContainer: View, endState: State): Boolean {
+        transitionContainer.getLocationOnScreen(transitionContainerLocation)
+        return endState.top <= transitionContainerLocation[1] &&
+            endState.bottom >= transitionContainerLocation[1] + transitionContainer.height &&
+            endState.left <= transitionContainerLocation[0] &&
+            endState.right >= transitionContainerLocation[0] + transitionContainer.width
     }
 
     private fun applyStateToWindowBackgroundLayer(
         drawable: GradientDrawable,
         state: State,
         linearProgress: Float,
-        launchContainer: View,
+        transitionContainer: View,
         fadeOutWindowBackgroundLayer: Boolean,
         drawHole: Boolean
     ) {
         // Update position.
-        launchContainer.getLocationOnScreen(launchContainerLocation)
+        transitionContainer.getLocationOnScreen(transitionContainerLocation)
         drawable.setBounds(
-            state.left - launchContainerLocation[0],
-            state.top - launchContainerLocation[1],
-            state.right - launchContainerLocation[0],
-            state.bottom - launchContainerLocation[1]
+            state.left - transitionContainerLocation[0],
+            state.top - transitionContainerLocation[1],
+            state.right - transitionContainerLocation[0],
+            state.bottom - transitionContainerLocation[1]
         )
 
         // Update radius.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
index 1290f00..e2a29ab 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
@@ -68,19 +68,19 @@
         }
     }
 
-    override fun createLaunchController(): LaunchAnimator.Controller {
+    override fun createTransitionController(): TransitionAnimator.Controller {
         val delegate = GhostedViewLaunchAnimatorController(source)
-        return object : LaunchAnimator.Controller by delegate {
-            override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+        return object : TransitionAnimator.Controller by delegate {
+            override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
                 // Remove the temporary ghost added by [startDrawingInOverlayOf]. Another
                 // ghost (that ghosts only the source content, and not its background) will
                 // be added right after this by the delegate and will be animated.
                 GhostView.removeGhost(source)
-                delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+                delegate.onTransitionAnimationStart(isExpandingFullyAbove)
             }
 
-            override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
-                delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+            override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+                delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
 
                 // At this point the view visibility is restored by the delegate, so we delay the
                 // visibility changes again and make it invisible while the dialog is shown.
@@ -94,7 +94,7 @@
         }
     }
 
-    override fun createExitController(): LaunchAnimator.Controller {
+    override fun createExitController(): TransitionAnimator.Controller {
         return GhostedViewLaunchAnimatorController(source)
     }
 
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
index ac1ef15..8eb2f2e 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt
@@ -75,7 +75,7 @@
 import androidx.lifecycle.setViewTreeLifecycleOwner
 import androidx.lifecycle.setViewTreeViewModelStoreOwner
 import com.android.systemui.animation.Expandable
-import com.android.systemui.animation.LaunchAnimator
+import com.android.systemui.animation.TransitionAnimator
 import kotlin.math.max
 import kotlin.math.min
 
@@ -301,7 +301,7 @@
 private fun AnimatedContentInOverlay(
     color: Color,
     sizeInOriginalLayout: Size,
-    animatorState: State<LaunchAnimator.State?>,
+    animatorState: State<TransitionAnimator.State?>,
     overlay: ViewGroupOverlay,
     controller: ExpandableControllerImpl,
     content: @Composable (Expandable) -> Unit,
@@ -407,7 +407,7 @@
 
 internal fun measureAndLayoutComposeViewInOverlay(
     view: View,
-    state: LaunchAnimator.State,
+    state: TransitionAnimator.State,
 ) {
     val exactWidth = state.width
     val exactHeight = state.height
@@ -449,7 +449,7 @@
 }
 
 private fun ContentDrawScope.drawBackground(
-    animatorState: LaunchAnimator.State,
+    animatorState: TransitionAnimator.State,
     color: Color,
     border: BorderStroke?,
 ) {
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
index 0e7694e..1020263 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
@@ -44,7 +44,7 @@
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.animation.Expandable
-import com.android.systemui.animation.LaunchAnimator
+import com.android.systemui.animation.TransitionAnimator
 import kotlin.math.roundToInt
 
 /** A controller that can control animated launches from an [Expandable]. */
@@ -70,7 +70,7 @@
     val layoutDirection = LocalLayoutDirection.current
 
     // The current animation state, if we are currently animating a dialog or activity.
-    val animatorState = remember { mutableStateOf<LaunchAnimator.State?>(null) }
+    val animatorState = remember { mutableStateOf<TransitionAnimator.State?>(null) }
 
     // Whether a dialog controlled by this ExpandableController is currently showing.
     val isDialogShowing = remember { mutableStateOf(false) }
@@ -123,7 +123,7 @@
     internal val borderStroke: BorderStroke?,
     internal val composeViewRoot: View,
     internal val density: Density,
-    internal val animatorState: MutableState<LaunchAnimator.State?>,
+    internal val animatorState: MutableState<TransitionAnimator.State?>,
     internal val isDialogShowing: MutableState<Boolean>,
     internal val overlay: MutableState<ViewGroupOverlay?>,
     internal val currentComposeViewInOverlay: MutableState<View?>,
@@ -153,32 +153,32 @@
         }
 
     /**
-     * Create a [LaunchAnimator.Controller] that is going to be used to drive an activity or dialog
-     * animation. This controller will:
+     * Create a [TransitionAnimator.Controller] that is going to be used to drive an activity or
+     * dialog animation. This controller will:
      * 1. Compute the start/end animation state using [boundsInComposeViewRoot] and the location of
      *    composeViewRoot on the screen.
      * 2. Update [animatorState] with the current animation state if we are animating, or null
      *    otherwise.
      */
-    private fun launchController(): LaunchAnimator.Controller {
-        return object : LaunchAnimator.Controller {
+    private fun transitionController(): TransitionAnimator.Controller {
+        return object : TransitionAnimator.Controller {
             private val rootLocationOnScreen = intArrayOf(0, 0)
 
-            override var launchContainer: ViewGroup = composeViewRoot.rootView as ViewGroup
+            override var transitionContainer: ViewGroup = composeViewRoot.rootView as ViewGroup
 
-            override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+            override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
                 animatorState.value = null
             }
 
-            override fun onLaunchAnimationProgress(
-                state: LaunchAnimator.State,
+            override fun onTransitionAnimationProgress(
+                state: TransitionAnimator.State,
                 progress: Float,
                 linearProgress: Float
             ) {
                 // We copy state given that it's always the same object that is mutated by
                 // ActivityLaunchAnimator.
                 animatorState.value =
-                    LaunchAnimator.State(
+                    TransitionAnimator.State(
                             state.top,
                             state.bottom,
                             state.left,
@@ -195,7 +195,7 @@
                 }
             }
 
-            override fun createAnimatorState(): LaunchAnimator.State {
+            override fun createAnimatorState(): TransitionAnimator.State {
                 val boundsInRoot = boundsInComposeViewRoot.value
                 val outline =
                     shape.createOutline(
@@ -236,7 +236,7 @@
                     }
 
                 val rootLocation = rootLocationOnScreen()
-                return LaunchAnimator.State(
+                return TransitionAnimator.State(
                     top = rootLocation.y.roundToInt(),
                     bottom = (rootLocation.y + boundsInRoot.height).roundToInt(),
                     left = rootLocation.x.roundToInt(),
@@ -258,17 +258,18 @@
 
     /** Create an [ActivityLaunchAnimator.Controller] that can be used to animate activities. */
     private fun activityController(cujType: Int?): ActivityLaunchAnimator.Controller {
-        val delegate = launchController()
-        return object : ActivityLaunchAnimator.Controller, LaunchAnimator.Controller by delegate {
-            override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
-                delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+        val delegate = transitionController()
+        return object :
+            ActivityLaunchAnimator.Controller, TransitionAnimator.Controller by delegate {
+            override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
+                delegate.onTransitionAnimationStart(isExpandingFullyAbove)
                 overlay.value = composeViewRoot.rootView.overlay as ViewGroupOverlay
                 cujType?.let { InteractionJankMonitor.getInstance().begin(composeViewRoot, it) }
             }
 
-            override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+            override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
                 cujType?.let { InteractionJankMonitor.getInstance().end(it) }
-                delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+                delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
                 overlay.value = null
             }
         }
@@ -293,11 +294,11 @@
                 }
             }
 
-            override fun createLaunchController(): LaunchAnimator.Controller {
-                val delegate = launchController()
-                return object : LaunchAnimator.Controller by delegate {
-                    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
-                        delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+            override fun createTransitionController(): TransitionAnimator.Controller {
+                val delegate = transitionController()
+                return object : TransitionAnimator.Controller by delegate {
+                    override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+                        delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
 
                         // Make sure we don't draw this expandable when the dialog is showing.
                         isDialogShowing.value = true
@@ -305,11 +306,11 @@
                 }
             }
 
-            override fun createExitController(): LaunchAnimator.Controller {
-                val delegate = launchController()
-                return object : LaunchAnimator.Controller by delegate {
-                    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
-                        delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+            override fun createExitController(): TransitionAnimator.Controller {
+                val delegate = transitionController()
+                return object : TransitionAnimator.Controller by delegate {
+                    override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+                        delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
                         isDialogShowing.value = false
                     }
                 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Color.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Color.kt
new file mode 100644
index 0000000..64b9f2d
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Color.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.systemui.common.ui.compose
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.ui.graphics.Color
+import com.android.compose.theme.colorAttr
+
+/** Resolves [com.android.systemui.common.shared.model.Color] into [Color] */
+@Composable
+@ReadOnlyComposable
+fun com.android.systemui.common.shared.model.Color.toColor(): Color {
+    return when (this) {
+        is com.android.systemui.common.shared.model.Color.Attribute -> colorAttr(attribute)
+        is com.android.systemui.common.shared.model.Color.Loaded -> Color(color)
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/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/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..dacbdb4 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -54,12 +54,8 @@
         private val layoutState =
             MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
 
-        val mutableUserActionsA: MutableMap<UserAction, SceneKey> =
-            mutableMapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
-
-        val mutableUserActionsB: MutableMap<UserAction, SceneKey> =
-            mutableMapOf(Swipe.Up to SceneC, Swipe.Down to SceneA)
-
+        val mutableUserActionsA = mutableMapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
+        val mutableUserActionsB = mutableMapOf(Swipe.Up to SceneC, Swipe.Down to SceneA)
         private val scenesBuilder: SceneTransitionLayoutScope.() -> Unit = {
             scene(
                 key = SceneA,
@@ -507,7 +503,7 @@
         onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
         assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
 
-        mutableUserActionsA[Swipe.Up] = SceneC
+        mutableUserActionsA[Swipe.Up] = UserActionResult(SceneC)
         onDelta(pixels = up(fractionOfScreen = 0.1f))
         // target stays B even though UserActions changed
         assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.2f)
@@ -524,7 +520,7 @@
         onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
         assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
 
-        mutableUserActionsA[Swipe.Up] = SceneC
+        mutableUserActionsA[Swipe.Up] = UserActionResult(SceneC)
         onDelta(pixels = up(fractionOfScreen = 0.1f))
         onDragStopped(velocity = down(fractionOfScreen = 0.1f))
 
diff --git a/packages/SystemUI/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/haptics/slider/SeekableSliderHapticPluginTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekableSliderHapticPluginTest.kt
index ea766f8..805b4a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekableSliderHapticPluginTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekableSliderHapticPluginTest.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.fakeSystemClock
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
@@ -75,7 +74,7 @@
     fun start_afterStop_startsTheTrackingAgain() = runOnStartedPlugin {
         // WHEN the plugin is restarted
         plugin.stop()
-        plugin.start()
+        plugin.startInScope(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // THEN the tracking begins again
         assertThat(plugin.isTracking).isTrue()
@@ -131,22 +130,21 @@
     private fun runOnStartedPlugin(test: suspend TestScope.() -> Unit) =
         with(kosmos) {
             testScope.runTest {
-                createPlugin(this, UnconfinedTestDispatcher(testScheduler))
-                // GIVEN that the plugin is started
-                plugin.start()
+                val pluginScope = CoroutineScope(UnconfinedTestDispatcher(testScheduler))
+                createPlugin()
+                // GIVEN that the plugin is started in a test scope
+                plugin.startInScope(pluginScope)
 
                 // THEN run the test
                 test()
             }
         }
 
-    private fun createPlugin(scope: CoroutineScope, dispatcher: CoroutineDispatcher) {
+    private fun createPlugin() {
         plugin =
             SeekableSliderHapticPlugin(
                 vibratorHelper,
                 kosmos.fakeSystemClock,
-                dispatcher,
-                scope,
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
index 0543bc2..d52696a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -20,6 +20,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -68,6 +69,8 @@
 
     @Before
     fun setUp() {
+        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+
         MockitoAnnotations.initMocks(this)
         whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow)
         kosmos.burnInInteractor = burnInInteractor
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 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/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/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/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..15688c5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1599,6 +1599,15 @@
     <!-- Accessibility label for hotspot icon [CHAR LIMIT=NONE] -->
     <string name="accessibility_status_bar_hotspot">Hotspot</string>
 
+    <!-- Accessibility label for no satellite connection [CHAR LIMIT=NONE] -->
+    <string name="accessibility_status_bar_satellite_no_connection">Satellite, no connection</string>
+    <!-- Accessibility label for poor satellite connection [CHAR LIMIT=NONE] -->
+    <string name="accessibility_status_bar_satellite_poor_connection">Satellite, poor connection</string>
+    <!-- Accessibility label for good satellite connection [CHAR LIMIT=NONE] -->
+    <string name="accessibility_status_bar_satellite_good_connection">Satellite, good connection</string>
+    <!-- Accessibility label for available satellite connection [CHAR LIMIT=NONE] -->
+    <string name="accessibility_status_bar_satellite_available">Satellite, connection available</string>
+
     <!-- Accessibility label for managed profile icon (not shown on screen) [CHAR LIMIT=NONE] -->
     <string name="accessibility_managed_profile">Work profile</string>
 
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/SystemUIAppComponentFactoryBase.kt b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
index e88aaf01..aab0b1e 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
@@ -22,7 +22,6 @@
 import android.content.ContentProvider
 import android.content.Context
 import android.content.Intent
-import android.util.Log
 import androidx.core.app.AppComponentFactory
 import com.android.systemui.dagger.ContextComponentHelper
 import com.android.systemui.dagger.SysUIComponent
@@ -91,7 +90,8 @@
         return app
     }
 
-    @UsesReflection(KeepTarget(instanceOfClassConstant = SysUIComponent::class, methodName = "inject"))
+    @UsesReflection(
+            KeepTarget(instanceOfClassConstant = SysUIComponent::class, methodName = "inject"))
     override fun instantiateProviderCompat(cl: ClassLoader, className: String): ContentProvider {
         val contentProvider = super.instantiateProviderCompat(cl, className)
         if (contentProvider is ContextInitializer) {
@@ -103,11 +103,12 @@
                         .getMethod("inject", contentProvider.javaClass)
                     injectMethod.invoke(rootComponent, contentProvider)
                 } catch (e: NoSuchMethodException) {
-                    Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e)
+                    throw RuntimeException("No injector for class: " + contentProvider.javaClass, e)
                 } catch (e: IllegalAccessException) {
-                    Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e)
+                    throw RuntimeException("Injector inaccessible for class: " +
+                            contentProvider.javaClass, e)
                 } catch (e: InvocationTargetException) {
-                    Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e)
+                    throw RuntimeException("Error while injecting: " + contentProvider.javaClass, e)
                 }
                 initializer
             }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 8aae206..15ef61e 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -42,6 +42,7 @@
 import com.android.systemui.dagger.GlobalRootComponent;
 import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.process.ProcessWrapper;
 import com.android.systemui.res.R;
 import com.android.systemui.startable.Dependencies;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -77,6 +78,7 @@
     private SystemUIAppComponentFactoryBase.ContextAvailableCallback mContextAvailableCallback;
     private SysUIComponent mSysUIComponent;
     private SystemUIInitializer mInitializer;
+    private ProcessWrapper mProcessWrapper;
 
     public SystemUIApplication() {
         super();
@@ -115,6 +117,7 @@
         // Enable Looper trace points.
         // This allows us to see Handler callbacks on traces.
         rootComponent.getMainLooper().setTraceTag(Trace.TRACE_TAG_APP);
+        mProcessWrapper = rootComponent.getProcessWrapper();
 
         // Set the application theme that is inherited by all services. Note that setting the
         // application theme in the manifest does only work for activities. Keep this in sync with
@@ -132,7 +135,7 @@
             View.setTraceLayoutSteps(true);
         }
 
-        if (rootComponent.getProcessWrapper().isSystemUser()) {
+        if (mProcessWrapper.isSystemUser()) {
             IntentFilter bootCompletedFilter = new
                     IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED);
             bootCompletedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
@@ -199,7 +202,11 @@
      * <p>This method must only be called from the main thread.</p>
      */
 
-    public void startServicesIfNeeded() {
+    public void startSystemUserServicesIfNeeded() {
+        if (!mProcessWrapper.isSystemUser()) {
+            Log.wtf(TAG, "Tried starting SystemUser services on non-SystemUser");
+            return;  // Per-user startables are handled in #startSystemUserServicesIfNeeded.
+        }
         final String vendorComponent = mInitializer.getVendorComponent(getResources());
 
         // Sort the startables so that we get a deterministic ordering.
@@ -219,6 +226,9 @@
      * <p>This method must only be called from the main thread.</p>
      */
     void startSecondaryUserServicesIfNeeded() {
+        if (mProcessWrapper.isSystemUser()) {
+            return;  // Per-user startables are handled in #startSystemUserServicesIfNeeded.
+        }
         // Sort the startables so that we get a deterministic ordering.
         Map<Class<?>, Provider<CoreStartable>> sortedStartables = new TreeMap<>(
                 Comparator.comparing(Class::getName));
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index 872b005..1a9b01f 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -22,10 +22,10 @@
 import android.os.HandlerThread;
 import android.util.Log;
 
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.GlobalRootComponent;
 import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dagger.WMComponent;
+import com.android.systemui.res.R;
 import com.android.systemui.util.InitializationChecker;
 import com.android.wm.shell.dagger.WMShellConcurrencyModule;
 import com.android.wm.shell.keyguard.KeyguardTransitions;
@@ -124,9 +124,6 @@
                     .setDesktopMode(Optional.ofNullable(null));
         }
         mSysUIComponent = builder.build();
-        if (initializeComponents) {
-            mSysUIComponent.init();
-        }
 
         // Every other part of our codebase currently relies on Dependency, so we
         // really need to ensure the Dependency gets initialized early on.
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java b/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java
index f4ec6f7..407f764 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java
@@ -19,12 +19,31 @@
 import android.app.Service;
 import android.content.Intent;
 import android.os.IBinder;
+import android.util.Log;
+
+import com.android.systemui.process.ProcessWrapper;
+
+import javax.inject.Inject;
 
 public class SystemUISecondaryUserService extends Service {
 
+    private static final String TAG = "SysUISecondaryService";
+
+    private final ProcessWrapper mProcessWrapper;
+
+    @Inject
+    SystemUISecondaryUserService(ProcessWrapper processWrapper) {
+        mProcessWrapper = processWrapper;
+    }
+
     @Override
     public void onCreate() {
         super.onCreate();
+        if (mProcessWrapper.isSystemUser()) {
+            Log.w(TAG, "SecondaryServices started for System User. Stopping it.");
+            stopSelf();
+            return;
+        }
         ((SystemUIApplication) getApplication()).startSecondaryUserServicesIfNeeded();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 76c2282..b26be0c 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -77,7 +77,7 @@
         super.onCreate();
 
         // Start all of SystemUI
-        ((SystemUIApplication) getApplication()).startServicesIfNeeded();
+        ((SystemUIApplication) getApplication()).startSystemUserServicesIfNeeded();
 
         // Finish initializing dump logic
         mLogBufferFreezer.attach(mBroadcastDispatcher);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
index bf121fb..e57323b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
@@ -26,6 +26,7 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Rect;
+import android.text.Layout;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewTreeObserver;
@@ -162,6 +163,7 @@
         mTextView.setPadding(/* left= */ 0, textPadding, /* right= */ 0, textPadding);
         mTextView.setTextSize(COMPLEX_UNIT_PX, textSize);
         mTextView.setTextColor(textColor);
+        mTextView.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL);
 
         final ColorStateList colorAccent = Utils.getColorAccent(getContext());
         mUndoButton.setText(res.getString(R.string.accessibility_floating_button_undo));
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/Color.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Color.kt
new file mode 100644
index 0000000..d235c95
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Color.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.systemui.common.shared.model
+
+import android.annotation.AttrRes
+import android.annotation.ColorInt
+
+/**
+ * Models a color that can be either a specific [Color.Loaded] value or a resolvable theme
+ * [Color.Attribute]
+ */
+sealed interface Color {
+
+    data class Loaded(@ColorInt val color: Int) : Color
+
+    data class Attribute(@AttrRes val attribute: Int) : Color
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
index 12be32c..964eb6f 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
@@ -24,17 +24,12 @@
 import androidx.annotation.LayoutRes
 import com.android.settingslib.Utils
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.onDensityOrFontScaleChanged
 import com.android.systemui.statusbar.policy.onThemeChanged
 import com.android.systemui.util.kotlin.emitOnStart
-import com.android.systemui.util.view.bindLatest
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 
@@ -91,46 +86,3 @@
             .map { layoutInflater.inflate(id, root, attachToRoot) as T }
     }
 }
-
-/**
- * Perform an inflation right away, then re-inflate whenever the device configuration changes, and
- * call [onInflate] on the resulting view each time. Disposes of the [DisposableHandle] returned by
- * [onInflate] when done.
- *
- * This never completes unless cancelled, it just suspends and waits for updates. It runs on a
- * background thread using [backgroundDispatcher].
- *
- * For parameters [resource], [root] and [attachToRoot], see [LayoutInflater.inflate].
- *
- * An example use-case of this is when a view needs to be re-inflated whenever a configuration
- * change occurs, which would require the ViewBinder to then re-bind the new view. For example, the
- * code in the parent view's binder would look like:
- * ```
- * parentView.repeatWhenAttached {
- *     configurationState
- *         .reinflateAndBindLatest(
- *             R.layout.my_layout,
- *             parentView,
- *             attachToRoot = false,
- *             coroutineScope = lifecycleScope,
- *             configurationController.onThemeChanged,
- *         ) { view: ChildView ->
- *             ChildViewBinder.bind(view, childViewModel)
- *         }
- * }
- * ```
- *
- * In turn, the bind method (passed through [onInflate]) uses [repeatWhenAttached], which returns a
- * [DisposableHandle].
- */
-suspend fun <T : View> ConfigurationState.reinflateAndBindLatest(
-    @LayoutRes resource: Int,
-    root: ViewGroup?,
-    attachToRoot: Boolean,
-    backgroundDispatcher: CoroutineDispatcher,
-    onInflate: (T) -> DisposableHandle?,
-) {
-    inflateLayout<T>(resource, root, attachToRoot)
-        .flowOn(backgroundDispatcher)
-        .bindLatest(onInflate)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index dd186d6..f7bc5cdc 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -26,10 +26,13 @@
 import com.android.systemui.ScreenDecorationsModule;
 import com.android.systemui.accessibility.SystemActionsModule;
 import com.android.systemui.battery.BatterySaverModule;
+import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerImpl;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.media.dagger.MediaModule;
+import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
+import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.navigationbar.NavigationBarControllerModule;
 import com.android.systemui.navigationbar.gestural.GestureModule;
 import com.android.systemui.plugins.qs.QSFactory;
@@ -63,6 +66,7 @@
 import com.android.systemui.statusbar.policy.SensorPrivacyController;
 import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
 import com.android.systemui.toast.ToastModule;
+import com.android.systemui.unfold.SysUIUnfoldStartableModule;
 import com.android.systemui.unfold.UnfoldTransitionModule;
 import com.android.systemui.volume.dagger.VolumeModule;
 import com.android.systemui.wallpapers.dagger.WallpaperModule;
@@ -92,12 +96,15 @@
         AospPolicyModule.class,
         BatterySaverModule.class,
         CollapsedStatusBarFragmentStartableModule.class,
+        ConnectingDisplayViewModel.StartableModule.class,
         GestureModule.class,
         HeadsUpModule.class,
         KeyboardShortcutsModule.class,
         MediaModule.class,
+        MediaMuteAwaitConnectionCli.StartableModule.class,
         MultiUserUtilsModule.class,
         NavigationBarControllerModule.class,
+        NearbyMediaDevicesManager.StartableModule.class,
         PowerModule.class,
         QSModule.class,
         RearDisplayModule.class,
@@ -108,6 +115,7 @@
         ShadeModule.class,
         StartCentralSurfacesModule.class,
         SceneContainerFrameworkModule.class,
+        SysUIUnfoldStartableModule.class,
         UnfoldTransitionModule.Startables.class,
         ToastModule.class,
         VolumeModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index e7b8773..3b0c281 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -19,25 +19,15 @@
 import com.android.systemui.BootCompleteCacheImpl;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.Dependency;
-import com.android.systemui.Flags;
 import com.android.systemui.InitController;
 import com.android.systemui.SystemUIAppComponentFactoryBase;
 import com.android.systemui.dagger.qualifiers.PerUser;
-import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
-import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
-import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.people.PeopleProvider;
 import com.android.systemui.statusbar.NotificationInsetsModule;
 import com.android.systemui.statusbar.QsFrameTranslateModule;
 import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.unfold.FoldStateLogger;
-import com.android.systemui.unfold.FoldStateLoggingProvider;
-import com.android.systemui.unfold.SysUIUnfoldComponent;
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
-import com.android.systemui.unfold.dagger.UnfoldBg;
-import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder;
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.desktopmode.DesktopMode;
@@ -126,42 +116,6 @@
     }
 
     /**
-     * Initializes all the SysUI components.
-     */
-    default void init() {
-        // Initialize components that have no direct tie to the dagger dependency graph,
-        // but are critical to this component's operation
-        getSysUIUnfoldComponent()
-                .ifPresent(
-                        c -> {
-                            c.getUnfoldLightRevealOverlayAnimation().init();
-                            c.getUnfoldTransitionWallpaperController().init();
-                            c.getUnfoldHapticsPlayer();
-                            c.getNaturalRotationUnfoldProgressProvider().init();
-                            c.getUnfoldLatencyTracker().init();
-                        });
-        // No init method needed, just needs to be gotten so that it's created.
-        getMediaMuteAwaitConnectionCli();
-        getNearbyMediaDevicesManager();
-        getConnectingDisplayViewModel().init();
-        getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
-        getFoldStateLogger().ifPresent(FoldStateLogger::init);
-
-        Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider;
-
-        if (Flags.unfoldAnimationBackgroundProgress()) {
-            unfoldTransitionProgressProvider = getBgUnfoldTransitionProgressProvider();
-        } else {
-            unfoldTransitionProgressProvider = getUnfoldTransitionProgressProvider();
-        }
-        unfoldTransitionProgressProvider
-                .ifPresent(
-                        (progressProvider) ->
-                                getUnfoldTransitionProgressForwarder()
-                                        .ifPresent(progressProvider::addCallback));
-    }
-
-    /**
      * Provides a BootCompleteCache.
      */
     @SysUISingleton
@@ -180,37 +134,6 @@
     ContextComponentHelper getContextComponentHelper();
 
     /**
-     * Creates a UnfoldTransitionProgressProvider that calculates progress in the background.
-     */
-    @SysUISingleton
-    @UnfoldBg
-    Optional<UnfoldTransitionProgressProvider> getBgUnfoldTransitionProgressProvider();
-
-    /**
-     * Creates a UnfoldTransitionProgressProvider that calculates progress in the main thread.
-     */
-    @SysUISingleton
-    Optional<UnfoldTransitionProgressProvider> getUnfoldTransitionProgressProvider();
-
-    /**
-     * Creates a UnfoldTransitionProgressForwarder.
-     */
-    @SysUISingleton
-    Optional<UnfoldTransitionProgressForwarder> getUnfoldTransitionProgressForwarder();
-
-    /**
-     * Creates a FoldStateLoggingProvider.
-     */
-    @SysUISingleton
-    Optional<FoldStateLoggingProvider> getFoldStateLoggingProvider();
-
-    /**
-     * Creates a FoldStateLogger.
-     */
-    @SysUISingleton
-    Optional<FoldStateLogger> getFoldStateLogger();
-
-    /**
      * Main dependency providing module.
      */
     @SysUISingleton
@@ -227,22 +150,6 @@
     InitController getInitController();
 
     /**
-     * For devices with a hinge: access objects within this component
-     */
-    Optional<SysUIUnfoldComponent> getSysUIUnfoldComponent();
-
-    /** */
-    MediaMuteAwaitConnectionCli getMediaMuteAwaitConnectionCli();
-
-    /** */
-    NearbyMediaDevicesManager getNearbyMediaDevicesManager();
-
-    /**
-     * Creates a ConnectingDisplayViewModel
-     */
-    ConnectingDisplayViewModel getConnectingDisplayViewModel();
-
-    /**
      * Returns {@link CoreStartable}s that should be started with the application.
      */
     Map<Class<?>, Provider<CoreStartable>> getStartables();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 28fd9a9..1720de8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -17,6 +17,7 @@
 package com.android.systemui.dagger;
 
 import android.app.INotificationManager;
+import android.app.Service;
 import android.content.Context;
 import android.service.dreams.IDreamManager;
 
@@ -28,6 +29,7 @@
 import com.android.systemui.BootCompleteCache;
 import com.android.systemui.BootCompleteCacheImpl;
 import com.android.systemui.CameraProtectionModule;
+import com.android.systemui.SystemUISecondaryUserService;
 import com.android.systemui.accessibility.AccessibilityModule;
 import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule;
 import com.android.systemui.appops.dagger.AppOpsModule;
@@ -150,6 +152,8 @@
 import dagger.BindsOptionalOf;
 import dagger.Module;
 import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
 
 import java.util.Collections;
 import java.util.Optional;
@@ -384,4 +388,9 @@
     @Binds
     abstract LargeScreenShadeInterpolator largeScreensShadeInterpolator(
             LargeScreenShadeInterpolatorImpl impl);
+
+    @Binds
+    @IntoMap
+    @ClassKey(SystemUISecondaryUserService.class)
+    abstract Service bindsSystemUISecondaryUserService(SystemUISecondaryUserService service);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
index 684627b..2461c26 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
@@ -39,4 +39,6 @@
     /** Provide the current status of fingerprint authentication. */
     val authenticationStatus: Flow<FingerprintAuthenticationStatus> =
         repository.authenticationStatus
+
+    val isLockedOut: Flow<Boolean> = repository.isLockedOut
 }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index 98130eb..cf91e14 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -36,7 +36,6 @@
 import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
 import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.log.FaceAuthenticationLogger
@@ -78,7 +77,7 @@
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val faceAuthenticationLogger: FaceAuthenticationLogger,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-    private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
+    private val deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
     private val userRepository: UserRepository,
     private val facePropertyRepository: FacePropertyRepository,
     private val faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig,
@@ -149,14 +148,24 @@
             }
             .launchIn(applicationScope)
 
-        deviceEntryFingerprintAuthRepository.isLockedOut
-            .onEach {
-                if (it) {
+        deviceEntryFingerprintAuthInteractor.isLockedOut
+            .sample(biometricSettingsRepository.isFaceAuthEnrolledAndEnabled, ::Pair)
+            .filter { (_, faceEnabledAndEnrolled) ->
+                // We don't care about this if face auth is not enabled.
+                faceEnabledAndEnrolled
+            }
+            .map { (fpLockedOut, _) -> fpLockedOut }
+            .sample(userRepository.selectedUser, ::Pair)
+            .onEach { (fpLockedOut, currentUser) ->
+                if (fpLockedOut) {
                     faceAuthenticationLogger.faceLockedOut("Fingerprint locked out")
-                    // We don't care about this if face auth is not enabled.
                     if (isFaceAuthEnabledAndEnrolled()) {
                         repository.setLockedOut(true)
                     }
+                } else {
+                    // Fingerprint is not locked out anymore, revert face lockout state back to
+                    // previous value.
+                    resetLockedOutState(currentUser.userInfo.id)
                 }
             }
             .launchIn(applicationScope)
@@ -169,10 +178,7 @@
                 val wasSwitching = previous.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
                 val isSwitching = curr.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
                 if (wasSwitching && !isSwitching) {
-                    val lockoutMode = facePropertyRepository.getLockoutMode(curr.userInfo.id)
-                    repository.setLockedOut(
-                        lockoutMode == LockoutMode.PERMANENT || lockoutMode == LockoutMode.TIMED
-                    )
+                    resetLockedOutState(curr.userInfo.id)
                     yield()
                     runFaceAuth(
                         FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING,
@@ -185,6 +191,13 @@
             .launchIn(applicationScope)
     }
 
+    private suspend fun resetLockedOutState(currentUserId: Int) {
+        val lockoutMode = facePropertyRepository.getLockoutMode(currentUserId)
+        repository.setLockedOut(
+            lockoutMode == LockoutMode.PERMANENT || lockoutMode == LockoutMode.TIMED
+        )
+    }
+
     override fun onSwipeUpOnBouncer() {
         runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
index 10aa703..190062c 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
@@ -18,6 +18,7 @@
 import android.app.Dialog
 import android.content.Context
 import com.android.server.policy.feature.flags.Flags
+import com.android.systemui.CoreStartable
 import com.android.systemui.biometrics.Utils
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -26,6 +27,10 @@
 import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
 import com.android.systemui.display.ui.view.MirroringConfirmationDialog
 import com.android.systemui.statusbar.policy.ConfigurationController
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -47,12 +52,12 @@
     @Application private val scope: CoroutineScope,
     @Background private val bgDispatcher: CoroutineDispatcher,
     private val configurationController: ConfigurationController,
-) {
+) : CoreStartable {
 
     private var dialog: Dialog? = null
 
     /** Starts listening for pending displays. */
-    fun init() {
+    override fun start() {
         val pendingDisplayFlow = connectedDisplayInteractor.pendingDisplay
         val concurrentDisplaysInProgessFlow =
             if (Flags.enableDualDisplayBlocking()) {
@@ -96,4 +101,12 @@
         dialog?.hide()
         dialog = null
     }
+
+    @Module
+    interface StartableModule {
+        @Binds
+        @IntoMap
+        @ClassKey(ConnectingDisplayViewModel::class)
+        fun bindsConnectingDisplayViewModel(impl: ConnectingDisplayViewModel): CoreStartable
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/HapticSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/HapticSliderViewBinder.kt
new file mode 100644
index 0000000..304fdd6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/HapticSliderViewBinder.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.slider
+
+import android.view.View
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.awaitCancellation
+
+object HapticSliderViewBinder {
+    /**
+     * Binds a [SeekableSliderHapticPlugin] to a [View]. The binded view should be a
+     * [android.widget.SeekBar] or a container of a [android.widget.SeekBar]
+     */
+    @JvmStatic
+    fun bind(view: View?, plugin: SeekableSliderHapticPlugin) {
+        view?.repeatWhenAttached {
+            plugin.startInScope(lifecycleScope)
+            try {
+                awaitCancellation()
+            } finally {
+                plugin.stop()
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
index 58fb6a9..931a869 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
@@ -20,11 +20,8 @@
 import android.view.VelocityTracker
 import android.widget.SeekBar
 import androidx.annotation.VisibleForTesting
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.delay
@@ -43,10 +40,8 @@
 constructor(
     vibratorHelper: VibratorHelper,
     systemClock: SystemClock,
-    @Main private val mainDispatcher: CoroutineDispatcher,
-    @Application private val applicationScope: CoroutineScope,
     sliderHapticFeedbackConfig: SliderHapticFeedbackConfig = SliderHapticFeedbackConfig(),
-    sliderTrackerConfig: SeekableSliderTrackerConfig = SeekableSliderTrackerConfig(),
+    private val sliderTrackerConfig: SeekableSliderTrackerConfig = SeekableSliderTrackerConfig(),
 ) {
 
     private val velocityTracker = VelocityTracker.obtain()
@@ -61,19 +56,15 @@
             systemClock,
         )
 
-    private val sliderTracker =
-        SeekableSliderTracker(
-            sliderHapticFeedbackProvider,
-            sliderEventProducer,
-            mainDispatcher,
-            sliderTrackerConfig,
-        )
+    private var sliderTracker: SeekableSliderTracker? = null
+
+    private var pluginScope: CoroutineScope? = null
 
     val isTracking: Boolean
-        get() = sliderTracker.isTracking
+        get() = sliderTracker?.isTracking == true
 
-    val trackerState: SliderState
-        get() = sliderTracker.currentState
+    val trackerState: SliderState?
+        get() = sliderTracker?.currentState
 
     /**
      * A waiting [Job] for a timer that estimates the key-up event when a key-down event is
@@ -89,14 +80,20 @@
         get() = keyUpJob != null && keyUpJob?.isActive == true
 
     /**
-     * Start the plugin.
-     *
-     * This starts the tracking of slider states, events and triggering of haptic feedback.
+     * Specify the scope for the plugin's operations and start the slider tracker in this scope.
+     * This also involves the key-up timer job.
      */
-    fun start() {
-        if (!isTracking) {
-            sliderTracker.startTracking()
-        }
+    fun startInScope(scope: CoroutineScope) {
+        if (sliderTracker != null) stop()
+        sliderTracker =
+            SeekableSliderTracker(
+                sliderHapticFeedbackProvider,
+                sliderEventProducer,
+                scope,
+                sliderTrackerConfig,
+            )
+        pluginScope = scope
+        sliderTracker?.startTracking()
     }
 
     /**
@@ -104,7 +101,7 @@
      *
      * This stops the tracking of slider states, events and triggers of haptic feedback.
      */
-    fun stop() = sliderTracker.stopTracking()
+    fun stop() = sliderTracker?.stopTracking()
 
     /** React to a touch event */
     fun onTouchEvent(event: MotionEvent?) {
@@ -147,9 +144,9 @@
     /**
      * An external key was pressed (e.g., a volume key).
      *
-     * This event is used to estimate the key-up event based on by running a timer as a waiting
-     * coroutine in the [keyUpTimerScope]. A key-up event in a slider corresponds to an onArrowUp
-     * event. Therefore, [onArrowUp] must be called after the timeout.
+     * This event is used to estimate the key-up event based on a running a timer as a waiting
+     * coroutine in the [pluginScope]. A key-up event in a slider corresponds to an onArrowUp event.
+     * Therefore, [onArrowUp] must be called after the timeout.
      */
     fun onKeyDown() {
         if (!isTracking) return
@@ -159,7 +156,7 @@
             keyUpJob?.cancel()
         }
         keyUpJob =
-            applicationScope.launch {
+            pluginScope?.launch {
                 delay(KEY_UP_TIMEOUT)
                 onArrowUp()
             }
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
index 10098fa..0af3038 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt
@@ -17,9 +17,7 @@
 package com.android.systemui.haptics.slider
 
 import androidx.annotation.VisibleForTesting
-import com.android.systemui.dagger.qualifiers.Main
 import kotlin.math.abs
-import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.delay
@@ -31,21 +29,20 @@
  *
  * The tracker runs a state machine to execute actions on touch-based events typical of a seekable
  * slider such as [android.widget.SeekBar]. Coroutines responsible for running the state machine,
- * collecting slider events and maintaining waiting states are run on the main thread via the
- * [com.android.systemui.dagger.qualifiers.Main] coroutine dispatcher.
+ * collecting slider events and maintaining waiting states are run on the provided [CoroutineScope].
  *
  * @param[sliderStateListener] Listener of the slider state.
  * @param[sliderEventProducer] Producer of slider events arising from the slider.
- * @param[mainDispatcher] [CoroutineDispatcher] used to launch coroutines for the collection of
- *   slider events and the launch of timer jobs.
+ * @param[trackerScope] [CoroutineScope] used to launch coroutines for the collection of slider
+ *   events and the launch of timer jobs.
  * @property[config] Configuration parameters of the slider tracker.
  */
 class SeekableSliderTracker(
     sliderStateListener: SliderStateListener,
     sliderEventProducer: SliderEventProducer,
-    @Main mainDispatcher: CoroutineDispatcher,
+    trackerScope: CoroutineScope,
     private val config: SeekableSliderTrackerConfig = SeekableSliderTrackerConfig(),
-) : SliderTracker(CoroutineScope(mainDispatcher), sliderStateListener, sliderEventProducer) {
+) : SliderTracker(trackerScope, sliderStateListener, sliderEventProducer) {
 
     // History of the latest progress collected from slider events
     private var latestProgress = 0f
diff --git a/packages/SystemUI/src/com/android/systemui/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..39bbf07 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -131,7 +131,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.animation.LaunchAnimator;
+import com.android.systemui.animation.TransitionAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -960,7 +960,7 @@
     final ActivityLaunchAnimator.Controller mOccludeAnimationController =
             new ActivityLaunchAnimator.Controller() {
                 @Override
-                public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {
+                public void onTransitionAnimationStart(boolean isExpandingFullyAbove) {
                     mOccludeAnimationPlaying = true;
                     mScrimControllerLazy.get().setOccludeAnimationPlaying(true);
                 }
@@ -977,7 +977,7 @@
                 }
 
                 @Override
-                public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
+                public void onTransitionAnimationEnd(boolean launchIsFullScreen) {
                     if (launchIsFullScreen) {
                         mShadeController.get().instantCollapseShade();
                     }
@@ -994,13 +994,13 @@
 
                 @NonNull
                 @Override
-                public ViewGroup getLaunchContainer() {
+                public ViewGroup getTransitionContainer() {
                     return ((ViewGroup) mKeyguardViewControllerLazy.get()
                             .getViewRootImpl().getView());
                 }
 
                 @Override
-                public void setLaunchContainer(@NonNull ViewGroup launchContainer) {
+                public void setTransitionContainer(@NonNull ViewGroup transitionContainer) {
                     // No-op, launch container is always the shade.
                     Log.wtf(TAG, "Someone tried to change the launch container for the "
                             + "ActivityLaunchAnimator, which should never happen.");
@@ -1008,9 +1008,9 @@
 
                 @NonNull
                 @Override
-                public LaunchAnimator.State createAnimatorState() {
-                    final int fullWidth = getLaunchContainer().getWidth();
-                    final int fullHeight = getLaunchContainer().getHeight();
+                public TransitionAnimator.State createAnimatorState() {
+                    final int fullWidth = getTransitionContainer().getWidth();
+                    final int fullHeight = getTransitionContainer().getHeight();
 
                     if (mUpdateMonitor.isSecureCameraLaunchedOverKeyguard()) {
                         final float initialHeight = fullHeight / 3f;
@@ -1018,7 +1018,7 @@
 
                         // Start the animation near the power button, at one-third size, since the
                         // camera was launched from the power button.
-                        return new LaunchAnimator.State(
+                        return new TransitionAnimator.State(
                                 (int) (mPowerButtonY - initialHeight / 2f) /* top */,
                                 (int) (mPowerButtonY + initialHeight / 2f) /* bottom */,
                                 (int) (fullWidth - initialWidth) /* left */,
@@ -1030,7 +1030,7 @@
 
                         // Start the animation in the center of the screen, scaled down to half
                         // size.
-                        return new LaunchAnimator.State(
+                        return new TransitionAnimator.State(
                                 (int) (fullHeight - initialHeight) / 2,
                                 (int) (initialHeight + (fullHeight - initialHeight) / 2),
                                 (int) (fullWidth - initialWidth) / 2,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/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/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/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/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/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/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/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/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/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 133fa12..73d229e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -113,7 +113,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.animation.LaunchAnimator;
+import com.android.systemui.animation.TransitionAnimator;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
@@ -1348,7 +1348,7 @@
         }
         updateClockAppearance();
         mQsController.updateQsState();
-        if (!KeyguardShadeMigrationNssl.isEnabled()) {
+        if (!KeyguardShadeMigrationNssl.isEnabled() && !FooterViewRefactor.isEnabled()) {
             mNotificationStackScrollLayoutController.updateFooter();
         }
     }
@@ -3231,7 +3231,7 @@
 
     @Override
     public void applyLaunchAnimationProgress(float linearProgress) {
-        boolean hideIcons = LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS,
+        boolean hideIcons = TransitionAnimator.getProgress(ActivityLaunchAnimator.TIMINGS,
                 linearProgress, ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
         if (hideIcons != mHideIconsDuringLaunchAnimation) {
             mHideIconsDuringLaunchAnimation = hideIcons;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 6e85074..ac510fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -156,6 +156,54 @@
     }
 }
 
+data class LinearSideLightRevealEffect(private val isVertical: Boolean) : LightRevealEffect {
+
+    override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
+        scrim.interpolatedRevealAmount = amount
+        scrim.startColorAlpha =
+            getPercentPastThreshold(1 - amount, threshold = 1 - START_COLOR_REVEAL_PERCENTAGE)
+        scrim.revealGradientEndColorAlpha =
+            1f -
+                getPercentPastThreshold(
+                    amount,
+                    threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE
+                )
+
+        val gradientBoundsAmount = lerp(GRADIENT_START_BOUNDS_PERCENTAGE, 1f, amount)
+        if (isVertical) {
+            scrim.setRevealGradientBounds(
+                left = -(scrim.viewWidth) * gradientBoundsAmount,
+                top = -(scrim.viewHeight) * gradientBoundsAmount,
+                right = (scrim.viewWidth) * gradientBoundsAmount,
+                bottom = (scrim.viewHeight) + (scrim.viewHeight) * gradientBoundsAmount
+            )
+        } else {
+            scrim.setRevealGradientBounds(
+                left = -(scrim.viewWidth) * gradientBoundsAmount,
+                top = -(scrim.viewHeight) * gradientBoundsAmount,
+                right = (scrim.viewWidth) + (scrim.viewWidth) * gradientBoundsAmount,
+                bottom = (scrim.viewHeight) * gradientBoundsAmount
+            )
+        }
+    }
+
+    private companion object {
+        // From which percentage we should start the gradient reveal width
+        // E.g. if 0 - starts with 0px width, 0.6f - starts with 60% width
+        private const val GRADIENT_START_BOUNDS_PERCENTAGE: Float = 1f
+
+        // When to start changing alpha color of the gradient scrim
+        // E.g. if 0.6f - starts fading the gradient away at 60% and becomes completely
+        // transparent at 100%
+        private const val REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE: Float = 1f
+
+        // When to finish displaying start color fill that reveals the content
+        // E.g. if 0.6f - the content won't be visible at 0% and it will gradually
+        // reduce the alpha until 60% (at this point the color fill is invisible)
+        private const val START_COLOR_REVEAL_PERCENTAGE: Float = 1f
+    }
+}
+
 data class CircleReveal(
     /** X-value of the circle center of the reveal. */
     val centerX: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
index 785e65d..6f4a7cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt
@@ -4,7 +4,7 @@
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.app.animation.Interpolators
-import com.android.systemui.animation.LaunchAnimator
+import com.android.systemui.animation.TransitionAnimator
 import kotlin.math.min
 
 /** Parameters for the notifications launch expanding animations. */
@@ -16,7 +16,7 @@
 
     topCornerRadius: Float = 0f,
     bottomCornerRadius: Float = 0f
-) : LaunchAnimator.State(top, bottom, left, right, topCornerRadius, bottomCornerRadius) {
+) : TransitionAnimator.State(top, bottom, left, right, topCornerRadius, bottomCornerRadius) {
     @VisibleForTesting
     constructor() : this(
         top = 0, bottom = 0, left = 0, right = 0, topCornerRadius = 0f, bottomCornerRadius = 0f
@@ -58,7 +58,7 @@
         }
 
     fun getProgress(delay: Long, duration: Long): Float {
-        return LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS, linearProgress, delay,
+        return TransitionAnimator.getProgress(ActivityLaunchAnimator.TIMINGS, linearProgress, delay,
             duration)
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
index 5983fc1..8fc9619 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
@@ -20,7 +20,7 @@
 import android.view.ViewGroup
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.LaunchAnimator
+import com.android.systemui.animation.TransitionAnimator
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
@@ -75,13 +75,13 @@
     private val notificationEntry = notification.entry
     private val notificationKey = notificationEntry.sbn.key
 
-    override var launchContainer: ViewGroup
+    override var transitionContainer: ViewGroup
         get() = notification.rootView as ViewGroup
         set(ignored) {
             // Do nothing. Notifications are always animated inside their rootView.
         }
 
-    override fun createAnimatorState(): LaunchAnimator.State {
+    override fun createAnimatorState(): TransitionAnimator.State {
         // If the notification panel is collapsed, the clip may be larger than the height.
         val height = max(0, notification.actualHeight - notification.clipBottomAmount)
         val location = notification.locationOnScreen
@@ -186,14 +186,14 @@
         onFinishAnimationCallback?.run()
     }
 
-    override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+    override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
         notification.isExpandAnimationRunning = true
         notificationListContainer.setExpandingNotification(notification)
 
         jankMonitor.begin(notification, InteractionJankMonitor.CUJ_NOTIFICATION_APP_START)
     }
 
-    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+    override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
         if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
             Log.d(TAG, "onLaunchAnimationEnd()")
         }
@@ -213,8 +213,8 @@
         notificationListContainer.applyLaunchAnimationParams(params)
     }
 
-    override fun onLaunchAnimationProgress(
-        state: LaunchAnimator.State,
+    override fun onTransitionAnimationProgress(
+        state: TransitionAnimator.State,
         progress: Float,
         linearProgress: Float
     ) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index 6e2beb4..8b0b973 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -56,8 +56,8 @@
             if (FooterViewRefactor.isEnabled) {
                 activeNotificationsInteractor.setNotifStats(notifStats)
             }
-            // TODO(b/293167744): This shouldn't be done if the footer flag is on, once the footer
-            //  visibility is handled in the new stack.
+            // TODO(b/293167744): This shouldn't be done if the footer flag is on, once the silent
+            //  section clear action is handled in the new stack.
             controller.setNotifStats(notifStats)
             if (NotificationIconContainerRefactor.isEnabled || FooterViewRefactor.isEnabled) {
                 renderListInteractor.setRenderedList(entries)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt
index 0a9e12a..ccf6f40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationSettingsInteractorModule.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
index 5111c11..b23ef35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -52,6 +52,9 @@
             isVisible =
                 activeNotificationsInteractor.hasClearableNotifications
                     .sample(
+                        // TODO(b/322167853): This check is currently duplicated in
+                        //  NotificationListViewModel, but instead it should be a field in
+                        //  ShadeAnimationInteractor.
                         combine(
                                 shadeInteractor.isShadeFullyExpanded,
                                 shadeInteractor.isShadeTouchable,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
index d903f06..8768ea2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
@@ -29,6 +29,8 @@
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
@@ -56,6 +58,8 @@
                 panelTouchesEnabled && isKeyguardVisible
             }
             .flowOn(bgContext)
+            .conflate()
+            .distinctUntilChanged()
 
     /** Amount of a "white" tint to be applied to the icons. */
     val tintAlpha: Flow<Float> =
@@ -70,6 +74,8 @@
                 aodAmt + dozeAmt // If transitioning between them, they should sum to 1f
             }
             .flowOn(bgContext)
+            .conflate()
+            .distinctUntilChanged()
 
     /** Are notification icons animated (ex: animated gif)? */
     val areIconAnimationsEnabled: Flow<Boolean> =
@@ -78,8 +84,10 @@
                 // Don't animate icons when we're on AOD / dozing
                 it != KeyguardState.AOD && it != KeyguardState.DOZING
             }
-            .flowOn(bgContext)
             .onStart { emit(true) }
+            .flowOn(bgContext)
+            .conflate()
+            .distinctUntilChanged()
 
     /** [NotificationIconsViewData] indicating which icons to display in the view. */
     val icons: Flow<NotificationIconsViewData> =
@@ -91,4 +99,6 @@
                 )
             }
             .flowOn(bgContext)
+            .conflate()
+            .distinctUntilChanged()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
index 3574828..260cccd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
@@ -21,6 +21,8 @@
 import javax.inject.Inject
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 
@@ -56,4 +58,6 @@
                 )
             }
             .flowOn(bgContext)
+            .conflate()
+            .distinctUntilChanged()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
index 38921c2..a64f888 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
@@ -35,6 +35,7 @@
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.conflate
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flowOn
@@ -64,10 +65,13 @@
                 panelTouchesEnabled && !isKeyguardShowing
             }
             .flowOn(bgContext)
+            .conflate()
+            .distinctUntilChanged()
 
     /** The colors with which to display the notification icons. */
     val iconColors: Flow<NotificationIconColorLookup> =
-        combine(darkIconInteractor.tintAreas, darkIconInteractor.tintColor) { areas, tint ->
+        darkIconInteractor.darkState
+            .map { (areas: Collection<Rect>, tint: Int) ->
                 NotificationIconColorLookup { viewBounds: Rect ->
                     if (DarkIconDispatcher.isInAreas(areas, viewBounds)) {
                         IconColorsImpl(tint, areas)
@@ -77,6 +81,8 @@
                 }
             }
             .flowOn(bgContext)
+            .conflate()
+            .distinctUntilChanged()
 
     /** [NotificationIconsViewData] indicating which icons to display in the view. */
     val icons: Flow<NotificationIconsViewData> =
@@ -88,6 +94,8 @@
                 )
             }
             .flowOn(bgContext)
+            .conflate()
+            .distinctUntilChanged()
 
     /** An Icon to show "isolated" in the IconContainer. */
     val isolatedIcon: Flow<AnimatedValue<NotificationIconInfo?>> =
@@ -99,6 +107,8 @@
             }
             .distinctUntilChanged()
             .flowOn(bgContext)
+            .conflate()
+            .distinctUntilChanged()
             .pairwise(initialValue = null)
             .sample(shadeInteractor.shadeExpansion) { (prev, iconInfo), shadeExpansion ->
                 val animate =
@@ -113,7 +123,7 @@
 
     /** Location to show an isolated icon, if there is one. */
     val isolatedIconLocation: Flow<Rect> =
-        headsUpIconInteractor.isolatedIconLocation.filterNotNull()
+        headsUpIconInteractor.isolatedIconLocation.filterNotNull().conflate().distinctUntilChanged()
 
     private class IconColorsImpl(
         override val tint: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 0a11eb2..42b56e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -752,6 +752,7 @@
     }
 
     public void setIsRemoteInputActive(boolean isActive) {
+        FooterViewRefactor.assertInLegacyMode();
         mIsRemoteInputActive = isActive;
         updateFooter();
     }
@@ -764,6 +765,7 @@
 
     @VisibleForTesting
     public void updateFooter() {
+        FooterViewRefactor.assertInLegacyMode();
         if (mFooterView == null || mController == null) {
             return;
         }
@@ -776,10 +778,12 @@
     }
 
     private boolean shouldShowDismissView() {
+        FooterViewRefactor.assertInLegacyMode();
         return mController.hasActiveClearableNotifications(ROWS_ALL);
     }
 
     private boolean shouldShowFooterView(boolean showDismissView) {
+        FooterViewRefactor.assertInLegacyMode();
         return (showDismissView || mController.getVisibleNotificationCount() > 0)
                 && mIsCurrentUserSetup // see: b/193149550
                 && !onKeyguard()
@@ -4359,6 +4363,12 @@
                     layoutEnd -= mShelf.getIntrinsicHeight() + mPaddingBetweenElements;
                 }
                 if (endPosition > layoutEnd) {
+                    // if Scene Container is active, send bottom notification expansion delta
+                    // to it so that it can scroll the stack and scrim accordingly.
+                    if (SceneContainerFlag.isEnabled()) {
+                        float diff = endPosition - layoutEnd;
+                        mController.sendSyntheticScrollToSceneFramework(diff);
+                    }
                     setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd));
                     mDisallowScrollingInThisMotion = true;
                 }
@@ -4723,9 +4733,6 @@
                 footerView.setClearAllButtonVisible(false /* visible */, true /* animate */);
             });
         }
-        if (FooterViewRefactor.isEnabled()) {
-            updateFooter();
-        }
     }
 
     public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
@@ -4790,16 +4797,15 @@
     }
 
     public void updateFooterView(boolean visible, boolean showDismissView, boolean showHistory) {
+        FooterViewRefactor.assertInLegacyMode();
         if (mFooterView == null || mNotificationStackSizeCalculator == null) {
             return;
         }
         boolean animate = mIsExpanded && mAnimationsEnabled;
         mFooterView.setVisible(visible, animate);
-        if (!FooterViewRefactor.isEnabled()) {
-            mFooterView.showHistory(showHistory);
-            mFooterView.setClearAllButtonVisible(showDismissView, animate);
-            mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
-        }
+        mFooterView.showHistory(showHistory);
+        mFooterView.setClearAllButtonVisible(showDismissView, animate);
+        mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
     }
 
     @VisibleForTesting
@@ -5070,7 +5076,7 @@
         if (mOwnScrollY > 0) {
             setOwnScrollY((int) MathUtils.lerp(mOwnScrollY, 0, mQsExpansionFraction));
         }
-        if (footerAffected) {
+        if (!FooterViewRefactor.isEnabled() && footerAffected) {
             updateFooter();
         }
     }
@@ -5081,6 +5087,10 @@
     }
 
     private void setOwnScrollY(int ownScrollY, boolean animateStackYChangeListener) {
+        // If scene container is active, NSSL should not control its own scrolling.
+        if (SceneContainerFlag.isEnabled()) {
+            return;
+        }
         // Avoid Flicking during clear all
         // when the shade finishes closing, onExpansionStopped will call
         // resetScrollPosition to setOwnScrollY to 0
@@ -5176,6 +5186,7 @@
     }
 
     void setUpcomingStatusBarState(int upcomingStatusBarState) {
+        FooterViewRefactor.assertInLegacyMode();
         mUpcomingStatusBarState = upcomingStatusBarState;
         if (mUpcomingStatusBarState != mStatusBarState) {
             updateFooter();
@@ -5193,7 +5204,9 @@
 
         setDimmed(onKeyguard, fromShadeLocked);
         setExpandingEnabled(!onKeyguard);
-        updateFooter();
+        if (!FooterViewRefactor.isEnabled()) {
+            updateFooter();
+        }
         requestChildrenUpdate();
         onUpdateRowStates();
         updateVisibility();
@@ -5270,8 +5283,11 @@
                     for (int i = 0; i < childCount; i++) {
                         ExpandableView child = getChildAtIndex(i);
                         child.dump(pw, args);
-                        if (child instanceof FooterView) {
-                            DumpUtilsKt.withIncreasedIndent(pw, () -> dumpFooterViewVisibility(pw));
+                        if (!FooterViewRefactor.isEnabled()) {
+                            if (child instanceof FooterView) {
+                                DumpUtilsKt.withIncreasedIndent(pw,
+                                        () -> dumpFooterViewVisibility(pw));
+                            }
                         }
                         pw.println();
                     }
@@ -5290,6 +5306,7 @@
     }
 
     private void dumpFooterViewVisibility(IndentingPrintWriter pw) {
+        FooterViewRefactor.assertInLegacyMode();
         final boolean showDismissView = shouldShowDismissView();
 
         pw.println("showFooterView: " + shouldShowFooterView(showDismissView));
@@ -5988,6 +6005,7 @@
      * Sets whether the current user is set up, which is required to show the footer (b/193149550)
      */
     public void setCurrentUserSetup(boolean isCurrentUserSetup) {
+        FooterViewRefactor.assertInLegacyMode();
         if (mIsCurrentUserSetup != isCurrentUserSetup) {
             mIsCurrentUserSetup = isCurrentUserSetup;
             updateFooter();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index a2ff406..5fa0624 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -314,8 +314,10 @@
             // The bottom might change because we're using the final actual height of the view
             mView.setAnimateBottomOnLayout(true);
         }
-        // Let's update the footer once the notifications have been updated (in the next frame)
-        mView.post(this::updateFooter);
+        if (!FooterViewRefactor.isEnabled()) {
+            // Let's update the footer once the notifications have been updated (in the next frame)
+            mView.post(this::updateFooter);
+        }
     };
 
     @VisibleForTesting
@@ -342,8 +344,8 @@
             mView.reinflateViews();
             if (!FooterViewRefactor.isEnabled()) {
                 updateShowEmptyShadeView();
+                updateFooter();
             }
-            updateFooter();
         }
 
         @Override
@@ -389,7 +391,9 @@
 
                 @Override
                 public void onUpcomingStateChanged(int newState) {
-                    mView.setUpcomingStatusBarState(newState);
+                    if (!FooterViewRefactor.isEnabled()) {
+                        mView.setUpcomingStatusBarState(newState);
+                    }
                 }
 
                 @Override
@@ -407,7 +411,9 @@
         public void onUserChanged(int userId) {
             updateSensitivenessWithAnimation(false);
             mHistoryEnabled = null;
-            updateFooter();
+            if (!FooterViewRefactor.isEnabled()) {
+                updateFooter();
+            }
         }
     };
 
@@ -810,14 +816,14 @@
         if (!FooterViewRefactor.isEnabled()) {
             mView.setFooterClearAllListener(() ->
                     mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES));
+            mView.setIsRemoteInputActive(mRemoteInputManager.isRemoteInputActive());
+            mRemoteInputManager.addControllerCallback(new RemoteInputController.Callback() {
+                @Override
+                public void onRemoteInputActive(boolean active) {
+                    mView.setIsRemoteInputActive(active);
+                }
+            });
         }
-        mView.setIsRemoteInputActive(mRemoteInputManager.isRemoteInputActive());
-        mRemoteInputManager.addControllerCallback(new RemoteInputController.Callback() {
-            @Override
-            public void onRemoteInputActive(boolean active) {
-                mView.setIsRemoteInputActive(active);
-            }
-        });
         mView.setClearAllFinishedWhilePanelExpandedRunnable(()-> {
             final Runnable doCollapseRunnable = () ->
                     mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
@@ -871,7 +877,9 @@
                     switch (key) {
                         case Settings.Secure.NOTIFICATION_HISTORY_ENABLED:
                             mHistoryEnabled = null;  // invalidate
-                            updateFooter();
+                            if (!FooterViewRefactor.isEnabled()) {
+                                updateFooter();
+                            }
                             break;
                         case HIGH_PRIORITY:
                             mView.setHighPriorityBeforeSpeedBump("1".equals(newValue));
@@ -893,9 +901,11 @@
             return kotlin.Unit.INSTANCE;
         });
 
-        // attach callback, and then call it to update mView immediately
-        mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
-        mDeviceProvisionedListener.onDeviceProvisionedChanged();
+        if (!FooterViewRefactor.isEnabled()) {
+            // attach callback, and then call it to update mView immediately
+            mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
+            mDeviceProvisionedListener.onDeviceProvisionedChanged();
+        }
 
         if (screenshareNotificationHiding()) {
             mSensitiveNotificationProtectionController
@@ -1106,8 +1116,7 @@
     }
 
     public int getVisibleNotificationCount() {
-        // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle footer
-        //  visibility in the refactored code
+        FooterViewRefactor.assertInLegacyMode();
         return mNotifStats.getNumActiveNotifs();
     }
 
@@ -1142,6 +1151,11 @@
         }
     }
 
+    /** Send internal notification expansion to the scene container framework. */
+    public void sendSyntheticScrollToSceneFramework(Float delta) {
+        mStackAppearanceInteractor.setSyntheticScroll(delta);
+    }
+
     /** Get the y-coordinate of the top bound of the stack. */
     public float getPlaceholderTop() {
         return mStackAppearanceInteractor.getStackBounds().getValue().getTop();
@@ -1509,14 +1523,14 @@
      * Return whether there are any clearable notifications
      */
     public boolean hasActiveClearableNotifications(@SelectedRows int selection) {
-        // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the footer
-        //  visibility in the refactored code
+        // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the silent
+        //  section clear action in the new stack.
         return hasNotifications(selection, true /* clearable */);
     }
 
     public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) {
-        // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the footer
-        //  visibility in the refactored code
+        // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the silent
+        //  section clear action in the new stack.
         boolean hasAlertingMatchingClearable = isClearable
                 ? mNotifStats.getHasClearableAlertingNotifs()
                 : mNotifStats.getHasNonClearableAlertingNotifs();
@@ -1558,7 +1572,9 @@
                     boolean remoteInputActive) {
                 mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
                 entry.notifyHeightChanged(true /* needsAnimation */);
-                updateFooter();
+                if (!FooterViewRefactor.isEnabled()) {
+                    updateFooter();
+                }
             }
 
             public void lockScrollTo(NotificationEntry entry) {
@@ -1573,6 +1589,7 @@
     }
 
     public void updateFooter() {
+        FooterViewRefactor.assertInLegacyMode();
         Trace.beginSection("NSSLC.updateFooter");
         mView.updateFooter();
         Trace.endSection();
@@ -2134,19 +2151,16 @@
     private class NotifStackControllerImpl implements NotifStackController {
         @Override
         public void setNotifStats(@NonNull NotifStats notifStats) {
-            // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once footer visibility
-            // is handled in the refactored stack.
+            // TODO(b/293167744): FooterViewRefactor.assertInLegacyMode() once we handle the silent
+            //  section clear action in the new stack.
             mNotifStats = notifStats;
 
             if (!FooterViewRefactor.isEnabled()) {
                 mView.setHasFilteredOutSeenNotifications(
                         mSeenNotificationsInteractor
                                 .getHasFilteredOutSeenNotifications().getValue());
-            }
 
-            updateFooter();
-
-            if (!FooterViewRefactor.isEnabled()) {
+                updateFooter();
                 updateShowEmptyShadeView();
                 updateImportantForAccessibility();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 664a6b6..15fde0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -33,6 +33,7 @@
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.SourceType;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -137,7 +138,7 @@
     }
 
     private void updateAlphaState(StackScrollAlgorithmState algorithmState,
-                                  AmbientState ambientState) {
+            AmbientState ambientState) {
         for (ExpandableView view : algorithmState.visibleChildren) {
             final ViewState viewState = view.getViewState();
             final boolean isHunGoingToShade = ambientState.isShadeExpanded()
@@ -295,7 +296,7 @@
     }
 
     private void updateSpeedBumpState(StackScrollAlgorithmState algorithmState,
-                                      int speedBumpIndex) {
+            int speedBumpIndex) {
         int childCount = algorithmState.visibleChildren.size();
         int belowSpeedBump = speedBumpIndex;
         for (int i = 0; i < childCount; i++) {
@@ -322,7 +323,7 @@
     }
 
     private void updateClipping(StackScrollAlgorithmState algorithmState,
-                                AmbientState ambientState) {
+            AmbientState ambientState) {
         float drawStart = ambientState.isOnKeyguard() ? 0
                 : ambientState.getStackY() - ambientState.getScrollY();
         float clipStart = 0;
@@ -454,7 +455,7 @@
     }
 
     private int updateNotGoneIndex(StackScrollAlgorithmState state, int notGoneIndex,
-                                   ExpandableView v) {
+            ExpandableView v) {
         ExpandableViewState viewState = v.getViewState();
         viewState.notGoneIndex = notGoneIndex;
         state.visibleChildren.add(v);
@@ -480,7 +481,7 @@
      * @param ambientState   The current ambient state
      */
     protected void updatePositionsForState(StackScrollAlgorithmState algorithmState,
-                                           AmbientState ambientState) {
+            AmbientState ambientState) {
         if (!ambientState.isOnKeyguard()
                 || (ambientState.isBypassEnabled() && ambientState.isPulseExpanding())) {
             algorithmState.mCurrentYPosition += mNotificationScrimPadding;
@@ -494,7 +495,7 @@
     }
 
     private void setLocation(ExpandableViewState expandableViewState, float currentYPosition,
-                             int i) {
+            int i) {
         expandableViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
         if (currentYPosition <= 0) {
             expandableViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
@@ -598,18 +599,31 @@
                 viewEnd, /* hunMax */ ambientState.getMaxHeadsUpTranslation()
         );
         if (view instanceof FooterView) {
-            final boolean shadeClosed = !ambientState.isShadeExpanded();
-            final boolean isShelfShowing = algorithmState.firstViewInShelf != null;
-            if (shadeClosed) {
-                viewState.hidden = true;
-            } else {
+            if (FooterViewRefactor.isEnabled()) {
                 final float footerEnd = algorithmState.mCurrentExpandedYPosition
                         + view.getIntrinsicHeight();
                 final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight();
+                // TODO(b/293167744): May be able to keep only noSpaceForFooter here if we add an
+                //  emission when clearAllNotifications is called, and then use that in the footer
+                //  visibility flow.
                 ((FooterView.FooterViewState) viewState).hideContent =
-                        isShelfShowing || noSpaceForFooter
-                                || (ambientState.isClearAllInProgress()
+                        noSpaceForFooter || (ambientState.isClearAllInProgress()
                                 && !hasNonClearableNotifs(algorithmState));
+
+            } else {
+                final boolean shadeClosed = !ambientState.isShadeExpanded();
+                final boolean isShelfShowing = algorithmState.firstViewInShelf != null;
+                if (shadeClosed) {
+                    viewState.hidden = true;
+                } else {
+                    final float footerEnd = algorithmState.mCurrentExpandedYPosition
+                            + view.getIntrinsicHeight();
+                    final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight();
+                    ((FooterView.FooterViewState) viewState).hideContent =
+                            isShelfShowing || noSpaceForFooter
+                                    || (ambientState.isClearAllInProgress()
+                                    && !hasNonClearableNotifs(algorithmState));
+                }
             }
         } else {
             if (view instanceof EmptyShadeView) {
@@ -731,7 +745,7 @@
 
     @VisibleForTesting
     void updatePulsingStates(StackScrollAlgorithmState algorithmState,
-                                     AmbientState ambientState) {
+            AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
         ExpandableNotificationRow pulsingRow = null;
         for (int i = 0; i < childCount; i++) {
@@ -761,7 +775,7 @@
     }
 
     private void updateHeadsUpStates(StackScrollAlgorithmState algorithmState,
-                                     AmbientState ambientState) {
+            AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
 
         // Move the tracked heads up into position during the appear animation, by interpolating
@@ -870,18 +884,18 @@
     boolean shouldHunBeVisibleWhenScrolled(boolean mustStayOnScreen, boolean headsUpIsVisible,
             boolean showingPulsing, boolean isOnKeyguard, boolean headsUpOnKeyguard) {
         return mustStayOnScreen && !headsUpIsVisible
-                        && !showingPulsing
-                        && (!isOnKeyguard || headsUpOnKeyguard);
+                && !showingPulsing
+                && (!isOnKeyguard || headsUpOnKeyguard);
     }
 
-     /**
+    /**
      * When shade is open and we are scrolled to the bottom of notifications,
      * clamp incoming HUN in its collapsed form, right below qs offset.
      * Transition pinned collapsed HUN to full height when scrolling back up.
      */
     @VisibleForTesting
     void clampHunToTop(float clampInset, float stackTranslation, float collapsedHeight,
-                       ExpandableViewState viewState) {
+            ExpandableViewState viewState) {
 
         final float newTranslation = Math.max(clampInset + stackTranslation,
                 viewState.getYTranslation());
@@ -896,7 +910,7 @@
     // Pin HUN to bottom of expanded QS
     // while the rest of notifications are scrolled offscreen.
     private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row,
-                                          ExpandableViewState childState) {
+            ExpandableViewState childState) {
         float maxHeadsUpTranslation = ambientState.getMaxHeadsUpTranslation();
         final float maxShelfPosition = ambientState.getInnerHeight() + ambientState.getTopPadding()
                 + ambientState.getStackTranslation();
@@ -919,7 +933,7 @@
 
     @VisibleForTesting
     float computeCornerRoundnessForPinnedHun(float hostViewHeight, float stackY,
-                                             float viewMaxHeight, float originalCornerRadius) {
+            float viewMaxHeight, float originalCornerRadius) {
 
         // Compute y where corner roundness should be in its original unpinned state.
         // We use view max height because the pinned collapsed HUN expands to max height
@@ -948,7 +962,7 @@
      * @param ambientState   The ambient state of the algorithm
      */
     private void updateZValuesForState(StackScrollAlgorithmState algorithmState,
-                                       AmbientState ambientState) {
+            AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
         float childrenOnTop = 0.0f;
 
@@ -976,13 +990,13 @@
      *                      vertically top of screen. Top HUNs should have drop shadows
      * @param childrenOnTop It is greater than 0 when there's an existing HUN that is elevated
      * @return childrenOnTop The decimal part represents the fraction of the elevated HUN's height
-     *                      that overlaps with QQS Panel. The integer part represents the count of
-     *                      previous HUNs whose Z positions are greater than 0.
+     * that overlaps with QQS Panel. The integer part represents the count of
+     * previous HUNs whose Z positions are greater than 0.
      */
     protected float updateChildZValue(int i, float childrenOnTop,
-                                      StackScrollAlgorithmState algorithmState,
-                                      AmbientState ambientState,
-                                      boolean isTopHun) {
+            StackScrollAlgorithmState algorithmState,
+            AmbientState ambientState,
+            boolean isTopHun) {
         ExpandableView child = algorithmState.visibleChildren.get(i);
         ExpandableViewState childViewState = child.getViewState();
         float baseZ = ambientState.getBaseZHeight();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
index 311ba83..9efe632 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
@@ -47,4 +47,11 @@
      * further.
      */
     val scrolledToTop = MutableStateFlow(true)
+
+    /**
+     * The amount in px that the notification stack should scroll due to internal expansion. This
+     * should only happen when a notification expansion hits the bottom of the screen, so it is
+     * necessary to scroll up to keep expanding the notification.
+     */
+    val syntheticScroll = MutableStateFlow(0f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index 9984ba9..08df473 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.stack.data.repository.NotificationStackAppearanceRepository
 import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
@@ -50,6 +51,13 @@
      */
     val scrolledToTop: StateFlow<Boolean> = repository.scrolledToTop.asStateFlow()
 
+    /**
+     * The amount in px that the notification stack should scroll due to internal expansion. This
+     * should only happen when a notification expansion hits the bottom of the screen, so it is
+     * necessary to scroll up to keep expanding the notification.
+     */
+    val syntheticScroll: Flow<Float> = repository.syntheticScroll.asStateFlow()
+
     /** Sets the position of the notification stack in the current scene. */
     fun setStackBounds(bounds: NotificationContainerBounds) {
         check(bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" }
@@ -70,4 +78,9 @@
     fun setScrolledToTop(scrolledToTop: Boolean) {
         repository.scrolledToTop.value = scrolledToTop
     }
+
+    /** Sets the amount (px) that the notification stack should scroll due to internal expansion. */
+    fun setSyntheticScroll(delta: Float) {
+        repository.syntheticScroll.value = delta
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 4d65b9d..883aa9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -18,11 +18,10 @@
 
 import android.view.LayoutInflater
 import androidx.lifecycle.lifecycleScope
-import com.android.app.tracing.traceSection
+import com.android.app.tracing.TraceUtils.traceAsync
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.logging.nano.MetricsProto
 import com.android.systemui.common.ui.ConfigurationState
-import com.android.systemui.common.ui.reinflateAndBindLatest
 import com.android.systemui.common.ui.view.setImportantForAccessibilityYesNo
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -33,6 +32,7 @@
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
 import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
+import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerShelfViewBinder
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder
@@ -43,12 +43,18 @@
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.HideNotificationsBinder.bindHideList
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
+import com.android.systemui.util.kotlin.awaitCancellationThenDispose
 import com.android.systemui.util.kotlin.getOrNull
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.value
 import java.util.Optional
 import javax.inject.Inject
 import javax.inject.Provider
 import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.launch
 
 /** Binds a [NotificationStackScrollLayout] to its [view model][NotificationListViewModel]. */
@@ -83,7 +89,7 @@
                 bindHideList(viewController, viewModel, hiderTracker)
 
                 if (FooterViewRefactor.isEnabled) {
-                    launch { bindFooter(view) }
+                    launch { reinflateAndBindFooter(view) }
                     launch { bindEmptyShade(view) }
                     launch {
                         viewModel.isImportantForAccessibility.collect { isImportantForAccessibility
@@ -108,42 +114,61 @@
         )
     }
 
-    private suspend fun bindFooter(parentView: NotificationStackScrollLayout) {
+    private suspend fun reinflateAndBindFooter(parentView: NotificationStackScrollLayout) {
         viewModel.footer.getOrNull()?.let { footerViewModel ->
             // The footer needs to be re-inflated every time the theme or the font size changes.
-            configuration.reinflateAndBindLatest(
-                R.layout.status_bar_notification_footer,
-                parentView,
-                attachToRoot = false,
-                backgroundDispatcher,
-            ) { footerView: FooterView ->
-                traceSection("bind FooterView") {
-                    val disposableHandle =
-                        FooterViewBinder.bindWhileAttached(
-                            footerView,
-                            footerViewModel,
-                            clearAllNotifications = {
-                                metricsLogger.action(
-                                    MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES
-                                )
-                                parentView.clearAllNotifications()
-                            },
-                            launchNotificationSettings = { view ->
-                                notificationActivityStarter
-                                    .get()
-                                    .startHistoryIntent(view, /* showHistory = */ false)
-                            },
-                            launchNotificationHistory = { view ->
-                                notificationActivityStarter
-                                    .get()
-                                    .startHistoryIntent(view, /* showHistory = */ true)
-                            },
-                        )
-                    parentView.setFooterView(footerView)
-                    return@reinflateAndBindLatest disposableHandle
+            configuration
+                .inflateLayout<FooterView>(
+                    R.layout.status_bar_notification_footer,
+                    parentView,
+                    attachToRoot = false,
+                )
+                .flowOn(backgroundDispatcher)
+                .collectLatest { footerView: FooterView ->
+                    traceAsync("bind FooterView") {
+                        parentView.setFooterView(footerView)
+                        bindFooter(footerView, footerViewModel, parentView)
+                    }
                 }
+        }
+    }
+
+    /**
+     * Binds the footer (including its visibility) and dispose of the [DisposableHandle] when done.
+     */
+    private suspend fun bindFooter(
+        footerView: FooterView,
+        footerViewModel: FooterViewModel,
+        parentView: NotificationStackScrollLayout
+    ): Unit = coroutineScope {
+        val disposableHandle =
+            FooterViewBinder.bindWhileAttached(
+                footerView,
+                footerViewModel,
+                clearAllNotifications = {
+                    metricsLogger.action(MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES)
+                    parentView.clearAllNotifications()
+                },
+                launchNotificationSettings = { view ->
+                    notificationActivityStarter
+                        .get()
+                        .startHistoryIntent(view, /* showHistory = */ false)
+                },
+                launchNotificationHistory = { view ->
+                    notificationActivityStarter
+                        .get()
+                        .startHistoryIntent(view, /* showHistory = */ true)
+                },
+            )
+        launch {
+            viewModel.shouldShowFooterView.collect { animatedVisibility ->
+                footerView.setVisible(
+                    /* visible = */ animatedVisibility.value,
+                    /* animate = */ animatedVisibility.isAnimating,
+                )
             }
         }
+        disposableHandle.awaitCancellationThenDispose()
     }
 
     private suspend fun bindEmptyShade(parentView: NotificationStackScrollLayout) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
index 814146c..a157785 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
@@ -69,6 +69,9 @@
                         controller.setMaxAlphaForExpansion(
                             ((expandFraction - 0.5f) / 0.5f).coerceAtLeast(0f)
                         )
+                        if (expandFraction == 0f || expandFraction == 1f) {
+                            controller.onExpansionStopped()
+                        }
                     }
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 86c0a678..a6c6586 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -16,21 +16,32 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
 import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
+import com.android.systemui.statusbar.policy.domain.interactor.UserSetupInteractor
 import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
+import com.android.systemui.util.kotlin.combine
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.ui.AnimatableEvent
+import com.android.systemui.util.ui.AnimatedValue
+import com.android.systemui.util.ui.toAnimatedValueFlow
 import java.util.Optional
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 
 /** ViewModel for the list of notifications. */
@@ -42,9 +53,13 @@
     val footer: Optional<FooterViewModel>,
     val logger: Optional<NotificationLoggerViewModel>,
     activeNotificationsInteractor: ActiveNotificationsInteractor,
+    keyguardInteractor: KeyguardInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    powerInteractor: PowerInteractor,
+    remoteInputInteractor: RemoteInputInteractor,
     seenNotificationsInteractor: SeenNotificationsInteractor,
     shadeInteractor: ShadeInteractor,
+    userSetupInteractor: UserSetupInteractor,
     zenModeInteractor: ZenModeInteractor,
 ) {
     /**
@@ -76,6 +91,10 @@
             combine(
                     activeNotificationsInteractor.areAnyNotificationsPresent,
                     shadeInteractor.isQsFullscreen,
+                    // TODO(b/293167744): It looks like we're essentially trying to check the same
+                    //  things for the empty shade visibility as we do for the footer, just in a
+                    //  slightly different way. We should change this so we also check
+                    //  statusBarState and isAwake instead of specific keyguard transitions.
                     keyguardTransitionInteractor.isInTransitionToState(KeyguardState.AOD).onStart {
                         emit(false)
                     },
@@ -97,6 +116,80 @@
         }
     }
 
+    val shouldShowFooterView: Flow<AnimatedValue<Boolean>> by lazy {
+        if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
+            flowOf(AnimatedValue.NotAnimating(false))
+        } else {
+            combine(
+                    activeNotificationsInteractor.areAnyNotificationsPresent,
+                    userSetupInteractor.isUserSetUp,
+                    keyguardInteractor.statusBarState.map { it == StatusBarState.KEYGUARD },
+                    shadeInteractor.qsExpansion,
+                    shadeInteractor.isQsFullscreen,
+                    powerInteractor.isAsleep,
+                    remoteInputInteractor.isRemoteInputActive,
+                    shadeInteractor.shadeExpansion.map { it == 0f }
+                ) {
+                    hasNotifications,
+                    isUserSetUp,
+                    isOnKeyguard,
+                    qsExpansion,
+                    qsFullScreen,
+                    isAsleep,
+                    isRemoteInputActive,
+                    isShadeClosed ->
+                    Pair(
+                        // Should the footer be visible?
+                        when {
+                            !hasNotifications -> false
+                            // Hide the footer until the user setup is complete, to prevent access
+                            // to settings (b/193149550).
+                            !isUserSetUp -> false
+                            // Do not show the footer if the lockscreen is visible (incl. AOD),
+                            // except if the shade is opened on top. See also b/219680200.
+                            isOnKeyguard -> false
+                            // Make sure we're not showing the footer in the transition to AOD while
+                            // going to sleep (b/190227875). The StatusBarState is unfortunately not
+                            // updated quickly enough when the power button is pressed, so this is
+                            // necessary in addition to the isOnKeyguard check.
+                            isAsleep -> false
+                            // Do not show the footer if quick settings are fully expanded (except
+                            // for the foldable split shade view). See b/201427195 && b/222699879.
+                            qsExpansion == 1f && qsFullScreen -> false
+                            // Hide the footer if remote input is active (i.e. user is replying to a
+                            // notification). See b/75984847.
+                            isRemoteInputActive -> false
+                            // Never show the footer if the shade is collapsed (e.g. when HUNing).
+                            isShadeClosed -> false
+                            else -> true
+                        },
+                        // This could in theory be in the .sample below, but it tends to be
+                        // inconsistent, so we're passing it on to make sure we have the same state.
+                        isOnKeyguard
+                    )
+                }
+                .distinctUntilChanged()
+                // Should we animate the visibility change?
+                .sample(
+                    // TODO(b/322167853): This check is currently duplicated in FooterViewModel,
+                    //  but instead it should be a field in ShadeAnimationInteractor.
+                    combine(
+                            shadeInteractor.isShadeFullyExpanded,
+                            shadeInteractor.isShadeTouchable,
+                            ::Pair
+                        )
+                        .onStart { emit(Pair(false, false)) }
+                ) { (visible, isOnKeyguard), (isShadeFullyExpanded, animationsEnabled) ->
+                    // Animate if the shade is interactive, but NOT on the lockscreen. Having
+                    // animations enabled while on the lockscreen makes the footer appear briefly
+                    // when transitioning between the shade and keyguard.
+                    val shouldAnimate = isShadeFullyExpanded && animationsEnabled && !isOnKeyguard
+                    AnimatableEvent(visible, shouldAnimate)
+                }
+                .toAnimatedValueFlow()
+        }
+    }
+
     // TODO(b/308591475): This should be tracked separately by the empty shade.
     val areNotificationsHiddenInShade: Flow<Boolean> by lazy {
         if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
index bdf1a64..3a0f03f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
@@ -28,6 +28,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 
 /** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */
 @SysUISingleton
@@ -45,31 +46,32 @@
      */
     val expandFraction: Flow<Float> =
         combine(
-            shadeInteractor.shadeExpansion,
-            sceneInteractor.transitionState,
-        ) { shadeExpansion, transitionState ->
-            when (transitionState) {
-                is ObservableTransitionState.Idle -> {
-                    if (transitionState.scene == SceneKey.Lockscreen) {
-                        1f
-                    } else {
-                        shadeExpansion
+                shadeInteractor.shadeExpansion,
+                sceneInteractor.transitionState,
+            ) { shadeExpansion, transitionState ->
+                when (transitionState) {
+                    is ObservableTransitionState.Idle -> {
+                        if (transitionState.scene == SceneKey.Lockscreen) {
+                            1f
+                        } else {
+                            shadeExpansion
+                        }
                     }
-                }
-                is ObservableTransitionState.Transition -> {
-                    if (
-                        (transitionState.fromScene == SceneKey.Shade &&
-                            transitionState.toScene == SceneKey.QuickSettings) ||
-                            (transitionState.fromScene == SceneKey.QuickSettings &&
-                                transitionState.toScene == SceneKey.Shade)
-                    ) {
-                        1f
-                    } else {
-                        shadeExpansion
+                    is ObservableTransitionState.Transition -> {
+                        if (
+                            (transitionState.fromScene == SceneKey.Shade &&
+                                transitionState.toScene == SceneKey.QuickSettings) ||
+                                (transitionState.fromScene == SceneKey.QuickSettings &&
+                                    transitionState.toScene == SceneKey.Shade)
+                        ) {
+                            1f
+                        } else {
+                            shadeExpansion
+                        }
                     }
                 }
             }
-        }
+            .distinctUntilChanged()
 
     /** The bounds of the notification stack in the current scene. */
     val stackBounds: Flow<NotificationContainerBounds> = stackAppearanceInteractor.stackBounds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 65d9c9f..7ac5cd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -86,6 +86,13 @@
      */
     val expandFraction: Flow<Float> = shadeInteractor.shadeExpansion
 
+    /**
+     * The amount in px that the notification stack should scroll due to internal expansion. This
+     * should only happen when a notification expansion hits the bottom of the screen, so it is
+     * necessary to scroll up to keep expanding the notification.
+     */
+    val syntheticScroll: Flow<Float> = interactor.syntheticScroll
+
     /** Sets the y-coord in px of the top of the contents of the notification stack. */
     fun onContentTopChanged(padding: Float) {
         interactor.setContentTop(padding)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 8a56da3..b49af0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -837,7 +837,7 @@
             if (animationController == null) {
                 return null
             }
-            val rootView = animationController.launchContainer.rootView
+            val rootView = animationController.transitionContainer.rootView
             val controllerFromStatusBar: Optional<ActivityLaunchAnimator.Controller> =
                 statusBarWindowController.wrapAnimationControllerIfInStatusBar(
                     rootView,
@@ -881,8 +881,8 @@
                         }
                     }
 
-                    override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
-                        super.onLaunchAnimationStart(isExpandingFullyAbove)
+                    override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
+                        super.onTransitionAnimationStart(isExpandingFullyAbove)
 
                         // Double check that the keyguard is still showing and not going
                         // away, but if so set the keyguard occluded. Typically, WM will let
@@ -902,14 +902,14 @@
                         }
                     }
 
-                    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+                    override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
                         // Set mIsLaunchingActivityOverLockscreen to false before actually
                         // finishing the animation so that we can assume that
                         // mIsLaunchingActivityOverLockscreen being true means that we will
                         // collapse the shade (or at least run the post collapse runnables)
                         // later on.
                         centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
-                        delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+                        delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
                     }
 
                     override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/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/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
index 8ca5bfc..d43f470 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
@@ -2,7 +2,7 @@
 
 import android.view.View
 import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.LaunchAnimator
+import com.android.systemui.animation.TransitionAnimator
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
@@ -34,8 +34,8 @@
         }
     }
 
-    override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
-        delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+    override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
+        delegate.onTransitionAnimationStart(isExpandingFullyAbove)
         shadeAnimationInteractor.setIsLaunchingActivity(true)
         if (!isExpandingFullyAbove) {
             shadeViewController.collapseWithDuration(
@@ -43,18 +43,18 @@
         }
     }
 
-    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
-        delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+    override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+        delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
         shadeAnimationInteractor.setIsLaunchingActivity(false)
         shadeController.onLaunchAnimationEnd(isExpandingFullyAbove)
     }
 
-    override fun onLaunchAnimationProgress(
-        state: LaunchAnimator.State,
+    override fun onTransitionAnimationProgress(
+        state: TransitionAnimator.State,
         progress: Float,
         linearProgress: Float
     ) {
-        delegate.onLaunchAnimationProgress(state, progress, linearProgress)
+        delegate.onTransitionAnimationProgress(state, progress, linearProgress)
         shadeViewController.applyLaunchAnimationProgress(linearProgress)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt
index 246645e..72f4540 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt
@@ -15,20 +15,14 @@
  */
 package com.android.systemui.statusbar.phone.domain.interactor
 
-import android.graphics.Rect
 import com.android.systemui.statusbar.phone.data.repository.DarkIconRepository
+import com.android.systemui.statusbar.phone.domain.model.DarkState
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.map
 
 /** States pertaining to calculating colors for icons in dark mode. */
 class DarkIconInteractor @Inject constructor(repository: DarkIconRepository) {
-    /** @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.areas */
-    val tintAreas: Flow<Collection<Rect>> = repository.darkState.map { it.areas }
-    /**
-     * @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.darkIntensity
-     */
-    val darkIntensity: Flow<Float> = repository.darkState.map { it.darkIntensity }
-    /** @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.tint */
-    val tintColor: Flow<Int> = repository.darkState.map { it.tint }
+    /** Dark-mode state for tinting icons. */
+    val darkState: Flow<DarkState> = repository.darkState.map { DarkState(it.areas, it.tint) }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/model/DarkState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/model/DarkState.kt
new file mode 100644
index 0000000..3cab7cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/model/DarkState.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.domain.model
+
+import android.graphics.Rect
+
+/** Dark mode visual states. */
+data class DarkState(
+    /** Areas on screen that require a dark-mode adjustment. */
+    val areas: Collection<Rect>,
+    /** Tint color to apply to UI elements that fall within [areas]. */
+    val tint: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt
index 63566ee..e1798d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.satellite.ui.model
 
+import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
@@ -36,7 +37,10 @@
             SatelliteConnectionState.On ->
                 Icon.Resource(
                     res = R.drawable.ic_satellite_not_connected,
-                    contentDescription = null,
+                    contentDescription =
+                        ContentDescription.Resource(
+                            R.string.accessibility_status_bar_satellite_available
+                        ),
                 )
             SatelliteConnectionState.Connected -> fromSignalStrength(signalStrength)
         }
@@ -51,15 +55,36 @@
         // TODO(b/316634365): these need content descriptions
         when (signalStrength) {
             // No signal
-            0 -> Icon.Resource(res = R.drawable.ic_satellite_connected_0, contentDescription = null)
+            0 ->
+                Icon.Resource(
+                    res = R.drawable.ic_satellite_connected_0,
+                    contentDescription =
+                        ContentDescription.Resource(
+                            R.string.accessibility_status_bar_satellite_no_connection
+                        )
+                )
 
             // Poor -> Moderate
             1,
-            2 -> Icon.Resource(res = R.drawable.ic_satellite_connected_1, contentDescription = null)
+            2 ->
+                Icon.Resource(
+                    res = R.drawable.ic_satellite_connected_1,
+                    contentDescription =
+                        ContentDescription.Resource(
+                            R.string.accessibility_status_bar_satellite_poor_connection
+                        )
+                )
 
             // Good -> Great
             3,
-            4 -> Icon.Resource(res = R.drawable.ic_satellite_connected_2, contentDescription = null)
+            4 ->
+                Icon.Resource(
+                    res = R.drawable.ic_satellite_connected_2,
+                    contentDescription =
+                        ContentDescription.Resource(
+                            R.string.accessibility_status_bar_satellite_good_connection
+                        )
+                )
             else -> null
         }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index b598782..21d3fa4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -47,13 +47,13 @@
 import android.view.WindowManager;
 
 import com.android.internal.policy.SystemBarUtils;
-import com.android.systemui.res.R;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.DelegateLaunchAnimatorController;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.fragments.FragmentService;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
 import com.android.systemui.unfold.util.JankMonitorTransitionProgressListener;
@@ -194,17 +194,17 @@
             return Optional.empty();
         }
 
-        animationController.setLaunchContainer(mLaunchAnimationContainer);
+        animationController.setTransitionContainer(mLaunchAnimationContainer);
         return Optional.of(new DelegateLaunchAnimatorController(animationController) {
             @Override
-            public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {
-                getDelegate().onLaunchAnimationStart(isExpandingFullyAbove);
+            public void onTransitionAnimationStart(boolean isExpandingFullyAbove) {
+                getDelegate().onTransitionAnimationStart(isExpandingFullyAbove);
                 setLaunchAnimationRunning(true);
             }
 
             @Override
-            public void onLaunchAnimationEnd(boolean isExpandingFullyAbove) {
-                getDelegate().onLaunchAnimationEnd(isExpandingFullyAbove);
+            public void onTransitionAnimationEnd(boolean isExpandingFullyAbove) {
+                getDelegate().onTransitionAnimationEnd(isExpandingFullyAbove);
                 setLaunchAnimationRunning(false);
             }
         });
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
new file mode 100644
index 0000000..5c53ff9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold
+
+import android.animation.ValueAnimator
+import android.annotation.BinderThread
+import android.content.Context
+import android.os.Handler
+import android.os.SystemProperties
+import android.util.Log
+import android.view.animation.DecelerateInterpolator
+import androidx.core.animation.addListener
+import com.android.internal.foldables.FoldLockSettingAvailabilityProvider
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DeviceStateRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.ScreenPowerState
+import com.android.systemui.statusbar.LinearSideLightRevealEffect
+import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.ALPHA_OPAQUE
+import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.ALPHA_TRANSPARENT
+import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.isVerticalRotation
+import com.android.systemui.unfold.dagger.UnfoldBg
+import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.android.asCoroutineDispatcher
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withTimeout
+
+class FoldLightRevealOverlayAnimation
+@Inject
+constructor(
+    private val context: Context,
+    @UnfoldBg private val bgHandler: Handler,
+    private val deviceStateRepository: DeviceStateRepository,
+    private val powerInteractor: PowerInteractor,
+    @Background private val applicationScope: CoroutineScope,
+    private val animationStatusRepository: AnimationStatusRepository,
+    private val controllerFactory: FullscreenLightRevealAnimationController.Factory
+) : FullscreenLightRevealAnimation {
+
+    private val revealProgressValueAnimator: ValueAnimator =
+        ValueAnimator.ofFloat(ALPHA_OPAQUE, ALPHA_TRANSPARENT)
+    private lateinit var controller: FullscreenLightRevealAnimationController
+    @Volatile private var readyCallback: CompletableDeferred<Runnable>? = null
+
+    override fun init() {
+        // This method will be called only on devices where this animation is enabled,
+        // so normally this thread won't be created
+        if (!FoldLockSettingAvailabilityProvider(context.resources).isFoldLockBehaviorAvailable) {
+            return
+        }
+
+        controller =
+            controllerFactory.create(
+                displaySelector = { minByOrNull { it.naturalWidth } },
+                effectFactory = { LinearSideLightRevealEffect(it.isVerticalRotation()) },
+                overlayContainerName = SURFACE_CONTAINER_NAME
+            )
+        controller.init()
+
+        applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
+            powerInteractor.screenPowerState.collect {
+                if (it == ScreenPowerState.SCREEN_ON) {
+                    readyCallback = null
+                }
+            }
+        }
+
+        applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
+            deviceStateRepository.state
+                .map { it != DeviceStateRepository.DeviceState.FOLDED }
+                .distinctUntilChanged()
+                .filter { isUnfolded -> isUnfolded }
+                .collect { controller.ensureOverlayRemoved() }
+        }
+
+        applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
+            deviceStateRepository.state
+                .filter {
+                    animationStatusRepository.areAnimationsEnabled().first() &&
+                        it == DeviceStateRepository.DeviceState.FOLDED
+                }
+                .collect {
+                    try {
+                        withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) {
+                            readyCallback = CompletableDeferred()
+                            val onReady = readyCallback?.await()
+                            readyCallback = null
+                            controller.addOverlay(ALPHA_OPAQUE, onReady)
+                            waitForScreenTurnedOn()
+                            playFoldLightRevealOverlayAnimation()
+                        }
+                    } catch (e: TimeoutCancellationException) {
+                        Log.e(TAG, "Fold light reveal animation timed out")
+                        ensureOverlayRemovedInternal()
+                    }
+                }
+        }
+    }
+
+    @BinderThread
+    override fun onScreenTurningOn(onOverlayReady: Runnable) {
+        readyCallback?.complete(onOverlayReady) ?: onOverlayReady.run()
+    }
+
+    private suspend fun waitForScreenTurnedOn() {
+        powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
+    }
+
+    private fun ensureOverlayRemovedInternal() {
+        revealProgressValueAnimator.cancel()
+        controller.ensureOverlayRemoved()
+    }
+
+    private fun playFoldLightRevealOverlayAnimation() {
+        revealProgressValueAnimator.duration = ANIMATION_DURATION
+        revealProgressValueAnimator.interpolator = DecelerateInterpolator()
+        revealProgressValueAnimator.addUpdateListener { animation ->
+            controller.updateRevealAmount(animation.animatedFraction)
+        }
+        revealProgressValueAnimator.addListener(onEnd = { controller.ensureOverlayRemoved() })
+        revealProgressValueAnimator.start()
+    }
+
+    private companion object {
+        const val TAG = "FoldLightRevealOverlayAnimation"
+        const val WAIT_FOR_ANIMATION_TIMEOUT_MS = 2000L
+        const val SURFACE_CONTAINER_NAME = "fold-overlay-container"
+        val ANIMATION_DURATION: Long
+            get() = SystemProperties.getLong("persist.fold_animation_duration", 200L)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt
new file mode 100644
index 0000000..668b143
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold
+
+import android.content.Context
+import android.graphics.PixelFormat
+import android.hardware.display.DisplayManager
+import android.os.Handler
+import android.os.Looper
+import android.os.Trace
+import android.view.Choreographer
+import android.view.Display
+import android.view.DisplayInfo
+import android.view.Surface
+import android.view.Surface.Rotation
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.SurfaceSession
+import android.view.WindowManager
+import android.view.WindowlessWindowManager
+import com.android.app.tracing.traceSection
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.statusbar.LightRevealEffect
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.unfold.dagger.UnfoldBg
+import com.android.systemui.unfold.updates.RotationChangeProvider
+import com.android.systemui.util.concurrency.ThreadFactory
+import com.android.wm.shell.displayareahelper.DisplayAreaHelper
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.lang.IllegalArgumentException
+import java.util.Optional
+import java.util.concurrent.Executor
+import java.util.function.Consumer
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.launch
+
+interface FullscreenLightRevealAnimation {
+    fun init()
+
+    fun onScreenTurningOn(onOverlayReady: Runnable)
+}
+
+class FullscreenLightRevealAnimationController
+@AssistedInject
+constructor(
+    private val context: Context,
+    private val displayManager: DisplayManager,
+    private val threadFactory: ThreadFactory,
+    @UnfoldBg private val bgHandler: Handler,
+    @UnfoldBg private val rotationChangeProvider: RotationChangeProvider,
+    private val displayAreaHelper: Optional<DisplayAreaHelper>,
+    private val displayTracker: DisplayTracker,
+    @Background private val applicationScope: CoroutineScope,
+    @Main private val executor: Executor,
+    @Assisted private val displaySelector: Sequence<DisplayInfo>.() -> DisplayInfo?,
+    @Assisted private val lightRevealEffectFactory: (rotation: Int) -> LightRevealEffect,
+    @Assisted private val overlayContainerName: String
+) {
+
+    private lateinit var bgExecutor: Executor
+    private lateinit var wwm: WindowlessWindowManager
+
+    private var currentRotation: Int = context.display.rotation
+    private var root: SurfaceControlViewHost? = null
+    private var scrimView: LightRevealScrim? = null
+
+    private val rotationWatcher = RotationWatcher()
+    private val internalDisplayInfos: Sequence<DisplayInfo>
+        get() =
+            displayManager
+                .getDisplays(DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
+                .asSequence()
+                .map { DisplayInfo().apply { it.getDisplayInfo(this) } }
+                .filter { it.type == Display.TYPE_INTERNAL }
+
+    var isTouchBlocked: Boolean = false
+        set(value) {
+            if (value != field) {
+                traceSection("$TAG#relayoutToUpdateTouch") { root?.relayout(getLayoutParams()) }
+                field = value
+            }
+        }
+
+    fun init() {
+        bgExecutor = threadFactory.buildDelayableExecutorOnHandler(bgHandler)
+        rotationChangeProvider.addCallback(rotationWatcher)
+
+        buildSurface { builder ->
+            applicationScope.launch(executor.asCoroutineDispatcher()) {
+                val overlayContainer = builder.build()
+
+                SurfaceControl.Transaction()
+                    .setLayer(overlayContainer, OVERLAY_LAYER_Z_INDEX)
+                    .show(overlayContainer)
+                    .apply()
+
+                wwm =
+                    WindowlessWindowManager(context.resources.configuration, overlayContainer, null)
+            }
+        }
+    }
+
+    fun addOverlay(
+        initialAlpha: Float,
+        onOverlayReady: Runnable? = null,
+    ) {
+        if (!::wwm.isInitialized) {
+            // Surface overlay is not created yet on the first SysUI launch
+            onOverlayReady?.run()
+            return
+        }
+        ensureInBackground()
+        ensureOverlayRemoved()
+        prepareOverlay(onOverlayReady, wwm, bgExecutor, initialAlpha)
+    }
+
+    fun ensureOverlayRemoved() {
+        ensureInBackground()
+
+        traceSection("ensureOverlayRemoved") {
+            root?.release()
+            root = null
+            scrimView = null
+        }
+    }
+
+    fun isOverlayVisible(): Boolean {
+        return scrimView == null
+    }
+
+    fun updateRevealAmount(revealAmount: Float) {
+        scrimView?.revealAmount = revealAmount
+    }
+
+    private fun buildSurface(onUpdated: Consumer<SurfaceControl.Builder>) {
+        val containerBuilder =
+            SurfaceControl.Builder(SurfaceSession())
+                .setContainerLayer()
+                .setName(overlayContainerName)
+
+        displayAreaHelper
+            .get()
+            .attachToRootDisplayArea(displayTracker.defaultDisplayId, containerBuilder, onUpdated)
+    }
+
+    private fun prepareOverlay(
+        onOverlayReady: Runnable? = null,
+        wwm: WindowlessWindowManager,
+        bgExecutor: Executor,
+        initialAlpha: Float,
+    ) {
+        val newRoot = SurfaceControlViewHost(context, context.display, wwm, javaClass.simpleName)
+
+        val params = getLayoutParams()
+        val newView =
+            LightRevealScrim(
+                    context,
+                    attrs = null,
+                    initialWidth = params.width,
+                    initialHeight = params.height
+                )
+                .apply {
+                    revealEffect = lightRevealEffectFactory(currentRotation)
+                    revealAmount = initialAlpha
+                }
+
+        newRoot.setView(newView, params)
+
+        if (onOverlayReady != null) {
+            Trace.beginAsyncSection("$TAG#relayout", 0)
+
+            newRoot.relayout(params) { transaction ->
+                val vsyncId = Choreographer.getSfInstance().vsyncId
+                transaction.setFrameTimelineVsync(vsyncId).apply()
+
+                transaction
+                    .setFrameTimelineVsync(vsyncId + 1)
+                    .addTransactionCommittedListener(bgExecutor) {
+                        Trace.endAsyncSection("$TAG#relayout", 0)
+                        onOverlayReady.run()
+                    }
+                    .apply()
+            }
+        }
+        root = newRoot
+        scrimView = newView
+    }
+
+    private fun ensureInBackground() {
+        check(Looper.myLooper() == bgHandler.looper) { "Not being executed in the background!" }
+    }
+
+    private fun getLayoutParams(): WindowManager.LayoutParams {
+        val displayInfo =
+            internalDisplayInfos.displaySelector()
+                ?: throw IllegalArgumentException("No internal displays found!")
+        return WindowManager.LayoutParams().apply {
+            if (currentRotation.isVerticalRotation()) {
+                height = displayInfo.naturalHeight
+                width = displayInfo.naturalWidth
+            } else {
+                height = displayInfo.naturalWidth
+                width = displayInfo.naturalHeight
+            }
+            format = PixelFormat.TRANSLUCENT
+            type = WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY
+            title = javaClass.simpleName
+            layoutInDisplayCutoutMode =
+                WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+            fitInsetsTypes = 0
+
+            flags =
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+                    WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+            setTrustedOverlay()
+
+            packageName = context.opPackageName
+        }
+    }
+
+    private inner class RotationWatcher : RotationChangeProvider.RotationListener {
+        override fun onRotationChanged(newRotation: Int) {
+            traceSection("$TAG#onRotationChanged") {
+                if (currentRotation != newRotation) {
+                    currentRotation = newRotation
+                    scrimView?.revealEffect = lightRevealEffectFactory(currentRotation)
+                    root?.relayout(getLayoutParams())
+                }
+            }
+        }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            displaySelector: Sequence<DisplayInfo>.() -> DisplayInfo?,
+            effectFactory: (rotation: Int) -> LightRevealEffect,
+            overlayContainerName: String
+        ): FullscreenLightRevealAnimationController
+    }
+
+    companion object {
+        private const val TAG = "FullscreenLightRevealAnimation"
+        private const val ROTATION_ANIMATION_OVERLAY_Z_INDEX = Integer.MAX_VALUE
+        private const val OVERLAY_LAYER_Z_INDEX = ROTATION_ANIMATION_OVERLAY_Z_INDEX - 1
+        const val ALPHA_TRANSPARENT = 1f
+        const val ALPHA_OPAQUE = 0f
+
+        fun @receiver:Rotation Int.isVerticalRotation(): Boolean =
+            this == Surface.ROTATION_0 || this == Surface.ROTATION_180
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
index 0016d95..139ac7e 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.unfold
 
 import com.android.keyguard.KeyguardUnfoldTransition
+import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.shade.NotificationPanelUnfoldAnimationController
 import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController
@@ -25,10 +26,14 @@
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
 import com.android.systemui.unfold.util.UnfoldKeyguardVisibilityManager
 import com.android.systemui.util.kotlin.getOrNull
+import dagger.Binds
 import dagger.BindsInstance
 import dagger.Module
 import dagger.Provides
 import dagger.Subcomponent
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
 import java.util.Optional
 import javax.inject.Named
 import javax.inject.Scope
@@ -70,8 +75,33 @@
     }
 }
 
+@Module
+interface SysUIUnfoldStartableModule {
+    @Binds
+    @IntoMap
+    @ClassKey(UnfoldInitializationStartable::class)
+    fun bindsUnfoldInitializationStartable(impl: UnfoldInitializationStartable): CoreStartable
+}
+
+@Module
+abstract class SysUIUnfoldInternalModule {
+    @Binds
+    @IntoSet
+    @SysUIUnfoldScope
+    abstract fun bindsUnfoldLightRevealOverlayAnimation(
+        anim: UnfoldLightRevealOverlayAnimation
+    ): FullscreenLightRevealAnimation
+
+    @Binds
+    @IntoSet
+    @SysUIUnfoldScope
+    abstract fun bindsFoldLightRevealOverlayAnimation(
+        anim: FoldLightRevealOverlayAnimation
+    ): FullscreenLightRevealAnimation
+}
+
 @SysUIUnfoldScope
-@Subcomponent
+@Subcomponent(modules = [SysUIUnfoldInternalModule::class])
 interface SysUIUnfoldComponent {
 
     @Subcomponent.Factory
@@ -92,12 +122,12 @@
 
     fun getFoldAodAnimationController(): FoldAodAnimationController
 
+    fun getFullScreenLightRevealAnimations(): Set<FullscreenLightRevealAnimation>
+
     fun getUnfoldTransitionWallpaperController(): UnfoldTransitionWallpaperController
 
     fun getUnfoldHapticsPlayer(): UnfoldHapticsPlayer
 
-    fun getUnfoldLightRevealOverlayAnimation(): UnfoldLightRevealOverlayAnimation
-
     fun getUnfoldKeyguardVisibilityManager(): UnfoldKeyguardVisibilityManager
 
     fun getUnfoldLatencyTracker(): UnfoldLatencyTracker
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldInitializationStartable.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldInitializationStartable.kt
new file mode 100644
index 0000000..75d8a58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldInitializationStartable.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.Flags
+import com.android.systemui.unfold.dagger.UnfoldBg
+import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder
+import java.util.Optional
+import javax.inject.Inject
+
+class UnfoldInitializationStartable
+@Inject
+constructor(
+    private val unfoldComponentOptional: Optional<SysUIUnfoldComponent>,
+    private val foldStateLoggingProviderOptional: Optional<FoldStateLoggingProvider>,
+    private val foldStateLoggerOptional: Optional<FoldStateLogger>,
+    @UnfoldBg
+    private val unfoldBgTransitionProgressProviderOptional:
+        Optional<UnfoldTransitionProgressProvider>,
+    private val unfoldTransitionProgressProviderOptional:
+        Optional<UnfoldTransitionProgressProvider>,
+    private val unfoldTransitionProgressForwarder: Optional<UnfoldTransitionProgressForwarder>
+) : CoreStartable {
+    override fun start() {
+        unfoldComponentOptional.ifPresent { c: SysUIUnfoldComponent ->
+            c.getFullScreenLightRevealAnimations().forEach { it: FullscreenLightRevealAnimation ->
+                it.init()
+            }
+            c.getUnfoldTransitionWallpaperController().init()
+            c.getUnfoldHapticsPlayer()
+            c.getNaturalRotationUnfoldProgressProvider().init()
+            c.getUnfoldLatencyTracker().init()
+        }
+
+        foldStateLoggingProviderOptional.ifPresent { obj: FoldStateLoggingProvider -> obj.init() }
+        foldStateLoggerOptional.ifPresent { obj: FoldStateLogger -> obj.init() }
+
+        val unfoldTransitionProgressProvider: Optional<UnfoldTransitionProgressProvider> =
+            if (Flags.unfoldAnimationBackgroundProgress()) {
+                unfoldBgTransitionProgressProviderOptional
+            } else {
+                unfoldTransitionProgressProviderOptional
+            }
+        unfoldTransitionProgressProvider.ifPresent {
+            progressProvider: UnfoldTransitionProgressProvider ->
+            unfoldTransitionProgressForwarder.ifPresent {
+                listener: UnfoldTransitionProgressForwarder ->
+                progressProvider.addCallback(listener)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index b72c6f1..f355dd8 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -18,42 +18,22 @@
 import android.annotation.BinderThread
 import android.content.ContentResolver
 import android.content.Context
-import android.graphics.PixelFormat
 import android.hardware.devicestate.DeviceStateManager
-import android.hardware.devicestate.DeviceStateManager.FoldStateListener
-import android.hardware.display.DisplayManager
 import android.hardware.input.InputManagerGlobal
 import android.os.Handler
-import android.os.Looper
 import android.os.Trace
-import android.view.Choreographer
-import android.view.Display
-import android.view.DisplayInfo
-import android.view.Surface
-import android.view.SurfaceControl
-import android.view.SurfaceControlViewHost
-import android.view.SurfaceSession
-import android.view.WindowManager
-import android.view.WindowlessWindowManager
-import com.android.app.tracing.traceSection
-import com.android.keyguard.logging.ScrimLogger
 import com.android.systemui.Flags.unfoldAnimationBackgroundProgress
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
-import com.android.systemui.settings.DisplayTracker
-import com.android.systemui.statusbar.LightRevealEffect
-import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.LinearLightRevealEffect
+import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.ALPHA_OPAQUE
+import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.ALPHA_TRANSPARENT
+import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.isVerticalRotation
 import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.FOLD
 import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.UNFOLD
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import com.android.systemui.unfold.dagger.UnfoldBg
-import com.android.systemui.unfold.updates.RotationChangeProvider
 import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled
 import com.android.systemui.util.concurrency.ThreadFactory
-import com.android.wm.shell.displayareahelper.DisplayAreaHelper
-import java.util.Optional
 import java.util.concurrent.Executor
 import java.util.function.Consumer
 import javax.inject.Inject
@@ -64,80 +44,43 @@
 @Inject
 constructor(
     private val context: Context,
-    private val featureFlags: FeatureFlags,
-    private val deviceStateManager: DeviceStateManager,
+    private val featureFlags: FeatureFlagsClassic,
     private val contentResolver: ContentResolver,
-    private val displayManager: DisplayManager,
+    @UnfoldBg private val unfoldProgressHandler: Handler,
     @UnfoldBg
     private val unfoldTransitionBgProgressProvider: Provider<UnfoldTransitionProgressProvider>,
     private val unfoldTransitionProgressProvider: Provider<UnfoldTransitionProgressProvider>,
-    private val displayAreaHelper: Optional<DisplayAreaHelper>,
-    @Main private val executor: Executor,
+    private val deviceStateManager: DeviceStateManager,
     private val threadFactory: ThreadFactory,
-    @UnfoldBg private val rotationChangeProvider: RotationChangeProvider,
-    @UnfoldBg private val unfoldProgressHandler: Handler,
-    private val displayTracker: DisplayTracker,
-    private val scrimLogger: ScrimLogger,
-) {
+    private val fullscreenLightRevealAnimationControllerFactory:
+        FullscreenLightRevealAnimationController.Factory
+) : FullscreenLightRevealAnimation {
 
     private val transitionListener = TransitionListener()
-    private val rotationWatcher = RotationWatcher()
-
-    private lateinit var bgHandler: Handler
-    private lateinit var bgExecutor: Executor
-
-    private lateinit var wwm: WindowlessWindowManager
-    private lateinit var unfoldedDisplayInfo: DisplayInfo
-    private lateinit var overlayContainer: SurfaceControl
-
-    private var root: SurfaceControlViewHost? = null
-    private var scrimView: LightRevealScrim? = null
     private var isFolded: Boolean = false
     private var isUnfoldHandled: Boolean = true
-    private var overlayAddReason: AddOverlayReason? = null
-    private var isTouchBlocked: Boolean = true
+    private var overlayAddReason: AddOverlayReason = UNFOLD
+    private lateinit var controller: FullscreenLightRevealAnimationController
+    private lateinit var bgExecutor: Executor
 
-    private var currentRotation: Int = context.display!!.rotation
-
-    fun init() {
+    override fun init() {
         // This method will be called only on devices where this animation is enabled,
         // so normally this thread won't be created
-        bgHandler = unfoldProgressHandler
-        bgExecutor = threadFactory.buildDelayableExecutorOnHandler(bgHandler)
 
+        controller =
+            fullscreenLightRevealAnimationControllerFactory.create(
+                displaySelector = { maxByOrNull { it.naturalWidth } },
+                effectFactory = { LinearLightRevealEffect(it.isVerticalRotation()) },
+                overlayContainerName = SURFACE_CONTAINER_NAME,
+            )
+        controller.init()
+        bgExecutor = threadFactory.buildDelayableExecutorOnHandler(unfoldProgressHandler)
         deviceStateManager.registerCallback(bgExecutor, FoldListener())
         if (unfoldAnimationBackgroundProgress()) {
             unfoldTransitionBgProgressProvider.get().addCallback(transitionListener)
         } else {
             unfoldTransitionProgressProvider.get().addCallback(transitionListener)
         }
-        rotationChangeProvider.addCallback(rotationWatcher)
-
-        val containerBuilder =
-            SurfaceControl.Builder(SurfaceSession())
-                .setContainerLayer()
-                .setName("unfold-overlay-container")
-
-        displayAreaHelper.get().attachToRootDisplayArea(
-            displayTracker.defaultDisplayId,
-            containerBuilder
-        ) { builder ->
-            executor.execute {
-                overlayContainer = builder.build()
-
-                SurfaceControl.Transaction()
-                    .setLayer(overlayContainer, UNFOLD_OVERLAY_LAYER_Z_INDEX)
-                    .show(overlayContainer)
-                    .apply()
-
-                wwm =
-                    WindowlessWindowManager(context.resources.configuration, overlayContainer, null)
-            }
-        }
-
-        // Get unfolded display size immediately as 'current display info' might be
-        // not up-to-date during unfolding
-        unfoldedDisplayInfo = getUnfoldedDisplayInfo()
     }
 
     /**
@@ -148,17 +91,18 @@
      * @see [com.android.systemui.keyguard.KeyguardViewMediator]
      */
     @BinderThread
-    fun onScreenTurningOn(onOverlayReady: Runnable) {
+    override fun onScreenTurningOn(onOverlayReady: Runnable) {
         executeInBackground {
             Trace.beginSection("$TAG#onScreenTurningOn")
             try {
                 // Add the view only if we are unfolding and this is the first screen on
                 if (!isFolded && !isUnfoldHandled && contentResolver.areAnimationsEnabled()) {
-                    addOverlay(onOverlayReady, reason = UNFOLD)
+                    overlayAddReason = UNFOLD
+                    controller.addOverlay(calculateRevealAmount(), onOverlayReady)
                     isUnfoldHandled = true
                 } else {
                     // No unfold transition, immediately report that overlay is ready
-                    ensureOverlayRemoved()
+                    controller.ensureOverlayRemoved()
                     onOverlayReady.run()
                 }
             } finally {
@@ -167,78 +111,15 @@
         }
     }
 
-    private fun addOverlay(onOverlayReady: Runnable? = null, reason: AddOverlayReason) {
-        if (!::wwm.isInitialized) {
-            // Surface overlay is not created yet on the first SysUI launch
-            onOverlayReady?.run()
-            return
-        }
-
-        ensureInBackground()
-        ensureOverlayRemoved()
-
-        overlayAddReason = reason
-
-        val newRoot =
-            SurfaceControlViewHost(
-                context,
-                context.display,
-                wwm,
-                "UnfoldLightRevealOverlayAnimation"
-            )
-        val params = getLayoutParams()
-        val newView =
-            LightRevealScrim(
-                    context,
-                    attrs = null,
-                    initialWidth = params.width,
-                    initialHeight = params.height
-                )
-                .apply {
-                    revealEffect = createLightRevealEffect()
-                    revealAmount = calculateRevealAmount()
-                    scrimLogger = 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/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/view/DisposableHandleExt.kt b/packages/SystemUI/src/com/android/systemui/util/view/DisposableHandleExt.kt
deleted file mode 100644
index d3653b4..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/view/DisposableHandleExt.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.util.view
-
-import android.view.View
-import com.android.systemui.util.kotlin.awaitCancellationThenDispose
-import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collectLatest
-
-/**
- * Use the [bind] method to bind the view every time this flow emits, and suspend to await for more
- * updates. New emissions lead to the previous binding call being cancelled if not completed.
- * Dispose of the [DisposableHandle] returned by [bind] when done.
- */
-suspend fun <T : View> Flow<T>.bindLatest(bind: (T) -> DisposableHandle?) {
-    this.collectLatest { view ->
-        val disposableHandle = bind(view)
-        disposableHandle?.awaitCancellationThenDispose()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index ce6d740..90c5c62 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -116,9 +116,8 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.Prefs;
-import com.android.systemui.dagger.qualifiers.Application;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.haptics.slider.HapticSliderViewBinder;
 import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin;
 import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
@@ -145,9 +144,6 @@
 import java.util.List;
 import java.util.function.Consumer;
 
-import kotlinx.coroutines.CoroutineDispatcher;
-import kotlinx.coroutines.CoroutineScope;
-
 /**
  * Visual presentation of the volume dialog.
  *
@@ -311,8 +307,6 @@
     private int mOrientation;
     private final Lazy<SecureSettings> mSecureSettings;
     private int mDialogTimeoutMillis;
-    private final CoroutineDispatcher mMainDispatcher;
-    private final CoroutineScope mApplicationScope;
     private final VibratorHelper mVibratorHelper;
     private final com.android.systemui.util.time.SystemClock mSystemClock;
 
@@ -333,14 +327,10 @@
             DumpManager dumpManager,
             Lazy<SecureSettings> secureSettings,
             VibratorHelper vibratorHelper,
-            @Main CoroutineDispatcher mainDispatcher,
-            @Application CoroutineScope applicationScope,
             com.android.systemui.util.time.SystemClock systemClock) {
         mContext =
                 new ContextThemeWrapper(context, R.style.volume_dialog_theme);
         mHandler = new H(looper);
-        mMainDispatcher = mainDispatcher;
-        mApplicationScope = applicationScope;
         mVibratorHelper = vibratorHelper;
         mSystemClock = systemClock;
         mShouldListenForJank = shouldListenForJank;
@@ -858,7 +848,10 @@
             row.header.setFilters(new InputFilter[] {new InputFilter.LengthFilter(13)});
         }
         row.slider = row.view.findViewById(R.id.volume_row_slider);
-        row.createPlugin(mVibratorHelper, mSystemClock, mMainDispatcher, mApplicationScope);
+        if (hapticVolumeSlider()) {
+            row.createPlugin(mVibratorHelper, mSystemClock);
+            HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin);
+        }
         row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
         row.number = row.view.findViewById(R.id.volume_number);
 
@@ -1498,7 +1491,7 @@
         for (int i = 0; i < mRows.size(); i++) {
             VolumeRow row = mRows.get(i);
             if (row.slider.getVisibility() == VISIBLE) {
-                row.addHaptics();
+                row.addTouchListener();
             }
         }
         Trace.endSection();
@@ -2620,17 +2613,13 @@
 
         void createPlugin(
                 VibratorHelper vibratorHelper,
-                com.android.systemui.util.time.SystemClock systemClock,
-                CoroutineDispatcher mainDispatcher,
-                CoroutineScope applicationScope) {
-            if (!hapticVolumeSlider() || mHapticPlugin != null) return;
+                com.android.systemui.util.time.SystemClock systemClock) {
+            if (mHapticPlugin != null) return;
 
             mHapticPlugin = new SeekableSliderHapticPlugin(
-                    vibratorHelper,
-                    systemClock,
-                    mainDispatcher,
-                    applicationScope,
-                    sSliderHapticFeedbackConfig);
+                vibratorHelper,
+                systemClock,
+                sSliderHapticFeedbackConfig);
         }
 
 
@@ -2647,19 +2636,9 @@
             });
         }
 
-        void addHaptics() {
-            if (mHapticPlugin != null) {
-                addTouchListener();
-                mHapticPlugin.start();
-            }
-        }
-
         @SuppressLint("ClickableViewAccessibility")
         void removeHaptics() {
             slider.setOnTouchListener(null);
-            if (mHapticPlugin != null) {
-                mHapticPlugin.stop();
-            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index ff1daea..278ffc6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -21,6 +21,8 @@
 import com.android.settingslib.volume.data.repository.AudioRepository
 import com.android.settingslib.volume.data.repository.AudioRepositoryImpl
 import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
+import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
+import com.android.settingslib.volume.shared.AudioManagerIntentsReceiverImpl
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import dagger.Module
@@ -35,13 +37,19 @@
     companion object {
 
         @Provides
-        fun provideAudioRepository(
+        fun provideAudioManagerIntentsReceiver(
             @Application context: Context,
+            @Application coroutineScope: CoroutineScope,
+        ): AudioManagerIntentsReceiver = AudioManagerIntentsReceiverImpl(context, coroutineScope)
+
+        @Provides
+        fun provideAudioRepository(
+            intentsReceiver: AudioManagerIntentsReceiver,
             audioManager: AudioManager,
             @Background coroutineContext: CoroutineContext,
             @Application coroutineScope: CoroutineScope,
         ): AudioRepository =
-            AudioRepositoryImpl(context, audioManager, coroutineContext, coroutineScope)
+            AudioRepositoryImpl(intentsReceiver, audioManager, coroutineContext, coroutineScope)
 
         @Provides
         fun provideAudioModeInteractor(repository: AudioRepository): AudioModeInteractor =
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
index 2ff9af9..ab76d45 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
@@ -16,11 +16,11 @@
 
 package com.android.systemui.volume.dagger
 
-import android.content.Context
 import android.media.session.MediaSessionManager
 import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.settingslib.volume.data.repository.MediaControllerRepository
 import com.android.settingslib.volume.data.repository.MediaControllerRepositoryImpl
+import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -37,14 +37,14 @@
         @Provides
         @SysUISingleton
         fun provideMediaDeviceSessionRepository(
-            @Application context: Context,
+            intentsReceiver: AudioManagerIntentsReceiver,
             mediaSessionManager: MediaSessionManager,
             localBluetoothManager: LocalBluetoothManager?,
             @Application coroutineScope: CoroutineScope,
             @Background backgroundContext: CoroutineContext,
         ): MediaControllerRepository =
             MediaControllerRepositoryImpl(
-                context,
+                intentsReceiver,
                 mediaSessionManager,
                 localBluetoothManager,
                 coroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 2718839..3285637 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -23,8 +23,6 @@
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.systemui.CoreStartable;
-import com.android.systemui.dagger.qualifiers.Application;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.VolumeDialog;
@@ -55,9 +53,6 @@
 import dagger.multibindings.IntoMap;
 import dagger.multibindings.IntoSet;
 
-import kotlinx.coroutines.CoroutineDispatcher;
-import kotlinx.coroutines.CoroutineScope;
-
 /** Dagger Module for code in the volume package. */
 @Module(
         includes = {
@@ -112,8 +107,6 @@
             DumpManager dumpManager,
             Lazy<SecureSettings> secureSettings,
             VibratorHelper vibratorHelper,
-            @Main CoroutineDispatcher mainDispatcher,
-            @Application CoroutineScope applicationScope,
             SystemClock systemClock) {
         VolumeDialogImpl impl = new VolumeDialogImpl(
                 context,
@@ -132,8 +125,6 @@
                 dumpManager,
                 secureSettings,
                 vibratorHelper,
-                mainDispatcher,
-                applicationScope,
                 systemClock);
         impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
         impl.setAutomute(true);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
index 57ac435..0a1ee24 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
@@ -15,8 +15,10 @@
  */
 package com.android.systemui.volume.panel.component.mediaoutput.data.repository
 
+import android.media.MediaRouter2Manager
 import com.android.settingslib.volume.data.repository.LocalMediaRepository
 import com.android.settingslib.volume.data.repository.LocalMediaRepositoryImpl
+import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.media.controls.pipeline.LocalMediaManagerFactory
@@ -27,6 +29,8 @@
 class LocalMediaRepositoryFactory
 @Inject
 constructor(
+    private val intentsReceiver: AudioManagerIntentsReceiver,
+    private val mediaRouter2Manager: MediaRouter2Manager,
     private val localMediaManagerFactory: LocalMediaManagerFactory,
     @Application private val coroutineScope: CoroutineScope,
     @Background private val backgroundCoroutineContext: CoroutineContext,
@@ -34,7 +38,9 @@
 
     fun create(packageName: String?): LocalMediaRepository =
         LocalMediaRepositoryImpl(
+            intentsReceiver,
             localMediaManagerFactory.create(packageName),
+            mediaRouter2Manager,
             coroutineScope,
             backgroundCoroutineContext,
         )
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index c2efc05..d048cbe 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -28,6 +28,7 @@
 import com.android.internal.widget.LockscreenCredential
 import com.android.keyguard.KeyguardPinViewController.PinBouncerUiEvent
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode
+import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.classifier.FalsingCollectorFake
@@ -88,6 +89,7 @@
 
     @Mock private val mEmergencyButtonController: EmergencyButtonController? = null
     private val falsingCollector: FalsingCollector = FalsingCollectorFake()
+    private val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
     @Mock lateinit var postureController: DevicePostureController
     @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
 
@@ -143,7 +145,7 @@
             featureFlags,
             mSelectedUserInteractor,
             uiEventLogger,
-            FakeKeyboardRepository()
+            keyguardKeyboardInteractor
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index 0959f1b..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/ActivityLaunchAnimatorTest.kt
index 8faf715..722107c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
@@ -45,11 +45,11 @@
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper
 class ActivityLaunchAnimatorTest : SysuiTestCase() {
-    private val launchContainer = LinearLayout(mContext)
-    private val testLaunchAnimator = fakeLaunchAnimator()
+    private val transitionContainer = LinearLayout(mContext)
+    private val testTransitionAnimator = fakeTransitionAnimator()
     @Mock lateinit var callback: ActivityLaunchAnimator.Callback
     @Mock lateinit var listener: ActivityLaunchAnimator.Listener
-    @Spy private val controller = TestLaunchAnimatorController(launchContainer)
+    @Spy private val controller = TestLaunchAnimatorController(transitionContainer)
     @Mock lateinit var iCallback: IRemoteAnimationFinishedCallback
 
     private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
@@ -58,7 +58,11 @@
     @Before
     fun setup() {
         activityLaunchAnimator =
-            ActivityLaunchAnimator(testLaunchAnimator, testLaunchAnimator, disableWmTimeout = true)
+            ActivityLaunchAnimator(
+                testTransitionAnimator,
+                testTransitionAnimator,
+                disableWmTimeout = true
+            )
         activityLaunchAnimator.callback = callback
         activityLaunchAnimator.addListener(listener)
     }
@@ -165,7 +169,7 @@
 
         waitForIdleSync()
         verify(controller).onLaunchAnimationCancelled()
-        verify(controller, never()).onLaunchAnimationStart(anyBoolean())
+        verify(controller, never()).onTransitionAnimationStart(anyBoolean())
         verify(listener).onLaunchAnimationCancelled()
         verify(listener, never()).onLaunchAnimationStart()
         assertNull(runner.delegate)
@@ -178,7 +182,7 @@
 
         waitForIdleSync()
         verify(controller).onLaunchAnimationCancelled()
-        verify(controller, never()).onLaunchAnimationStart(anyBoolean())
+        verify(controller, never()).onTransitionAnimationStart(anyBoolean())
         verify(listener).onLaunchAnimationCancelled()
         verify(listener, never()).onLaunchAnimationStart()
         assertNull(runner.delegate)
@@ -190,7 +194,7 @@
         runner.onAnimationStart(0, arrayOf(fakeWindow()), emptyArray(), emptyArray(), iCallback)
         waitForIdleSync()
         verify(listener).onLaunchAnimationStart()
-        verify(controller).onLaunchAnimationStart(anyBoolean())
+        verify(controller).onTransitionAnimationStart(anyBoolean())
     }
 
     @Test
@@ -240,10 +244,10 @@
  * A simple implementation of [ActivityLaunchAnimator.Controller] which throws if it is called
  * outside of the main thread.
  */
-private class TestLaunchAnimatorController(override var launchContainer: ViewGroup) :
+private class TestLaunchAnimatorController(override var transitionContainer: ViewGroup) :
     ActivityLaunchAnimator.Controller {
     override fun createAnimatorState() =
-        LaunchAnimator.State(
+        TransitionAnimator.State(
             top = 100,
             bottom = 200,
             left = 300,
@@ -262,19 +266,19 @@
         assertOnMainThread()
     }
 
-    override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+    override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
         assertOnMainThread()
     }
 
-    override fun onLaunchAnimationProgress(
-        state: LaunchAnimator.State,
+    override fun onTransitionAnimationProgress(
+        state: TransitionAnimator.State,
         progress: Float,
         linearProgress: Float
     ) {
         assertOnMainThread()
     }
 
-    override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+    override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
         assertOnMainThread()
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index 2233e322..a586421 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -129,14 +129,14 @@
 
         // The dialog shouldn't be dismissable during the animation.
         runOnMainThreadAndWaitForIdleSync {
-            controller.onLaunchAnimationStart(isExpandingFullyAbove = true)
+            controller.onTransitionAnimationStart(isExpandingFullyAbove = true)
             secondDialog.dismiss()
         }
         assertTrue(secondDialog.isShowing)
 
         // Both dialogs should be dismissed at the end of the animation.
         runOnMainThreadAndWaitForIdleSync {
-            controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
+            controller.onTransitionAnimationEnd(isExpandingFullyAbove = true)
         }
         assertFalse(firstDialog.isShowing)
         assertFalse(secondDialog.isShowing)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
index d1ac0e8..8442a62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
@@ -32,13 +32,13 @@
 class GhostedViewLaunchAnimatorControllerTest : SysuiTestCase() {
     @Test
     fun animatingOrphanViewDoesNotCrash() {
-        val state = LaunchAnimator.State(top = 0, bottom = 0, left = 0, right = 0)
+        val state = TransitionAnimator.State(top = 0, bottom = 0, left = 0, right = 0)
 
         val controller = GhostedViewLaunchAnimatorController(LaunchableFrameLayout(mContext))
         controller.onIntentStarted(willAnimate = true)
-        controller.onLaunchAnimationStart(isExpandingFullyAbove = true)
-        controller.onLaunchAnimationProgress(state, progress = 0f, linearProgress = 0f)
-        controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
+        controller.onTransitionAnimationStart(isExpandingFullyAbove = true)
+        controller.onTransitionAnimationProgress(state, progress = 0f, linearProgress = 0f)
+        controller.onTransitionAnimationEnd(isExpandingFullyAbove = true)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/ConfigurationStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/ui/ConfigurationStateTest.kt
deleted file mode 100644
index 112cec2..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/ConfigurationStateTest.kt
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- *
- */
-
-package com.android.systemui.common.ui
-
-import android.content.Context
-import android.testing.AndroidTestingRunner
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.mockito.captureMany
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.cancelAndJoin
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.atLeastOnce
-import org.mockito.Mockito.verify
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class ConfigurationStateTest : SysuiTestCase() {
-
-    private val configurationController: ConfigurationController = mock()
-    private val layoutInflater = TestLayoutInflater()
-    private val backgroundDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(backgroundDispatcher)
-
-    val underTest = ConfigurationState(configurationController, context, layoutInflater)
-
-    @Test
-    fun reinflateAndBindLatest_inflatesWithoutEmission() =
-        testScope.runTest {
-            var callbackCount = 0
-            backgroundScope.launch {
-                underTest.reinflateAndBindLatest<View>(
-                    resource = 0,
-                    root = null,
-                    attachToRoot = false,
-                    backgroundDispatcher,
-                ) {
-                    callbackCount++
-                    null
-                }
-            }
-
-            // Inflates without an emission
-            runCurrent()
-            assertThat(layoutInflater.inflationCount).isEqualTo(1)
-            assertThat(callbackCount).isEqualTo(1)
-        }
-
-    @Test
-    fun reinflateAndBindLatest_reinflatesOnThemeChanged() =
-        testScope.runTest {
-            var callbackCount = 0
-            backgroundScope.launch {
-                underTest.reinflateAndBindLatest<View>(
-                    resource = 0,
-                    root = null,
-                    attachToRoot = false,
-                    backgroundDispatcher,
-                ) {
-                    callbackCount++
-                    null
-                }
-            }
-            runCurrent()
-
-            val configListeners: List<ConfigurationController.ConfigurationListener> = captureMany {
-                verify(configurationController, atLeastOnce()).addCallback(capture())
-            }
-
-            listOf(1, 2, 3).forEach { count ->
-                assertThat(layoutInflater.inflationCount).isEqualTo(count)
-                assertThat(callbackCount).isEqualTo(count)
-                configListeners.forEach { it.onThemeChanged() }
-                runCurrent()
-            }
-        }
-
-    @Test
-    fun reinflateAndBindLatest_reinflatesOnDensityOrFontScaleChanged() =
-        testScope.runTest {
-            var callbackCount = 0
-            backgroundScope.launch {
-                underTest.reinflateAndBindLatest<View>(
-                    resource = 0,
-                    root = null,
-                    attachToRoot = false,
-                    backgroundDispatcher,
-                ) {
-                    callbackCount++
-                    null
-                }
-            }
-            runCurrent()
-
-            val configListeners: List<ConfigurationController.ConfigurationListener> = captureMany {
-                verify(configurationController, atLeastOnce()).addCallback(capture())
-            }
-
-            listOf(1, 2, 3).forEach { count ->
-                assertThat(layoutInflater.inflationCount).isEqualTo(count)
-                assertThat(callbackCount).isEqualTo(count)
-                configListeners.forEach { it.onDensityOrFontScaleChanged() }
-                runCurrent()
-            }
-        }
-
-    @Test
-    fun testReinflateAndBindLatest_disposesOnCancel() =
-        testScope.runTest {
-            var callbackCount = 0
-            var disposed = false
-            val job = launch {
-                underTest.reinflateAndBindLatest<View>(
-                    resource = 0,
-                    root = null,
-                    attachToRoot = false,
-                    backgroundDispatcher,
-                ) {
-                    callbackCount++
-                    DisposableHandle { disposed = true }
-                }
-            }
-
-            runCurrent()
-            job.cancelAndJoin()
-            assertThat(disposed).isTrue()
-        }
-
-    inner class TestLayoutInflater : LayoutInflater(context) {
-
-        var inflationCount = 0
-
-        override fun inflate(resource: Int, root: ViewGroup?, attachToRoot: Boolean): View {
-            inflationCount++
-            return View(context)
-        }
-
-        override fun cloneInContext(p0: Context?): LayoutInflater {
-            // not needed for this test
-            return this
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index b28d0c8..e30dd35d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -33,8 +33,7 @@
 import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig
-import com.android.systemui.deviceentry.data.repository.faceWakeUpTriggersConfig
+import com.android.systemui.deviceentry.data.repository.fakeFaceWakeUpTriggersConfig
 import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
 import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
 import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
@@ -56,10 +55,9 @@
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -68,37 +66,33 @@
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() {
-    val kosmos =
-        testKosmos().apply { this.faceWakeUpTriggersConfig = mock<FaceWakeUpTriggersConfig>() }
+    private val kosmos = testKosmos()
+    private val testScope: TestScope = kosmos.testScope
 
     private lateinit var underTest: SystemUIDeviceEntryFaceAuthInteractor
-    private val testScope = kosmos.testScope
     private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
     private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
     private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
     private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
     private val fakeUserRepository = kosmos.fakeUserRepository
     private val facePropertyRepository = kosmos.facePropertyRepository
-    private val fakeDeviceEntryFingerprintAuthRepository =
-        kosmos.fakeDeviceEntryFingerprintAuthRepository
+    private val fakeDeviceEntryFingerprintAuthInteractor =
+        kosmos.deviceEntryFingerprintAuthInteractor
     private val powerInteractor = kosmos.powerInteractor
     private val fakeBiometricSettingsRepository = kosmos.fakeBiometricSettingsRepository
 
     private val keyguardUpdateMonitor = kosmos.keyguardUpdateMonitor
-    private val faceWakeUpTriggersConfig = kosmos.faceWakeUpTriggersConfig
+    private val faceWakeUpTriggersConfig = kosmos.fakeFaceWakeUpTriggersConfig
     private val trustManager = kosmos.trustManager
 
     @Before
     fun setup() {
-        MockitoAnnotations.initMocks(this)
         fakeUserRepository.setUserInfos(listOf(primaryUser, secondaryUser))
-
         underTest =
             SystemUIDeviceEntryFaceAuthInteractor(
                 mContext,
@@ -110,7 +104,7 @@
                 keyguardTransitionInteractor,
                 FaceAuthenticationLogger(logcatLogBuffer("faceAuthBuffer")),
                 keyguardUpdateMonitor,
-                fakeDeviceEntryFingerprintAuthRepository,
+                fakeDeviceEntryFingerprintAuthInteractor,
                 fakeUserRepository,
                 facePropertyRepository,
                 faceWakeUpTriggersConfig,
@@ -126,10 +120,9 @@
             underTest.start()
 
             powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_LID)
-            whenever(
-                    faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(WakeSleepReason.LID)
-                )
-                .thenReturn(true)
+            faceWakeUpTriggersConfig.setTriggerFaceAuthOnWakeUpFrom(
+                setOf(WakeSleepReason.LID.powerManagerWakeReason)
+            )
 
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
@@ -168,10 +161,9 @@
             underTest.start()
 
             powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_LID)
-            whenever(
-                    faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(WakeSleepReason.LID)
-                )
-                .thenReturn(true)
+            faceWakeUpTriggersConfig.setTriggerFaceAuthOnWakeUpFrom(
+                setOf(WakeSleepReason.LID.powerManagerWakeReason)
+            )
 
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
@@ -194,10 +186,9 @@
             underTest.start()
 
             powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_LIFT)
-            whenever(
-                    faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(WakeSleepReason.LIFT)
-                )
-                .thenReturn(false)
+            faceWakeUpTriggersConfig.setTriggerFaceAuthOnWakeUpFrom(
+                setOf(WakeSleepReason.LID.powerManagerWakeReason)
+            )
 
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
@@ -217,10 +208,9 @@
             underTest.start()
 
             powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_LID)
-            whenever(
-                    faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(WakeSleepReason.LID)
-                )
-                .thenReturn(true)
+            faceWakeUpTriggersConfig.setTriggerFaceAuthOnWakeUpFrom(
+                setOf(WakeSleepReason.LID.powerManagerWakeReason)
+            )
 
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
@@ -440,7 +430,45 @@
             underTest.start()
             fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
 
-            fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)
+            runCurrent()
+
+            assertThat(faceAuthRepository.isLockedOut.value).isTrue()
+        }
+
+    @Test
+    fun faceLockoutStateIsResetWheneverFingerprintIsNotLockedOut() =
+        testScope.runTest {
+            underTest.start()
+            fakeUserRepository.setSelectedUserInfo(primaryUser, SelectionStatus.SELECTION_COMPLETE)
+            fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)
+            runCurrent()
+
+            assertThat(faceAuthRepository.isLockedOut.value).isTrue()
+            facePropertyRepository.setLockoutMode(primaryUserId, LockoutMode.NONE)
+
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(false)
+            runCurrent()
+
+            assertThat(faceAuthRepository.isLockedOut.value).isFalse()
+        }
+
+    @Test
+    fun faceLockoutStateIsSetToUsersLockoutStateWheneverFingerprintIsNotLockedOut() =
+        testScope.runTest {
+            underTest.start()
+            fakeUserRepository.setSelectedUserInfo(primaryUser, SelectionStatus.SELECTION_COMPLETE)
+            fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)
+            runCurrent()
+
+            assertThat(faceAuthRepository.isLockedOut.value).isTrue()
+            facePropertyRepository.setLockoutMode(primaryUserId, LockoutMode.TIMED)
+
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setLockedOut(false)
             runCurrent()
 
             assertThat(faceAuthRepository.isLockedOut.value).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
index db04962..796d6d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt
@@ -20,8 +20,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestCoroutineScheduler
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runTest
@@ -36,6 +36,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class SeekableSliderTrackerTest : SysuiTestCase() {
 
@@ -51,7 +52,7 @@
     @Test
     fun initializeSliderTracker_startsTracking() = runTest {
         // GIVEN Initialized tracker
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // THEN the tracker job is active
         assertThat(mSeekableSliderTracker.isTracking).isTrue()
@@ -61,7 +62,7 @@
     fun stopTracking_onAnyState_resetsToIdle() = runTest {
         enumValues<SliderState>().forEach {
             // GIVEN Initialized tracker
-            initTracker(testScheduler)
+            initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
             // GIVEN a state in the state machine
             mSeekableSliderTracker.setState(it)
@@ -79,7 +80,7 @@
     @Test
     fun initializeSliderTracker_isIdle() = runTest {
         // GIVEN Initialized tracker
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // THEN The state is idle and the listener is not called to play haptics
         assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.IDLE)
@@ -88,7 +89,7 @@
 
     @Test
     fun startsTrackingTouch_onIdle_entersWaitState() = runTest {
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // GIVEN a start of tracking touch event
         val progress = 0f
@@ -106,7 +107,7 @@
     @Test
     fun waitCompletes_onWait_movesToHandleAcquired() = runTest {
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
         // GIVEN a start of tracking touch event that moves the tracker to WAIT
         val progress = 0f
@@ -126,7 +127,7 @@
     @Test
     fun impreciseTouch_onWait_movesToHandleAcquired() = runTest {
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
         // GIVEN a start of tracking touch event that moves the tracker to WAIT at the middle of the
         // slider
@@ -151,7 +152,7 @@
     @Test
     fun trackJump_onWait_movesToJumpTrackLocationSelected() = runTest {
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
         // GIVEN a start of tracking touch event that moves the tracker to WAIT at the middle of the
         // slider
@@ -175,7 +176,7 @@
     @Test
     fun upperBookendSelection_onWait_movesToBookendSelected() = runTest {
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
         // GIVEN a start of tracking touch event that moves the tracker to WAIT at the middle of the
         // slider
@@ -197,7 +198,7 @@
     @Test
     fun lowerBookendSelection_onWait_movesToBookendSelected() = runTest {
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
         // GIVEN a start of tracking touch event that moves the tracker to WAIT at the middle of the
         // slider
@@ -219,7 +220,7 @@
     @Test
     fun stopTracking_onWait_whenWaitingJobIsActive_resetsToIdle() = runTest {
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
         // GIVEN a start of tracking touch event that moves the tracker to WAIT at the middle of the
         // slider
@@ -240,7 +241,7 @@
 
     @Test
     fun progressChangeByUser_onJumpTrackLocationSelected_movesToDragHandleDragging() = runTest {
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // GIVEN a JUMP_TRACK_LOCATION_SELECTED state
         mSeekableSliderTracker.setState(SliderState.JUMP_TRACK_LOCATION_SELECTED)
@@ -256,7 +257,7 @@
 
     @Test
     fun touchRelease_onJumpTrackLocationSelected_movesToIdle() = runTest {
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // GIVEN a JUMP_TRACK_LOCATION_SELECTED state
         mSeekableSliderTracker.setState(SliderState.JUMP_TRACK_LOCATION_SELECTED)
@@ -272,7 +273,7 @@
 
     @Test
     fun progressChangeByUser_onJumpBookendSelected_movesToDragHandleDragging() = runTest {
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // GIVEN a JUMP_BOOKEND_SELECTED state
         mSeekableSliderTracker.setState(SliderState.JUMP_BOOKEND_SELECTED)
@@ -288,7 +289,7 @@
 
     @Test
     fun touchRelease_onJumpBookendSelected_movesToIdle() = runTest {
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // GIVEN a JUMP_BOOKEND_SELECTED state
         mSeekableSliderTracker.setState(SliderState.JUMP_BOOKEND_SELECTED)
@@ -306,7 +307,7 @@
 
     @Test
     fun progressChangeByUser_onHandleAcquired_movesToDragHandleDragging() = runTest {
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // GIVEN a DRAG_HANDLE_ACQUIRED_BY_TOUCH state
         mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_ACQUIRED_BY_TOUCH)
@@ -325,7 +326,7 @@
 
     @Test
     fun touchRelease_onHandleAcquired_movesToIdle() = runTest {
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // GIVEN a DRAG_HANDLE_ACQUIRED_BY_TOUCH state
         mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_ACQUIRED_BY_TOUCH)
@@ -344,7 +345,7 @@
     @Test
     fun progressChangeByUser_onHandleDragging_progressOutsideOfBookends_doesNotChangeState() =
         runTest {
-            initTracker(testScheduler)
+            initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
             // GIVEN a DRAG_HANDLE_DRAGGING state
             mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_DRAGGING)
@@ -366,7 +367,7 @@
     fun progressChangeByUser_onHandleDragging_reachesLowerBookend_movesToHandleReachedBookend() =
         runTest {
             val config = SeekableSliderTrackerConfig()
-            initTracker(testScheduler, config)
+            initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
             // GIVEN a DRAG_HANDLE_DRAGGING state
             mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_DRAGGING)
@@ -389,7 +390,7 @@
     fun progressChangeByUser_onHandleDragging_reachesUpperBookend_movesToHandleReachedBookend() =
         runTest {
             val config = SeekableSliderTrackerConfig()
-            initTracker(testScheduler, config)
+            initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
             // GIVEN a DRAG_HANDLE_DRAGGING state
             mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_DRAGGING)
@@ -410,7 +411,7 @@
 
     @Test
     fun touchRelease_onHandleDragging_movesToIdle() = runTest {
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // GIVEN a DRAG_HANDLE_DRAGGING state
         mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_DRAGGING)
@@ -430,7 +431,7 @@
     fun progressChangeByUser_outsideOfBookendRange_onLowerBookend_movesToDragHandleDragging() =
         runTest {
             val config = SeekableSliderTrackerConfig()
-            initTracker(testScheduler, config)
+            initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
             // GIVEN a DRAG_HANDLE_REACHED_BOOKEND state
             mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_REACHED_BOOKEND)
@@ -451,7 +452,7 @@
     @Test
     fun progressChangeByUser_insideOfBookendRange_onLowerBookend_doesNotChangeState() = runTest {
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
         // GIVEN a DRAG_HANDLE_REACHED_BOOKEND state
         mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_REACHED_BOOKEND)
@@ -473,7 +474,7 @@
     fun progressChangeByUser_outsideOfBookendRange_onUpperBookend_movesToDragHandleDragging() =
         runTest {
             val config = SeekableSliderTrackerConfig()
-            initTracker(testScheduler, config)
+            initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
             // GIVEN a DRAG_HANDLE_REACHED_BOOKEND state
             mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_REACHED_BOOKEND)
@@ -494,7 +495,7 @@
     @Test
     fun progressChangeByUser_insideOfBookendRange_onUpperBookend_doesNotChangeState() = runTest {
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
         // GIVEN a DRAG_HANDLE_REACHED_BOOKEND state
         mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_REACHED_BOOKEND)
@@ -514,7 +515,7 @@
 
     @Test
     fun touchRelease_onHandleReachedBookend_movesToIdle() = runTest {
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // GIVEN a DRAG_HANDLE_REACHED_BOOKEND state
         mSeekableSliderTracker.setState(SliderState.DRAG_HANDLE_REACHED_BOOKEND)
@@ -531,7 +532,7 @@
     @Test
     fun onProgressChangeByProgram_atTheMiddle_onIdle_movesToArrowHandleMovedOnce() = runTest {
         // GIVEN an initialized tracker in the IDLE state
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
 
         // GIVEN a progress due to an external source that lands at the middle of the slider
         val progress = 0.5f
@@ -550,7 +551,7 @@
     fun onProgressChangeByProgram_atUpperBookend_onIdle_movesToIdle() = runTest {
         // GIVEN an initialized tracker in the IDLE state
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
         // GIVEN a progress due to an external source that lands at the upper bookend
         val progress = config.upperBookendThreshold + 0.01f
@@ -567,7 +568,7 @@
     fun onProgressChangeByProgram_atLowerBookend_onIdle_movesToIdle() = runTest {
         // GIVEN an initialized tracker in the IDLE state
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
 
         // WHEN a progress is recorded due to an external source that lands at the lower bookend
         val progress = config.lowerBookendThreshold - 0.01f
@@ -583,7 +584,7 @@
     @Test
     fun onArrowUp_onArrowMovedOnce_movesToIdle() = runTest {
         // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
         mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
 
         // WHEN the external stimulus is released
@@ -598,7 +599,7 @@
     @Test
     fun onStartTrackingTouch_onArrowMovedOnce_movesToWait() = runTest {
         // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
         mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
 
         // WHEN the slider starts tracking touch
@@ -615,7 +616,7 @@
     @Test
     fun onProgressChangeByProgram_onArrowMovedOnce_movesToArrowMovesContinuously() = runTest {
         // GIVEN an initialized tracker in the ARROW_HANDLE_MOVED_ONCE state
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
         mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVED_ONCE)
 
         // WHEN the slider gets an external progress change
@@ -634,7 +635,7 @@
     @Test
     fun onArrowUp_onArrowMovesContinuously_movesToIdle() = runTest {
         // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
         mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
 
         // WHEN the external stimulus is released
@@ -649,7 +650,7 @@
     @Test
     fun onStartTrackingTouch_onArrowMovesContinuously_movesToWait() = runTest {
         // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
         mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
 
         // WHEN the slider starts tracking touch
@@ -665,7 +666,7 @@
     @Test
     fun onProgressChangeByProgram_onArrowMovesContinuously_preservesState() = runTest {
         // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
-        initTracker(testScheduler)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)))
         mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
 
         // WHEN the slider changes progress programmatically at the middle
@@ -684,7 +685,7 @@
     fun onProgramProgress_atLowerBookend_onArrowMovesContinuously_movesToIdle() = runTest {
         // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
         mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
 
         // WHEN the slider reaches the lower bookend programmatically
@@ -702,7 +703,7 @@
     fun onProgramProgress_atUpperBookend_onArrowMovesContinuously_movesToIdle() = runTest {
         // GIVEN an initialized tracker in the ARROW_HANDLE_MOVES_CONTINUOUSLY state
         val config = SeekableSliderTrackerConfig()
-        initTracker(testScheduler, config)
+        initTracker(CoroutineScope(UnconfinedTestDispatcher(testScheduler)), config)
         mSeekableSliderTracker.setState(SliderState.ARROW_HANDLE_MOVES_CONTINUOUSLY)
 
         // WHEN the slider reaches the lower bookend programmatically
@@ -718,16 +719,11 @@
 
     @OptIn(ExperimentalCoroutinesApi::class)
     private fun initTracker(
-        scheduler: TestCoroutineScheduler,
+        scope: CoroutineScope,
         config: SeekableSliderTrackerConfig = SeekableSliderTrackerConfig(),
     ) {
         mSeekableSliderTracker =
-            SeekableSliderTracker(
-                sliderStateListener,
-                sliderEventProducer,
-                UnconfinedTestDispatcher(scheduler),
-                config
-            )
+            SeekableSliderTracker(sliderStateListener, sliderEventProducer, scope, config)
         mSeekableSliderTracker.startTracking()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 14cae0b..24cf164 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -94,7 +94,6 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.flags.SystemPropertiesHelper;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
@@ -754,7 +753,7 @@
 
     @Test
     public void testUpdateIsKeyguardAfterOccludeAnimationEnds() {
-        mViewMediator.mOccludeAnimationController.onLaunchAnimationEnd(
+        mViewMediator.mOccludeAnimationController.onTransitionAnimationEnd(
                 false /* isExpandingFullyAbove */);
 
         // Since the updateIsKeyguard call is delayed during the animation, ensure it's called once
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/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/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/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/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
index cd74410..f58ff0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
@@ -115,7 +115,7 @@
     @Test
     fun testHunIsRemovedAndCallbackIsInvokedWhenAnimationEnds() {
         flagNotificationAsHun()
-        controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
+        controller.onTransitionAnimationEnd(isExpandingFullyAbove = true)
 
         assertFalse(HeadsUpUtil.isClickedHeadsUpNotification(notification))
         assertFalse(notification.entry.isExpandAnimationRunning)
@@ -157,7 +157,7 @@
         assertNotSame(GROUP_ALERT_SUMMARY, summary.sbn.notification.groupAlertBehavior)
         assertNotSame(GROUP_ALERT_SUMMARY, notification.entry.sbn.notification.groupAlertBehavior)
 
-        controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
+        controller.onTransitionAnimationEnd(isExpandingFullyAbove = true)
 
         verify(headsUpManager)
             .removeNotification(summary.key, true /* releaseImmediately */, false /* animate */)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 7589a49..354f3f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -797,6 +797,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateFooter_remoteInput() {
         ArgumentCaptor<RemoteInputController.Callback> callbackCaptor =
                 ArgumentCaptor.forClass(RemoteInputController.Callback.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 4afcc8c..04f3216 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -440,6 +440,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateFooter_noNotifications() {
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(true);
@@ -451,6 +452,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateFooter_remoteInput() {
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(true);
@@ -467,6 +469,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateFooter_withoutNotifications() {
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(true);
@@ -482,6 +485,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateFooter_oneClearableNotification() {
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(true);
@@ -497,6 +501,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateFooter_withoutHistory() {
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(true);
@@ -513,6 +518,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateFooter_oneClearableNotification_beforeUserSetup() {
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(false);
@@ -528,6 +534,7 @@
     }
 
     @Test
+    @DisableFlags(FooterViewRefactor.FLAG_NAME)
     public void testUpdateFooter_oneNonClearableNotification() {
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(true);
@@ -544,9 +551,7 @@
     }
 
     @Test
-    public void testUpdateFooter_atEnd() {
-        mStackScroller.setCurrentUserSetup(true);
-
+    public void testFooterPosition_atEnd() {
         // add footer
         FooterView view = mock(FooterView.class);
         mStackScroller.setFooterView(view);
@@ -559,8 +564,6 @@
 
         // Expecting the footer to be the last child
         int expected = mStackScroller.getChildCount() - 1;
-
-        // move footer to end
         verify(mStackScroller).changeViewPosition(any(FooterView.class), eq(expected));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index 88e4f5a..3a7659d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -27,19 +27,27 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.shared.model.WakefulnessState
 import com.android.systemui.res.R
 import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.statusbar.data.repository.fakeRemoteInputRepository
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
+import com.android.systemui.statusbar.policy.data.repository.fakeUserSetupRepository
 import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
 import com.android.systemui.statusbar.policy.fakeConfigurationController
 import com.android.systemui.testKosmos
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.value
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
@@ -58,11 +66,16 @@
             fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
         }
     private val testScope = kosmos.testScope
+
     private val activeNotificationListRepository = kosmos.activeNotificationListRepository
-    private val fakeKeyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
-    private val fakeShadeRepository = kosmos.fakeShadeRepository
-    private val zenModeRepository = kosmos.zenModeRepository
     private val fakeConfigurationController = kosmos.fakeConfigurationController
+    private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
+    private val fakeKeyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val fakePowerRepository = kosmos.fakePowerRepository
+    private val fakeRemoteInputRepository = kosmos.fakeRemoteInputRepository
+    private val fakeShadeRepository = kosmos.fakeShadeRepository
+    private val fakeUserSetupRepository = kosmos.fakeUserSetupRepository
+    private val zenModeRepository = kosmos.zenModeRepository
 
     val underTest = kosmos.notificationListViewModel
 
@@ -273,4 +286,193 @@
 
             assertThat(hasFilteredNotifs).isFalse()
         }
+
+    @Test
+    fun testShouldShowFooterView_trueWhenShade() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+            // WHEN has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND shade is open
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            runCurrent()
+
+            // THEN footer is visible
+            assertThat(shouldShow?.value).isTrue()
+        }
+
+    @Test
+    fun testShouldShowFooterView_trueWhenLockedShade() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+            // WHEN has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND shade is open on lockscreen
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
+            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            runCurrent()
+
+            // THEN footer is visible
+            assertThat(shouldShow?.value).isTrue()
+        }
+
+    @Test
+    fun testShouldShowFooterView_falseWhenKeyguard() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+            // WHEN has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND is on keyguard
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+            runCurrent()
+
+            // THEN footer is not visible
+            assertThat(shouldShow?.value).isFalse()
+        }
+
+    @Test
+    fun testShouldShowFooterView_falseWhenUserNotSetUp() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+            // WHEN has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND shade is open
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            // AND user is not set up
+            fakeUserSetupRepository.setUserSetUp(false)
+            runCurrent()
+
+            // THEN footer is not visible
+            assertThat(shouldShow?.value).isFalse()
+        }
+
+    @Test
+    fun testShouldShowFooterView_falseWhenStartingToSleep() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+            // WHEN has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND shade is open
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            // AND device is starting to go to sleep
+            fakePowerRepository.updateWakefulness(WakefulnessState.STARTING_TO_SLEEP)
+            runCurrent()
+
+            // THEN footer is not visible
+            assertThat(shouldShow?.value).isFalse()
+        }
+
+    @Test
+    fun testShouldShowFooterView_falseWhenQsExpandedDefault() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+            // WHEN has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND shade is open
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            // AND quick settings are expanded
+            fakeShadeRepository.setQsExpansion(1f)
+            fakeShadeRepository.legacyQsFullscreen.value = true
+            runCurrent()
+
+            // THEN footer is not visible
+            assertThat(shouldShow?.value).isFalse()
+        }
+
+    @Test
+    fun testShouldShowFooterView_trueWhenQsExpandedSplitShade() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+            // WHEN has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND shade is open
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            // AND quick settings are expanded
+            fakeShadeRepository.setQsExpansion(1f)
+            // AND split shade is enabled
+            overrideResource(R.bool.config_use_split_notification_shade, true)
+            fakeConfigurationController.notifyConfigurationChanged()
+            runCurrent()
+
+            // THEN footer is visible
+            assertThat(shouldShow?.value).isTrue()
+        }
+
+    @Test
+    fun testShouldShowFooterView_falseWhenRemoteInputActive() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+            // WHEN has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND shade is open
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            // AND remote input is active
+            fakeRemoteInputRepository.isRemoteInputActive.value = true
+            runCurrent()
+
+            // THEN footer is not visible
+            assertThat(shouldShow?.value).isFalse()
+        }
+
+    @Test
+    fun testShouldShowFooterView_falseWhenShadeIsClosed() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+            // WHEN has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND shade is closed
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            fakeShadeRepository.setLegacyShadeExpansion(0f)
+            runCurrent()
+
+            // THEN footer is not visible
+            assertThat(shouldShow?.value).isFalse()
+        }
+
+    @Test
+    fun testShouldShowFooterView_animatesWhenShade() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+            // WHEN has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND shade is open and fully expanded
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            runCurrent()
+
+            // THEN footer visibility animates
+            assertThat(shouldShow?.isAnimating).isTrue()
+        }
+
+    @Test
+    fun testShouldShowFooterView_notAnimatingOnKeyguard() =
+        testScope.runTest {
+            val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+            // WHEN has notifs
+            activeNotificationListRepository.setActiveNotifs(count = 2)
+            // AND we are on the keyguard
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+            fakeShadeRepository.setLegacyShadeExpansion(1f)
+            runCurrent()
+
+            // THEN footer visibility does not animate
+            assertThat(shouldShow?.isAnimating).isFalse()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/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/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt
index f723a9e5..5b84a41 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt
@@ -36,7 +36,7 @@
             object : AnimationFeatureFlags {
                 override val isPredictiveBackQsDialogAnim = isPredictiveBackQsDialogAnim
             },
-        launchAnimator = fakeLaunchAnimator(),
+        transitionAnimator = fakeTransitionAnimator(),
         isForTesting = true,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt
similarity index 79%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt
index 0983041..bc7ec3f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt
@@ -16,19 +16,19 @@
 
 import com.android.app.animation.Interpolators
 
-/** A [LaunchAnimator] to be used in tests. */
-fun fakeLaunchAnimator(): LaunchAnimator {
-    return LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
+/** A [TransitionAnimator] to be used in tests. */
+fun fakeTransitionAnimator(): TransitionAnimator {
+    return TransitionAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
 }
 
 /**
- * A [LaunchAnimator.Timings] to be used in tests.
+ * A [TransitionAnimator.Timings] to be used in tests.
  *
  * Note that all timings except the total duration are non-zero to avoid divide-by-zero exceptions
  * when computing the progress of a sub-animation (the contents fade in/out).
  */
 private val TEST_TIMINGS =
-    LaunchAnimator.Timings(
+    TransitionAnimator.Timings(
         totalDuration = 0L,
         contentBeforeFadeOutDelay = 1L,
         contentBeforeFadeOutDuration = 1L,
@@ -36,9 +36,9 @@
         contentAfterFadeInDuration = 1L
     )
 
-/** A [LaunchAnimator.Interpolators] to be used in tests. */
+/** A [TransitionAnimator.Interpolators] to be used in tests. */
 private val TEST_INTERPOLATORS =
-    LaunchAnimator.Interpolators(
+    TransitionAnimator.Interpolators(
         positionInterpolator = Interpolators.STANDARD,
         positionXInterpolator = Interpolators.STANDARD,
         contentBeforeFadeOutInterpolator = Interpolators.STANDARD,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigKosmos.kt
index 21cff0d..3b3e23e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigKosmos.kt
@@ -18,5 +18,7 @@
 
 import com.android.systemui.kosmos.Kosmos
 
+var Kosmos.fakeFaceWakeUpTriggersConfig by Kosmos.Fixture { FakeFaceWakeUpTriggersConfig() }
+
 var Kosmos.faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig by
-    Kosmos.Fixture { FakeFaceWakeUpTriggersConfig() }
+    Kosmos.Fixture { fakeFaceWakeUpTriggersConfig }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
index 5575b05..a8fc27a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
@@ -27,7 +27,6 @@
 import com.android.systemui.deviceentry.data.repository.faceWakeUpTriggersConfig
 import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -51,7 +50,7 @@
             keyguardTransitionInteractor = keyguardTransitionInteractor,
             faceAuthenticationLogger = faceAuthLogger,
             keyguardUpdateMonitor = keyguardUpdateMonitor,
-            deviceEntryFingerprintAuthRepository = deviceEntryFingerprintAuthRepository,
+            deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
             userRepository = userRepository,
             facePropertyRepository = facePropertyRepository,
             faceWakeUpTriggersConfig = faceWakeUpTriggersConfig,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/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/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
index 998e579..37b2b76 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
@@ -16,10 +16,14 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.shade.domain.interactor.shadeAnimationInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
 import com.android.systemui.statusbar.notification.footer.ui.viewmodel.footerViewModel
@@ -30,14 +34,18 @@
 
 val Kosmos.notificationListViewModel by Fixture {
     NotificationListViewModel(
-        shelf = notificationShelfViewModel,
-        hideListViewModel = hideListViewModel,
-        footer = Optional.of(footerViewModel),
-        logger = Optional.of(notificationListLoggerViewModel),
-        activeNotificationsInteractor = activeNotificationsInteractor,
-        keyguardTransitionInteractor = keyguardTransitionInteractor,
-        seenNotificationsInteractor = seenNotificationsInteractor,
-        shadeInteractor = shadeInteractor,
-        zenModeInteractor = zenModeInteractor,
+        notificationShelfViewModel,
+        hideListViewModel,
+        Optional.of(footerViewModel),
+        Optional.of(notificationListLoggerViewModel),
+        activeNotificationsInteractor,
+        keyguardInteractor,
+        keyguardTransitionInteractor,
+        powerInteractor,
+        remoteInputInteractor,
+        seenNotificationsInteractor,
+        shadeInteractor,
+        userSetupInteractor,
+        zenModeInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/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/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 78f07e4..a11cf8c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -818,7 +818,7 @@
             }
 
             // Don't need to add the embedded hierarchy windows into the accessibility windows list.
-            if (mHostEmbeddedMap.size() > 0 && isEmbeddedHierarchyWindowsLocked(windowId)) {
+            if (isEmbeddedHierarchyWindowsLocked(windowId)) {
                 return null;
             }
             final AccessibilityWindowInfo reportedWindow = AccessibilityWindowInfo.obtain();
@@ -866,21 +866,6 @@
             return reportedWindow;
         }
 
-        private boolean isEmbeddedHierarchyWindowsLocked(int windowId) {
-            final IBinder leashToken = mWindowIdMap.get(windowId);
-            if (leashToken == null) {
-                return false;
-            }
-
-            for (int i = 0; i < mHostEmbeddedMap.size(); i++) {
-                if (mHostEmbeddedMap.keyAt(i).equals(leashToken)) {
-                    return true;
-                }
-            }
-
-            return false;
-        }
-
         private int getTypeForWindowManagerWindowType(int windowType) {
             switch (windowType) {
                 case WindowManager.LayoutParams.TYPE_APPLICATION:
@@ -1490,7 +1475,7 @@
      * @return The windowId of the parent window, or self if no parent exists
      */
     public int resolveParentWindowIdLocked(int windowId) {
-        final IBinder token = getTokenLocked(windowId);
+        final IBinder token = getLeashTokenLocked(windowId);
         if (token == null) {
             return windowId;
         }
@@ -2095,7 +2080,7 @@
      * @param windowId The windowID.
      * @return The token, or {@code NULL} if this windowID doesn't exist
      */
-    IBinder getTokenLocked(int windowId) {
+    IBinder getLeashTokenLocked(int windowId) {
         return mWindowIdMap.get(windowId);
     }
 
@@ -2124,6 +2109,23 @@
     }
 
     /**
+     * Checks if the window is embedded into another window so that the window should be excluded
+     * from the exposed accessibility windows, and the node tree should be embedded in the host.
+     */
+    boolean isEmbeddedHierarchyWindowsLocked(int windowId) {
+        if (mHostEmbeddedMap.size() == 0) {
+            return false;
+        }
+
+        final IBinder leashToken = getLeashTokenLocked(windowId);
+        if (leashToken == null) {
+            return false;
+        }
+
+        return mHostEmbeddedMap.containsKey(leashToken);
+    }
+
+    /**
      * Checks if the window belongs to a proxy display and if so clears the focused window id.
      * @param focusClearedWindowId the cleared window id.
      * @return true if an observer is proxy-ed and has cleared its focused window id.
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 83d9cdb..2ff23ee 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);
@@ -4690,6 +4715,11 @@
 
         }
 
+        if (isCredmanIntegrationActive(response)) {
+            Slog.d(TAG, "Attempting to add Credential Manager callback to pinned entries");
+            addCredentialManagerCallback(response);
+        }
+
         if (response.supportsInlineSuggestions()) {
             synchronized (mLock) {
                 if (requestShowInlineSuggestionsLocked(response, filterText)) {
@@ -4749,6 +4779,11 @@
         }
     }
 
+    private boolean isCredmanIntegrationActive(FillResponse response) {
+        return Flags.autofillCredmanIntegration()
+                && (response.getFlags() & FillResponse.FLAG_CREDENTIAL_MANAGER_RESPONSE) != 0;
+    }
+
     @GuardedBy("mLock")
     private void updateFillDialogTriggerIdsLocked() {
         final FillResponse response = getLastResponseLocked(null);
@@ -4964,6 +4999,69 @@
         return mInlineSessionController.setInlineFillUiLocked(inlineFillUi);
     }
 
+    private void addCredentialManagerCallback(FillResponse response) {
+        if (response.getDatasets() == null) {
+            return;
+        }
+        for (Dataset dataset: response.getDatasets()) {
+            if (isPinnedDataset(dataset)) {
+                Slog.d(TAG, "Adding Credential Manager callback to a pinned entry");
+                addCredentialManagerCallbackForDataset(dataset, response.getRequestId());
+            }
+        }
+    }
+
+    private void addCredentialManagerCallbackForDataset(Dataset dataset, int requestId) {
+        final ResultReceiver resultReceiver = new ResultReceiver(mHandler) {
+            @Override
+            protected void onReceiveResult(int resultCode, Bundle resultData) {
+                if (resultCode == SUCCESS_CREDMAN_SELECTOR) {
+                    Slog.d(TAG, "onReceiveResult from Credential Manager bottom sheet");
+                    GetCredentialResponse getCredentialResponse =
+                            resultData.getParcelable(
+                                    CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
+                                    GetCredentialResponse.class);
+                    Dataset datasetFromCredential = getDatasetFromCredentialResponse(
+                            getCredentialResponse);
+                    if (datasetFromCredential != null) {
+                        autoFill(requestId, /*datasetIndex=*/-1,
+                                datasetFromCredential, false,
+                                UI_TYPE_CREDMAN_BOTTOM_SHEET);
+                    }
+                } else if (resultCode == FAILURE_CREDMAN_SELECTOR) {
+                    GetCredentialException exception =  resultData.getParcelable(
+                            CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION,
+                            GetCredentialException.class);
+                    Slog.d(TAG, "Credman bottom sheet from pinned "
+                            + "entry failed with: + " + exception.getType() + " , "
+                            + exception.getMessage());
+                } else {
+                    Slog.d(TAG, "Unknown resultCode from credential "
+                            + "manager bottom sheet: " + resultCode);
+                }
+            }
+        };
+        ResultReceiver ipcFriendlyResultReceiver =
+                toIpcFriendlyResultReceiver(resultReceiver);
+
+        Intent metadataIntent = dataset.getCredentialFillInIntent();
+        metadataIntent.putExtra(
+                android.credentials.selection.Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
+                ipcFriendlyResultReceiver);
+        dataset.setCredentialFillInIntent(metadataIntent);
+    }
+
+    private ResultReceiver toIpcFriendlyResultReceiver(ResultReceiver resultReceiver) {
+        final Parcel parcel = Parcel.obtain();
+        resultReceiver.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        return ipcFriendly;
+    }
+
     boolean isDestroyed() {
         synchronized (mLock) {
             return mDestroyed;
@@ -5669,8 +5767,14 @@
             // does not matter the value of isPrimary because null response won't be overridden.
             setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH,
                     /* clearResponse= */ false, /* isPrimary= */ true);
-            final Intent fillInIntent = createAuthFillInIntentLocked(requestId, mClientState,
-                    dataset.getAuthenticationExtras());
+            final Intent fillInIntent;
+            if (dataset.getCredentialFillInIntent() != null && Flags.autofillCredmanIntegration()) {
+                Slog.d(TAG, "Setting credential fill intent");
+                fillInIntent = dataset.getCredentialFillInIntent();
+            } else {
+                fillInIntent = createAuthFillInIntentLocked(requestId, mClientState);
+            }
+
             if (fillInIntent == null) {
                 forceRemoveFromServiceLocked();
                 return;
@@ -5686,8 +5790,7 @@
     // TODO: this should never be null, but we got at least one occurrence, probably due to a race.
     @GuardedBy("mLock")
     @Nullable
-    private Intent createAuthFillInIntentLocked(int requestId, Bundle extras,
-            @Nullable Bundle authExtras) {
+    private Intent createAuthFillInIntentLocked(int requestId, Bundle extras) {
         final Intent fillInIntent = new Intent();
 
         final FillContext context = getFillContextByRequestIdLocked(requestId);
@@ -5704,9 +5807,6 @@
         }
         fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, context.getStructure());
         fillInIntent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, extras);
-        if (authExtras != null) {
-            fillInIntent.putExtra(AutofillManager.EXTRA_AUTH_STATE, authExtras);
-        }
         return fillInIntent;
     }
 
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 8962bf0..1b49f18e 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -692,32 +692,37 @@
 
         private int mInputDeviceId = IInputConstants.INVALID_INPUT_DEVICE_ID;
 
-        WaitForDevice(String deviceName, int vendorId, int productId) {
+        WaitForDevice(String deviceName, int vendorId, int productId, int associatedDisplayId) {
             mListener = new InputManager.InputDeviceListener() {
                 @Override
                 public void onInputDeviceAdded(int deviceId) {
-                    final InputDevice device = InputManagerGlobal.getInstance().getInputDevice(
-                            deviceId);
-                    Objects.requireNonNull(device, "Newly added input device was null.");
-                    if (!device.getName().equals(deviceName)) {
-                        return;
-                    }
-                    final InputDeviceIdentifier id = device.getIdentifier();
-                    if (id.getVendorId() != vendorId || id.getProductId() != productId) {
-                        return;
-                    }
-                    mInputDeviceId = deviceId;
-                    mDeviceAddedLatch.countDown();
+                    onInputDeviceChanged(deviceId);
                 }
 
                 @Override
                 public void onInputDeviceRemoved(int deviceId) {
-
                 }
 
                 @Override
                 public void onInputDeviceChanged(int deviceId) {
+                    if (isMatchingDevice(deviceId)) {
+                        mInputDeviceId = deviceId;
+                        mDeviceAddedLatch.countDown();
+                    }
+                }
 
+                private boolean isMatchingDevice(int deviceId) {
+                    final InputDevice device = InputManagerGlobal.getInstance().getInputDevice(
+                            deviceId);
+                    Objects.requireNonNull(device, "Newly added input device was null.");
+                    if (!device.getName().equals(deviceName)) {
+                        return false;
+                    }
+                    final InputDeviceIdentifier id = device.getIdentifier();
+                    if (id.getVendorId() != vendorId || id.getProductId() != productId) {
+                        return false;
+                    }
+                    return device.getAssociatedDisplayId() == associatedDisplayId;
                 }
             };
             InputManagerGlobal.getInstance().registerInputDeviceListener(mListener, mHandler);
@@ -799,7 +804,7 @@
         final int inputDeviceId;
 
         setUniqueIdAssociation(displayId, phys);
-        try (WaitForDevice waiter = new WaitForDevice(deviceName, vendorId, productId)) {
+        try (WaitForDevice waiter = new WaitForDevice(deviceName, vendorId, productId, displayId)) {
             ptr = deviceOpener.get();
             // See INVALID_PTR in libs/input/VirtualInputDevice.cpp.
             if (ptr == 0) {
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 9d9e7c9..7979936 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -16,8 +16,8 @@
 
 package com.android.server;
 
-import static android.os.Flags.stateOfHealthPublic;
 import static android.os.Flags.batteryServiceSupportCurrentAdbCommand;
+import static android.os.Flags.stateOfHealthPublic;
 
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import static com.android.server.health.Utils.copyV1Battery;
@@ -81,6 +81,7 @@
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.NoSuchElementException;
+import java.util.concurrent.CopyOnWriteArraySet;
 
 /**
  * <p>BatteryService monitors the charging status, and charge level of the device
@@ -157,6 +158,12 @@
     private int mLastChargeCounter;
     private int mLastBatteryCycleCount;
     private int mLastCharingState;
+    /**
+     * The last seen charging policy. This requires the
+     * {@link android.Manifest.permission#BATTERY_STATS} permission and should therefore not be
+     * included in the ACTION_BATTERY_CHANGED intent extras.
+     */
+    private int mLastChargingPolicy;
 
     private int mSequence = 1;
 
@@ -197,6 +204,9 @@
     private ArrayDeque<Bundle> mBatteryLevelsEventQueue;
     private long mLastBatteryLevelChangedSentMs;
 
+    private final CopyOnWriteArraySet<BatteryManagerInternal.ChargingPolicyChangeListener>
+            mChargingPolicyChangeListeners = new CopyOnWriteArraySet<>();
+
     private Bundle mBatteryChangedOptions = BroadcastOptions.makeBasic()
             .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
             .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
@@ -527,6 +537,11 @@
         shutdownIfNoPowerLocked();
         shutdownIfOverTempLocked();
 
+        if (force || mHealthInfo.chargingPolicy != mLastChargingPolicy) {
+            mLastChargingPolicy = mHealthInfo.chargingPolicy;
+            mHandler.post(this::notifyChargingPolicyChanged);
+        }
+
         if (force
                 || (mHealthInfo.batteryStatus != mLastBatteryStatus
                         || mHealthInfo.batteryHealth != mLastBatteryHealth
@@ -827,6 +842,17 @@
         mLastBatteryLevelChangedSentMs = SystemClock.elapsedRealtime();
     }
 
+    private void notifyChargingPolicyChanged() {
+        final int newPolicy;
+        synchronized (mLock) {
+            newPolicy = mLastChargingPolicy;
+        }
+        for (BatteryManagerInternal.ChargingPolicyChangeListener listener
+                : mChargingPolicyChangeListeners) {
+            listener.onChargingPolicyChanged(newPolicy);
+        }
+    }
+
     // TODO: Current code doesn't work since "--unplugged" flag in BSS was purposefully removed.
     private void logBatteryStatsLocked() {
         IBinder batteryInfoService = ServiceManager.getService(BatteryStats.SERVICE_NAME);
@@ -1220,6 +1246,8 @@
                 pw.println("  voltage: " + mHealthInfo.batteryVoltageMillivolts);
                 pw.println("  temperature: " + mHealthInfo.batteryTemperatureTenthsCelsius);
                 pw.println("  technology: " + mHealthInfo.batteryTechnology);
+                pw.println("  Charging state: " + mHealthInfo.chargingState);
+                pw.println("  Charging policy: " + mHealthInfo.chargingPolicy);
             } else {
                 Shell shell = new Shell();
                 shell.exec(mBinderService, null, fd, null, args, null, new ResultReceiver(null));
@@ -1452,6 +1480,19 @@
         }
 
         @Override
+        public void registerChargingPolicyChangeListener(
+                BatteryManagerInternal.ChargingPolicyChangeListener listener) {
+            mChargingPolicyChangeListeners.add(listener);
+        }
+
+        @Override
+        public int getChargingPolicy() {
+            synchronized (mLock) {
+                return mLastChargingPolicy;
+            }
+        }
+
+        @Override
         public int getInvalidCharger() {
             synchronized (mLock) {
                 return mInvalidCharger;
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index 9554e63..fb527c1 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -20,8 +20,9 @@
 import android.content.Context;
 import android.content.Intent;
 import android.database.ContentObserver;
-import android.media.AudioAttributes;
+import android.media.AudioManager;
 import android.media.Ringtone;
+import android.media.RingtoneManager;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Handler;
@@ -305,16 +306,11 @@
                     if (soundPath != null) {
                         final Uri soundUri = Uri.parse("file://" + soundPath);
                         if (soundUri != null) {
-                            AudioAttributes audioAttributes = new AudioAttributes.Builder()
-                                    .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
-                                    .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-                                    .build();
-                            final Ringtone sfx = new Ringtone.Builder(getContext(),
-                                    Ringtone.MEDIA_SOUND, audioAttributes)
-                                    .setUri(soundUri)
-                                    .setPreferBuiltinDevice()
-                                    .build();
+                            final Ringtone sfx = RingtoneManager.getRingtone(
+                                    getContext(), soundUri);
                             if (sfx != null) {
+                                sfx.setStreamType(AudioManager.STREAM_SYSTEM);
+                                sfx.preferBuiltinDevice(true);
                                 sfx.play();
                             }
                         }
diff --git a/services/core/java/com/android/server/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/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/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/pm/BackgroundInstallControlCallbackHelper.java b/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java
new file mode 100644
index 0000000..1553618
--- /dev/null
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+
+import android.annotation.NonNull;
+import android.app.BackgroundInstallControlManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IRemoteCallback;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.ServiceThread;
+
+public class BackgroundInstallControlCallbackHelper {
+
+    @VisibleForTesting static final String FLAGGED_PACKAGE_NAME_KEY = "packageName";
+    @VisibleForTesting static final String FLAGGED_USER_ID_KEY = "userId";
+    private static final String TAG = "BackgroundInstallControlCallbackHelper";
+
+    private final Handler mHandler;
+
+    BackgroundInstallControlCallbackHelper() {
+        HandlerThread backgroundThread =
+                new ServiceThread(
+                        "BackgroundInstallControlCallbackHelperBg",
+                        THREAD_PRIORITY_BACKGROUND,
+                        true);
+        backgroundThread.start();
+        mHandler = new Handler(backgroundThread.getLooper());
+    }
+
+    @NonNull @VisibleForTesting
+    final RemoteCallbackList<IRemoteCallback> mCallbacks = new RemoteCallbackList<>();
+
+    /** Registers callback that gets invoked upon detection of an MBA
+     *
+     * NOTE: The callback is user context agnostic and currently broadcasts to all users of other
+     * users app installs. This is fine because the API is for SystemServer use only.
+     */
+    public void registerBackgroundInstallCallback(IRemoteCallback callback) {
+        synchronized (mCallbacks) {
+            mCallbacks.register(callback, null);
+        }
+    }
+
+    /** Unregisters callback */
+    public void unregisterBackgroundInstallCallback(IRemoteCallback callback) {
+        synchronized (mCallbacks) {
+            mCallbacks.unregister(callback);
+        }
+    }
+
+    /**
+     * Invokes all registered callbacks Callbacks are processed through user provided-threads and
+     * parameters are passed in via {@link BackgroundInstallControlManager} InstallEvent
+     */
+    public void notifyAllCallbacks(int userId, String packageName) {
+        Bundle extras = new Bundle();
+        extras.putCharSequence(FLAGGED_PACKAGE_NAME_KEY, packageName);
+        extras.putInt(FLAGGED_USER_ID_KEY, userId);
+        synchronized (mCallbacks) {
+            mHandler.post(
+                    () ->
+                            mCallbacks.broadcast(
+                                    callback -> {
+                                        try {
+                                            callback.sendResult(extras);
+                                        } catch (RemoteException e) {
+                                            Slog.e(
+                                                    TAG,
+                                                    "error detected: " + e.getLocalizedMessage(),
+                                                    e);
+                                        }
+                                    }));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 200b17b..3468081 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -36,6 +36,7 @@
 import android.os.Build;
 import android.os.Environment;
 import android.os.Handler;
+import android.os.IRemoteCallback;
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemClock;
@@ -93,6 +94,8 @@
     private final File mDiskFile;
     private final Context mContext;
 
+    private final BackgroundInstallControlCallbackHelper mCallbackHelper;
+
     private SparseSetArray<String> mBackgroundInstalledPackages = null;
 
     // User ID -> package name -> set of foreground time frame
@@ -112,6 +115,7 @@
         mHandler = new EventHandler(injector.getLooper(), this);
         mDiskFile = injector.getDiskFile();
         mContext = injector.getContext();
+        mCallbackHelper = injector.getBackgroundInstallControlCallbackHelper();
         UsageStatsManagerInternal usageStatsManagerInternal =
                 injector.getUsageStatsManagerInternal();
         usageStatsManagerInternal.registerListener(
@@ -150,6 +154,16 @@
             }
         }
 
+        @Override
+        public void registerBackgroundInstallCallback(IRemoteCallback callback) {
+            mService.mCallbackHelper.registerBackgroundInstallCallback(callback);
+        }
+
+        @Override
+        public void unregisterBackgroundInstallCallback(IRemoteCallback callback) {
+            mService.mCallbackHelper.unregisterBackgroundInstallCallback(callback);
+        }
+
     }
 
     @RequiresPermission(GET_BACKGROUND_INSTALLED_PACKAGES)
@@ -274,6 +288,7 @@
 
         initBackgroundInstalledPackages();
         mBackgroundInstalledPackages.add(userId, packageName);
+        mCallbackHelper.notifyAllCallbacks(userId, packageName);
         writeBackgroundInstalledPackagesToDisk();
     }
 
@@ -568,6 +583,8 @@
 
         File getDiskFile();
 
+        BackgroundInstallControlCallbackHelper getBackgroundInstallControlCallbackHelper();
+
     }
 
     private static final class InjectorImpl implements Injector {
@@ -617,5 +634,10 @@
             File file = new File(dir, DISK_FILE_NAME);
             return file;
         }
+
+        @Override
+        public BackgroundInstallControlCallbackHelper getBackgroundInstallControlCallbackHelper() {
+            return new BackgroundInstallControlCallbackHelper();
+        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 33f481c..db5acc2 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -592,9 +592,11 @@
             mPm.addAllPackageProperties(pkg);
 
             if (oldPkgSetting == null || oldPkgSetting.getPkg() == null) {
-                mPm.mDomainVerificationManager.addPackage(pkgSetting);
+                mPm.mDomainVerificationManager.addPackage(pkgSetting,
+                        request.getPreVerifiedDomains());
             } else {
-                mPm.mDomainVerificationManager.migrateState(oldPkgSetting, pkgSetting);
+                mPm.mDomainVerificationManager.migrateState(oldPkgSetting, pkgSetting,
+                        request.getPreVerifiedDomains());
             }
 
             int collectionSize = ArrayUtils.size(pkg.getInstrumentations());
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b8960da..afd4fb1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -569,7 +569,8 @@
      * target sdk apps as malware can target older sdk versions to avoid
      * the enforcement of new API behavior.
      */
-    public static final int MIN_INSTALLABLE_TARGET_SDK = Build.VERSION_CODES.M;
+    public static final int MIN_INSTALLABLE_TARGET_SDK =
+            Flags.minTargetSdk24() ? Build.VERSION_CODES.N : Build.VERSION_CODES.M;
 
     // Compilation reasons.
     // TODO(b/260124949): Clean this up with the legacy dexopt code.
@@ -6483,6 +6484,17 @@
         }
 
         @Override
+        @Nullable
+        public ComponentName getDomainVerificationAgent() {
+            final int callerUid = Binder.getCallingUid();
+            if (!PackageManagerServiceUtils.isRootOrShell(callerUid)) {
+                throw new SecurityException("Not allowed to query domain verification agent");
+            }
+            final Computer snapshot = snapshotComputer();
+            return getDomainVerificationAgentComponentNameLPr(snapshot);
+        }
+
+        @Override
         public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                 throws RemoteException {
             try {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 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/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/wearable/RemoteWearableSensingService.java b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
index b2bbcda..e1abae8 100644
--- a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
+++ b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
@@ -32,6 +32,8 @@
 
 import com.android.internal.infra.ServiceConnector;
 
+import java.io.IOException;
+
 /** Manages the connection to the remote wearable sensing service. */
 final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearableSensingService> {
     private static final String TAG =
@@ -56,6 +58,29 @@
     }
 
     /**
+     * Provides a secure connection to the wearable.
+     *
+     * @param secureWearableConnection The secure connection to the wearable
+     * @param callback The callback for service status
+     */
+    public void provideSecureWearableConnection(
+            ParcelFileDescriptor secureWearableConnection, RemoteCallback callback) {
+        if (DEBUG) {
+            Slog.i(TAG, "Providing secure wearable connection.");
+        }
+        var unused = post(
+                service -> {
+                    service.provideSecureWearableConnection(secureWearableConnection, callback);
+                    try {
+                        // close the local fd after it has been sent to the WSS process
+                        secureWearableConnection.close();
+                    } catch (IOException ex) {
+                        Slog.w(TAG, "Unable to close the local parcelFileDescriptor.", ex);
+                    }
+                });
+    }
+
+    /**
      * Provides the implementation a data stream to the wearable.
      *
      * @param parcelFileDescriptor The data stream to the wearable
@@ -66,7 +91,16 @@
         if (DEBUG) {
             Slog.i(TAG, "Providing data stream.");
         }
-        post(service -> service.provideDataStream(parcelFileDescriptor, callback));
+        var unused = post(
+                service -> {
+                    service.provideDataStream(parcelFileDescriptor, callback);
+                    try {
+                        // close the local fd after it has been sent to the WSS process
+                        parcelFileDescriptor.close();
+                    } catch (IOException ex) {
+                        Slog.w(TAG, "Unable to close the local parcelFileDescriptor.", ex);
+                    }
+                });
     }
 
     /**
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
index e73fd0f..a8d6322 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
@@ -22,17 +22,19 @@
 import android.annotation.UserIdInt;
 import android.app.AppGlobals;
 import android.app.ambientcontext.AmbientContextEvent;
+import android.app.wearable.Flags;
 import android.app.wearable.WearableSensingManager;
+import android.companion.CompanionDeviceManager;
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.os.Bundle;
-import android.system.OsConstants;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.SharedMemory;
+import android.system.OsConstants;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
@@ -40,6 +42,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.infra.AbstractPerUserSystemService;
 
+import java.io.IOException;
 import java.io.PrintWriter;
 
 /**
@@ -55,6 +58,10 @@
     RemoteWearableSensingService mRemoteService;
 
     private ComponentName mComponentName;
+    private final Object mSecureChannelLock = new Object();
+
+    @GuardedBy("mSecureChannelLock")
+    private WearableSensingSecureChannel mSecureChannel;
 
     WearableSensingManagerPerUserService(
             @NonNull WearableSensingManagerService master, Object lock, @UserIdInt int userId) {
@@ -76,6 +83,11 @@
                 mRemoteService = null;
             }
         }
+        synchronized (mSecureChannelLock) {
+            if (mSecureChannel != null) {
+                mSecureChannel.close();
+            }
+        }
     }
 
     @GuardedBy("mLock")
@@ -156,6 +168,63 @@
     }
 
     /**
+     * Creates a CompanionDeviceManager secure channel and sends a proxy to the wearable sensing
+     * service.
+     */
+    public void onProvideWearableConnection(
+            ParcelFileDescriptor wearableConnection, RemoteCallback callback) {
+        Slog.i(TAG, "onProvideWearableConnection in per user service.");
+        synchronized (mLock) {
+            if (!setUpServiceIfNeeded()) {
+                Slog.w(TAG, "Detection service is not available at this moment.");
+                notifyStatusCallback(callback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+        }
+        synchronized (mSecureChannelLock) {
+            if (mSecureChannel != null) {
+                // TODO(b/321012559): Kill the WearableSensingService process if it has not been
+                // killed from onError
+                mSecureChannel.close();
+            }
+            try {
+                mSecureChannel =
+                        WearableSensingSecureChannel.create(
+                                getContext().getSystemService(CompanionDeviceManager.class),
+                                wearableConnection,
+                                new WearableSensingSecureChannel.SecureTransportListener() {
+                                    @Override
+                                    public void onSecureTransportAvailable(
+                                            ParcelFileDescriptor secureTransport) {
+                                        Slog.i(TAG, "calling over to remote service.");
+                                        synchronized (mLock) {
+                                            ensureRemoteServiceInitiated();
+                                            mRemoteService.provideSecureWearableConnection(
+                                                    secureTransport, callback);
+                                        }
+                                    }
+
+                                    @Override
+                                    public void onError() {
+                                        // TODO(b/321012559): Kill the WearableSensingService
+                                        // process if mSecureChannel has not been reassigned
+                                        if (Flags.enableProvideWearableConnectionApi()) {
+                                            notifyStatusCallback(
+                                                    callback,
+                                                    WearableSensingManager.STATUS_CHANNEL_ERROR);
+                                        }
+                                    }
+                                });
+            } catch (IOException ex) {
+                Slog.e(TAG, "Unable to create the secure channel.", ex);
+                if (Flags.enableProvideWearableConnectionApi()) {
+                    notifyStatusCallback(callback, WearableSensingManager.STATUS_CHANNEL_ERROR);
+                }
+            }
+        }
+    }
+
+    /**
      * Handles sending the provided data stream for the wearable to the wearable sensing service.
      */
     public void onProvideDataStream(
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index 4cc2c02..28c8f87 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -211,9 +211,27 @@
     private final class WearableSensingManagerInternal extends IWearableSensingManager.Stub {
 
         @Override
+        public void provideWearableConnection(
+                ParcelFileDescriptor wearableConnection, RemoteCallback callback) {
+            Slog.i(TAG, "WearableSensingManagerInternal provideWearableConnection.");
+            Objects.requireNonNull(wearableConnection);
+            Objects.requireNonNull(callback);
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG);
+            if (!mIsServiceEnabled) {
+                Slog.w(TAG, "Service not available.");
+                WearableSensingManagerPerUserService.notifyStatusCallback(
+                        callback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+            callPerUserServiceIfExist(
+                    service -> service.onProvideWearableConnection(wearableConnection, callback),
+                    callback);
+        }
+
+        @Override
         public void provideDataStream(
-                ParcelFileDescriptor parcelFileDescriptor,
-                RemoteCallback callback) {
+                ParcelFileDescriptor parcelFileDescriptor, RemoteCallback callback) {
             Slog.i(TAG, "WearableSensingManagerInternal provideDataStream.");
             Objects.requireNonNull(parcelFileDescriptor);
             Objects.requireNonNull(callback);
diff --git a/services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java b/services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java
new file mode 100644
index 0000000..a16ff51
--- /dev/null
+++ b/services/core/java/com/android/server/wearable/WearableSensingSecureChannel.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wearable;
+
+import android.annotation.NonNull;
+import android.companion.AssociationInfo;
+import android.companion.AssociationRequest;
+import android.companion.CompanionDeviceManager;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.AutoCloseInputStream;
+import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+/**
+ * A wrapper that manages a CompanionDeviceManager secure channel for wearable sensing.
+ *
+ * <p>This wrapper accepts a connection to a wearable from the caller. It then attaches the
+ * connection to the CompanionDeviceManager via {@link
+ * CompanionDeviceManager#attachSystemDataTransport(int, InputStream, OutputStream)}, which will
+ * create an encrypted channel using the provided connection as the raw underlying connection. The
+ * wearable device is expected to attach its side of the raw connection to its
+ * CompanionDeviceManager via the same method so that the two CompanionDeviceManagers on the two
+ * devices can perform attestation and set up the encrypted channel. Attestation requirements are
+ * listed in {@link com.android.server.security.AttestationVerificationPeerDeviceVerifier}.
+ *
+ * <p>When the encrypted channel is available, it will be provided to the caller via the
+ * SecureTransportListener.
+ */
+final class WearableSensingSecureChannel {
+
+    /** A listener for secure transport and its error signal. */
+    interface SecureTransportListener {
+
+        /** Called when the secure transport is available. */
+        void onSecureTransportAvailable(ParcelFileDescriptor secureTransport);
+
+        /**
+         * Called when there is a non-recoverable error. The secure channel will be automatically
+         * closed.
+         */
+        void onError();
+    }
+
+    private static final String TAG = WearableSensingSecureChannel.class.getSimpleName();
+    private static final String CDM_ASSOCIATION_DISPLAY_NAME = "PlaceholderDisplayNameFromWSM";
+    // The batch size of reading from the ParcelFileDescriptor returned to mSecureTransportListener
+    private static final int READ_BUFFER_SIZE = 8192;
+
+    private final Object mLock = new Object();
+    // CompanionDeviceManager (CDM) can continue to call these ExecutorServices even after the
+    // corresponding cleanup methods in CDM have been called (e.g.
+    // removeOnTransportsChangedListener). Since we shut down these ExecutorServices after
+    // clean up, we use SoftShutdownExecutor to suppress RejectedExecutionExceptions.
+    private final SoftShutdownExecutor mMessageFromWearableExecutor =
+            new SoftShutdownExecutor(Executors.newSingleThreadExecutor());
+    private final SoftShutdownExecutor mMessageToWearableExecutor =
+            new SoftShutdownExecutor(Executors.newSingleThreadExecutor());
+    private final SoftShutdownExecutor mLightWeightExecutor =
+            new SoftShutdownExecutor(Executors.newSingleThreadExecutor());
+    private final CompanionDeviceManager mCompanionDeviceManager;
+    private final ParcelFileDescriptor mUnderlyingTransport;
+    private final SecureTransportListener mSecureTransportListener;
+    private final AtomicBoolean mTransportAvailable = new AtomicBoolean(false);
+    private final Consumer<List<AssociationInfo>> mOnTransportsChangedListener =
+            this::onTransportsChanged;
+    private final BiConsumer<Integer, byte[]> mOnMessageReceivedListener = this::onMessageReceived;
+    private final ParcelFileDescriptor mRemoteFd; // To be returned to mSecureTransportListener
+    // read input received from the ParcelFileDescriptor returned to mSecureTransportListener
+    private final InputStream mLocalIn;
+    // send output to the ParcelFileDescriptor returned to mSecureTransportListener
+    private final OutputStream mLocalOut;
+
+    @GuardedBy("mLock")
+    private boolean mClosed = false;
+
+    private Integer mAssociationId = null;
+
+    /**
+     * Creates a WearableSensingSecureChannel. When the secure transport is ready,
+     * secureTransportListener will be notified.
+     *
+     * @param companionDeviceManager The CompanionDeviceManager system service.
+     * @param underlyingTransport The underlying transport to create the secure channel on.
+     * @param secureTransportListener The listener to receive the secure transport when it is ready.
+     * @throws IOException if it cannot create a {@link ParcelFileDescriptor} socket pair.
+     */
+    static WearableSensingSecureChannel create(
+            @NonNull CompanionDeviceManager companionDeviceManager,
+            @NonNull ParcelFileDescriptor underlyingTransport,
+            @NonNull SecureTransportListener secureTransportListener)
+            throws IOException {
+        Objects.requireNonNull(companionDeviceManager);
+        Objects.requireNonNull(underlyingTransport);
+        Objects.requireNonNull(secureTransportListener);
+        ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair();
+        WearableSensingSecureChannel channel =
+                new WearableSensingSecureChannel(
+                        companionDeviceManager,
+                        underlyingTransport,
+                        secureTransportListener,
+                        pair[0],
+                        pair[1]);
+        channel.initialize();
+        return channel;
+    }
+
+    private WearableSensingSecureChannel(
+            CompanionDeviceManager companionDeviceManager,
+            ParcelFileDescriptor underlyingTransport,
+            SecureTransportListener secureTransportListener,
+            ParcelFileDescriptor remoteFd,
+            ParcelFileDescriptor localFd) {
+        mCompanionDeviceManager = companionDeviceManager;
+        mUnderlyingTransport = underlyingTransport;
+        mSecureTransportListener = secureTransportListener;
+        mRemoteFd = remoteFd;
+        mLocalIn = new AutoCloseInputStream(localFd);
+        mLocalOut = new AutoCloseOutputStream(localFd);
+    }
+
+    private void initialize() {
+        final long originalCallingIdentity = Binder.clearCallingIdentity();
+        try {
+            Slog.d(TAG, "Requesting CDM association.");
+            mCompanionDeviceManager.associate(
+                    new AssociationRequest.Builder()
+                            .setDisplayName(CDM_ASSOCIATION_DISPLAY_NAME)
+                            .setSelfManaged(true)
+                            .build(),
+                    mLightWeightExecutor,
+                    new CompanionDeviceManager.Callback() {
+                        @Override
+                        public void onAssociationCreated(AssociationInfo associationInfo) {
+                            WearableSensingSecureChannel.this.onAssociationCreated(
+                                    associationInfo.getId());
+                        }
+
+                        @Override
+                        public void onFailure(CharSequence error) {
+                            Slog.e(
+                                    TAG,
+                                    "Failed to create CompanionDeviceManager association: "
+                                            + error);
+                            onError();
+                        }
+                    });
+        } finally {
+            Binder.restoreCallingIdentity(originalCallingIdentity);
+        }
+    }
+
+    private void onAssociationCreated(int associationId) {
+        Slog.i(TAG, "CDM association created.");
+        synchronized (mLock) {
+            if (mClosed) {
+                return;
+            }
+            mAssociationId = associationId;
+            mCompanionDeviceManager.addOnMessageReceivedListener(
+                    mMessageFromWearableExecutor,
+                    CompanionDeviceManager.MESSAGE_ONEWAY_FROM_WEARABLE,
+                    mOnMessageReceivedListener);
+            mCompanionDeviceManager.addOnTransportsChangedListener(
+                    mLightWeightExecutor, mOnTransportsChangedListener);
+            mCompanionDeviceManager.attachSystemDataTransport(
+                    associationId,
+                    new AutoCloseInputStream(mUnderlyingTransport),
+                    new AutoCloseOutputStream(mUnderlyingTransport));
+        }
+    }
+
+    private void onTransportsChanged(List<AssociationInfo> associationInfos) {
+        synchronized (mLock) {
+            if (mClosed) {
+                return;
+            }
+            if (mAssociationId == null) {
+                Slog.e(TAG, "mAssociationId is null when transport changed");
+                return;
+            }
+        }
+        // Do not call onTransportAvailable() or onError() when holding the lock because it can
+        // cause a deadlock if the callback holds another lock.
+        boolean transportAvailable =
+                associationInfos.stream().anyMatch(info -> info.getId() == mAssociationId);
+        if (transportAvailable && mTransportAvailable.compareAndSet(false, true)) {
+            onTransportAvailable();
+        } else if (!transportAvailable && mTransportAvailable.compareAndSet(true, false)) {
+            Slog.i(TAG, "CDM transport is detached. This is not recoverable.");
+            onError();
+        }
+    }
+
+    private void onTransportAvailable() {
+        // Start sending data received from the remote stream to the wearable.
+        Slog.i(TAG, "Transport available");
+        mMessageToWearableExecutor.execute(
+                () -> {
+                    int[] associationIdsToSendMessageTo = new int[] {mAssociationId};
+                    byte[] buffer = new byte[READ_BUFFER_SIZE];
+                    int readLen;
+                    try {
+                        while ((readLen = mLocalIn.read(buffer)) != -1) {
+                            byte[] data = new byte[readLen];
+                            System.arraycopy(buffer, 0, data, 0, readLen);
+                            Slog.v(TAG, "Sending message to wearable");
+                            mCompanionDeviceManager.sendMessage(
+                                    CompanionDeviceManager.MESSAGE_ONEWAY_TO_WEARABLE,
+                                    data,
+                                    associationIdsToSendMessageTo);
+                        }
+                    } catch (IOException e) {
+                        Slog.i(TAG, "IOException while reading from remote stream.");
+                        onError();
+                        return;
+                    }
+                    Slog.i(
+                            TAG,
+                            "Reached EOF when reading from remote stream. Reporting this as an"
+                                    + " error.");
+                    onError();
+                });
+        mSecureTransportListener.onSecureTransportAvailable(mRemoteFd);
+    }
+
+    private void onMessageReceived(int associationIdForMessage, byte[] data) {
+        if (associationIdForMessage == mAssociationId) {
+            Slog.v(TAG, "Received message from wearable.");
+            try {
+                mLocalOut.write(data);
+                mLocalOut.flush();
+            } catch (IOException e) {
+                Slog.i(
+                        TAG,
+                        "IOException when writing to remote stream. Closing the secure channel.");
+                onError();
+            }
+        } else {
+            Slog.v(
+                    TAG,
+                    "Received CDM message of type MESSAGE_ONEWAY_FROM_WEARABLE, but it is for"
+                        + " another association. Ignoring the message.");
+        }
+    }
+
+    private void onError() {
+        synchronized (mLock) {
+            if (mClosed) {
+                return;
+            }
+        }
+        mSecureTransportListener.onError();
+        close();
+    }
+
+    /** Closes this secure channel and releases all resources. */
+    void close() {
+        synchronized (mLock) {
+            if (mClosed) {
+                return;
+            }
+            Slog.i(TAG, "Closing WearableSensingSecureChannel.");
+            mClosed = true;
+            if (mAssociationId != null) {
+                final long originalCallingIdentity = Binder.clearCallingIdentity();
+                try {
+                    mCompanionDeviceManager.removeOnTransportsChangedListener(
+                            mOnTransportsChangedListener);
+                    mCompanionDeviceManager.removeOnMessageReceivedListener(
+                            CompanionDeviceManager.MESSAGE_ONEWAY_FROM_WEARABLE,
+                            mOnMessageReceivedListener);
+                    mCompanionDeviceManager.detachSystemDataTransport(mAssociationId);
+                    mCompanionDeviceManager.disassociate(mAssociationId);
+                } finally {
+                    Binder.restoreCallingIdentity(originalCallingIdentity);
+                }
+            }
+            try {
+                mLocalIn.close();
+            } catch (IOException ex) {
+                Slog.e(TAG, "Encountered IOException when closing local input stream.", ex);
+            }
+            try {
+                mLocalOut.close();
+            } catch (IOException ex) {
+                Slog.e(TAG, "Encountered IOException when closing local output stream.", ex);
+            }
+            mMessageFromWearableExecutor.shutdown();
+            mMessageToWearableExecutor.shutdown();
+            mLightWeightExecutor.shutdown();
+        }
+    }
+
+    /**
+     * An executor that can be shutdown. Unlike an ExecutorService, it will not throw a
+     * RejectedExecutionException if {@link #execute(Runnable)} is called after shutdown.
+     */
+    private static class SoftShutdownExecutor implements Executor {
+
+        private final ExecutorService mExecutorService;
+
+        SoftShutdownExecutor(ExecutorService executorService) {
+            mExecutorService = executorService;
+        }
+
+        @Override
+        public void execute(Runnable runnable) {
+            try {
+                mExecutorService.execute(runnable);
+            } catch (RejectedExecutionException ex) {
+                Slog.d(TAG, "Received new runnable after shutdown. Ignoring.");
+            }
+        }
+
+        /** Shutdown the underlying ExecutorService. */
+        void shutdown() {
+            mExecutorService.shutdown();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 7b59759..c2117ea 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2441,11 +2441,11 @@
             return false;
         }
 
-        if (mStartingData != null) {
+        if (hasStartingWindow()) {
             return false;
         }
 
-        final WindowState mainWin = findMainWindow();
+        final WindowState mainWin = findMainWindow(false /* includeStartingApp */);
         if (mainWin != null && mainWin.mWinAnimator.getShown()) {
             // App already has a visible window...why would you want a starting window?
             return false;
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index 4ced5d5..f2dc55f 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -333,7 +333,9 @@
         if (aInfo != null && overrideTaskTransition) {
             final int startTasksFromRecentsPerm = ActivityTaskManagerService.checkPermission(
                     START_TASKS_FROM_RECENTS, callingPid, callingUid);
-            if (startTasksFromRecentsPerm != PERMISSION_GRANTED) {
+            // Allow if calling uid is from assistant, or start task from recents
+            if (startTasksFromRecentsPerm != PERMISSION_GRANTED
+                    && !isAssistant(supervisor.mService, callingUid)) {
                 final String msg = "Permission Denial: starting " + getIntentString(intent)
                         + " from " + callerApp + " (pid=" + callingPid
                         + ", uid=" + callingUid + ") with overrideTaskTransition=true";
diff --git a/services/core/java/com/android/server/wm/WindowList.java b/services/core/java/com/android/server/wm/WindowList.java
index dfeba40..1e888f5 100644
--- a/services/core/java/com/android/server/wm/WindowList.java
+++ b/services/core/java/com/android/server/wm/WindowList.java
@@ -24,7 +24,7 @@
  */
 class WindowList<E> extends ArrayList<E> {
 
-    void addFirst(E e) {
+    public void addFirst(E e) {
         add(0, e);
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 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/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..6f0985a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -34,6 +34,7 @@
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CAMERA;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CERTIFICATES;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_DEFAULT_SMS;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_DISPLAY;
@@ -110,6 +111,8 @@
 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER;
 import static android.app.admin.DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED;
+import static android.app.admin.DevicePolicyManager.CONTENT_PROTECTION_DISABLED;
+import static android.app.admin.DevicePolicyManager.ContentProtectionPolicy;
 import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
 import static android.app.admin.DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL;
 import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
@@ -220,6 +223,7 @@
 import static android.app.admin.ProvisioningException.ERROR_SETTING_PROFILE_OWNER_FAILED;
 import static android.app.admin.ProvisioningException.ERROR_SET_DEVICE_OWNER_FAILED;
 import static android.app.admin.ProvisioningException.ERROR_STARTING_PROFILE_FAILED;
+import static android.app.admin.flags.Flags.backupServiceSecurityLogEventEnabled;
 import static android.app.admin.flags.Flags.dumpsysPolicyEngineMigrationEnabled;
 import static android.app.admin.flags.Flags.policyEngineMigrationV2Enabled;
 import static android.content.Intent.ACTION_MANAGED_PROFILE_AVAILABLE;
@@ -17926,6 +17930,13 @@
                 || isProfileOwner(caller) || isFinancedDeviceOwner(caller));
 
         toggleBackupServiceActive(caller.getUserId(), enabled);
+
+        if (backupServiceSecurityLogEventEnabled()) {
+            if (SecurityLog.isLoggingEnabled()) {
+                SecurityLog.writeEvent(SecurityLog.TAG_BACKUP_SERVICE_TOGGLED,
+                        caller.getPackageName(), caller.getUserId(), enabled ? 1 : 0);
+            }
+        }
     }
 
     @Override
@@ -23165,6 +23176,90 @@
         }
     }
 
+    private EnforcingAdmin enforceCanCallContentProtectionLocked(
+            ComponentName who, String callerPackageName) {
+        CallerIdentity caller = getCallerIdentity(who, callerPackageName);
+        final int userId = caller.getUserId();
+
+        EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+                who,
+                MANAGE_DEVICE_POLICY_CONTENT_PROTECTION,
+                caller.getPackageName(),
+                userId
+        );
+        if ((isDeviceOwner(caller) || isProfileOwner(caller))
+                && !canDPCManagedUserUseLockTaskLocked(userId)) {
+            throw new SecurityException(
+                    "User " + userId + " is not allowed to use content protection");
+        }
+        return enforcingAdmin;
+    }
+
+    private void enforceCanQueryContentProtectionLocked(
+            ComponentName who, String callerPackageName) {
+        CallerIdentity caller = getCallerIdentity(who, callerPackageName);
+        final int userId = caller.getUserId();
+
+        enforceCanQuery(MANAGE_DEVICE_POLICY_CONTENT_PROTECTION, caller.getPackageName(), userId);
+        if ((isDeviceOwner(caller) || isProfileOwner(caller))
+                && !canDPCManagedUserUseLockTaskLocked(userId)) {
+            throw new SecurityException(
+                    "User " + userId + " is not allowed to use content protection");
+        }
+    }
+
+    @Override
+    public void setContentProtectionPolicy(
+            ComponentName who, String callerPackageName, @ContentProtectionPolicy int policy)
+            throws SecurityException {
+        if (!android.view.contentprotection.flags.Flags.manageDevicePolicyEnabled()) {
+            return;
+        }
+
+        CallerIdentity caller = getCallerIdentity(who, callerPackageName);
+        checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_CONTENT_PROTECTION_POLICY);
+
+        EnforcingAdmin enforcingAdmin;
+        synchronized (getLockObject()) {
+            enforcingAdmin = enforceCanCallContentProtectionLocked(who, caller.getPackageName());
+        }
+
+        if (policy == CONTENT_PROTECTION_DISABLED) {
+            mDevicePolicyEngine.removeLocalPolicy(
+                    PolicyDefinition.CONTENT_PROTECTION,
+                    enforcingAdmin,
+                    caller.getUserId());
+        } else {
+            mDevicePolicyEngine.setLocalPolicy(
+                    PolicyDefinition.CONTENT_PROTECTION,
+                    enforcingAdmin,
+                    new IntegerPolicyValue(policy),
+                    caller.getUserId());
+        }
+    }
+
+    @Override
+    public @ContentProtectionPolicy int getContentProtectionPolicy(
+            ComponentName who, String callerPackageName) {
+        if (!android.view.contentprotection.flags.Flags.manageDevicePolicyEnabled()) {
+            return CONTENT_PROTECTION_DISABLED;
+        }
+
+        CallerIdentity caller = getCallerIdentity(who, callerPackageName);
+        final int userHandle = caller.getUserId();
+
+        synchronized (getLockObject()) {
+            enforceCanQueryContentProtectionLocked(who, caller.getPackageName());
+        }
+        Integer policy = mDevicePolicyEngine.getResolvedPolicy(
+                PolicyDefinition.CONTENT_PROTECTION, userHandle);
+        if (policy == null) {
+            return CONTENT_PROTECTION_DISABLED;
+        } else {
+            return policy;
+        }
+    }
+
     @Override
     public ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy() {
         synchronized (getLockObject()) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 0fc8c5e..27f1834 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -341,6 +341,13 @@
                 PolicyEnforcerCallbacks.setUsbDataSignalingEnabled(value, context),
             new BooleanPolicySerializer());
 
+    static PolicyDefinition<Integer> CONTENT_PROTECTION = new PolicyDefinition<>(
+            new NoArgsPolicyKey(DevicePolicyIdentifiers.CONTENT_PROTECTION_POLICY),
+            new MostRecent<>(),
+            POLICY_FLAG_LOCAL_ONLY_POLICY,
+            (Integer value, Context context, Integer userId, PolicyKey policyKey) -> true,
+            new IntegerPolicySerializer());
+
     private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>();
     private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>();
 
@@ -374,6 +381,8 @@
                 PERSONAL_APPS_SUSPENDED);
         POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.USB_DATA_SIGNALING_POLICY,
                 USB_DATA_SIGNALING);
+        POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.CONTENT_PROTECTION_POLICY,
+                CONTENT_PROTECTION);
 
         // User Restriction Policies
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MODIFY_ACCOUNTS, /* flags= */ 0);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b79d20a..c55d709 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -48,6 +48,7 @@
 import android.content.res.Configuration;
 import android.content.res.Resources.Theme;
 import android.credentials.CredentialManager;
+import android.credentials.flags.Flags;
 import android.database.sqlite.SQLiteCompatibilityWalFlags;
 import android.database.sqlite.SQLiteGlobal;
 import android.graphics.GraphicsStatsService;
@@ -2795,9 +2796,14 @@
                     DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CREDENTIAL,
                     CredentialManager.DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER, true);
             if (credentialManagerEnabled) {
-                t.traceBegin("StartCredentialManagerService");
-                mSystemServiceManager.startService(CREDENTIAL_MANAGER_SERVICE_CLASS);
-                t.traceEnd();
+                if(isWatch &&
+                  !android.credentials.flags.Flags.wearCredentialManagerEnabled()) {
+                  Slog.d(TAG, "CredentialManager disabled on wear.");
+                } else {
+                  t.traceBegin("StartCredentialManagerService");
+                  mSystemServiceManager.startService(CREDENTIAL_MANAGER_SERVICE_CLASS);
+                  t.traceEnd();
+                }
             } else {
                 Slog.d(TAG, "CredentialManager disabled.");
             }
diff --git a/services/java/com/android/server/flags.aconfig b/services/java/com/android/server/flags.aconfig
new file mode 100644
index 0000000..4b578af
--- /dev/null
+++ b/services/java/com/android/server/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.server"
+
+flag {
+     namespace: "system_performance"
+     name: "telemetry_apis_service"
+     description: "Control service portion of telemetry APIs feature."
+     is_fixed_read_only: true
+     bug: "324153471"
+}
diff --git a/services/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/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/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
index 2dfabd0..b12d6da 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -893,13 +893,13 @@
 
     @Test
     public void getTokenLocked_windowIsRegistered_shouldReturnToken() {
-        final IBinder token = mA11yWindowManager.getTokenLocked(HOST_WINDOW_ID);
+        final IBinder token = mA11yWindowManager.getLeashTokenLocked(HOST_WINDOW_ID);
         assertEquals(token, mMockHostToken);
     }
 
     @Test
     public void getTokenLocked_windowIsNotRegistered_shouldReturnNull() {
-        final IBinder token = mA11yWindowManager.getTokenLocked(OTHER_WINDOW_ID);
+        final IBinder token = mA11yWindowManager.getLeashTokenLocked(OTHER_WINDOW_ID);
         assertNull(token);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index 5943832..07e6ab2 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -19,11 +19,10 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertThrows;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.startsWith;
-import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
 
 import android.hardware.display.DisplayManagerInternal;
@@ -86,9 +85,8 @@
         LocalServices.removeServiceForTest(InputManagerInternal.class);
         LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
 
-        final DisplayInfo displayInfo = new DisplayInfo();
-        displayInfo.uniqueId = "uniqueId";
-        doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
+        setUpDisplay(1 /* displayId */);
+        setUpDisplay(2 /* displayId */);
         LocalServices.removeServiceForTest(DisplayManagerInternal.class);
         LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
 
@@ -100,6 +98,16 @@
                 threadVerifier);
     }
 
+    void setUpDisplay(int displayId) {
+        final String uniqueId = "uniqueId:" + displayId;
+        doAnswer((inv) -> {
+            final DisplayInfo displayInfo = new DisplayInfo();
+            displayInfo.uniqueId = uniqueId;
+            return displayInfo;
+        }).when(mDisplayManagerInternalMock).getDisplayInfo(eq(displayId));
+        mInputManagerMockHelper.addDisplayIdMapping(uniqueId, displayId);
+    }
+
     @After
     public void tearDown() {
         mInputManagerMockHelper.tearDown();
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
index 3722247..74e854e4 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java
@@ -20,7 +20,6 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.when;
 
 import android.hardware.input.IInputDevicesChangedListener;
@@ -28,12 +27,15 @@
 import android.hardware.input.InputManagerGlobal;
 import android.os.RemoteException;
 import android.testing.TestableLooper;
+import android.view.Display;
 import android.view.InputDevice;
 
 import org.mockito.invocation.InvocationOnMock;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.stream.IntStream;
 
@@ -49,6 +51,10 @@
     private final InputManagerGlobal.TestSession mInputManagerGlobalSession;
     private final List<InputDevice> mDevices = new ArrayList<>();
     private IInputDevicesChangedListener mDevicesChangedListener;
+    private final Map<String /* uniqueId */, Integer /* displayId */> mDisplayIdMapping =
+            new HashMap<>();
+    private final Map<String /* phys */, String /* uniqueId */> mUniqueIdAssociation =
+            new HashMap<>();
 
     InputManagerMockHelper(TestableLooper testableLooper,
             InputController.NativeWrapper nativeWrapperMock, IInputManager iInputManagerMock)
@@ -73,8 +79,10 @@
         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
         doAnswer(inv -> mDevices.get(inv.getArgument(0)))
                 .when(mIInputManagerMock).getInputDevice(anyInt());
-        doNothing().when(mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
-        doNothing().when(mIInputManagerMock).removeUniqueIdAssociation(anyString());
+        doAnswer(inv -> mUniqueIdAssociation.put(inv.getArgument(0), inv.getArgument(1))).when(
+                mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString());
+        doAnswer(inv -> mUniqueIdAssociation.remove(inv.getArgument(0))).when(
+                mIInputManagerMock).removeUniqueIdAssociation(anyString());
 
         // Set a new instance of InputManager for testing that uses the IInputManager mock as the
         // interface to the server.
@@ -87,17 +95,25 @@
         }
     }
 
+    public void addDisplayIdMapping(String uniqueId, int displayId) {
+        mDisplayIdMapping.put(uniqueId, displayId);
+    }
+
     private long handleNativeOpenInputDevice(InvocationOnMock inv) {
         Objects.requireNonNull(mDevicesChangedListener,
                 "InputController did not register an InputDevicesChangedListener.");
 
+        final String phys = inv.getArgument(3);
         final InputDevice device = new InputDevice.Builder()
                 .setId(mDevices.size())
                 .setName(inv.getArgument(0))
                 .setVendorId(inv.getArgument(1))
                 .setProductId(inv.getArgument(2))
-                .setDescriptor(inv.getArgument(3))
+                .setDescriptor(phys)
                 .setExternal(true)
+                .setAssociatedDisplayId(
+                        mDisplayIdMapping.getOrDefault(mUniqueIdAssociation.get(phys),
+                                Display.INVALID_DISPLAY))
                 .build();
 
         mDevices.add(device);
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 5442af8..157e893 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -39,6 +39,7 @@
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.argThat;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -2015,6 +2016,13 @@
                 eq(virtualDevice), any(), any())).thenReturn(displayId);
         virtualDevice.createVirtualDisplay(VIRTUAL_DISPLAY_CONFIG, mVirtualDisplayCallback,
                 NONBLOCKED_APP_PACKAGE_NAME);
+        final String uniqueId = UNIQUE_ID + displayId;
+        doAnswer(inv -> {
+            final DisplayInfo displayInfo = new DisplayInfo();
+            displayInfo.uniqueId = uniqueId;
+            return displayInfo;
+        }).when(mDisplayManagerInternalMock).getDisplayInfo(eq(displayId));
+        mInputManagerMockHelper.addDisplayIdMapping(uniqueId, displayId);
     }
 
     private ComponentName getPermissionDialogComponent() {
diff --git a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
index 8656f60..bf87e3a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
@@ -111,6 +111,8 @@
     private UsageStatsManagerInternal mUsageStatsManagerInternal;
     @Mock
     private PermissionManagerServiceInternal mPermissionManager;
+    @Mock
+    private BackgroundInstallControlCallbackHelper mCallbackHelper;
 
     @Captor
     private ArgumentCaptor<PackageManagerInternal.PackageListObserver> mPackageListObserverCaptor;
@@ -982,5 +984,11 @@
         public File getDiskFile() {
             return mFile;
         }
+
+
+        @Override
+        public BackgroundInstallControlCallbackHelper getBackgroundInstallControlCallbackHelper() {
+            return mCallbackHelper;
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index 3778a32..f1d3ba9 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -23,7 +23,6 @@
 import android.content.pm.UserProperties;
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.Xml;
 
 import androidx.test.filters.MediumTest;
@@ -32,7 +31,6 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -54,13 +52,10 @@
 @RunWith(AndroidJUnit4.class)
 @MediumTest
 public class UserManagerServiceUserPropertiesTest {
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     /** Test that UserProperties can properly read the xml information that it writes. */
     @Test
     public void testWriteReadXml() throws Exception {
-        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         final UserProperties defaultProps = new UserProperties.Builder()
                 .setShowInLauncher(21)
                 .setStartWithParent(false)
@@ -123,7 +118,6 @@
     /** Tests parcelling an object in which all properties are present. */
     @Test
     public void testParcelUnparcel() throws Exception {
-        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         final UserProperties originalProps = new UserProperties.Builder()
                 .setShowInLauncher(2145)
                 .build();
@@ -134,7 +128,6 @@
     /** Tests copying a UserProperties object varying permissions. */
     @Test
     public void testCopyLacksPermissions() throws Exception {
-        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         final UserProperties defaultProps = new UserProperties.Builder()
                 .setShowInLauncher(2145)
                 .setStartWithParent(true)
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 6cdbc74..3047bcf 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -41,7 +41,6 @@
 import android.os.Bundle;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.ArrayMap;
 
 import androidx.test.InstrumentationRegistry;
@@ -51,7 +50,6 @@
 import com.android.frameworks.servicestests.R;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -73,11 +71,8 @@
     public void setup() {
         mResources = InstrumentationRegistry.getTargetContext().getResources();
     }
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     @Test
     public void testUserTypeBuilder_createUserType() {
-        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         final Bundle restrictions = makeRestrictionsBundle("r1", "r2");
         final Bundle systemSettings = makeSettingsBundle("s1", "s2");
         final Bundle secureSettings = makeSettingsBundle("secure_s1", "secure_s2");
@@ -207,7 +202,6 @@
 
     @Test
     public void testUserTypeBuilder_defaults() {
-        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         UserTypeDetails type = new UserTypeDetails.Builder()
                 .setName("name") // Required (no default allowed)
                 .setBaseType(FLAG_FULL) // Required (no default allowed)
@@ -321,7 +315,6 @@
     /** Tests {@link UserTypeFactory#customizeBuilders} for a reasonable xml file. */
     @Test
     public void testUserTypeFactoryCustomize_profile() throws Exception {
-        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         final String userTypeAosp1 = "android.test.1"; // Profile user that is not customized
         final String userTypeAosp2 = "android.test.2"; // Profile user that is customized
         final String userTypeOem1 = "custom.test.1"; // Custom-defined profile
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 9323b48..df2069e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -39,7 +39,6 @@
 import android.os.UserManager;
 import android.platform.test.annotations.Postsubmit;
 import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
@@ -56,7 +55,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -99,8 +97,6 @@
     private UserSwitchWaiter mUserSwitchWaiter;
     private UserRemovalWaiter mUserRemovalWaiter;
     private int mOriginalCurrentUserId;
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Before
     public void setUp() throws Exception {
@@ -172,7 +168,6 @@
 
     @Test
     public void testCloneUser() throws Exception {
-        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         assumeCloneEnabled();
         UserHandle mainUser = mUserManager.getMainUser();
         assumeTrue("Main user is null", mainUser != null);
@@ -229,7 +224,8 @@
                 .isEqualTo(cloneUserProperties.getCrossProfileContentSharingStrategy());
         assertThrows(SecurityException.class, cloneUserProperties::getDeleteAppWithParent);
         assertThrows(SecurityException.class, cloneUserProperties::getAlwaysVisible);
-        assertThrows(SecurityException.class, cloneUserProperties::getProfileApiVisibility);
+        assertThat(typeProps.getProfileApiVisibility()).isEqualTo(
+                cloneUserProperties.getProfileApiVisibility());
         compareDrawables(mUserManager.getUserBadge(),
                 Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain()));
 
@@ -311,7 +307,6 @@
 
     @Test
     public void testPrivateProfile() throws Exception {
-        mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
         UserHandle mainUser = mUserManager.getMainUser();
         assumeTrue("Main user is null", mainUser != null);
         // Get the default properties for private profile user type.
@@ -353,8 +348,8 @@
         assertThrows(SecurityException.class, privateProfileUserProperties::getDeleteAppWithParent);
         assertThrows(SecurityException.class,
                 privateProfileUserProperties::getAllowStoppingUserWithDelayedLocking);
-        assertThrows(SecurityException.class,
-                privateProfileUserProperties::getProfileApiVisibility);
+        assertThat(typeProps.getProfileApiVisibility()).isEqualTo(
+                privateProfileUserProperties.getProfileApiVisibility());
         assertThrows(SecurityException.class,
                 privateProfileUserProperties::areItemsRestrictedOnHomeScreen);
         compareDrawables(mUserManager.getUserBadge(),
diff --git a/services/tests/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/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index b7706a9..6bdc43e 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -810,6 +810,9 @@
          * If this setter method is never called or cleared using
          * {@link #clearSimultaneousCallingRestriction()}, there is no restriction and all
          * {@link PhoneAccount}s registered to Telecom by this package support simultaneous calling.
+         * If this setter is called and set as an empty Set, then this {@link PhoneAccount} does
+         * not support simultaneous calling with any other {@link PhoneAccount}s registered by the
+         * same application.
          * <p>
          * Note: Simultaneous calling restrictions can only be placed on {@link PhoneAccount}s that
          * were registered by the same application. Simultaneous calling across applications is
diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java
index b59e855..5af2c34 100644
--- a/telephony/java/android/service/euicc/EuiccService.java
+++ b/telephony/java/android/service/euicc/EuiccService.java
@@ -19,6 +19,7 @@
 
 import android.Manifest;
 import android.annotation.CallSuper;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -38,6 +39,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.lang.annotation.Retention;
@@ -759,6 +762,20 @@
     public abstract int onRetainSubscriptionsForFactoryReset(int slotId);
 
     /**
+     * Return the available memory in bytes of the eUICC.
+     *
+     * @param slotId ID of the SIM slot being queried.
+     * @return the available memory in bytes.
+     * @see android.telephony.euicc.EuiccManager#getAvailableMemoryInBytes
+     */
+    @FlaggedApi(Flags.FLAG_ESIM_AVAILABLE_MEMORY)
+    public long onGetAvailableMemoryInBytes(int slotId) {
+        // stub implementation, LPA needs to implement this
+        throw new UnsupportedOperationException("The connected LPA does not implement"
+                + "EuiccService#onGetAvailableMemoryInBytes(int)");
+    }
+
+    /**
      * Dump to a provided printWriter.
      */
     public void dump(@NonNull PrintWriter printWriter) {
@@ -834,6 +851,22 @@
         }
 
         @Override
+        @FlaggedApi(Flags.FLAG_ESIM_AVAILABLE_MEMORY)
+        public void getAvailableMemoryInBytes(
+                int slotId, IGetAvailableMemoryInBytesCallback callback) {
+            mExecutor.execute(
+                    () -> {
+                        long availableMemoryInBytes =
+                                EuiccService.this.onGetAvailableMemoryInBytes(slotId);
+                        try {
+                            callback.onSuccess(availableMemoryInBytes);
+                        } catch (RemoteException e) {
+                            // Can't communicate with the phone process; ignore.
+                        }
+                    });
+        }
+
+        @Override
         public void startOtaIfNecessary(
                 int slotId, IOtaStatusChangedCallback statusChangedCallback) {
             mExecutor.execute(new Runnable() {
diff --git a/telephony/java/android/service/euicc/IEuiccService.aidl b/telephony/java/android/service/euicc/IEuiccService.aidl
index f8d5ae9..0f8c72b 100644
--- a/telephony/java/android/service/euicc/IEuiccService.aidl
+++ b/telephony/java/android/service/euicc/IEuiccService.aidl
@@ -19,6 +19,7 @@
 import android.service.euicc.IDeleteSubscriptionCallback;
 import android.service.euicc.IDownloadSubscriptionCallback;
 import android.service.euicc.IEraseSubscriptionsCallback;
+import android.service.euicc.IGetAvailableMemoryInBytesCallback;
 import android.service.euicc.IGetDefaultDownloadableSubscriptionListCallback;
 import android.service.euicc.IGetDownloadableSubscriptionMetadataCallback;
 import android.service.euicc.IGetEidCallback;
@@ -60,4 +61,5 @@
     void retainSubscriptionsForFactoryReset(
             int slotId, in IRetainSubscriptionsForFactoryResetCallback callback);
     void dump(in IEuiccServiceDumpResultCallback callback);
-}
\ No newline at end of file
+    void getAvailableMemoryInBytes(int slotId, in IGetAvailableMemoryInBytesCallback callback);
+}
diff --git a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java b/telephony/java/android/service/euicc/IGetAvailableMemoryInBytesCallback.aidl
similarity index 62%
rename from packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java
rename to telephony/java/android/service/euicc/IGetAvailableMemoryInBytesCallback.aidl
index 48fd4fe..bd6d19b 100644
--- a/packages/SoundPicker2/src/com/android/soundpicker/RingtonePickerApplication.java
+++ 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,15 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.soundpicker;
+package android.service.euicc;
 
-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 {
+/** @hide */
+oneway interface IGetAvailableMemoryInBytesCallback {
+    void onSuccess(long availableMemoryInBytes);
 }
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index a188581..82ed340 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -40,6 +40,7 @@
 import android.telephony.SubscriptionManager.ProfileClass;
 import android.telephony.SubscriptionManager.SimDisplayNameSource;
 import android.telephony.SubscriptionManager.SubscriptionType;
+import android.telephony.SubscriptionManager.TransferStatus;
 import android.telephony.SubscriptionManager.UsageSetting;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
@@ -236,6 +237,11 @@
     @UsageSetting
     private final int mUsageSetting;
 
+    /**
+     * Subscription's transfer status
+     */
+    private final int mTransferStatus;
+
     // Below are the fields that do not exist in the database.
 
     /**
@@ -393,6 +399,7 @@
         this.mUsageSetting = usageSetting;
         this.mIsOnlyNonTerrestrialNetwork = false;
         this.mServiceCapabilities = 0;
+        this.mTransferStatus = 0;
     }
 
     /**
@@ -433,6 +440,7 @@
         this.mUsageSetting = builder.mUsageSetting;
         this.mIsOnlyNonTerrestrialNetwork = builder.mIsOnlyNonTerrestrialNetwork;
         this.mServiceCapabilities = builder.mServiceCapabilities;
+        this.mTransferStatus = builder.mTransferStatus;
     }
 
     /**
@@ -928,6 +936,19 @@
         return SubscriptionManager.getServiceCapabilitiesSet(mServiceCapabilities);
     }
 
+    /**
+     * Get the transfer status for this subscription.
+     *
+     * @return The transfer status for this subscription.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+    @SystemApi
+    public @TransferStatus int getTransferStatus() {
+        return mTransferStatus;
+    }
+
     @NonNull
     public static final Parcelable.Creator<SubscriptionInfo> CREATOR =
             new Parcelable.Creator<SubscriptionInfo>() {
@@ -967,6 +988,7 @@
                     .setOnlyNonTerrestrialNetwork(source.readBoolean())
                     .setServiceCapabilities(
                             SubscriptionManager.getServiceCapabilitiesSet(source.readInt()))
+                    .setTransferStatus(source.readInt())
                     .build();
         }
 
@@ -1010,6 +1032,7 @@
         dest.writeInt(mUsageSetting);
         dest.writeBoolean(mIsOnlyNonTerrestrialNetwork);
         dest.writeInt(mServiceCapabilities);
+        dest.writeInt(mTransferStatus);
     }
 
     @Override
@@ -1075,6 +1098,7 @@
                 + " isOnlyNonTerrestrialNetwork=" + mIsOnlyNonTerrestrialNetwork
                 + " serviceCapabilities=" + SubscriptionManager.getServiceCapabilitiesSet(
                 mServiceCapabilities).toString()
+                + " transferStatus=" + mTransferStatus
                 + "]";
     }
 
@@ -1101,7 +1125,8 @@
                 that.mCarrierConfigAccessRules) && Objects.equals(mGroupUuid, that.mGroupUuid)
                 && mCountryIso.equals(that.mCountryIso) && mGroupOwner.equals(that.mGroupOwner)
                 && mIsOnlyNonTerrestrialNetwork == that.mIsOnlyNonTerrestrialNetwork
-                && mServiceCapabilities == that.mServiceCapabilities;
+                && mServiceCapabilities == that.mServiceCapabilities
+                && mTransferStatus == that.mTransferStatus;
     }
 
     @Override
@@ -1110,7 +1135,8 @@
                 mDisplayNameSource, mIconTint, mNumber, mDataRoaming, mMcc, mMnc, mIsEmbedded,
                 mCardString, mIsOpportunistic, mGroupUuid, mCountryIso, mCarrierId, mProfileClass,
                 mType, mGroupOwner, mAreUiccApplicationsEnabled, mPortIndex, mUsageSetting, mCardId,
-                mIsGroupDisabled, mIsOnlyNonTerrestrialNetwork, mServiceCapabilities);
+                mIsGroupDisabled, mIsOnlyNonTerrestrialNetwork, mServiceCapabilities,
+                mTransferStatus);
         result = 31 * result + Arrays.hashCode(mEhplmns);
         result = 31 * result + Arrays.hashCode(mHplmns);
         result = 31 * result + Arrays.hashCode(mNativeAccessRules);
@@ -1314,6 +1340,8 @@
          */
         private boolean mIsOnlyNonTerrestrialNetwork = false;
 
+        private int mTransferStatus = 0;
+
         /**
          * Service capabilities bitmasks the subscription supports.
          */
@@ -1363,6 +1391,7 @@
             mUsageSetting = info.mUsageSetting;
             mIsOnlyNonTerrestrialNetwork = info.mIsOnlyNonTerrestrialNetwork;
             mServiceCapabilities = info.mServiceCapabilities;
+            mTransferStatus = info.mTransferStatus;
         }
 
         /**
@@ -1785,6 +1814,18 @@
             mServiceCapabilities = combinedCapabilities;
             return this;
         }
+         /**
+         * Set subscription's transfer status
+         *
+         * @param status Subscription's transfer status
+         * @return The builder.
+         */
+        @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+        @NonNull
+        public Builder setTransferStatus(@TransferStatus int status) {
+            mTransferStatus = status;
+            return this;
+        }
 
         /**
          * Build the {@link SubscriptionInfo}.
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 1749545..3fde3b6 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1148,6 +1148,14 @@
      */
     public static final String SERVICE_CAPABILITIES = SimInfo.COLUMN_SERVICE_CAPABILITIES;
 
+    /**
+     * TelephonyProvider column name to identify eSIM's transfer status.
+     * By default, it's disabled.
+     * <P>Type: INTEGER (int)</P>
+     * @hide
+     */
+    public static final String TRANSFER_STATUS = SimInfo.COLUMN_TRANSFER_STATUS;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"USAGE_SETTING_"},
@@ -1453,6 +1461,41 @@
     private static final LruCache<Pair<String, Configuration>, Resources> sResourcesCache =
             new LruCache<>(MAX_RESOURCE_CACHE_ENTRY_COUNT);
 
+
+    /**
+     * The profile has not been transferred or converted to an eSIM.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+    @SystemApi
+    public static final int TRANSFER_STATUS_NONE = 0;
+
+    /**
+     * The existing profile of the old device has been transferred to an eSIM of the new device.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+    @SystemApi
+    public static final int TRANSFER_STATUS_TRANSFERRED_OUT = 1;
+
+    /**
+     * The existing profile of the same device has been converted to an eSIM of the same device
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+    @SystemApi
+    public static final int TRANSFER_STATUS_CONVERTED = 2;
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"TRANSFER_STATUS"},
+            value = {
+                    TRANSFER_STATUS_NONE,
+                    TRANSFER_STATUS_TRANSFERRED_OUT,
+                    TRANSFER_STATUS_CONVERTED,
+            })
+    public @interface TransferStatus {}
+
+
     /**
      * A listener class for monitoring changes to {@link SubscriptionInfo} records.
      * <p>
@@ -4716,4 +4759,27 @@
     public static int serviceCapabilityToBitmask(@ServiceCapability int capability) {
         return 1 << (capability - 1);
     }
+
+    /**
+     * Set the transfer status of the subscriptionInfo of the subId.
+     * @param subscriptionId The unique SubscriptionInfo key in database.
+     * @param status The transfer status to change.
+     *
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+    @SystemApi
+    @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+    public void setTransferStatus(int subscriptionId, @TransferStatus int status) {
+        try {
+            ISub iSub = TelephonyManager.getSubscriptionService();
+            if (iSub != null) {
+                iSub.setTransferStatus(subscriptionId, status);
+            }
+        } catch (RemoteException ex) {
+            logd("setTransferStatus for subId = " + subscriptionId + " failed.");
+            throw ex.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 9b8e62f..7935d24 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -16,12 +16,14 @@
 package android.telephony.euicc;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
+import android.annotation.SuppressAutoDoc;
 import android.annotation.SystemApi;
 import android.app.Activity;
 import android.app.PendingIntent;
@@ -50,6 +52,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -861,6 +864,10 @@
      */
     public static final int ERROR_INVALID_PORT = 10017;
 
+    /** Temporary failure to retrieve available memory because eUICC is not ready. */
+    @FlaggedApi(Flags.FLAG_ESIM_AVAILABLE_MEMORY)
+    public static final long EUICC_MEMORY_FIELD_UNAVAILABLE = -1L;
+
     /**
      * Apps targeting on Android T and beyond will get exception whenever switchToSubscription
      * without portIndex is called for disable subscription.
@@ -961,6 +968,35 @@
     }
 
     /**
+     * Returns the available memory in bytes of the eUICC.
+     *
+     * @return the available memory in bytes. May be {@link #EUICC_MEMORY_FIELD_UNAVAILABLE} if the
+     *     eUICC is not ready. Check {@link #isEnabled} for more information.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC} or
+     *          device doesn't support querying this information from the eUICC.
+     */
+    @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
+    @FlaggedApi(Flags.FLAG_ESIM_AVAILABLE_MEMORY)
+    @RequiresPermission(
+            anyOf = {
+                Manifest.permission.READ_PHONE_STATE,
+                Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+                "carrier privileges"
+            })
+    public long getAvailableMemoryInBytes() {
+        if (!isEnabled()) {
+            return EUICC_MEMORY_FIELD_UNAVAILABLE;
+        }
+        try {
+            return getIEuiccController()
+                    .getAvailableMemoryInBytes(mCardId, mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns the current status of eUICC OTA.
      *
      * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
@@ -1707,4 +1743,57 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Sets the supported carrier ids for pSIM conversion.
+     *
+     * <p>Any existing pSIM conversion supported carrier list will be replaced
+     * by the {@code carrierIds} set here.
+     *
+     * @param carrierIds is a list of carrierIds that supports pSIM conversion
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
+     * @throws IllegalStateException if this method is called when {@link #isEnabled} is false.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+    @SystemApi
+    @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+    public void setPsimConversionSupportedCarriers(@NonNull Set<Integer> carrierIds) {
+        if (!isEnabled()) {
+            throw new IllegalStateException("Euicc is not enabled");
+        }
+        try {
+            int[] arr = carrierIds.stream().mapToInt(Integer::intValue).toArray();
+            getIEuiccController().setPsimConversionSupportedCarriers(arr);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Returns whether the given carrier id supports pSIM conversion or not.
+     *
+     * @param carrierId to check whether pSIM conversion is supported or not
+     * @return whether the given carrier id supports pSIM conversion or not,
+     *         or false if {@link #isEnabled} is false
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORT_PSIM_TO_ESIM_CONVERSION)
+    @SystemApi
+    @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+    public boolean isPsimConversionSupported(int carrierId) {
+        if (!isEnabled()) {
+            return false;
+        }
+        try {
+            return getIEuiccController().isPsimConversionSupported(carrierId);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/satellite/EnableRequestAttributes.java b/telephony/java/android/telephony/satellite/EnableRequestAttributes.java
new file mode 100644
index 0000000..bc9d230
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/EnableRequestAttributes.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.telephony.flags.Flags;
+
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * EnableRequestAttributes is used to store the attributes of the request
+ * {@link SatelliteManager#requestEnabled(EnableRequestAttributes, Executor, Consumer)}
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+public class EnableRequestAttributes {
+    /** {@code true} to enable satellite and {@code false} to disable satellite */
+    private boolean mIsEnabled;
+    /**
+     * {@code true} to enable demo mode and {@code false} to disable. When disabling satellite,
+     * {@code mIsDemoMode} is always considered as {@code false} by Telephony.
+     */
+    private boolean mIsDemoMode;
+    /**
+     * {@code true} means satellite is enabled for emergency mode, {@code false} otherwise. When
+     * disabling satellite, {@code isEmergencyMode} is always considered as {@code false} by
+     * Telephony.
+     */
+    private boolean mIsEmergencyMode;
+
+    /**
+     * Constructor from builder.
+     *
+     * @param builder Builder of {@link EnableRequestAttributes}.
+     */
+    private EnableRequestAttributes(@NonNull Builder builder) {
+        this.mIsEnabled = builder.mIsEnabled;
+        this.mIsDemoMode = builder.mIsDemoMode;
+        this.mIsEmergencyMode = builder.mIsEmergencyMode;
+    }
+
+    /**
+     * @return Whether satellite is to be enabled
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public boolean isEnabled() {
+        return mIsEnabled;
+    }
+
+    /**
+     * @return Whether demo mode is to be enabled
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public boolean isDemoMode() {
+        return mIsDemoMode;
+    }
+
+    /**
+     * @return Whether satellite is to be enabled for emergency mode
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public boolean isEmergencyMode() {
+        return mIsEmergencyMode;
+    }
+
+    /**
+     * The builder class of {@link EnableRequestAttributes}
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final class Builder {
+        private boolean mIsEnabled;
+        private boolean mIsDemoMode = false;
+        private boolean mIsEmergencyMode = false;
+
+        @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+        public Builder(boolean isEnabled) {
+            mIsEnabled = isEnabled;
+        }
+
+        /**
+         * Set demo mode
+         *
+         * @param isDemoMode {@code true} to enable demo mode and {@code false} to disable. When
+         *                   disabling satellite, {@code isDemoMode} is always considered as
+         *                   {@code false} by Telephony.
+         * @return The builder object
+         */
+        @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+        @NonNull
+        public Builder setDemoMode(boolean isDemoMode) {
+            if (mIsEnabled) {
+                mIsDemoMode = isDemoMode;
+            }
+            return this;
+        }
+
+        /**
+         * Set emergency mode
+         *
+         * @param isEmergencyMode {@code true} means satellite is enabled for emergency mode,
+         *                        {@code false} otherwise. When disabling satellite,
+         *                        {@code isEmergencyMode} is always considered as {@code false} by
+         *                        Telephony.
+         * @return The builder object
+         */
+        @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+        @NonNull
+        public Builder setEmergencyMode(boolean isEmergencyMode) {
+            if (mIsEnabled) {
+                mIsEmergencyMode = isEmergencyMode;
+            }
+            return this;
+        }
+
+        /**
+         * Build the {@link EnableRequestAttributes}
+         *
+         * @return The {@link EnableRequestAttributes} instance.
+         */
+        @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+        @NonNull
+        public EnableRequestAttributes build() {
+            return new EnableRequestAttributes(this);
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index b97822a..2c141cf 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -162,6 +162,13 @@
 
     /**
      * Bundle key to get the response from
+     * {@link #requestIsEmergencyModeEnabled(Executor, OutcomeReceiver)}.
+     * @hide
+     */
+    public static final String KEY_EMERGENCY_MODE_ENABLED = "emergency_mode_enabled";
+
+    /**
+     * Bundle key to get the response from
      * {@link #requestIsSupported(Executor, OutcomeReceiver)}.
      * @hide
      */
@@ -341,6 +348,13 @@
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     public static final int SATELLITE_RESULT_ILLEGAL_STATE = 23;
 
+    /**
+     * Telephony framework timeout to receive ACK or response from the satellite modem after
+     * sending a request to the modem.
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final int SATELLITE_RESULT_MODEM_TIMEOUT = 24;
+
     /** @hide */
     @IntDef(prefix = {"SATELLITE_RESULT_"}, value = {
             SATELLITE_RESULT_SUCCESS,
@@ -366,7 +380,8 @@
             SATELLITE_RESULT_NOT_SUPPORTED,
             SATELLITE_RESULT_REQUEST_IN_PROGRESS,
             SATELLITE_RESULT_MODEM_BUSY,
-            SATELLITE_RESULT_ILLEGAL_STATE
+            SATELLITE_RESULT_ILLEGAL_STATE,
+            SATELLITE_RESULT_MODEM_TIMEOUT
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SatelliteResult {}
@@ -482,9 +497,7 @@
      * aligned with the satellite, user can send a message and also receive a reply in demo mode.
      * If enableSatellite is {@code false}, enableDemoMode has no impact on the behavior.
      *
-     * @param enableSatellite {@code true} to enable the satellite modem and
-     *                        {@code false} to disable.
-     * @param enableDemoMode {@code true} to enable demo mode and {@code false} to disable.
+     * @param attributes The attributes of the enable request.
      * @param executor The executor on which the error code listener will be called.
      * @param resultListener Listener for the {@link SatelliteResult} result of the operation.
      *
@@ -493,9 +506,10 @@
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    public void requestEnabled(boolean enableSatellite, boolean enableDemoMode,
+    public void requestEnabled(@NonNull EnableRequestAttributes attributes,
             @NonNull @CallbackExecutor Executor executor,
             @SatelliteResult @NonNull Consumer<Integer> resultListener) {
+        Objects.requireNonNull(attributes);
         Objects.requireNonNull(executor);
         Objects.requireNonNull(resultListener);
 
@@ -509,8 +523,8 @@
                                 () -> resultListener.accept(result)));
                     }
                 };
-                telephony.requestSatelliteEnabled(mSubId, enableSatellite, enableDemoMode,
-                        errorCallback);
+                telephony.requestSatelliteEnabled(mSubId, attributes.isEnabled(),
+                        attributes.isDemoMode(), attributes.isEmergencyMode(), errorCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
@@ -631,6 +645,61 @@
     }
 
     /**
+     * Request to get whether the satellite service is enabled for emergency mode.
+     *
+     * @param executor The executor on which the callback will be called.
+     * @param callback The callback object to which the result will be delivered.
+     *                 If the request is successful, {@link OutcomeReceiver#onResult(Object)}
+     *                 will return a {@code boolean} with value {@code true} if satellite is enabled
+     *                 for emergency mode and {@code false} otherwise.
+     *                 If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
+     *                 will return a {@link SatelliteException} with the {@link SatelliteResult}.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public void requestIsEmergencyModeEnabled(@NonNull @CallbackExecutor Executor executor,
+            @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                ResultReceiver receiver = new ResultReceiver(null) {
+                    @Override
+                    protected void onReceiveResult(int resultCode, Bundle resultData) {
+                        if (resultCode == SATELLITE_RESULT_SUCCESS) {
+                            if (resultData.containsKey(KEY_EMERGENCY_MODE_ENABLED)) {
+                                boolean isEmergencyModeEnabled =
+                                        resultData.getBoolean(KEY_EMERGENCY_MODE_ENABLED);
+                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                        callback.onResult(isEmergencyModeEnabled)));
+                            } else {
+                                loge("KEY_EMERGENCY_MODE_ENABLED does not exist.");
+                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                        callback.onError(new SatelliteException(
+                                                SATELLITE_RESULT_REQUEST_FAILED))));
+                            }
+                        } else {
+                            executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                    callback.onError(new SatelliteException(resultCode))));
+                        }
+                    }
+                };
+                telephony.requestIsEmergencyModeEnabled(mSubId, receiver);
+            } else {
+                executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+                        new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+            }
+        } catch (RemoteException ex) {
+            loge("requestIsEmergencyModeEnabled() RemoteException: " + ex);
+            ex.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Request to get whether the satellite service is supported on the device.
      *
      * <p>
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 3f41d56..cc770aa 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -376,4 +376,12 @@
      * @param data with the sim specific configs to be backed up.
      */
     void restoreAllSimSpecificSettingsFromBackup(in byte[] data);
+
+    /**
+     * Set the transfer status of the subscriptionInfo that corresponds to subId.
+     * @param subId The unique SubscriptionInfo key in database.
+     * @param status The transfer status to change. This value must be one of the following.
+     */
+    @EnforcePermission("WRITE_EMBEDDED_SUBSCRIPTIONS")
+    void setTransferStatus(int subId, int status);
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 213fbc5..bd47b1f 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2742,14 +2742,19 @@
      * Request to enable or disable the satellite modem.
      *
      * @param subId The subId of the subscription to enable or disable the satellite modem for.
-     * @param enable True to enable the satellite modem and false to disable.
-     * @param isDemoModeEnabled True if demo mode is enabled and false otherwise.
+     * @param enableSatellite True to enable the satellite modem and false to disable.
+     * @param enableDemoMode True if demo mode is enabled and false otherwise. When
+     *                       disabling satellite, {@code enableDemoMode} is always considered as
+     *                       {@code false} by Telephony.
+     * @param isEmergency {@code true} means satellite is enabled for emergency mode, {@code false}
+     *                    otherwise. When disabling satellite, {@code isEmergency} is always
+     *                    considered as {@code false} by Telephony.
      * @param callback The callback to get the result of the request.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestSatelliteEnabled(int subId, boolean enable, boolean isDemoModeEnabled,
-            in IIntegerConsumer callback);
+    void requestSatelliteEnabled(int subId, boolean enableSatellite, boolean enableDemoMode,
+            boolean isEmergency, in IIntegerConsumer callback);
 
     /**
      * Request to get whether the satellite modem is enabled.
@@ -2775,6 +2780,18 @@
     void requestIsDemoModeEnabled(int subId, in ResultReceiver receiver);
 
     /**
+     * Request to get whether the satellite service is enabled with emergency mode.
+     *
+     * @param subId The subId of the subscription to request whether the satellite demo mode is
+     *              enabled for.
+     * @param receiver Result receiver to get the error code of the request and whether the
+     *                 satellite is enabled with emergency mode.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    void requestIsEmergencyModeEnabled(int subId, in ResultReceiver receiver);
+
+    /**
      * Request to get whether the satellite service is supported on the device.
      *
      * @param subId The subId of the subscription to check whether satellite is supported for.
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index 07f2916..a9ebd5c 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -250,9 +250,6 @@
      */
     public static final int DOMAIN_NON_3GPP_PS = 4;
 
-    /** Key to enable comparison of domain selection results from legacy and new code. */
-    public static final String EXTRA_COMPARE_DOMAIN = "compare_domain";
-
     /** The key to specify the emergency service category */
     public static final String EXTRA_EMERGENCY_SERVICE_CATEGORY = "emergency_service_category";
 }
diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
index 19f1a5b..053bc7d 100644
--- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
@@ -57,4 +57,7 @@
     boolean isSimPortAvailable(int cardId, int portIndex, String callingPackage);
     boolean hasCarrierPrivilegesForPackageOnAnyPhone(String callingPackage);
     boolean isCompatChangeEnabled(String callingPackage, long changeId);
+    void setPsimConversionSupportedCarriers(in int[] carrierIds);
+    boolean isPsimConversionSupported(in int carrierId);
+    long getAvailableMemoryInBytes(int cardId, String callingPackage);
 }
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 73cc2f2..f628af1 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -340,6 +340,14 @@
         wmHelper.StateSyncBuilder().withPipGone().withHomeActivityVisible().waitForAndVerify()
     }
 
+    open fun tapPipToShowMenu(wmHelper: WindowManagerStateHelper) {
+        val windowRect = getWindowRect(wmHelper)
+        uiDevice.click(windowRect.centerX(), windowRect.centerY())
+        // search and interact with the dismiss button
+        val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
+        uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
+    }
+
     /** Close the pip window by pressing the expand button */
     fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) {
         val windowRect = getWindowRect(wmHelper)