Merge "Use the new inspirational text in mode interstitials" into main
diff --git a/aconfig/settings_flag_declarations.aconfig b/aconfig/settings_flag_declarations.aconfig
index 2c8eade..0007bbf 100644
--- a/aconfig/settings_flag_declarations.aconfig
+++ b/aconfig/settings_flag_declarations.aconfig
@@ -49,3 +49,10 @@
     description: "Flag to gate support of injected preference icons containing raw data"
     bug: "351884562"
 }
+
+flag {
+  name: "catalyst_legal_information"
+  namespace: "android_settings"
+  description: "This flag controls the About phone > Legal information page migration"
+  bug: "323791114"
+}
diff --git a/res/drawable/ic_head_tracking.xml b/res/drawable/ic_head_tracking.xml
new file mode 100644
index 0000000..d4a44fd
--- /dev/null
+++ b/res/drawable/ic_head_tracking.xml
@@ -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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M480,520Q414,520 367,473Q320,426 320,360Q320,294 367,247Q414,200 480,200Q546,200 593,247Q640,294 640,360Q640,426 593,473Q546,520 480,520ZM160,840L160,728Q160,695 177,666Q194,637 224,622Q275,596 339,578Q403,560 480,560Q557,560 621,578Q685,596 736,622Q766,637 783,666Q800,695 800,728L800,840L160,840ZM240,760L720,760L720,728Q720,717 714.5,708Q709,699 700,694Q664,676 607.5,658Q551,640 480,640Q409,640 352.5,658Q296,676 260,694Q251,699 245.5,708Q240,717 240,728L240,760ZM480,440Q513,440 536.5,416.5Q560,393 560,360Q560,327 536.5,303.5Q513,280 480,280Q447,280 423.5,303.5Q400,327 400,360Q400,393 423.5,416.5Q447,440 480,440ZM39,200L39,120Q56,120 70,113.5Q84,107 95,96Q106,85 112,71Q118,57 118,40L199,40Q199,73 186.5,102Q174,131 152,153Q130,175 101,187.5Q72,200 39,200ZM39,361L39,281Q90,281 133.5,262Q177,243 209,210Q241,177 260,133.5Q279,90 279,40L360,40Q360,106 335,164.5Q310,223 266,267Q222,311 164,336Q106,361 39,361ZM920,361Q854,361 795.5,336Q737,311 693,267Q649,223 624,164.5Q599,106 599,40L679,40Q679,90 698,133.5Q717,177 750,210Q783,243 826.5,262Q870,281 920,281L920,361ZM920,200Q887,200 858,187.5Q829,175 807,153Q785,131 772.5,102Q760,73 760,40L840,40Q840,57 846.5,71Q853,85 864,96Q875,107 889,113.5Q903,120 920,120L920,200ZM480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360ZM480,760L480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760L480,760L480,760Z" />
+</vector>
diff --git a/res/drawable/ic_spatial_audio.xml b/res/drawable/ic_spatial_audio.xml
new file mode 100644
index 0000000..0ee609a
--- /dev/null
+++ b/res/drawable/ic_spatial_audio.xml
@@ -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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M920,401Q848,401 782,373.5Q716,346 665,295Q614,244 586.5,178Q559,112 559,40L639,40Q639,97 660,148Q681,199 721,239Q761,279 812,300.5Q863,322 920,322L920,401ZM920,242Q879,242 842.5,227Q806,212 777,183Q748,154 733,117.5Q718,81 718,40L797,40Q797,65 806.5,87.5Q816,110 833,127Q850,144 872.5,153Q895,162 920,162L920,242ZM400,520Q334,520 287,473Q240,426 240,360Q240,294 287,247Q334,200 400,200Q466,200 513,247Q560,294 560,360Q560,426 513,473Q466,520 400,520ZM80,840L80,728Q80,695 97,666Q114,637 144,622Q195,596 259,578Q323,560 400,560Q477,560 541,578Q605,596 656,622Q686,637 703,666Q720,695 720,728L720,840L80,840ZM160,760L640,760L640,728Q640,717 634.5,708Q629,699 620,694Q584,676 527.5,658Q471,640 400,640Q329,640 272.5,658Q216,676 180,694Q171,699 165.5,708Q160,717 160,728L160,760ZM400,440Q433,440 456.5,416.5Q480,393 480,360Q480,327 456.5,303.5Q433,280 400,280Q367,280 343.5,303.5Q320,327 320,360Q320,393 343.5,416.5Q367,440 400,440ZM400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360ZM400,760L400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760L400,760L400,760Z" />
+</vector>
diff --git a/res/drawable/ic_spatial_audio_off.xml b/res/drawable/ic_spatial_audio_off.xml
new file mode 100644
index 0000000..c7d3272
--- /dev/null
+++ b/res/drawable/ic_spatial_audio_off.xml
@@ -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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M750,550L806,494Q766,454 743.5,402.5Q721,351 721,294Q721,237 743.5,186Q766,135 806,95L750,37Q699,88 670,155Q641,222 641,294Q641,366 670,432.5Q699,499 750,550ZM862,436L918,380Q901,363 891,341Q881,319 881,294Q881,269 891,247Q901,225 918,208L862,151Q833,180 817,216Q801,252 801,293Q801,334 817,371Q833,408 862,436ZM400,520Q334,520 287,473Q240,426 240,360Q240,294 287,247Q334,200 400,200Q466,200 513,247Q560,294 560,360Q560,426 513,473Q466,520 400,520ZM80,840L80,728Q80,695 97,666Q114,637 144,622Q195,596 259,578Q323,560 400,560Q477,560 541,578Q605,596 656,622Q686,637 703,666Q720,695 720,728L720,840L80,840ZM160,760L640,760L640,728Q640,717 634.5,708Q629,699 620,694Q584,676 527.5,658Q471,640 400,640Q329,640 272.5,658Q216,676 180,694Q171,699 165.5,708Q160,717 160,728L160,760ZM400,440Q433,440 456.5,416.5Q480,393 480,360Q480,327 456.5,303.5Q433,280 400,280Q367,280 343.5,303.5Q320,327 320,360Q320,393 343.5,416.5Q367,440 400,440ZM400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360ZM400,760L400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760L400,760L400,760Z" />
+</vector>
diff --git a/res/layout/preference_circular_icons.xml b/res/layout/preference_circular_icons.xml
index 863d228..e1d7cfe 100644
--- a/res/layout/preference_circular_icons.xml
+++ b/res/layout/preference_circular_icons.xml
@@ -58,8 +58,8 @@
             android:lineBreakWordStyle="phrase"
             android:maxLines="10"/>
 
-        <!-- Circular icons (32dp) will be ImageViews under this LinearLayout -->
-        <LinearLayout
+        <!-- Circular icons (32dp) will be ImageViews under this container -->
+        <com.android.settings.notification.modes.CircularIconsView
             android:id="@+id/circles_container"
             android:importantForAccessibility="noHideDescendants"
             android:orientation="horizontal"
diff --git a/res/layout/preference_spinner.xml b/res/layout/preference_spinner.xml
deleted file mode 100644
index 4129303..0000000
--- a/res/layout/preference_spinner.xml
+++ /dev/null
@@ -1,24 +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.
-  -->
-
-<Spinner
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/spinner"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_marginStart="24dp"
-    android:layout_marginTop="8dp"
-    android:theme="@style/Widget.PopupWindow.Settings" />
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index f8337b3..8cbf5dc 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -1554,7 +1554,7 @@
         <item>@*android:drawable/ic_zen_mode_type_theater</item> <!-- Film reel -->
         <item>@*android:drawable/ic_zen_mode_icon_book</item>
         <!-- Wellbeing -->
-        <item>@*android:drawable/ic_zen_mode_type_unknown</item> <!-- Lotus flower -->
+        <item>@*android:drawable/ic_zen_mode_icon_lotus_flower</item>
         <item>@*android:drawable/ic_zen_mode_type_immersive</item>
         <item>@*android:drawable/ic_zen_mode_icon_headphones</item>
         <item>@*android:drawable/ic_zen_mode_icon_tv</item>
@@ -1565,10 +1565,10 @@
         <item>@*android:drawable/ic_zen_mode_icon_fork_and_knife</item>
         <item>@*android:drawable/ic_zen_mode_icon_shopping_cart</item>
         <item>@*android:drawable/ic_zen_mode_icon_child</item>
-        <item>@*android:drawable/ic_zen_mode_icon_rabbit</item>
         <item>@*android:drawable/ic_zen_mode_icon_animal_paw</item>
         <!-- Generic / abstract -->
-        <item>@*android:drawable/ic_zen_mode_type_managed</item> <!-- Account -->
+        <item>@*android:drawable/ic_zen_mode_type_unknown</item> <!-- Star badge -->
+        <item>@*android:drawable/ic_zen_mode_type_managed</item> <!-- Two people / Supervisor -->
         <item>@*android:drawable/ic_zen_mode_type_other</item> <!-- Star -->
         <item>@*android:drawable/ic_zen_mode_icon_heart</item>
         <item>@*android:drawable/ic_zen_mode_icon_house</item>
@@ -1616,10 +1616,10 @@
         <item>Fork and knife</item>
         <item>Shopping cart</item>
         <item>Child</item>
-        <item>Rabbit</item>
         <item>Animal paw</item>
         <!-- Generic / abstract -->
-        <item>Supervisor</item>
+        <item>Star badge</item>
+        <item>Two people</item>
         <item>Star</item>
         <item>Heart</item>
         <item>House</item>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 20e4ce8..939befe 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -7946,6 +7946,18 @@
     <!-- Sound: Footer hyperlink text to launch the Connected devices settings page. [CHAR LIMIT=NONE]-->
     <string name="spatial_audio_footer_learn_more_text">Connected devices settings</string>
 
+    <!-- Bluetooth device details: spatial audio multi-toggle title. [CHAR LIMIT=20]-->
+    <string name="spatial_audio_multi_toggle_title">Spatial Audio</string>
+
+    <!-- Bluetooth device details: spatial audio is off. [CHAR LIMIT=20]-->
+    <string name="spatial_audio_multi_toggle_off">Off</string>
+
+    <!-- Bluetooth device details: spatial audio is on. [CHAR LIMIT=20]-->
+    <string name="spatial_audio_multi_toggle_on">Off</string>
+
+    <!-- Bluetooth device details: head tracking is on. [CHAR LIMIT=20]-->
+    <string name="spatial_audio_multi_toggle_head_tracking_on">Off</string>
+
     <!-- Zen Modes: Summary for the Do not Disturb option that describes how many automatic rules (schedules) are enabled [CHAR LIMIT=NONE]-->
     <string name="zen_mode_settings_schedules_summary">
         {count, plural,
diff --git a/res/xml/power_usage_advanced.xml b/res/xml/power_usage_advanced.xml
index 816e197..f2c3d87 100644
--- a/res/xml/power_usage_advanced.xml
+++ b/res/xml/power_usage_advanced.xml
@@ -57,7 +57,7 @@
             "com.android.settings.fuelgauge.batteryusage.BatteryUsageBreakdownController"
         settings:isPreferenceVisible="false">
 
-        <com.android.settings.fuelgauge.batteryusage.SpinnerPreference
+        <com.android.settingslib.widget.SettingsSpinnerPreference
             android:key="battery_usage_spinner"
             settings:isPreferenceVisible="false" />
 
diff --git a/src/com/android/settings/SettingsApplication.java b/src/com/android/settings/SettingsApplication.java
index 5b052f2..d208fdf 100644
--- a/src/com/android/settings/SettingsApplication.java
+++ b/src/com/android/settings/SettingsApplication.java
@@ -18,7 +18,9 @@
 
 import android.app.Application;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.database.ContentObserver;
+import android.hardware.fingerprint.FingerprintManager;
 import android.net.Uri;
 import android.provider.Settings;
 import android.util.FeatureFlagUtils;
@@ -74,9 +76,6 @@
 
         // Set Spa environment.
         setSpaEnvironment();
-        if (Flags.fingerprintV2Enrollment()) {
-            mBiometricsEnvironment = new BiometricsEnvironment(this);
-        }
 
         if (ActivityEmbeddingUtils.isSettingsSplitEnabled(this)
                 && FeatureFlagUtils.isEnabled(this,
@@ -120,7 +119,20 @@
 
     @Nullable
     public BiometricsEnvironment getBiometricEnvironment() {
-        return mBiometricsEnvironment;
+        if (Flags.fingerprintV2Enrollment()) {
+            if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+                final FingerprintManager fpm = getSystemService(FingerprintManager.class);
+                if (mBiometricsEnvironment == null) {
+                    mBiometricsEnvironment = new BiometricsEnvironment(this, fpm);
+                }
+                return  mBiometricsEnvironment;
+
+            } else {
+                return null;
+            }
+
+        }
+        return null;
     }
 
     @Override
diff --git a/src/com/android/settings/accessibility/DaltonizerPreferenceUtil.java b/src/com/android/settings/accessibility/DaltonizerPreferenceUtil.java
new file mode 100644
index 0000000..459dbb9
--- /dev/null
+++ b/src/com/android/settings/accessibility/DaltonizerPreferenceUtil.java
@@ -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.settings.accessibility;
+
+import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
+import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+import android.view.accessibility.AccessibilityManager;
+
+import com.google.common.primitives.Ints;
+
+/**
+ * Utility class for retrieving accessibility daltonizer related values in secure settings.
+ */
+public class DaltonizerPreferenceUtil {
+
+    /**
+     * Return the daltonizer display mode stored in
+     * {@link Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER}.
+     * By default it returns {@link DALTONIZER_CORRECT_DEUTERANOMALY}.
+     */
+    public static int getSecureAccessibilityDaltonizerValue(ContentResolver resolver) {
+        final String daltonizerStringValue = Settings.Secure.getString(
+                resolver, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER);
+        if (daltonizerStringValue == null) {
+            return AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY;
+        }
+        final Integer daltonizerIntValue = Ints.tryParse(daltonizerStringValue);
+        return daltonizerIntValue == null ? AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY
+                : daltonizerIntValue;
+    }
+
+    /**
+     * Returns the daltonizer enabled value in
+     * {@link Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED}.
+     * By default it returns false.
+     */
+    public static boolean isSecureAccessibilityDaltonizerEnabled(ContentResolver resolver) {
+        return Settings.Secure.getInt(
+                resolver,
+                Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
+                OFF) == ON;
+    }
+}
diff --git a/src/com/android/settings/accessibility/DaltonizerRadioButtonPreferenceController.java b/src/com/android/settings/accessibility/DaltonizerRadioButtonPreferenceController.java
index 296536c..5a8c710 100644
--- a/src/com/android/settings/accessibility/DaltonizerRadioButtonPreferenceController.java
+++ b/src/com/android/settings/accessibility/DaltonizerRadioButtonPreferenceController.java
@@ -24,7 +24,6 @@
 import android.os.Looper;
 import android.provider.Settings;
 import android.view.View;
-import android.view.accessibility.AccessibilityManager;
 
 import androidx.annotation.NonNull;
 import androidx.lifecycle.DefaultLifecycleObserver;
@@ -36,8 +35,6 @@
 import com.android.settings.core.BasePreferenceController;
 import com.android.settingslib.widget.SelectorWithWidgetPreference;
 
-import com.google.common.primitives.Ints;
-
 import java.util.HashMap;
 import java.util.Map;
 
@@ -70,17 +67,6 @@
         };
     }
 
-    protected static int getSecureAccessibilityDaltonizerValue(ContentResolver resolver) {
-        final String daltonizerStringValue = Settings.Secure.getString(
-                resolver, DALTONIZER_TYPE_SETTINGS_KEY);
-        if (daltonizerStringValue == null) {
-            return AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY;
-        }
-        final Integer daltonizerIntValue = Ints.tryParse(daltonizerStringValue);
-        return daltonizerIntValue == null ? AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY
-                : daltonizerIntValue;
-    }
-
     private Map<String, Integer> getDaltonizerValueToKeyMap() {
         if (mAccessibilityDaltonizerKeyToValueMap.isEmpty()) {
 
@@ -123,7 +109,8 @@
     }
 
     private int getAccessibilityDaltonizerValue() {
-        final int daltonizerValue = getSecureAccessibilityDaltonizerValue(mContentResolver);
+        final int daltonizerValue =
+                DaltonizerPreferenceUtil.getSecureAccessibilityDaltonizerValue(mContentResolver);
         return daltonizerValue;
     }
 
diff --git a/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java b/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java
index 2997185..6a9977a 100644
--- a/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java
+++ b/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java
@@ -15,6 +15,9 @@
  */
 package com.android.settings.accessibility;
 
+import static com.android.settings.accessibility.DaltonizerPreferenceUtil.isSecureAccessibilityDaltonizerEnabled;
+import static com.android.settings.accessibility.DaltonizerPreferenceUtil.getSecureAccessibilityDaltonizerValue;
+
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
@@ -158,14 +161,11 @@
     }
 
     private boolean shouldSeekBarEnabled() {
-        int enabled = Settings.Secure.getInt(
-                mContentResolver, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0);
-        int mode = Settings.Secure.getInt(
-                mContentResolver, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, -1);
+        boolean enabled = isSecureAccessibilityDaltonizerEnabled(mContentResolver);
+        int mode = getSecureAccessibilityDaltonizerValue(mContentResolver);
 
-        // enabled == 0 is disabled and also default.
         // mode == 0 is gray scale where saturation level isn't applicable.
         // mode == -1 is disabled and also default.
-        return enabled != 0 && mode != -1 && mode != 0;
+        return enabled && mode != -1 && mode != 0;
     }
 }
diff --git a/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceController.java b/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceController.java
index 833638b..818eb5e 100644
--- a/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceController.java
+++ b/src/com/android/settings/accessibility/KeyboardVibrationTogglePreferenceController.java
@@ -110,7 +110,7 @@
     @Override
     public int getAvailabilityStatus() {
         if (mContext.getResources().getBoolean(
-                        com.android.internal.R.bool.config_keyboardVibrationSettingsSupported)) {
+                com.android.internal.R.bool.config_keyboardVibrationSettingsSupported)) {
             return AVAILABLE;
         }
         return UNSUPPORTED_ON_DEVICE;
@@ -128,15 +128,9 @@
         mMetricsFeatureProvider.action(mContext,
                 SettingsEnums.ACTION_KEYBOARD_VIBRATION_CHANGED, isChecked);
         if (success && isChecked) {
-            // Play the preview vibration effect when the toggle is on.
-            final VibrationAttributes touchAttrs =
-                    VibrationPreferenceConfig.createPreviewVibrationAttributes(
-                            VibrationAttributes.USAGE_TOUCH);
-            final VibrationAttributes keyboardAttrs =
-                    new VibrationAttributes.Builder(touchAttrs)
-                            .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
-                            .build();
-            VibrationPreferenceConfig.playVibrationPreview(mVibrator, keyboardAttrs);
+            // Play the preview vibration effect for the IME feedback when the toggle is on.
+            VibrationPreferenceConfig.playVibrationPreview(
+                    mVibrator, VibrationAttributes.USAGE_IME_FEEDBACK);
         }
         return true;
     }
diff --git a/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java
index 52f1695..86ddd71 100644
--- a/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java
@@ -21,6 +21,7 @@
 import static com.android.settings.accessibility.AccessibilityStatsLogUtils.logAccessibilityServiceEnabled;
 import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
 import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
+import static com.android.settings.accessibility.DaltonizerPreferenceUtil.isSecureAccessibilityDaltonizerEnabled;
 
 import android.app.settings.SettingsEnums;
 import android.content.ComponentName;
@@ -145,7 +146,8 @@
 
     @Override
     protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
-        final boolean isEnabled = Settings.Secure.getInt(getContentResolver(), ENABLED, OFF) == ON;
+        final boolean isEnabled =
+                isSecureAccessibilityDaltonizerEnabled(getContentResolver());
         if (enabled == isEnabled) {
             return;
         }
diff --git a/src/com/android/settings/accessibility/VibrationPreferenceConfig.java b/src/com/android/settings/accessibility/VibrationPreferenceConfig.java
index a304862..ec1fab1 100644
--- a/src/com/android/settings/accessibility/VibrationPreferenceConfig.java
+++ b/src/com/android/settings/accessibility/VibrationPreferenceConfig.java
@@ -68,19 +68,8 @@
     /** Play a vibration effect with intensity just selected by the user. */
     public static void playVibrationPreview(Vibrator vibrator,
             @VibrationAttributes.Usage int vibrationUsage) {
-        playVibrationPreview(vibrator, createPreviewVibrationAttributes(vibrationUsage));
-    }
-
-    /**
-     * Play a vibration effect with intensity just selected by the user.
-     *
-     * @param vibrator The {@link Vibrator} used to play the vibration.
-     * @param vibrationAttributes The {@link VibrationAttributes} to indicate the
-     *        vibration information.
-     */
-    public static void playVibrationPreview(Vibrator vibrator,
-            VibrationAttributes vibrationAttributes) {
-        vibrator.vibrate(PREVIEW_VIBRATION_EFFECT, vibrationAttributes);
+        vibrator.vibrate(PREVIEW_VIBRATION_EFFECT,
+                createPreviewVibrationAttributes(vibrationUsage));
     }
 
     public VibrationPreferenceConfig(Context context, String settingKey,
diff --git a/src/com/android/settings/biometrics/fingerprint2/BiometricsEnvironment.kt b/src/com/android/settings/biometrics/fingerprint2/BiometricsEnvironment.kt
index 9bc920a..e3233ed 100644
--- a/src/com/android/settings/biometrics/fingerprint2/BiometricsEnvironment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/BiometricsEnvironment.kt
@@ -16,12 +16,9 @@
 
 package com.android.settings.biometrics.fingerprint2
 
-import android.content.pm.PackageManager
 import android.hardware.fingerprint.FingerprintManager
-import android.os.ServiceManager.ServiceNotFoundException
 import android.view.MotionEvent
 import android.view.accessibility.AccessibilityManager
-import androidx.fragment.app.FragmentActivity
 import androidx.lifecycle.ViewModelStore
 import androidx.lifecycle.ViewModelStoreOwner
 import com.android.internal.widget.LockPatternUtils
@@ -29,33 +26,47 @@
 import com.android.settings.biometrics.GatekeeperPasswordProvider
 import com.android.settings.biometrics.fingerprint2.data.repository.DebuggingRepository
 import com.android.settings.biometrics.fingerprint2.data.repository.DebuggingRepositoryImpl
+import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintEnrollmentRepositoryImpl
 import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
 import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepositoryImpl
+import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSettingsRepositoryImpl
+import com.android.settings.biometrics.fingerprint2.data.repository.UserRepoImpl
 import com.android.settings.biometrics.fingerprint2.debug.data.repository.UdfpsEnrollDebugRepositoryImpl
 import com.android.settings.biometrics.fingerprint2.debug.domain.interactor.DebugTouchEventInteractorImpl
 import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractor
 import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractorImpl
+import com.android.settings.biometrics.fingerprint2.domain.interactor.AuthenticateInteractorImpl
+import com.android.settings.biometrics.fingerprint2.domain.interactor.CanEnrollFingerprintsInteractorImpl
 import com.android.settings.biometrics.fingerprint2.domain.interactor.DebuggingInteractor
 import com.android.settings.biometrics.fingerprint2.domain.interactor.DebuggingInteractorImpl
 import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractor
 import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractorImpl
+import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollFingerprintInteractorImpl
 import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractor
 import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractorImpl
-import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractor
-import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractorImpl
-import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
+import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrolledFingerprintsInteractorImpl
 import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintSensorInteractor
 import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintSensorInteractorImpl
 import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractor
 import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractorImpl
+import com.android.settings.biometrics.fingerprint2.domain.interactor.GenerateChallengeInteractorImpl
 import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
 import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractorImpl
+import com.android.settings.biometrics.fingerprint2.domain.interactor.RemoveFingerprintsInteractorImpl
+import com.android.settings.biometrics.fingerprint2.domain.interactor.RenameFingerprintsInteractorImpl
+import com.android.settings.biometrics.fingerprint2.domain.interactor.SensorInteractorImpl
 import com.android.settings.biometrics.fingerprint2.domain.interactor.TouchEventInteractor
 import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractor
 import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractorImpl
 import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor
 import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractorImpl
-import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.AuthenitcateInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrollFingerprintInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RemoveFingerprintInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RenameFingerprintInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
 import com.android.settings.biometrics.fingerprint2.lib.model.Settings
 import java.util.concurrent.Executors
 import kotlinx.coroutines.MainScope
@@ -70,43 +81,53 @@
  * This code is instantiated within the [SettingsApplication], all repos should be private &
  * immutable and all interactors should public and immutable
  */
-class BiometricsEnvironment(context: SettingsApplication) : ViewModelStoreOwner {
-
+class BiometricsEnvironment(
+  val context: SettingsApplication,
+  private val fingerprintManager: FingerprintManager,
+) : ViewModelStoreOwner {
   private val executorService = Executors.newSingleThreadExecutor()
   private val backgroundDispatcher = executorService.asCoroutineDispatcher()
   private val applicationScope = MainScope()
   private val gateKeeperPasswordProvider = GatekeeperPasswordProvider(LockPatternUtils(context))
-  private val fingerprintManager = try {
-    if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
-      context.getSystemService(FragmentActivity.FINGERPRINT_SERVICE) as FingerprintManager?
-    } else {
-      null
-    }
-  } catch (exception: ServiceNotFoundException){
-    null
-  }
 
+  private val userRepo = UserRepoImpl(context.userId)
+  private val fingerprintSettingsRepository =
+    FingerprintSettingsRepositoryImpl(
+      context.resources.getInteger(
+        com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser
+      )
+    )
+  private val fingerprintEnrollmentRepository =
+    FingerprintEnrollmentRepositoryImpl(fingerprintManager, userRepo, fingerprintSettingsRepository,
+      backgroundDispatcher, applicationScope)
   private val fingerprintSensorRepository: FingerprintSensorRepository =
     FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, applicationScope)
   private val debuggingRepository: DebuggingRepository = DebuggingRepositoryImpl()
   private val udfpsDebugRepo = UdfpsEnrollDebugRepositoryImpl()
 
-  /** For now, interactors are public to those with access to the [BiometricsEnvironment] class */
-  val fingerprintEnrollInteractor: FingerprintEnrollInteractor by lazy {
-    FingerprintEnrollInteractorImpl(context, fingerprintManager, Settings)
-  }
+  fun createSensorPropertiesInteractor(): SensorInteractor =
+    SensorInteractorImpl(fingerprintSensorRepository)
 
-  /** [FingerprintManagerInteractor] to be used to construct view models */
-  val fingerprintManagerInteractor: FingerprintManagerInteractor by lazy {
-    FingerprintManagerInteractorImpl(
-      context,
-      backgroundDispatcher,
-      fingerprintManager,
-      fingerprintSensorRepository,
-      gateKeeperPasswordProvider,
-      fingerprintEnrollInteractor,
-    )
-  }
+  fun createCanEnrollFingerprintsInteractor(): CanEnrollFingerprintsInteractor =
+    CanEnrollFingerprintsInteractorImpl(fingerprintEnrollmentRepository)
+
+  fun createGenerateChallengeInteractor(): GenerateChallengeInteractor =
+    GenerateChallengeInteractorImpl(fingerprintManager, context.userId, gateKeeperPasswordProvider)
+
+  fun createFingerprintEnrollInteractor(): EnrollFingerprintInteractor =
+    EnrollFingerprintInteractorImpl(context.userId, fingerprintManager, Settings)
+
+  fun createFingerprintsEnrolledInteractor(): EnrolledFingerprintsInteractorImpl =
+    EnrolledFingerprintsInteractorImpl(fingerprintManager, context.userId)
+
+  fun createAuthenticateInteractor(): AuthenitcateInteractor =
+    AuthenticateInteractorImpl(fingerprintManager, context.userId)
+
+  fun createRemoveFingerprintInteractor(): RemoveFingerprintInteractor =
+    RemoveFingerprintsInteractorImpl(fingerprintManager, context.userId)
+
+  fun createRenameFingerprintInteractor(): RenameFingerprintInteractor =
+    RenameFingerprintsInteractorImpl(fingerprintManager, context.userId, backgroundDispatcher)
 
   val accessibilityInteractor: AccessibilityInteractor by lazy {
     AccessibilityInteractorImpl(
diff --git a/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintEnrollmentRepo.kt b/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintEnrollmentRepo.kt
new file mode 100644
index 0000000..22904e9
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintEnrollmentRepo.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.data.repository
+
+import android.hardware.biometrics.BiometricStateListener
+import android.hardware.fingerprint.FingerprintManager
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/** Repository that contains information about fingerprint enrollments. */
+interface FingerprintEnrollmentRepository {
+  /** The current enrollments of the user */
+  val currentEnrollments: Flow<List<FingerprintData>?>
+
+  /** Indicates if a user can enroll another fingerprint */
+  val canEnrollUser: Flow<Boolean>
+
+  fun maxFingerprintsEnrollable(): Int
+}
+
+class FingerprintEnrollmentRepositoryImpl(
+  fingerprintManager: FingerprintManager,
+  userRepo: UserRepo,
+  private val settingsRepository: FingerprintSettingsRepository,
+  backgroundDispatcher: CoroutineDispatcher,
+  applicationScope: CoroutineScope,
+) : FingerprintEnrollmentRepository {
+
+  private val enrollmentChangedFlow: Flow<Int?> =
+    callbackFlow {
+        val callback =
+          object : BiometricStateListener() {
+            override fun onEnrollmentsChanged(userId: Int, sensorId: Int, hasEnrollments: Boolean) {
+              trySend(userId)
+            }
+          }
+        withContext(backgroundDispatcher) {
+          fingerprintManager.registerBiometricStateListener(callback)
+        }
+        awaitClose {
+          // no way to unregister
+        }
+      }
+      .stateIn(applicationScope, started = SharingStarted.Eagerly, initialValue = null)
+
+  override val currentEnrollments: Flow<List<FingerprintData>> =
+    userRepo.currentUser
+      .distinctUntilChanged()
+      .flatMapLatest { currentUser ->
+        enrollmentChangedFlow.map { enrollmentChanged ->
+          if (enrollmentChanged == null || enrollmentChanged == currentUser) {
+            fingerprintManager
+              .getEnrolledFingerprints(currentUser)
+              ?.map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) }
+              ?.toList()
+          } else {
+            null
+          }
+        }
+      }
+      .filterNotNull()
+      .flowOn(backgroundDispatcher)
+
+  override val canEnrollUser: Flow<Boolean> =
+    currentEnrollments.map {
+      it?.size?.let { it < settingsRepository.maxEnrollableFingerprints() } ?: false
+    }
+
+  override fun maxFingerprintsEnrollable(): Int {
+    return settingsRepository.maxEnrollableFingerprints()
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintSensorRepository.kt b/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintSensorRepository.kt
index 516549e..1cca532 100644
--- a/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintSensorRepository.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintSensorRepository.kt
@@ -31,6 +31,8 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.flow.transform
 import kotlinx.coroutines.withContext
@@ -43,10 +45,13 @@
 interface FingerprintSensorRepository {
   /** Get the [FingerprintSensor] */
   val fingerprintSensor: Flow<FingerprintSensor>
+
+  /** Indicates if this device supports the side fingerprint sensor */
+  val hasSideFps: Flow<Boolean>
 }
 
 class FingerprintSensorRepositoryImpl(
-  fingerprintManager: FingerprintManager?,
+  private val fingerprintManager: FingerprintManager,
   backgroundDispatcher: CoroutineDispatcher,
   activityScope: CoroutineScope,
 ) : FingerprintSensorRepository {
@@ -66,7 +71,7 @@
             }
           }
         withContext(backgroundDispatcher) {
-          fingerprintManager?.addAuthenticatorsRegisteredCallback(callback)
+          fingerprintManager.addAuthenticatorsRegisteredCallback(callback)
         }
         awaitClose {}
       }
@@ -75,6 +80,9 @@
   override val fingerprintSensor: Flow<FingerprintSensor> =
     fingerprintPropsInternal.transform { emit(it.toFingerprintSensor()) }
 
+  override val hasSideFps: Flow<Boolean> =
+    fingerprintSensor.flatMapLatest { flow { emit(fingerprintManager.isPowerbuttonFps()) } }
+
   companion object {
 
     private val DEFAULT_PROPS =
diff --git a/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintSettingsRepository.kt b/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintSettingsRepository.kt
new file mode 100644
index 0000000..fe6676c
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/data/repository/FingerprintSettingsRepository.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.settings.biometrics.fingerprint2.data.repository
+
+/**
+ * Repository for storing metadata about fingerprint enrollments.
+ */
+interface FingerprintSettingsRepository {
+    /**
+     * Indicates the maximum number of fingerprints enrollable
+     */
+    fun maxEnrollableFingerprints(): Int
+}
+
+class FingerprintSettingsRepositoryImpl(private val maxFingerprintsEnrollable: Int) :
+    FingerprintSettingsRepository {
+    override fun maxEnrollableFingerprints() = maxFingerprintsEnrollable
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/data/repository/SimulatedTouchEventsRepository.kt b/src/com/android/settings/biometrics/fingerprint2/data/repository/SimulatedTouchEventsRepository.kt
index 3c355e7..9b7f280 100644
--- a/src/com/android/settings/biometrics/fingerprint2/data/repository/SimulatedTouchEventsRepository.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/data/repository/SimulatedTouchEventsRepository.kt
@@ -16,7 +16,6 @@
 
 package com.android.settings.biometrics.fingerprint2.data.repository
 
-import android.graphics.Point
 import android.view.MotionEvent
 import kotlinx.coroutines.flow.Flow
 
diff --git a/src/com/android/settings/biometrics/fingerprint2/data/repository/UserRepo.kt b/src/com/android/settings/biometrics/fingerprint2/data/repository/UserRepo.kt
new file mode 100644
index 0000000..720e778
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/data/repository/UserRepo.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/**
+ * A repository responsible for indicating the current user.
+ */
+interface UserRepo {
+    /**
+     * This flow indicates the current user.
+     */
+    val currentUser: Flow<Int>
+}
+
+class UserRepoImpl(val currUser: Int): UserRepo {
+    override val currentUser: Flow<Int> = flowOf(currUser)
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/debug/data/repository/UdfpsEnrollDebugRepository.kt b/src/com/android/settings/biometrics/fingerprint2/debug/data/repository/UdfpsEnrollDebugRepository.kt
index 0c3152a..bc48f07 100644
--- a/src/com/android/settings/biometrics/fingerprint2/debug/data/repository/UdfpsEnrollDebugRepository.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/debug/data/repository/UdfpsEnrollDebugRepository.kt
@@ -97,6 +97,8 @@
   }
 
   override val fingerprintSensor: Flow<FingerprintSensor> = flowOf(sensorProps)
+  override val hasSideFps: Flow<Boolean>
+    get() = flowOf(false)
 
   private fun pointToLeftOfSensor(sensorLocation: Rect): MotionEvent =
     MotionEvent.obtain(
diff --git a/src/com/android/settings/biometrics/fingerprint2/debug/domain/interactor/DebugTouchEventInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/debug/domain/interactor/DebugTouchEventInteractorImpl.kt
index fff6b66..f6627e1 100644
--- a/src/com/android/settings/biometrics/fingerprint2/debug/domain/interactor/DebugTouchEventInteractorImpl.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/debug/domain/interactor/DebugTouchEventInteractorImpl.kt
@@ -26,4 +26,4 @@
 ) : TouchEventInteractor {
   override val touchEvent: Flow<MotionEvent> =
     udfpsSimulatedTouchEventsRepository.touchExplorationDebug
-}
\ No newline at end of file
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/AuthenticateInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/AuthenticateInteractorImpl.kt
new file mode 100644
index 0000000..df93092
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/AuthenticateInteractorImpl.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.domain.interactor
+
+import android.hardware.fingerprint.FingerprintManager
+import android.os.CancellationSignal
+import android.util.Log
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.AuthenitcateInteractor
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
+import kotlin.coroutines.resume
+import kotlinx.coroutines.CancellableContinuation
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+class AuthenticateInteractorImpl(
+  private val fingerprintManager: FingerprintManager,
+  private val userId: Int,
+) : AuthenitcateInteractor {
+
+  override suspend fun authenticate(): FingerprintAuthAttemptModel =
+    suspendCancellableCoroutine { c: CancellableContinuation<FingerprintAuthAttemptModel> ->
+      val authenticationCallback =
+        object : FingerprintManager.AuthenticationCallback() {
+
+          override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
+            super.onAuthenticationError(errorCode, errString)
+            if (c.isCompleted) {
+              Log.d(TAG, "framework sent down onAuthError after finish")
+              return
+            }
+            c.resume(FingerprintAuthAttemptModel.Error(errorCode, errString.toString()))
+          }
+
+          override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
+            super.onAuthenticationSucceeded(result)
+            if (c.isCompleted) {
+              Log.d(TAG, "framework sent down onAuthError after finish")
+              return
+            }
+            c.resume(FingerprintAuthAttemptModel.Success(result.fingerprint?.biometricId ?: -1))
+          }
+        }
+
+      val cancellationSignal = CancellationSignal()
+      c.invokeOnCancellation { cancellationSignal.cancel() }
+      fingerprintManager.authenticate(
+        null,
+        cancellationSignal,
+        authenticationCallback,
+        null,
+        userId,
+      )
+    }
+
+  companion object {
+    private const val TAG = "AuthenticateInteractor"
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/CanEnrollFingerprintsInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/CanEnrollFingerprintsInteractorImpl.kt
new file mode 100644
index 0000000..caeea4e
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/CanEnrollFingerprintsInteractorImpl.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.settings.biometrics.fingerprint2.domain.interactor
+
+import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintEnrollmentRepository
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor
+import kotlinx.coroutines.flow.Flow
+
+class CanEnrollFingerprintsInteractorImpl(
+  val fingerprintEnrollmentRepository: FingerprintEnrollmentRepository
+) : CanEnrollFingerprintsInteractor {
+  override val canEnrollFingerprints: Flow<Boolean> = fingerprintEnrollmentRepository.canEnrollUser
+  /** Indicates the maximum fingerprints enrollable for a given user */
+  override fun maxFingerprintsEnrollable(): Int {
+    return fingerprintEnrollmentRepository.maxFingerprintsEnrollable()
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/EnrollFingerprintInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/EnrollFingerprintInteractorImpl.kt
new file mode 100644
index 0000000..3e14a64
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/EnrollFingerprintInteractorImpl.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.domain.interactor
+
+import android.hardware.fingerprint.FingerprintEnrollOptions
+import android.hardware.fingerprint.FingerprintManager
+import android.os.CancellationSignal
+import android.util.Log
+import com.android.settings.biometrics.fingerprint2.conversion.Util.toEnrollError
+import com.android.settings.biometrics.fingerprint2.conversion.Util.toOriginalReason
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrollFingerprintInteractor
+import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow
+import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.channels.onFailure
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.update
+
+class EnrollFingerprintInteractorImpl(
+  private val userId: Int,
+  private val fingerprintManager: FingerprintManager,
+  private val fingerprintFlow: FingerprintFlow,
+) : EnrollFingerprintInteractor {
+  private val enrollRequestOutstanding = MutableStateFlow(false)
+
+  override suspend fun enroll(
+    hardwareAuthToken: ByteArray?,
+    enrollReason: EnrollReason,
+    fingerprintEnrollOptions: FingerprintEnrollOptions,
+  ): Flow<FingerEnrollState> = callbackFlow {
+    // TODO (b/308456120) Improve this logic
+    if (enrollRequestOutstanding.value) {
+      Log.d(TAG, "Outstanding enroll request, waiting 150ms")
+      delay(150)
+      if (enrollRequestOutstanding.value) {
+        Log.e(TAG, "Request still present, continuing")
+      }
+    }
+
+    enrollRequestOutstanding.update { true }
+
+    var streamEnded = false
+    var totalSteps: Int? = null
+    val enrollmentCallback =
+      object : FingerprintManager.EnrollmentCallback() {
+        override fun onEnrollmentProgress(remaining: Int) {
+          // This is sort of an implementation detail, but unfortunately the API isn't
+          // very expressive. If anything we should look at changing the FingerprintManager API.
+          if (totalSteps == null) {
+            totalSteps = remaining + 1
+          }
+
+          trySend(FingerEnrollState.EnrollProgress(remaining, totalSteps!!)).onFailure { error ->
+            Log.d(TAG, "onEnrollmentProgress($remaining) failed to send, due to $error")
+          }
+
+          if (remaining == 0) {
+            streamEnded = true
+            enrollRequestOutstanding.update { false }
+          }
+        }
+
+        override fun onEnrollmentHelp(helpMsgId: Int, helpString: CharSequence?) {
+          trySend(FingerEnrollState.EnrollHelp(helpMsgId, helpString.toString())).onFailure { error
+            ->
+            Log.d(TAG, "onEnrollmentHelp failed to send, due to $error")
+          }
+        }
+
+        override fun onEnrollmentError(errMsgId: Int, errString: CharSequence?) {
+          trySend(errMsgId.toEnrollError(fingerprintFlow == SetupWizard)).onFailure { error ->
+            Log.d(TAG, "onEnrollmentError failed to send, due to $error")
+          }
+          Log.d(TAG, "onEnrollmentError($errMsgId)")
+          streamEnded = true
+          enrollRequestOutstanding.update { false }
+        }
+
+        override fun onUdfpsPointerDown(sensorId: Int) {
+          trySend(FingerEnrollState.PointerDown(sensorId)).onFailure { error ->
+            Log.d(TAG, "onUdfpsPointerDown failed to send, due to $error")
+          }
+        }
+
+        override fun onUdfpsPointerUp(sensorId: Int) {
+          trySend(FingerEnrollState.PointerUp(sensorId)).onFailure { error ->
+            Log.d(TAG, "onUdfpsPointerUp failed to send, due to $error")
+          }
+        }
+
+        override fun onUdfpsOverlayShown() {
+          trySend(FingerEnrollState.OverlayShown).onFailure { error ->
+            Log.d(TAG, "OverlayShown failed to send, due to $error")
+          }
+        }
+
+        override fun onAcquired(isAcquiredGood: Boolean) {
+          trySend(FingerEnrollState.Acquired(isAcquiredGood)).onFailure { error ->
+            Log.d(TAG, "Acquired failed to send, due to $error")
+          }
+        }
+      }
+
+    val cancellationSignal = CancellationSignal()
+
+    fingerprintManager.enroll(
+      hardwareAuthToken,
+      cancellationSignal,
+      userId,
+      enrollmentCallback,
+      enrollReason.toOriginalReason(),
+      fingerprintEnrollOptions,
+    )
+    awaitClose {
+      // If the stream has not been ended, and the user has stopped collecting the flow
+      // before it was over, send cancel.
+      if (!streamEnded) {
+        Log.e(TAG, "Cancel is sent from settings for enroll()")
+        cancellationSignal.cancel()
+      }
+    }
+  }
+
+  companion object {
+    private const val TAG = "FingerprintEnrollStateRepository"
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/EnrolledFingerprintsInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/EnrolledFingerprintsInteractorImpl.kt
new file mode 100644
index 0000000..83b532e
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/EnrolledFingerprintsInteractorImpl.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.domain.interactor
+
+import android.hardware.fingerprint.FingerprintManager
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrolledFingerprintsInteractor
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+
+class EnrolledFingerprintsInteractorImpl(
+  private val fingerprintManager: FingerprintManager,
+  userId: Int,
+) : EnrolledFingerprintsInteractor {
+  override val enrolledFingerprints: Flow<List<FingerprintData>?> = flow {
+    emit(
+      fingerprintManager
+        .getEnrolledFingerprints(userId)
+        ?.map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) }
+        ?.toList()
+    )
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintEnrollInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintEnrollInteractor.kt
index a36832d..56a1257 100644
--- a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintEnrollInteractor.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintEnrollInteractor.kt
@@ -16,7 +16,6 @@
 
 package com.android.settings.biometrics.fingerprint2.domain.interactor
 
-import android.content.Context
 import android.hardware.fingerprint.FingerprintEnrollOptions
 import android.hardware.fingerprint.FingerprintManager
 import android.os.CancellationSignal
@@ -49,7 +48,7 @@
 }
 
 class FingerprintEnrollInteractorImpl(
-  private val applicationContext: Context,
+  private val userId: Int,
   private val fingerprintManager: FingerprintManager?,
   private val fingerprintFlow: FingerprintFlow,
 ) : FingerprintEnrollInteractor {
@@ -138,7 +137,7 @@
     fingerprintManager?.enroll(
       hardwareAuthToken,
       cancellationSignal,
-      applicationContext.userId,
+      userId,
       enrollmentCallback,
       enrollReason.toOriginalReason(),
       fingerprintEnrollOptions,
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt
deleted file mode 100644
index f03c94e..0000000
--- a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintManagerInteractorImpl.kt
+++ /dev/null
@@ -1,173 +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.settings.biometrics.fingerprint2.domain.interactor
-
-import android.content.Context
-import android.content.Intent
-import android.hardware.fingerprint.FingerprintEnrollOptions
-import android.hardware.fingerprint.FingerprintManager
-import android.hardware.fingerprint.FingerprintManager.GenerateChallengeCallback
-import android.hardware.fingerprint.FingerprintManager.RemovalCallback
-import android.os.CancellationSignal
-import android.util.Log
-import com.android.settings.biometrics.GatekeeperPasswordProvider
-import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
-import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
-import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
-import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
-import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
-import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
-import com.android.settings.password.ChooseLockSettingsHelper
-import kotlin.coroutines.resume
-import kotlin.coroutines.suspendCoroutine
-import kotlinx.coroutines.CancellableContinuation
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.suspendCancellableCoroutine
-import kotlinx.coroutines.withContext
-
-private const val TAG = "FingerprintManagerInteractor"
-
-class FingerprintManagerInteractorImpl(
-  applicationContext: Context,
-  private val backgroundDispatcher: CoroutineDispatcher,
-  private val fingerprintManager: FingerprintManager?,
-  fingerprintSensorRepository: FingerprintSensorRepository,
-  private val gatekeeperPasswordProvider: GatekeeperPasswordProvider,
-  private val fingerprintEnrollStateRepository: FingerprintEnrollInteractor,
-) : FingerprintManagerInteractor {
-
-  private val maxFingerprints =
-    applicationContext.resources.getInteger(
-      com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser
-    )
-  private val applicationContext = applicationContext.applicationContext
-
-  override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> =
-    suspendCoroutine {
-      val callback = GenerateChallengeCallback { _, userId, challenge ->
-        val intent = Intent()
-        intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gateKeeperPasswordHandle)
-        val challengeToken =
-          gatekeeperPasswordProvider.requestGatekeeperHat(intent, challenge, userId)
-
-        gatekeeperPasswordProvider.removeGatekeeperPasswordHandle(intent, false)
-        val p = Pair(challenge, challengeToken)
-        it.resume(p)
-      }
-      fingerprintManager?.generateChallenge(applicationContext.userId, callback)
-    }
-
-  override val enrolledFingerprints: Flow<List<FingerprintData>?> = flow {
-    emit(
-      fingerprintManager?.getEnrolledFingerprints(applicationContext.userId)
-        ?.map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) }?.toList()
-    )
-  }
-
-  override val canEnrollFingerprints: Flow<Boolean> = flow {
-    emit(
-      fingerprintManager?.getEnrolledFingerprints(applicationContext.userId)?.size  ?: maxFingerprints < maxFingerprints
-    )
-  }
-
-  override val sensorPropertiesInternal = fingerprintSensorRepository.fingerprintSensor
-
-  override val maxEnrollableFingerprints = flow { emit(maxFingerprints) }
-
-  override suspend fun enroll(
-    hardwareAuthToken: ByteArray?,
-    enrollReason: EnrollReason,
-    fingerprintEnrollOptions: FingerprintEnrollOptions,
-  ): Flow<FingerEnrollState> =
-    fingerprintEnrollStateRepository.enroll(
-      hardwareAuthToken,
-      enrollReason,
-      fingerprintEnrollOptions,
-    )
-
-  override suspend fun removeFingerprint(fp: FingerprintData): Boolean = suspendCoroutine {
-    val callback =
-      object : RemovalCallback() {
-        override fun onRemovalError(
-          fp: android.hardware.fingerprint.Fingerprint,
-          errMsgId: Int,
-          errString: CharSequence,
-        ) {
-          it.resume(false)
-        }
-
-        override fun onRemovalSucceeded(
-          fp: android.hardware.fingerprint.Fingerprint?,
-          remaining: Int,
-        ) {
-          it.resume(true)
-        }
-      }
-    fingerprintManager?.remove(
-      android.hardware.fingerprint.Fingerprint(fp.name, fp.fingerId, fp.deviceId),
-      applicationContext.userId,
-      callback,
-    )
-  }
-
-  override suspend fun renameFingerprint(fp: FingerprintData, newName: String) {
-    withContext(backgroundDispatcher) {
-      fingerprintManager?.rename(fp.fingerId, applicationContext.userId, newName)
-    }
-  }
-
-  override suspend fun hasSideFps(): Boolean? = suspendCancellableCoroutine {
-    it.resume(fingerprintManager?.isPowerbuttonFps)
-  }
-
-  override suspend fun authenticate(): FingerprintAuthAttemptModel =
-    suspendCancellableCoroutine { c: CancellableContinuation<FingerprintAuthAttemptModel> ->
-      val authenticationCallback =
-        object : FingerprintManager.AuthenticationCallback() {
-
-          override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
-            super.onAuthenticationError(errorCode, errString)
-            if (c.isCompleted) {
-              Log.d(TAG, "framework sent down onAuthError after finish")
-              return
-            }
-            c.resume(FingerprintAuthAttemptModel.Error(errorCode, errString.toString()))
-          }
-
-          override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
-            super.onAuthenticationSucceeded(result)
-            if (c.isCompleted) {
-              Log.d(TAG, "framework sent down onAuthError after finish")
-              return
-            }
-            c.resume(FingerprintAuthAttemptModel.Success(result.fingerprint?.biometricId ?: -1))
-          }
-        }
-
-      val cancellationSignal = CancellationSignal()
-      c.invokeOnCancellation { cancellationSignal.cancel() }
-      fingerprintManager?.authenticate(
-        null,
-        cancellationSignal,
-        authenticationCallback,
-        null,
-        applicationContext.userId,
-      )
-    }
-}
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintSensorInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintSensorInteractor.kt
index 073629c..7b1d4fd 100644
--- a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintSensorInteractor.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/FingerprintSensorInteractor.kt
@@ -20,9 +20,7 @@
 import com.android.systemui.biometrics.shared.model.FingerprintSensor
 import kotlinx.coroutines.flow.Flow
 
-/**
- * Interactor that propagates the type of [FingerprintSensor] this device supports.
- */
+/** Interactor that propagates the type of [FingerprintSensor] this device supports. */
 interface FingerprintSensorInteractor {
   /** Get the [FingerprintSensor] */
   val fingerprintSensor: Flow<FingerprintSensor>
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/GenerateChallengeInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/GenerateChallengeInteractorImpl.kt
new file mode 100644
index 0000000..a2080fb
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/GenerateChallengeInteractorImpl.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.domain.interactor
+
+import android.content.Intent
+import android.hardware.fingerprint.FingerprintManager
+import com.android.settings.biometrics.GatekeeperPasswordProvider
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor
+import com.android.settings.password.ChooseLockSettingsHelper
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+class GenerateChallengeInteractorImpl(
+  private val fingerprintManager: FingerprintManager,
+  private val userId: Int,
+  private val gatekeeperPasswordProvider: GatekeeperPasswordProvider,
+) : GenerateChallengeInteractor {
+
+  override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> =
+    suspendCoroutine {
+      val callback =
+        FingerprintManager.GenerateChallengeCallback { _, userId, challenge ->
+          val intent = Intent()
+          intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, gateKeeperPasswordHandle)
+          val challengeToken =
+            gatekeeperPasswordProvider.requestGatekeeperHat(intent, challenge, userId)
+
+          gatekeeperPasswordProvider.removeGatekeeperPasswordHandle(intent, false)
+          val p = Pair(challenge, challengeToken)
+          it.resume(p)
+        }
+      fingerprintManager.generateChallenge(userId, callback)
+    }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/RemoveFingerprintsInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/RemoveFingerprintsInteractorImpl.kt
new file mode 100644
index 0000000..4232963
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/RemoveFingerprintsInteractorImpl.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.domain.interactor
+
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintManager.RemovalCallback
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RemoveFingerprintInteractor
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+class RemoveFingerprintsInteractorImpl(
+  private val fingerprintManager: FingerprintManager,
+  private val userId: Int,
+) : RemoveFingerprintInteractor {
+
+  override suspend fun removeFingerprint(fp: FingerprintData): Boolean = suspendCoroutine {
+    val callback =
+      object : RemovalCallback() {
+        override fun onRemovalError(
+          fp: android.hardware.fingerprint.Fingerprint,
+          errMsgId: Int,
+          errString: CharSequence,
+        ) {
+          it.resume(false)
+        }
+
+        override fun onRemovalSucceeded(
+          fp: android.hardware.fingerprint.Fingerprint?,
+          remaining: Int,
+        ) {
+          it.resume(true)
+        }
+      }
+    fingerprintManager.remove(
+      android.hardware.fingerprint.Fingerprint(fp.name, fp.fingerId, fp.deviceId),
+      userId,
+      callback,
+    )
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/RenameFingerprintsInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/RenameFingerprintsInteractorImpl.kt
new file mode 100644
index 0000000..f238e7c
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/RenameFingerprintsInteractorImpl.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.domain.interactor
+
+import android.hardware.fingerprint.FingerprintManager
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RenameFingerprintInteractor
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+class RenameFingerprintsInteractorImpl(
+  private val fingerprintManager: FingerprintManager,
+  private val userId: Int,
+  private val backgroundDispatcher: CoroutineDispatcher,
+) : RenameFingerprintInteractor {
+
+  override suspend fun renameFingerprint(fp: FingerprintData, newName: String) {
+    withContext(backgroundDispatcher) { fingerprintManager.rename(fp.fingerId, userId, newName) }
+  }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/SensorInteractorImpl.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/SensorInteractorImpl.kt
new file mode 100644
index 0000000..7df0795
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/SensorInteractorImpl.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.settings.biometrics.fingerprint2.domain.interactor
+
+import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
+import kotlinx.coroutines.flow.Flow
+
+class SensorInteractorImpl(private val repo: FingerprintSensorRepository) :
+  SensorInteractor {
+  override val sensorPropertiesInternal = repo.fingerprintSensor
+  override val hasSideFps: Flow<Boolean> = repo.hasSideFps
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/TouchEventInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/TouchEventInteractor.kt
index 4ef2afa..778837d 100644
--- a/src/com/android/settings/biometrics/fingerprint2/domain/interactor/TouchEventInteractor.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/domain/interactor/TouchEventInteractor.kt
@@ -24,4 +24,3 @@
   /** A flow simulating user touches. */
   val touchEvent: Flow<MotionEvent>
 }
-
diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/AndroidManifest.xml b/src/com/android/settings/biometrics/fingerprint2/lib/AndroidManifest.xml
index 250f0af..0b7ea28 100644
--- a/src/com/android/settings/biometrics/fingerprint2/lib/AndroidManifest.xml
+++ b/src/com/android/settings/biometrics/fingerprint2/lib/AndroidManifest.xml
@@ -13,6 +13,6 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+<manifest
     package="com.android.settings.biometrics.fingerprint2.lib">
 </manifest>
diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/AuthenitcateInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/AuthenitcateInteractor.kt
new file mode 100644
index 0000000..4fc9413
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/AuthenitcateInteractor.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.lib.domain.interactor
+
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
+
+/** Interactor responsible for coordinating authentication. */
+interface AuthenitcateInteractor {
+  /** Runs the authenticate flow */
+  suspend fun authenticate(): FingerprintAuthAttemptModel
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/CanEnrollFingerprintsInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/CanEnrollFingerprintsInteractor.kt
new file mode 100644
index 0000000..11a9258
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/CanEnrollFingerprintsInteractor.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.settings.biometrics.fingerprint2.lib.domain.interactor
+
+import kotlinx.coroutines.flow.Flow
+
+/** Returns whether or not a user can enroll a fingerprint */
+interface CanEnrollFingerprintsInteractor {
+  /** Returns true if a user can enroll a fingerprint false otherwise. */
+  val canEnrollFingerprints: Flow<Boolean>
+  /** Indicates the maximum fingerprints enrollable for a given user */
+  fun maxFingerprintsEnrollable(): Int
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/EnrollFingerprintInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/EnrollFingerprintInteractor.kt
new file mode 100644
index 0000000..be7b4d0
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/EnrollFingerprintInteractor.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.lib.domain.interactor
+
+import android.hardware.fingerprint.FingerprintEnrollOptions
+import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
+import kotlinx.coroutines.flow.Flow
+
+/** Interactor that enrolls a fingerprint */
+interface EnrollFingerprintInteractor {
+  /**
+   * Runs [FingerprintManager.enroll] with the [hardwareAuthToken] and [EnrollReason] for this
+   * enrollment. If successful data in the [fingerprintEnrollState] should be populated.
+   */
+  suspend fun enroll(
+    hardwareAuthToken: ByteArray?,
+    enrollReason: EnrollReason,
+    fingerprintEnrollOptions: FingerprintEnrollOptions,
+  ): Flow<FingerEnrollState>
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/EnrolledFingerprintsInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/EnrolledFingerprintsInteractor.kt
new file mode 100644
index 0000000..14fc1e5
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/EnrolledFingerprintsInteractor.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.settings.biometrics.fingerprint2.lib.domain.interactor
+
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
+import kotlinx.coroutines.flow.Flow
+
+/** Interface to obtain the enrolled fingerprints */
+interface EnrolledFingerprintsInteractor {
+  /** Returns the list of current fingerprints. */
+  val enrolledFingerprints: Flow<List<FingerprintData>?>
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/FingerprintManagerInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/FingerprintManagerInteractor.kt
deleted file mode 100644
index 5f4ceca..0000000
--- a/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/FingerprintManagerInteractor.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.biometrics.fingerprint2.lib.domain.interactor
-
-import android.hardware.fingerprint.FingerprintEnrollOptions
-import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
-import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
-import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
-import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
-import com.android.systemui.biometrics.shared.model.FingerprintSensor
-import kotlinx.coroutines.flow.Flow
-
-/**
- * Interface to obtain the necessary data for FingerprintEnrollment/Settings
- *
- * Note that this interface should not have dependencies on heavyweight libraries such as the
- * framework, hidl/aidl, etc. This makes it much easier to test and create fakes for.
- */
-interface FingerprintManagerInteractor {
-  /** Returns the list of current fingerprints. */
-  val enrolledFingerprints: Flow<List<FingerprintData>?>
-
-  /** Returns the max enrollable fingerprints, note during SUW this might be 1 */
-  val maxEnrollableFingerprints: Flow<Int>
-
-  /** Returns true if a user can enroll a fingerprint false otherwise. */
-  val canEnrollFingerprints: Flow<Boolean>
-
-  /** Retrieves the sensor properties of a device */
-  val sensorPropertiesInternal: Flow<FingerprintSensor?>
-
-  /** Runs the authenticate flow */
-  suspend fun authenticate(): FingerprintAuthAttemptModel
-
-  /**
-   * Generates a challenge with the provided [gateKeeperPasswordHandle] and on success returns a
-   * challenge and challenge token. This info can be used for secure operations such as enrollment
-   *
-   * @param gateKeeperPasswordHandle GateKeeper password handle generated by a Confirm
-   * @return A [Pair] of the challenge and challenge token
-   */
-  suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray>
-
-  /**
-   * Runs [FingerprintManager.enroll] with the [hardwareAuthToken] and [EnrollReason] for this
-   * enrollment. If successful data in the [fingerprintEnrollState] should be populated.
-   */
-  suspend fun enroll(
-    hardwareAuthToken: ByteArray?,
-    enrollReason: EnrollReason,
-    fingerprintEnrollOptions: FingerprintEnrollOptions,
-  ): Flow<FingerEnrollState>
-
-  /**
-   * Removes the given fingerprint, returning true if it was successfully removed and false
-   * otherwise
-   */
-  suspend fun removeFingerprint(fp: FingerprintData): Boolean
-
-  /** Renames the given fingerprint if one exists */
-  suspend fun renameFingerprint(fp: FingerprintData, newName: String)
-
-  /** Indicates if the device has side fingerprint */
-  suspend fun hasSideFps(): Boolean?
-}
diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/GenerateChallengeInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/GenerateChallengeInteractor.kt
new file mode 100644
index 0000000..82667fe
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/GenerateChallengeInteractor.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.lib.domain.interactor
+
+/** This interactor is responsible for generating a challenge. */
+interface GenerateChallengeInteractor {
+  /**
+   * Generates a challenge with the provided [gateKeeperPasswordHandle] and on success returns a
+   * challenge and challenge token. This info can be used for secure operations such as enrollment
+   *
+   * @param gateKeeperPasswordHandle GateKeeper password handle generated by a Confirm
+   * @return A [Pair] of the challenge and challenge token
+   */
+  suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray>
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/RemoveFingerprintInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/RemoveFingerprintInteractor.kt
new file mode 100644
index 0000000..6d0e5641
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/RemoveFingerprintInteractor.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.lib.domain.interactor
+
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
+
+/** Interactor in charge of removing a fingerprint */
+interface RemoveFingerprintInteractor {
+  /**
+   * Removes the given fingerprint, returning true if it was successfully removed and false
+   * otherwise
+   */
+  suspend fun removeFingerprint(fp: FingerprintData): Boolean
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/RenameFingerprintInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/RenameFingerprintInteractor.kt
new file mode 100644
index 0000000..d7fe1c0
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/RenameFingerprintInteractor.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.lib.domain.interactor
+
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
+
+/** Interactor that can rename a fingerprint. */
+interface RenameFingerprintInteractor {
+  /** Renames the given fingerprint if one exists */
+  suspend fun renameFingerprint(fp: FingerprintData, newName: String)
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/SensorInteractor.kt b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/SensorInteractor.kt
new file mode 100644
index 0000000..f265c32
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint2/lib/domain/interactor/SensorInteractor.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.biometrics.fingerprint2.lib.domain.interactor
+
+import com.android.systemui.biometrics.shared.model.FingerprintSensor
+import kotlinx.coroutines.flow.Flow
+
+/** Interactor that has various information about a fingerprint sensor */
+interface SensorInteractor {
+  /** Retrieves the sensor properties of the device */
+  val sensorPropertiesInternal: Flow<FingerprintSensor?>
+  /** Indicates if the device supports side fps */
+  val hasSideFps: Flow<Boolean>
+}
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt
index 421548f..77d070e 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/activity/FingerprintEnrollmentV2Activity.kt
@@ -96,8 +96,8 @@
   }
 
   /**
-   * View models below this line are not used by this class but must be initialized
-   * in the activity view model store to be used by other view models.
+   * View models below this line are not used by this class but must be initialized in the activity
+   * view model store to be used by other view models.
    */
   private val fingerprintEnrollViewModel: FingerprintEnrollViewModel by viewModels {
     FingerprintEnrollViewModel.Factory
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSViewModel.kt
index c95020d..932c408 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/rfps/ui/viewmodel/RFPSViewModel.kt
@@ -25,7 +25,7 @@
 import androidx.lifecycle.viewmodel.viewModelFactory
 import com.android.settings.SettingsApplication
 import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
-import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
 import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintAction
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
@@ -46,10 +46,10 @@
 
 /** View Model used by the rear fingerprint enrollment fragment. */
 class RFPSViewModel(
-  private val fingerprintEnrollViewModel: FingerprintEnrollEnrollingViewModel,
-  private val navigationViewModel: FingerprintNavigationViewModel,
-  orientationInteractor: OrientationInteractor,
-  private val fingerprintManager: FingerprintManagerInteractor,
+    private val fingerprintEnrollViewModel: FingerprintEnrollEnrollingViewModel,
+    private val navigationViewModel: FingerprintNavigationViewModel,
+    orientationInteractor: OrientationInteractor,
+    private val sensorInteractor: SensorInteractor,
 ) : ViewModel() {
 
   private val _textViewIsVisible = MutableStateFlow(false)
@@ -62,7 +62,7 @@
   val shouldAnimateIcon = _shouldAnimateIcon
 
   private var enrollFlow: Flow<FingerEnrollState?> =
-    fingerprintManager.sensorPropertiesInternal.filterNotNull().combine(
+    sensorInteractor.sensorPropertiesInternal.filterNotNull().combine(
       fingerprintEnrollViewModel.enrollFlow
     ) { props, enroll ->
       if (props.sensorType == FingerprintSensorType.REAR) {
@@ -181,7 +181,7 @@
           provider[FingerprintEnrollEnrollingViewModel::class],
           provider[FingerprintNavigationViewModel::class],
           biometricEnvironment.orientationInteractor,
-          biometricEnvironment.fingerprintManagerInteractor,
+          biometricEnvironment.createSensorPropertiesInteractor(),
         )
       }
     }
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsViewModel.kt
index 3396cdc..658c6c7 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/modules/enrolling/udfps/ui/viewmodel/UdfpsViewModel.kt
@@ -38,7 +38,7 @@
 import com.android.settings.biometrics.fingerprint2.domain.interactor.TouchEventInteractor
 import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractor
 import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor
-import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
 import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model.DescriptionText
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model.HeaderText
@@ -76,17 +76,17 @@
   enrollStageInteractor: EnrollStageInteractor,
   orientationInteractor: OrientationInteractor,
   udfpsEnrollInteractor: UdfpsEnrollInteractor,
-  fingerprintManager: FingerprintManagerInteractor,
   accessibilityInteractor: AccessibilityInteractor,
   sensorRepository: FingerprintSensorInteractor,
   touchEventInteractor: TouchEventInteractor,
+  sensorInteractor: SensorInteractor,
 ) : ViewModel() {
 
   private val isSetupWizard = flowOf(false)
   private var shouldResetErollment = false
 
   private var _enrollState: Flow<FingerEnrollState?> =
-    fingerprintManager.sensorPropertiesInternal.filterNotNull().combine(
+    sensorInteractor.sensorPropertiesInternal.filterNotNull().combine(
       fingerprintEnrollEnrollingViewModel.enrollFlow
     ) { props, enroll ->
       if (props.sensorType.isUdfps()) {
@@ -198,8 +198,7 @@
       .distinctUntilChanged()
 
   private val _touchEvent: MutableStateFlow<MotionEvent?> = MutableStateFlow(null)
-  val touchEvent =
-    _touchEvent.asStateFlow().filterNotNull()
+  val touchEvent = _touchEvent.asStateFlow().filterNotNull()
 
   /** Determines the current [EnrollStageModel] enrollment is in */
   private val enrollStage: Flow<EnrollStageModel> =
@@ -267,11 +266,7 @@
       backgroundViewModel.background.filter { it }.collect { didGoToBackground() }
     }
 
-    viewModelScope.launch {
-      touchEventInteractor.touchEvent.collect {
-        _touchEvent.update { it }
-      }
-    }
+    viewModelScope.launch { touchEventInteractor.touchEvent.collect { _touchEvent.update { it } } }
   }
 
   /** Indicates if we should show the lottie. */
@@ -430,10 +425,10 @@
           biometricEnvironment.enrollStageInteractor,
           biometricEnvironment.orientationInteractor,
           biometricEnvironment.udfpsEnrollInteractor,
-          biometricEnvironment.fingerprintManagerInteractor,
           biometricEnvironment.accessibilityInteractor,
           biometricEnvironment.sensorInteractor,
           biometricEnvironment.touchEventInteractor,
+          biometricEnvironment.createSensorPropertiesInteractor(),
         )
       }
     }
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollConfirmationViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollConfirmationViewModel.kt
index 5ce2ed7..0803f89 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollConfirmationViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollConfirmationViewModel.kt
@@ -16,27 +16,27 @@
 
 package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
 
-import android.util.Log
 import androidx.lifecycle.VIEW_MODEL_STORE_OWNER_KEY
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.viewmodel.initializer
 import androidx.lifecycle.viewmodel.viewModelFactory
 import com.android.settings.SettingsApplication
-import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor
 import kotlinx.coroutines.flow.Flow
 
 /** Models the UI state for [FingerprintEnrollConfirmationV2Fragment] */
 class FingerprintEnrollConfirmationViewModel(
   private val navigationViewModel: FingerprintNavigationViewModel,
-  fingerprintInteractor: FingerprintManagerInteractor,
+  private val canEnrollFingerprintsInteractor: CanEnrollFingerprintsInteractor,
 ) : ViewModel() {
 
   /**
    * Indicates if the add another button is possible. This should only be true when the user is able
    * to enroll more fingerprints.
    */
-  val isAddAnotherButtonVisible: Flow<Boolean> = fingerprintInteractor.canEnrollFingerprints
+  val isAddAnotherButtonVisible: Flow<Boolean> =
+    canEnrollFingerprintsInteractor.canEnrollFingerprints
 
   /**
    * Indicates that the user has clicked the next button and is done with fingerprint enrollment.
@@ -64,7 +64,7 @@
         val provider = ViewModelProvider(this[VIEW_MODEL_STORE_OWNER_KEY]!!)
         FingerprintEnrollConfirmationViewModel(
           provider[FingerprintNavigationViewModel::class],
-          biometricEnvironment!!.fingerprintManagerInteractor,
+          biometricEnvironment!!.createCanEnrollFingerprintsInteractor(),
         )
       }
     }
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt
index 3568dbd..9b2cdde 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollFindSensorViewModel.kt
@@ -27,7 +27,7 @@
 import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractor
 import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractor
 import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
-import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
 import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
 import com.android.settings.biometrics.fingerprint2.lib.model.SetupWizard
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Education
@@ -44,20 +44,20 @@
 
 /** Models the UI state for fingerprint enroll education */
 class FingerprintEnrollFindSensorViewModel(
-  private val navigationViewModel: FingerprintNavigationViewModel,
-  private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
-  private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
-  backgroundViewModel: BackgroundViewModel,
-  fingerprintFlowViewModel: FingerprintFlowViewModel,
-  accessibilityInteractor: AccessibilityInteractor,
-  foldStateInteractor: FoldStateInteractor,
-  orientationInteractor: OrientationInteractor,
-  fingerprintManagerInteractor: FingerprintManagerInteractor,
+    private val navigationViewModel: FingerprintNavigationViewModel,
+    private val fingerprintEnrollViewModel: FingerprintEnrollViewModel,
+    private val gatekeeperViewModel: FingerprintGatekeeperViewModel,
+    backgroundViewModel: BackgroundViewModel,
+    fingerprintFlowViewModel: FingerprintFlowViewModel,
+    accessibilityInteractor: AccessibilityInteractor,
+    foldStateInteractor: FoldStateInteractor,
+    orientationInteractor: OrientationInteractor,
+    sensorInteractor: SensorInteractor,
 ) : ViewModel() {
 
   /** Represents the stream of sensor type. */
   val sensorType: Flow<FingerprintSensorType> =
-    fingerprintManagerInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType }
+    sensorInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType }
   private val _isUdfps: Flow<Boolean> =
     sensorType.map {
       it == FingerprintSensorType.UDFPS_OPTICAL || it == FingerprintSensorType.UDFPS_ULTRASONIC
@@ -216,7 +216,7 @@
           biometricEnvironment.accessibilityInteractor,
           biometricEnvironment.foldStateInteractor,
           biometricEnvironment.orientationInteractor,
-          biometricEnvironment.fingerprintManagerInteractor,
+          biometricEnvironment.createSensorPropertiesInteractor(),
         )
       }
     }
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollIntroViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollIntroViewModel.kt
index 6ec2048..e103cbc 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollIntroViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollIntroViewModel.kt
@@ -22,7 +22,7 @@
 import androidx.lifecycle.viewmodel.initializer
 import androidx.lifecycle.viewmodel.viewModelFactory
 import com.android.settings.SettingsApplication
-import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
 import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Introduction
 import com.android.systemui.biometrics.shared.model.FingerprintSensor
@@ -30,13 +30,13 @@
 
 /** A view model for fingerprint enroll introduction. */
 class FingerprintEnrollIntroViewModel(
-  val navigationViewModel: FingerprintNavigationViewModel,
-  fingerprintFlowViewModel: FingerprintFlowViewModel,
-  fingerprintManagerInteractor: FingerprintManagerInteractor,
+    val navigationViewModel: FingerprintNavigationViewModel,
+    fingerprintFlowViewModel: FingerprintFlowViewModel,
+    sensorInteractor: SensorInteractor,
 ) : ViewModel() {
 
   /** Represents a stream of [FingerprintSensor] */
-  val sensor: Flow<FingerprintSensor?> = fingerprintManagerInteractor.sensorPropertiesInternal
+  val sensor: Flow<FingerprintSensor?> = sensorInteractor.sensorPropertiesInternal
 
   /** Represents a stream of [FingerprintFlow] */
   val fingerprintFlow: Flow<FingerprintFlow?> = fingerprintFlowViewModel.fingerprintFlow
@@ -67,7 +67,7 @@
         FingerprintEnrollIntroViewModel(
           provider[FingerprintNavigationViewModel::class],
           provider[FingerprintFlowViewModel::class],
-          biometricEnvironment!!.fingerprintManagerInteractor,
+          biometricEnvironment!!.createSensorPropertiesInteractor(),
         )
       }
     }
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt
index 2669b8b..fb8a182 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollViewModel.kt
@@ -24,7 +24,8 @@
 import androidx.lifecycle.viewmodel.initializer
 import androidx.lifecycle.viewmodel.viewModelFactory
 import com.android.settings.SettingsApplication
-import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrollFingerprintInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
 import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
 import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Education
@@ -42,9 +43,10 @@
 
 /** Represents all of the fingerprint information needed for a fingerprint enrollment process. */
 class FingerprintEnrollViewModel(
-  private val fingerprintManagerInteractor: FingerprintManagerInteractor,
-  gatekeeperViewModel: FingerprintGatekeeperViewModel,
-  val navigationViewModel: FingerprintNavigationViewModel,
+    gatekeeperViewModel: FingerprintGatekeeperViewModel,
+    val navigationViewModel: FingerprintNavigationViewModel,
+    private val sensorInteractor: SensorInteractor,
+    private val fingerprintEnrollInteractor: EnrollFingerprintInteractor,
 ) : ViewModel() {
 
   /**
@@ -67,7 +69,7 @@
 
   /** Represents the stream of [FingerprintSensorType] */
   val sensorType: Flow<FingerprintSensorType?> =
-    fingerprintManagerInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType }
+    sensorInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType }
 
   /**
    * A flow that contains a [FingerprintEnrollViewModel] which contains the relevant information for
@@ -90,7 +92,7 @@
             enrollReason != null &&
             enrollOptions != null
         ) {
-          fingerprintManagerInteractor
+          fingerprintEnrollInteractor
             .enroll(hardwareAuthToken.token, enrollReason, enrollOptions)
             .collect { emit(it) }
         }
@@ -137,9 +139,10 @@
         val biometricEnvironment = settingsApplication.biometricEnvironment
         val provider = ViewModelProvider(this[VIEW_MODEL_STORE_OWNER_KEY]!!)
         FingerprintEnrollViewModel(
-          biometricEnvironment!!.fingerprintManagerInteractor,
           provider[FingerprintGatekeeperViewModel::class],
           provider[FingerprintNavigationViewModel::class],
+          biometricEnvironment!!.createSensorPropertiesInteractor(),
+          biometricEnvironment!!.createFingerprintEnrollInteractor(),
         )
       }
     }
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintGatekeeperViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintGatekeeperViewModel.kt
index b5be165..c2b0a0f 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintGatekeeperViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintGatekeeperViewModel.kt
@@ -24,7 +24,7 @@
 import androidx.lifecycle.viewmodel.initializer
 import androidx.lifecycle.viewmodel.viewModelFactory
 import com.android.settings.SettingsApplication
-import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
@@ -50,7 +50,7 @@
  * in as a parameter to this class.
  */
 class FingerprintGatekeeperViewModel(
-  private val fingerprintManagerInteractor: FingerprintManagerInteractor
+  private val generateChallengeInteractor: GenerateChallengeInteractor
 ) : ViewModel() {
 
   private var _gatekeeperInfo: MutableStateFlow<GatekeeperInfo?> = MutableStateFlow(null)
@@ -78,7 +78,7 @@
       _gatekeeperInfo.update { GatekeeperInfo.Invalid }
     } else {
       viewModelScope.launch {
-        val res = fingerprintManagerInteractor.generateChallenge(theGatekeeperPasswordHandle!!)
+        val res = generateChallengeInteractor.generateChallenge(theGatekeeperPasswordHandle!!)
         _gatekeeperInfo.update { GatekeeperInfo.GatekeeperPasswordInfo(res.second, res.first) }
         if (shouldStartTimer) {
           startTimeout()
@@ -119,7 +119,7 @@
         val settingsApplication =
           this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as SettingsApplication
         val biometricEnvironment = settingsApplication.biometricEnvironment
-        FingerprintGatekeeperViewModel(biometricEnvironment!!.fingerprintManagerInteractor)
+        FingerprintGatekeeperViewModel(biometricEnvironment!!.createGenerateChallengeInteractor())
       }
     }
   }
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintNavigationViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintNavigationViewModel.kt
index caf7d2a..d9bcf7f 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintNavigationViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/enrollment/viewmodel/FingerprintNavigationViewModel.kt
@@ -23,7 +23,7 @@
 import androidx.lifecycle.viewmodel.initializer
 import androidx.lifecycle.viewmodel.viewModelFactory
 import com.android.settings.SettingsApplication
-import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
 import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Finish
 import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.TransitionStep
@@ -46,7 +46,7 @@
  * fragments/viewmodels that want to consume these events. It should provide no additional
  * functionality beyond what is available in [FingerprintNavigationStep].
  */
-class FingerprintNavigationViewModel(fingerprintManagerInteractor: FingerprintManagerInteractor) :
+class FingerprintNavigationViewModel(sensorInteractor: SensorInteractor) :
   ViewModel() {
 
   private val _flowInternal: MutableStateFlow<FingerprintFlow?> = MutableStateFlow(null)
@@ -55,7 +55,7 @@
     combine(
         _flowInternal,
         _hasConfirmedDeviceCredential,
-        fingerprintManagerInteractor.sensorPropertiesInternal,
+        sensorInteractor.sensorPropertiesInternal,
       ) { flow, hasConfirmed, sensorType ->
         if (flow == null || sensorType == null) {
           return@combine null
@@ -144,7 +144,7 @@
         val settingsApplication =
           this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as SettingsApplication
         val biometricEnvironment = settingsApplication.biometricEnvironment
-        FingerprintNavigationViewModel(biometricEnvironment!!.fingerprintManagerInteractor)
+        FingerprintNavigationViewModel(biometricEnvironment!!.createSensorPropertiesInteractor())
       }
     }
   }
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt
index 4c3773b..241eaea 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/fragment/FingerprintSettingsV2Fragment.kt
@@ -35,19 +35,16 @@
 import androidx.lifecycle.lifecycleScope
 import androidx.preference.Preference
 import androidx.preference.PreferenceCategory
-import com.android.internal.widget.LockPatternUtils
 import com.android.settings.R
+import com.android.settings.SettingsApplication
 import com.android.settings.Utils.SETTINGS_PACKAGE_NAME
 import com.android.settings.biometrics.BiometricEnrollBase
 import com.android.settings.biometrics.BiometricEnrollBase.CONFIRM_REQUEST
 import com.android.settings.biometrics.BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY
 import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED
-import com.android.settings.biometrics.GatekeeperPasswordProvider
 import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling
 import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal
 import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepositoryImpl
-import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractorImpl
-import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
 import com.android.settings.biometrics.fingerprint2.domain.interactor.PressToAuthInteractorImpl
 import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
 import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
@@ -223,35 +220,24 @@
     val fingerprintSensorProvider =
       FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, lifecycleScope)
     val pressToAuthInteractor = PressToAuthInteractorImpl(context, backgroundDispatcher)
-    val fingerprintEnrollStateRepository =
-      FingerprintEnrollInteractorImpl(
-        requireContext().applicationContext,
-        fingerprintManager,
-        Settings,
-      )
-
-    val interactor =
-      FingerprintManagerInteractorImpl(
-        context.applicationContext,
-        backgroundDispatcher,
-        fingerprintManager,
-        fingerprintSensorProvider,
-        GatekeeperPasswordProvider(LockPatternUtils(context.applicationContext)),
-        fingerprintEnrollStateRepository,
-      )
 
     val token = intent.getByteArrayExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN)
     val challenge = intent.getLongExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, -1L)
+    val application = requireActivity().application as SettingsApplication
+    val environment =
+      application.biometricEnvironment
+        ?: throw IllegalStateException("The biometric environment must be present")
 
     navigationViewModel =
       ViewModelProvider(
         this,
         FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
           userId,
-          interactor,
           backgroundDispatcher,
           token,
           challenge,
+          environment.createFingerprintsEnrolledInteractor(),
+          environment.createGenerateChallengeInteractor(),
         ),
       )[FingerprintSettingsNavigationViewModel::class.java]
 
@@ -260,9 +246,14 @@
         this,
         FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
           userId,
-          interactor,
           backgroundDispatcher,
           navigationViewModel,
+          environment.createCanEnrollFingerprintsInteractor(),
+          environment.createSensorPropertiesInteractor(),
+          environment.createAuthenticateInteractor(),
+          environment.createRenameFingerprintInteractor(),
+          environment.createRemoveFingerprintInteractor(),
+          environment.createFingerprintsEnrolledInteractor(),
         ),
       )[FingerprintSettingsViewModel::class.java]
 
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsNavigationViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsNavigationViewModel.kt
index 8a694ae..73b2b1c 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsNavigationViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsNavigationViewModel.kt
@@ -21,7 +21,8 @@
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.viewModelScope
 import com.android.settings.biometrics.BiometricEnrollBase
-import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrolledFingerprintsInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -33,10 +34,11 @@
 /** A Viewmodel that represents the navigation of the FingerprintSettings activity. */
 class FingerprintSettingsNavigationViewModel(
   private val userId: Int,
-  private val fingerprintManagerInteractor: FingerprintManagerInteractor,
   private val backgroundDispatcher: CoroutineDispatcher,
   tokenInit: ByteArray?,
   challengeInit: Long?,
+  private val enrolledFingerprintsInteractor: EnrolledFingerprintsInteractor,
+  private val generateChallengeInteractor: GenerateChallengeInteractor,
 ) : ViewModel() {
 
   private var token = tokenInit
@@ -52,7 +54,7 @@
       _nextStep.update { LaunchConfirmDeviceCredential(userId) }
     } else {
       viewModelScope.launch {
-        if (fingerprintManagerInteractor.enrolledFingerprints.last()?.isEmpty() == true) {
+        if (enrolledFingerprintsInteractor.enrolledFingerprints.last()?.isEmpty() == true) {
           _nextStep.update { EnrollFirstFingerprint(userId, null, challenge, token) }
         } else {
           showSettingsHelper()
@@ -148,13 +150,13 @@
   }
 
   private suspend fun launchEnrollNextStep(gateKeeperPasswordHandle: Long?) {
-    fingerprintManagerInteractor.enrolledFingerprints.collect {
+    enrolledFingerprintsInteractor.enrolledFingerprints.collect {
       if (it?.isEmpty() == true) {
         _nextStep.update { EnrollFirstFingerprint(userId, gateKeeperPasswordHandle, null, null) }
       } else {
         viewModelScope.launch(backgroundDispatcher) {
           val challengePair =
-            fingerprintManagerInteractor.generateChallenge(gateKeeperPasswordHandle!!)
+            generateChallengeInteractor.generateChallenge(gateKeeperPasswordHandle!!)
           challenge = challengePair.first
           token = challengePair.second
 
@@ -174,10 +176,11 @@
 
   class FingerprintSettingsNavigationModelFactory(
     private val userId: Int,
-    private val interactor: FingerprintManagerInteractor,
     private val backgroundDispatcher: CoroutineDispatcher,
     private val token: ByteArray?,
     private val challenge: Long?,
+    private val enrolledFingerprintsInteractor: EnrolledFingerprintsInteractor,
+    private val generateChallengeInteractor: GenerateChallengeInteractor,
   ) : ViewModelProvider.Factory {
 
     @Suppress("UNCHECKED_CAST")
@@ -185,10 +188,11 @@
 
       return FingerprintSettingsNavigationViewModel(
         userId,
-        interactor,
         backgroundDispatcher,
         token,
         challenge,
+        enrolledFingerprintsInteractor,
+        generateChallengeInteractor,
       )
         as T
     }
diff --git a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsViewModel.kt b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsViewModel.kt
index cf8c527..c306c78 100644
--- a/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsViewModel.kt
+++ b/src/com/android/settings/biometrics/fingerprint2/ui/settings/viewmodel/FingerprintSettingsViewModel.kt
@@ -21,7 +21,12 @@
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.viewModelScope
-import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.AuthenitcateInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrolledFingerprintsInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RemoveFingerprintInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RenameFingerprintInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
 import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
 import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
@@ -49,9 +54,14 @@
 /** Models the UI state for fingerprint settings. */
 class FingerprintSettingsViewModel(
   private val userId: Int,
-  private val fingerprintManagerInteractor: FingerprintManagerInteractor,
   private val backgroundDispatcher: CoroutineDispatcher,
   private val navigationViewModel: FingerprintSettingsNavigationViewModel,
+  private val canEnrollFingerprintsInteractor: CanEnrollFingerprintsInteractor,
+  private val sensorInteractor: SensorInteractor,
+  private val authenticateInteractor: AuthenitcateInteractor,
+  private val renameFingerprintInteractor: RenameFingerprintInteractor,
+  private val removeFingerprintInteractor: RemoveFingerprintInteractor,
+  private val enrolledFingerprintsInteractor: EnrolledFingerprintsInteractor,
 ) : ViewModel() {
   private val _enrolledFingerprints: MutableStateFlow<List<FingerprintData>?> =
     MutableStateFlow(null)
@@ -62,19 +72,18 @@
 
   /** Represents the stream of the information of "Add Fingerprint" preference. */
   val addFingerprintPrefInfo: Flow<Pair<Boolean, Int>> =
-    _enrolledFingerprints.filterOnlyWhenSettingsIsShown().transform {
-      emit(
-        Pair(
-          fingerprintManagerInteractor.canEnrollFingerprints.first(),
-          fingerprintManagerInteractor.maxEnrollableFingerprints.first(),
-        )
-      )
+    _enrolledFingerprints.filterOnlyWhenSettingsIsShown().combine(
+      canEnrollFingerprintsInteractor.canEnrollFingerprints
+    ) { _, canEnrollFingerprints ->
+      Pair(canEnrollFingerprints, canEnrollFingerprintsInteractor.maxFingerprintsEnrollable())
     }
 
   /** Represents the stream of visibility of sfps preference. */
   val isSfpsPrefVisible: Flow<Boolean> =
-    _enrolledFingerprints.filterOnlyWhenSettingsIsShown().transform {
-      emit(fingerprintManagerInteractor.hasSideFps() == true && !it.isNullOrEmpty())
+    _enrolledFingerprints.filterOnlyWhenSettingsIsShown().combine(sensorInteractor.hasSideFps) {
+      fingerprints,
+      hasSideFps ->
+      hasSideFps && !fingerprints.isNullOrEmpty()
     }
 
   private val _isShowingDialog: MutableStateFlow<PreferenceViewModel?> = MutableStateFlow(null)
@@ -90,10 +99,10 @@
   private val _consumerShouldAuthenticate: MutableStateFlow<Boolean> = MutableStateFlow(false)
 
   private val _fingerprintSensorType: Flow<FingerprintSensorType> =
-    fingerprintManagerInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType }
+    sensorInteractor.sensorPropertiesInternal.filterNotNull().map { it.sensorType }
 
   private val _sensorNullOrEmpty: Flow<Boolean> =
-    fingerprintManagerInteractor.sensorPropertiesInternal.map { it == null }
+    sensorInteractor.sensorPropertiesInternal.map { it == null }
 
   private val _isLockedOut: MutableStateFlow<FingerprintAuthAttemptModel.Error?> =
     MutableStateFlow(null)
@@ -172,7 +181,7 @@
           while (it && navigationViewModel.nextStep.value is ShowSettings) {
             Log.d(TAG, "canAuthenticate authing")
             attemptingAuth()
-            when (val authAttempt = fingerprintManagerInteractor.authenticate()) {
+            when (val authAttempt = authenticateInteractor.authenticate()) {
               is FingerprintAuthAttemptModel.Success -> {
                 onAuthSuccess(authAttempt)
                 emit(authAttempt)
@@ -243,7 +252,7 @@
   /** A request to delete a fingerprint */
   fun deleteFingerprint(fp: FingerprintData) {
     viewModelScope.launch(backgroundDispatcher) {
-      if (fingerprintManagerInteractor.removeFingerprint(fp)) {
+      if (removeFingerprintInteractor.removeFingerprint(fp)) {
         updateEnrolledFingerprints()
       }
     }
@@ -252,7 +261,7 @@
   /** A request to rename a fingerprint */
   fun renameFingerprint(fp: FingerprintData, newName: String) {
     viewModelScope.launch {
-      fingerprintManagerInteractor.renameFingerprint(fp, newName)
+      renameFingerprintInteractor.renameFingerprint(fp, newName)
       updateEnrolledFingerprints()
     }
   }
@@ -271,7 +280,7 @@
   }
 
   private suspend fun updateEnrolledFingerprints() {
-    _enrolledFingerprints.update { fingerprintManagerInteractor.enrolledFingerprints.first() }
+    _enrolledFingerprints.update { enrolledFingerprintsInteractor.enrolledFingerprints.first() }
   }
 
   /** Used to indicate whether the consumer of the view model is ready for authentication. */
@@ -288,9 +297,14 @@
 
   class FingerprintSettingsViewModelFactory(
     private val userId: Int,
-    private val interactor: FingerprintManagerInteractor,
     private val backgroundDispatcher: CoroutineDispatcher,
     private val navigationViewModel: FingerprintSettingsNavigationViewModel,
+    private val canEnrollFingerprintsInteractor: CanEnrollFingerprintsInteractor,
+    private val sensorInteractor: SensorInteractor,
+    private val authenticateInteractor: AuthenitcateInteractor,
+    private val renameFingerprintInteractor: RenameFingerprintInteractor,
+    private val removeFingerprintInteractor: RemoveFingerprintInteractor,
+    private val enrolledFingerprintsInteractor: EnrolledFingerprintsInteractor,
   ) : ViewModelProvider.Factory {
 
     @Suppress("UNCHECKED_CAST")
@@ -298,9 +312,14 @@
 
       return FingerprintSettingsViewModel(
         userId,
-        interactor,
         backgroundDispatcher,
         navigationViewModel,
+        canEnrollFingerprintsInteractor,
+        sensorInteractor,
+        authenticateInteractor,
+        renameFingerprintInteractor,
+        removeFingerprintInteractor,
+        enrolledFingerprintsInteractor,
       )
         as T
     }
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java b/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java
index 4ff7136..398edb6 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsSpatialAudioController.java
@@ -39,8 +39,8 @@
 
 import com.android.settings.R;
 import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.bluetooth.BluetoothUtils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.LocalBluetoothProfile;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 import com.android.settingslib.flags.Flags;
 import com.android.settingslib.utils.ThreadUtils;
@@ -299,57 +299,14 @@
                         + " profiles: "
                         + mCachedDevice.getProfiles());
 
-        AudioDeviceAttributes saDevice = null;
-        for (LocalBluetoothProfile profile : mCachedDevice.getProfiles()) {
-            // pick first enabled profile that is compatible with spatial audio
-            if (SA_PROFILES.contains(profile.getProfileId())
-                    && profile.isEnabled(mCachedDevice.getDevice())) {
-                switch (profile.getProfileId()) {
-                    case BluetoothProfile.A2DP:
-                        saDevice =
-                                new AudioDeviceAttributes(
-                                        AudioDeviceAttributes.ROLE_OUTPUT,
-                                        AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
-                                        mCachedDevice.getAddress());
-                        break;
-                    case BluetoothProfile.LE_AUDIO:
-                        if (mAudioManager.getBluetoothAudioDeviceCategory(
-                                mCachedDevice.getAddress())
-                                == AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER) {
-                            saDevice =
-                                    new AudioDeviceAttributes(
-                                            AudioDeviceAttributes.ROLE_OUTPUT,
-                                            AudioDeviceInfo.TYPE_BLE_SPEAKER,
-                                            mCachedDevice.getAddress());
-                        } else {
-                            saDevice =
-                                    new AudioDeviceAttributes(
-                                            AudioDeviceAttributes.ROLE_OUTPUT,
-                                            AudioDeviceInfo.TYPE_BLE_HEADSET,
-                                            mCachedDevice.getAddress());
-                        }
-
-                        break;
-                    case BluetoothProfile.HEARING_AID:
-                        saDevice =
-                                new AudioDeviceAttributes(
-                                        AudioDeviceAttributes.ROLE_OUTPUT,
-                                        AudioDeviceInfo.TYPE_HEARING_AID,
-                                        mCachedDevice.getAddress());
-                        break;
-                    default:
-                        Log.i(
-                                TAG,
-                                "unrecognized profile for spatial audio: "
-                                        + profile.getProfileId());
-                        break;
-                }
-                break;
-            }
-        }
-        mAudioDevice = null;
+        AudioDeviceAttributes saDevice =
+                BluetoothUtils.getAudioDeviceAttributesForSpatialAudio(
+                        mCachedDevice,
+                        mAudioManager.getBluetoothAudioDeviceCategory(mCachedDevice.getAddress()));
         if (saDevice != null && mSpatializer.isAvailableForDevice(saDevice)) {
             mAudioDevice = saDevice;
+        } else {
+            mAudioDevice = null;
         }
 
         Log.d(
diff --git a/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java b/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java
index 5941344..be0f6f3 100644
--- a/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java
+++ b/src/com/android/settings/bluetooth/BluetoothFeatureProvider.java
@@ -20,6 +20,7 @@
 import android.bluetooth.BluetoothDevice;
 import android.content.ComponentName;
 import android.content.Context;
+import android.media.AudioManager;
 import android.media.Spatializer;
 import android.net.Uri;
 
@@ -28,6 +29,7 @@
 import androidx.preference.Preference;
 
 import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor;
 import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatter;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository;
@@ -98,6 +100,13 @@
             @NonNull BluetoothAdapter bluetoothAdapter,
             @NonNull LifecycleCoroutineScope scope);
 
+    /** Gets spatial audio interactor. */
+    @NonNull
+    SpatialAudioInteractor getSpatialAudioInteractor(
+            @NonNull Context context,
+            @NonNull AudioManager audioManager,
+            @NonNull LifecycleCoroutineScope scope);
+
     /** Gets device details fragment layout formatter. */
     @NonNull
     DeviceDetailsFragmentFormatter getDeviceDetailsFragmentFormatter(
diff --git a/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java
deleted file mode 100644
index ae6e740..0000000
--- a/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.java
+++ /dev/null
@@ -1,105 +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.settings.bluetooth;
-
-import static com.android.settings.bluetooth.utils.DeviceSettingUtilsKt.createDeviceSettingRepository;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.content.ComponentName;
-import android.content.Context;
-import android.media.AudioManager;
-import android.media.Spatializer;
-import android.net.Uri;
-
-import androidx.annotation.NonNull;
-import androidx.lifecycle.LifecycleCoroutineScope;
-import androidx.preference.Preference;
-
-import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatter;
-import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatterImpl;
-import com.android.settingslib.bluetooth.BluetoothUtils;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-
-import java.util.List;
-import java.util.Set;
-
-/**
- * Impl of {@link BluetoothFeatureProvider}
- */
-public class BluetoothFeatureProviderImpl implements BluetoothFeatureProvider {
-
-    @Override
-    public Uri getBluetoothDeviceSettingsUri(BluetoothDevice bluetoothDevice) {
-        final byte[] uriByte = bluetoothDevice.getMetadata(
-                BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI);
-        return uriByte == null ? null : Uri.parse(new String(uriByte));
-    }
-
-    @Override
-    public String getBluetoothDeviceControlUri(BluetoothDevice bluetoothDevice) {
-        return BluetoothUtils.getControlUriMetaData(bluetoothDevice);
-    }
-
-    @Override
-    public List<ComponentName> getRelatedTools() {
-        return null;
-    }
-
-    @Override
-    public Spatializer getSpatializer(Context context) {
-        AudioManager audioManager = context.getSystemService(AudioManager.class);
-        return audioManager.getSpatializer();
-    }
-
-    @Override
-    public List<Preference> getBluetoothExtraOptions(Context context,
-            CachedBluetoothDevice device) {
-        return ImmutableList.of();
-    }
-
-    @Override
-    public Set<String> getInvisibleProfilePreferenceKeys(
-            Context context, BluetoothDevice bluetoothDevice) {
-        return ImmutableSet.of();
-    }
-
-    @Override
-    @NonNull
-    public DeviceSettingRepository getDeviceSettingRepository(
-            @NonNull Context context,
-            @NonNull BluetoothAdapter bluetoothAdapter,
-            @NonNull LifecycleCoroutineScope scope) {
-        return createDeviceSettingRepository(context, bluetoothAdapter, scope);
-    }
-
-    @Override
-    @NonNull
-    public DeviceDetailsFragmentFormatter getDeviceDetailsFragmentFormatter(
-            @NonNull Context context,
-            @NonNull SettingsPreferenceFragment fragment,
-            @NonNull BluetoothAdapter bluetoothAdapter,
-            @NonNull CachedBluetoothDevice cachedDevice) {
-        return new DeviceDetailsFragmentFormatterImpl(
-                context, fragment, bluetoothAdapter, cachedDevice);
-    }
-}
diff --git a/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt
new file mode 100644
index 0000000..3a549c6
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothFeatureProviderImpl.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.settings.bluetooth
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.content.ComponentName
+import android.content.Context
+import android.media.AudioManager
+import android.media.Spatializer
+import android.net.Uri
+import androidx.lifecycle.LifecycleCoroutineScope
+import androidx.preference.Preference
+import com.android.settings.SettingsPreferenceFragment
+import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor
+import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractorImpl
+import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatter
+import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatterImpl
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository
+import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepositoryImpl
+import com.android.settingslib.media.data.repository.SpatializerRepositoryImpl
+import com.android.settingslib.media.domain.interactor.SpatializerInteractor
+import com.google.common.collect.ImmutableList
+import com.google.common.collect.ImmutableSet
+import kotlinx.coroutines.Dispatchers
+
+/** Impl of [BluetoothFeatureProvider] */
+open class BluetoothFeatureProviderImpl : BluetoothFeatureProvider {
+    override fun getBluetoothDeviceSettingsUri(bluetoothDevice: BluetoothDevice): Uri? {
+        val uriByte = bluetoothDevice.getMetadata(BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI)
+        return uriByte?.let { Uri.parse(String(it)) }
+    }
+
+    override fun getBluetoothDeviceControlUri(bluetoothDevice: BluetoothDevice): String? {
+        return BluetoothUtils.getControlUriMetaData(bluetoothDevice)
+    }
+
+    override fun getRelatedTools(): List<ComponentName>? {
+        return null
+    }
+
+    override fun getSpatializer(context: Context): Spatializer? {
+        val audioManager = context.getSystemService(AudioManager::class.java)
+        return audioManager.spatializer
+    }
+
+    override fun getBluetoothExtraOptions(
+        context: Context,
+        device: CachedBluetoothDevice
+    ): List<Preference>? {
+        return ImmutableList.of<Preference>()
+    }
+
+    override fun getInvisibleProfilePreferenceKeys(
+        context: Context,
+        bluetoothDevice: BluetoothDevice
+    ): Set<String> {
+        return ImmutableSet.of()
+    }
+
+    override fun getDeviceSettingRepository(
+        context: Context,
+        bluetoothAdapter: BluetoothAdapter,
+        scope: LifecycleCoroutineScope
+    ): DeviceSettingRepository =
+        DeviceSettingRepositoryImpl(context, bluetoothAdapter, scope, Dispatchers.IO)
+
+    override fun getSpatialAudioInteractor(
+        context: Context,
+        audioManager: AudioManager,
+        scope: LifecycleCoroutineScope
+    ): SpatialAudioInteractor {
+        return SpatialAudioInteractorImpl(
+            context, audioManager,
+            SpatializerInteractor(
+                SpatializerRepositoryImpl(
+                    audioManager.spatializer,
+                    Dispatchers.IO
+                )
+            ), scope, Dispatchers.IO)
+    }
+
+    override fun getDeviceDetailsFragmentFormatter(
+        context: Context,
+        fragment: SettingsPreferenceFragment,
+        bluetoothAdapter: BluetoothAdapter,
+        cachedDevice: CachedBluetoothDevice
+    ): DeviceDetailsFragmentFormatter {
+        return DeviceDetailsFragmentFormatterImpl(context, fragment, bluetoothAdapter, cachedDevice)
+    }
+}
diff --git a/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractor.kt b/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractor.kt
new file mode 100644
index 0000000..6b72b53
--- /dev/null
+++ b/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractor.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth.domain.interactor
+
+import android.content.Context
+import android.media.AudioManager
+import android.util.Log
+import com.android.settings.R
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
+import com.android.settingslib.media.domain.interactor.SpatializerInteractor
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Provides device setting for spatial audio. */
+interface SpatialAudioInteractor {
+    /** Gets device setting for spatial audio */
+    fun getDeviceSetting(
+        cachedDevice: CachedBluetoothDevice,
+    ): Flow<DeviceSettingModel?>
+}
+
+class SpatialAudioInteractorImpl(
+    private val context: Context,
+    private val audioManager: AudioManager,
+    private val spatializerInteractor: SpatializerInteractor,
+    private val coroutineScope: CoroutineScope,
+    private val backgroundCoroutineContext: CoroutineContext,
+) : SpatialAudioInteractor {
+    private val spatialAudioOffToggle =
+        ToggleModel(
+            context.getString(R.string.spatial_audio_multi_toggle_off),
+            DeviceSettingIcon.ResourceIcon(R.drawable.ic_spatial_audio_off))
+    private val spatialAudioOnToggle =
+        ToggleModel(
+            context.getString(R.string.spatial_audio_multi_toggle_on),
+            DeviceSettingIcon.ResourceIcon(R.drawable.ic_spatial_audio))
+    private val headTrackingOnToggle =
+        ToggleModel(
+            context.getString(R.string.spatial_audio_multi_toggle_head_tracking_on),
+            DeviceSettingIcon.ResourceIcon(R.drawable.ic_head_tracking))
+    private val changes = MutableSharedFlow<Unit>()
+
+    override fun getDeviceSetting(
+        cachedDevice: CachedBluetoothDevice,
+    ): Flow<DeviceSettingModel?> =
+        changes
+            .onStart { emit(Unit) }
+            .map { getSpatialAudioDeviceSettingModel(cachedDevice) }
+            .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = null)
+
+    private suspend fun getSpatialAudioDeviceSettingModel(
+        cachedDevice: CachedBluetoothDevice,
+    ): DeviceSettingModel? {
+        // TODO(b/343317785): use audio repository instead of calling AudioManager directly.
+        Log.i(TAG, "CachedDevice: $cachedDevice profiles: ${cachedDevice.profiles}")
+        val attributes =
+            BluetoothUtils.getAudioDeviceAttributesForSpatialAudio(
+                cachedDevice, audioManager.getBluetoothAudioDeviceCategory(cachedDevice.address))
+                ?: run {
+                    Log.i(TAG, "No audio profiles in cachedDevice: ${cachedDevice.address}.")
+                    return null
+                }
+
+        Log.i(TAG, "Audio device attributes for ${cachedDevice.address}: $attributes.")
+        val spatialAudioAvailable = spatializerInteractor.isSpatialAudioAvailable(attributes)
+        if (!spatialAudioAvailable) {
+            Log.i(TAG, "Spatial audio is not available for ${cachedDevice.address}")
+            return null
+        }
+        val headTrackingAvailable =
+            spatialAudioAvailable && spatializerInteractor.isHeadTrackingAvailable(attributes)
+        val toggles =
+            if (headTrackingAvailable) {
+                listOf(spatialAudioOffToggle, spatialAudioOnToggle, headTrackingOnToggle)
+            } else {
+                listOf(spatialAudioOffToggle, spatialAudioOnToggle)
+            }
+        val spatialAudioEnabled = spatializerInteractor.isSpatialAudioEnabled(attributes)
+        val headTrackingEnabled =
+            spatialAudioEnabled && spatializerInteractor.isHeadTrackingEnabled(attributes)
+
+        val activeIndex =
+            when {
+                headTrackingEnabled -> INDEX_HEAD_TRACKING_ENABLED
+                spatialAudioEnabled -> INDEX_SPATIAL_AUDIO_ON
+                else -> INDEX_SPATIAL_AUDIO_OFF
+            }
+        Log.i(
+            TAG,
+            "Head tracking available: $headTrackingAvailable, " +
+                "spatial audio enabled: $spatialAudioEnabled, " +
+                "head tracking enabled: $headTrackingEnabled")
+        return DeviceSettingModel.MultiTogglePreference(
+            cachedDevice = cachedDevice,
+            id = DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE,
+            title = context.getString(R.string.spatial_audio_multi_toggle_title),
+            toggles = toggles,
+            isActive = spatialAudioEnabled,
+            state = DeviceSettingStateModel.MultiTogglePreferenceState(activeIndex),
+            isAllowedChangingState = true,
+            updateState = { newState ->
+                coroutineScope.launch(backgroundCoroutineContext) {
+                    Log.i(TAG, "Update spatial audio state: $newState")
+                    when (newState.selectedIndex) {
+                        INDEX_SPATIAL_AUDIO_OFF -> {
+                            spatializerInteractor.setSpatialAudioEnabled(attributes, false)
+                        }
+                        INDEX_SPATIAL_AUDIO_ON -> {
+                            spatializerInteractor.setSpatialAudioEnabled(attributes, true)
+                            spatializerInteractor.setHeadTrackingEnabled(attributes, false)
+                        }
+                        INDEX_HEAD_TRACKING_ENABLED -> {
+                            spatializerInteractor.setSpatialAudioEnabled(attributes, true)
+                            spatializerInteractor.setHeadTrackingEnabled(attributes, true)
+                        }
+                    }
+                    changes.emit(Unit)
+                }
+            })
+    }
+
+    companion object {
+        private const val TAG = "SpatialAudioInteractorImpl"
+        private const val INDEX_SPATIAL_AUDIO_OFF = 0
+        private const val INDEX_SPATIAL_AUDIO_ON = 1
+        private const val INDEX_HEAD_TRACKING_ENABLED = 2
+    }
+}
diff --git a/src/com/android/settings/bluetooth/ui/composable/Icon.kt b/src/com/android/settings/bluetooth/ui/composable/Icon.kt
new file mode 100644
index 0000000..676bd14
--- /dev/null
+++ b/src/com/android/settings/bluetooth/ui/composable/Icon.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth.ui.composable
+
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.res.painterResource
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
+
+@Composable
+fun Icon(
+    icon: DeviceSettingIcon,
+    modifier: Modifier = Modifier,
+    tint: Color = LocalContentColor.current,
+) {
+    when (icon) {
+        is DeviceSettingIcon.BitmapIcon ->
+            androidx.compose.material3.Icon(
+                icon.bitmap.asImageBitmap(),
+                contentDescription = null,
+                modifier = modifier,
+                tint = LocalContentColor.current)
+        is DeviceSettingIcon.ResourceIcon ->
+            androidx.compose.material3.Icon(
+                painterResource(icon.resId),
+                contentDescription = null,
+                modifier = modifier,
+                tint = tint)
+        else -> {}
+    }
+}
diff --git a/src/com/android/settings/bluetooth/ui/composable/MultiTogglePreferenceGroup.kt b/src/com/android/settings/bluetooth/ui/composable/MultiTogglePreferenceGroup.kt
index b42e7d0..8fe3c25 100644
--- a/src/com/android/settings/bluetooth/ui/composable/MultiTogglePreferenceGroup.kt
+++ b/src/com/android/settings/bluetooth/ui/composable/MultiTogglePreferenceGroup.kt
@@ -51,7 +51,6 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.asImageBitmap
 import androidx.compose.ui.layout.boundsInParent
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.platform.LocalDensity
@@ -67,6 +66,7 @@
 import androidx.compose.ui.unit.sp
 import androidx.compose.ui.window.DialogProperties
 import com.android.settings.R
+import com.android.settings.bluetooth.ui.composable.Icon as DeviceSettingComposeIcon
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
 import com.android.settingslib.spa.framework.theme.SettingsDimension
@@ -97,35 +97,29 @@
                     Surface(
                         modifier = Modifier.height(64.dp),
                         shape = RoundedCornerShape(28.dp),
-                        color = MaterialTheme.colorScheme.surface
-                    ) {
-                        Button(
-                            modifier =
-                                Modifier.fillMaxSize().padding(8.dp).semantics {
-                                    role = Role.Switch
-                                    toggleableState =
-                                        if (preferenceModel.isActive) {
-                                            ToggleableState.On
-                                        } else {
-                                            ToggleableState.Off
-                                        }
-                                    contentDescription = preferenceModel.title
-                                },
-                            onClick = { settingIdForPopUp = preferenceModel.id },
-                            shape = RoundedCornerShape(20.dp),
-                            colors = getButtonColors(preferenceModel.isActive),
-                            contentPadding = PaddingValues(0.dp)
-                        ) {
-                            Icon(
-                                preferenceModel.toggles[preferenceModel.state.selectedIndex]
-                                    .icon
-                                    .asImageBitmap(),
-                                contentDescription = null,
-                                modifier = Modifier.size(24.dp),
-                                tint = LocalContentColor.current
-                            )
+                        color = MaterialTheme.colorScheme.surface) {
+                            Button(
+                                modifier =
+                                    Modifier.fillMaxSize().padding(8.dp).semantics {
+                                        role = Role.Switch
+                                        toggleableState =
+                                            if (preferenceModel.isActive) {
+                                                ToggleableState.On
+                                            } else {
+                                                ToggleableState.Off
+                                            }
+                                        contentDescription = preferenceModel.title
+                                    },
+                                onClick = { settingIdForPopUp = preferenceModel.id },
+                                shape = RoundedCornerShape(20.dp),
+                                colors = getButtonColors(preferenceModel.isActive),
+                                contentPadding = PaddingValues(0.dp)) {
+                                    DeviceSettingComposeIcon(
+                                        preferenceModel.toggles[preferenceModel.state.selectedIndex]
+                                            .icon,
+                                        modifier = Modifier.size(24.dp))
+                                }
                         }
-                    }
                 }
                 Row { Text(text = preferenceModel.title, fontSize = 12.sp) }
             }
@@ -173,8 +167,7 @@
                             Icon(
                                 painterResource(id = R.drawable.ic_close),
                                 null,
-                                tint = MaterialTheme.colorScheme.inverseSurface
-                            )
+                                tint = MaterialTheme.colorScheme.inverseSurface)
                         }
                         Box(modifier = Modifier.padding(horizontal = 8.dp, vertical = 20.dp)) {
                             dialogContent(multiTogglePreference)
@@ -182,8 +175,7 @@
                     }
                 },
             )
-        }
-    )
+        })
 }
 
 @Composable
@@ -208,9 +200,7 @@
                 Modifier.fillMaxWidth()
                     .height(64.dp)
                     .background(
-                        MaterialTheme.colorScheme.surface,
-                        shape = RoundedCornerShape(28.dp)
-                    ),
+                        MaterialTheme.colorScheme.surface, shape = RoundedCornerShape(28.dp)),
             verticalAlignment = Alignment.CenterVertically,
             horizontalArrangement = Arrangement.SpaceEvenly,
         ) {
@@ -224,9 +214,7 @@
                                     .width(selectedRect!!.width.toDp())
                                     .background(
                                         MaterialTheme.colorScheme.tertiaryContainer,
-                                        shape = RoundedCornerShape(20.dp)
-                                    )
-                        )
+                                        shape = RoundedCornerShape(20.dp)))
                     }
                 }
                 Row {
@@ -238,9 +226,7 @@
                                     .padding(horizontal = 8.dp)
                                     .height(48.dp)
                                     .background(
-                                        Color.Transparent,
-                                        shape = RoundedCornerShape(28.dp)
-                                    )
+                                        Color.Transparent, shape = RoundedCornerShape(28.dp))
                                     .onGloballyPositioned { layoutCoordinates ->
                                         if (selected) {
                                             selectedRect = layoutCoordinates.boundsInParent()
@@ -252,22 +238,16 @@
                             Button(
                                 onClick = {
                                     multiTogglePreference.updateState(
-                                        DeviceSettingStateModel.MultiTogglePreferenceState(idx)
-                                    )
+                                        DeviceSettingStateModel.MultiTogglePreferenceState(idx))
                                 },
                                 modifier = Modifier.fillMaxSize(),
                                 colors =
                                     ButtonDefaults.buttonColors(
                                         containerColor = Color.Transparent,
-                                        contentColor = LocalContentColor.current
-                                    ),
+                                        contentColor = LocalContentColor.current),
                             ) {
-                                Icon(
-                                    bitmap = toggle.icon.asImageBitmap(),
-                                    null,
-                                    modifier = Modifier.size(24.dp),
-                                    tint = LocalContentColor.current
-                                )
+                                DeviceSettingComposeIcon(
+                                    toggle.icon, modifier = Modifier.size(24.dp))
                             }
                         }
                     }
@@ -285,8 +265,7 @@
                     text = toggle.label,
                     fontSize = 12.sp,
                     textAlign = TextAlign.Center,
-                    modifier = Modifier.weight(1f).padding(horizontal = 8.dp)
-                )
+                    modifier = Modifier.weight(1f).padding(horizontal = 8.dp))
             }
         }
     }
diff --git a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt
index 3b77aae..b75579d 100644
--- a/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt
+++ b/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatter.kt
@@ -18,20 +18,19 @@
 
 import android.bluetooth.BluetoothAdapter
 import android.content.Context
+import android.media.AudioManager
 import android.util.Log
 import androidx.compose.foundation.layout.size
-import androidx.compose.material3.Icon
-import androidx.compose.material3.LocalContentColor
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.asImageBitmap
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.lifecycle.lifecycleScope
 import androidx.preference.Preference
 import com.android.settings.SettingsPreferenceFragment
+import com.android.settings.bluetooth.ui.composable.Icon
 import com.android.settings.bluetooth.ui.composable.MultiTogglePreferenceGroup
 import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout
 import com.android.settings.bluetooth.ui.viewmodel.BluetoothDeviceDetailsViewModel
@@ -42,7 +41,6 @@
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
 import com.android.settingslib.spa.framework.theme.SettingsDimension
-import com.android.settingslib.spa.widget.preference.Preference as SpaPreference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.preference.SwitchPreference
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
@@ -52,6 +50,8 @@
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.runBlocking
+import com.android.settingslib.spa.widget.preference.Preference as SpaPreference
+
 
 /** Handles device details fragment layout according to config. */
 interface DeviceDetailsFragmentFormatter {
@@ -72,19 +72,24 @@
     private val repository =
         featureFactory.bluetoothFeatureProvider.getDeviceSettingRepository(
             context, bluetoothAdapter, fragment.lifecycleScope)
+    private val spatialAudioInteractor =
+        featureFactory.bluetoothFeatureProvider.getSpatialAudioInteractor(
+            context, context.getSystemService(AudioManager::class.java), fragment.lifecycleScope)
     private val viewModel: BluetoothDeviceDetailsViewModel =
         ViewModelProvider(
                 fragment,
                 BluetoothDeviceDetailsViewModel.Factory(
                     repository,
+                    spatialAudioInteractor,
                     cachedDevice,
                 ))
             .get(BluetoothDeviceDetailsViewModel::class.java)
 
     override fun getVisiblePreferenceKeysForMainPage(): List<String>? = runBlocking {
-        viewModel.getItems()?.filterIsInstance<DeviceSettingConfigItemModel.BuiltinItem>()?.map {
-            it.preferenceKey
-        }
+        viewModel
+            .getItems()
+            ?.filterIsInstance<DeviceSettingConfigItemModel.BuiltinItem>()
+            ?.mapNotNull { it.preferenceKey }
     }
 
     /** Updates bluetooth device details fragment layout. */
@@ -208,12 +213,8 @@
 
     @Composable
     private fun deviceSettingIcon(model: DeviceSettingModel.ActionSwitchPreference) {
-        model.icon?.let { bitmap ->
-            Icon(
-                bitmap.asImageBitmap(),
-                contentDescription = null,
-                modifier = Modifier.size(SettingsDimension.itemIconSize),
-                tint = LocalContentColor.current)
+        model.icon?.let { icon ->
+            Icon(icon, modifier = Modifier.size(SettingsDimension.itemIconSize))
         }
     }
 
diff --git a/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModel.kt b/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModel.kt
index 1c48614..befff83 100644
--- a/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModel.kt
+++ b/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModel.kt
@@ -19,6 +19,7 @@
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.viewModelScope
+import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor
 import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout
 import com.android.settings.bluetooth.ui.layout.DeviceSettingLayoutRow
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
@@ -29,6 +30,7 @@
 import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.async
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flowOf
@@ -37,6 +39,7 @@
 
 class BluetoothDeviceDetailsViewModel(
     private val deviceSettingRepository: DeviceSettingRepository,
+    private val spatialAudioInteractor: SpatialAudioInteractor,
     private val cachedDevice: CachedBluetoothDevice,
 ) : ViewModel() {
     private val items =
@@ -46,8 +49,16 @@
 
     suspend fun getItems(): List<DeviceSettingConfigItemModel>? = items.await()?.mainItems
 
-    fun getDeviceSetting(cachedDevice: CachedBluetoothDevice, @DeviceSettingId settingId: Int) =
-        deviceSettingRepository.getDeviceSetting(cachedDevice, settingId)
+    fun getDeviceSetting(
+        cachedDevice: CachedBluetoothDevice,
+        @DeviceSettingId settingId: Int
+    ): Flow<DeviceSettingModel?> {
+        return when (settingId) {
+            DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE ->
+                spatialAudioInteractor.getDeviceSetting(cachedDevice)
+            else -> deviceSettingRepository.getDeviceSetting(cachedDevice, settingId)
+        }
+    }
 
     suspend fun getLayout(): DeviceSettingLayout? {
         val configItems = getItems() ?: return null
@@ -93,11 +104,14 @@
 
     class Factory(
         private val deviceSettingRepository: DeviceSettingRepository,
+        private val spatialAudioInteractor: SpatialAudioInteractor,
         private val cachedDevice: CachedBluetoothDevice,
     ) : ViewModelProvider.Factory {
         override fun <T : ViewModel> create(modelClass: Class<T>): T {
             @Suppress("UNCHECKED_CAST")
-            return BluetoothDeviceDetailsViewModel(deviceSettingRepository, cachedDevice) as T
+            return BluetoothDeviceDetailsViewModel(
+                deviceSettingRepository, spatialAudioInteractor, cachedDevice)
+                as T
         }
     }
 
diff --git a/src/com/android/settings/bluetooth/utils/DeviceSettingUtils.kt b/src/com/android/settings/bluetooth/utils/DeviceSettingUtils.kt
deleted file mode 100644
index 1bb8f20..0000000
--- a/src/com/android/settings/bluetooth/utils/DeviceSettingUtils.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.bluetooth.utils
-
-import android.bluetooth.BluetoothAdapter
-import android.content.Context
-import androidx.lifecycle.LifecycleCoroutineScope
-import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepositoryImpl
-import kotlinx.coroutines.Dispatchers
-
-fun createDeviceSettingRepository(
-    context: Context,
-    bluetoothAdapter: BluetoothAdapter,
-    coroutineScope: LifecycleCoroutineScope
-) = DeviceSettingRepositoryImpl(context, bluetoothAdapter, coroutineScope, Dispatchers.IO)
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java
index f451f0a..09940b3 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.drawable.Drawable;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.text.TextUtils;
@@ -46,9 +47,13 @@
 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnCreate;
 import com.android.settingslib.core.lifecycle.events.OnDestroy;
 import com.android.settingslib.core.lifecycle.events.OnResume;
+import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
 import com.android.settingslib.widget.FooterPreference;
+import com.android.settingslib.widget.SettingsSpinnerAdapter;
+import com.android.settingslib.widget.SettingsSpinnerPreference;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -58,7 +63,7 @@
 
 /** Controller for battery usage breakdown preference group. */
 public class BatteryUsageBreakdownController extends BasePreferenceController
-        implements LifecycleObserver, OnResume, OnDestroy {
+        implements LifecycleObserver, OnResume, OnDestroy, OnCreate, OnSaveInstanceState {
     private static final String TAG = "BatteryUsageBreakdownController";
     private static final String ROOT_PREFERENCE_KEY = "battery_usage_breakdown";
     private static final String FOOTER_PREFERENCE_KEY = "battery_usage_footer";
@@ -67,6 +72,7 @@
     private static final String PACKAGE_NAME_NONE = "none";
     private static final String SLOT_TIMESTAMP = "slot_timestamp";
     private static final String ANOMALY_KEY = "anomaly_key";
+    private static final String KEY_SPINNER_POSITION = "spinner_position";
     private static final List<BatteryDiffEntry> EMPTY_ENTRY_LIST = new ArrayList<>();
 
     private static int sUiMode = Configuration.UI_MODE_NIGHT_UNDEFINED;
@@ -78,12 +84,12 @@
 
     @VisibleForTesting final Map<String, Preference> mPreferenceCache = new ArrayMap<>();
 
-    private int mSpinnerPosition;
     private String mSlotInformation;
+    private SettingsSpinnerPreference mSpinnerPreference;
+    private SettingsSpinnerAdapter<CharSequence> mSpinnerAdapter;
 
     @VisibleForTesting Context mPrefContext;
     @VisibleForTesting PreferenceCategory mRootPreference;
-    @VisibleForTesting SpinnerPreference mSpinnerPreference;
     @VisibleForTesting PreferenceGroup mAppListPreferenceGroup;
     @VisibleForTesting FooterPreference mFooterPreference;
     @VisibleForTesting BatteryDiffData mBatteryDiffData;
@@ -92,6 +98,7 @@
     @VisibleForTesting String mPercentLessThanThresholdContentDescription;
     @VisibleForTesting boolean mIsHighlightSlot;
     @VisibleForTesting int mAnomalyKeyNumber;
+    @VisibleForTesting int mSpinnerPosition;
     @VisibleForTesting String mAnomalyEntryKey;
     @VisibleForTesting String mAnomalyHintString;
     @VisibleForTesting String mAnomalyHintPrefKey;
@@ -111,6 +118,15 @@
     }
 
     @Override
+    public void onCreate(Bundle savedInstanceState) {
+        if (savedInstanceState == null) {
+            return;
+        }
+        mSpinnerPosition = savedInstanceState.getInt(KEY_SPINNER_POSITION, mSpinnerPosition);
+        Log.d(TAG, "onCreate() spinnerPosition=" + mSpinnerPosition);
+    }
+
+    @Override
     public void onResume() {
         final int currentUiMode =
                 mContext.getResources().getConfiguration().uiMode
@@ -140,6 +156,15 @@
         return false;
     }
 
+    @Override
+    public void onSaveInstanceState(Bundle savedInstanceState) {
+        if (savedInstanceState == null) {
+            return;
+        }
+        savedInstanceState.putInt(KEY_SPINNER_POSITION, mSpinnerPosition);
+        Log.d(TAG, "onSaveInstanceState() spinnerPosition=" + mSpinnerPosition);
+    }
+
     private boolean isAnomalyBatteryDiffEntry(BatteryDiffEntry entry) {
         return mIsHighlightSlot
                 && mAnomalyEntryKey != null
@@ -218,11 +243,14 @@
                         formatPercentage);
 
         mAppListPreferenceGroup.setOrderingAsAdded(false);
-        mSpinnerPreference.initializeSpinner(
+        mSpinnerAdapter = new SettingsSpinnerAdapter<>(mPrefContext);
+        mSpinnerAdapter.addAll(
                 new String[] {
                     mPrefContext.getString(R.string.battery_usage_spinner_view_by_apps),
                     mPrefContext.getString(R.string.battery_usage_spinner_view_by_systems)
-                },
+                });
+        mSpinnerPreference.setAdapter(mSpinnerAdapter);
+        mSpinnerPreference.setOnItemSelectedListener(
                 new AdapterView.OnItemSelectedListener() {
                     @Override
                     public void onItemSelected(
@@ -244,6 +272,7 @@
                     @Override
                     public void onNothingSelected(AdapterView<?> parent) {}
                 });
+        mSpinnerPreference.setSelection(mSpinnerPosition);
     }
 
     /**
diff --git a/src/com/android/settings/fuelgauge/batteryusage/SpinnerPreference.java b/src/com/android/settings/fuelgauge/batteryusage/SpinnerPreference.java
deleted file mode 100644
index 886d00d..0000000
--- a/src/com/android/settings/fuelgauge/batteryusage/SpinnerPreference.java
+++ /dev/null
@@ -1,134 +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.settings.fuelgauge.batteryusage;
-
-import android.content.Context;
-import android.os.Parcelable;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.widget.AdapterView;
-import android.widget.Spinner;
-
-import androidx.preference.Preference;
-import androidx.preference.PreferenceViewHolder;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.settings.R;
-import com.android.settingslib.widget.SettingsSpinnerAdapter;
-
-/** A preference which contains a spinner. */
-public class SpinnerPreference extends Preference {
-    private static final String TAG = "SpinnerPreference";
-
-    private AdapterView.OnItemSelectedListener mOnItemSelectedListener;
-
-    @VisibleForTesting Spinner mSpinner;
-    @VisibleForTesting String[] mItems;
-    @VisibleForTesting int mSavedSpinnerPosition;
-
-    public SpinnerPreference(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        setLayoutResource(R.layout.preference_spinner);
-    }
-
-    void initializeSpinner(
-            String[] items, AdapterView.OnItemSelectedListener onItemSelectedListener) {
-        mItems = items;
-        mOnItemSelectedListener = onItemSelectedListener;
-    }
-
-    @Override
-    public void onBindViewHolder(PreferenceViewHolder view) {
-        if (mSpinner != null) {
-            return;
-        }
-
-        mSpinner = (Spinner) view.findViewById(R.id.spinner);
-        mSpinner.setAdapter(new SpinnerAdapter(getContext(), mItems));
-        mSpinner.setSelection(mSavedSpinnerPosition);
-        mSpinner.setLongClickable(false);
-        if (mOnItemSelectedListener != null) {
-            mSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
-        }
-    }
-
-    @Override
-    protected Parcelable onSaveInstanceState() {
-        if (mSpinner == null) {
-            return super.onSaveInstanceState();
-        }
-        Log.d(TAG, "onSaveInstanceState() spinnerPosition=" + mSpinner.getSelectedItemPosition());
-        return new SavedState(super.onSaveInstanceState(), mSpinner.getSelectedItemPosition());
-    }
-
-    @Override
-    protected void onRestoreInstanceState(Parcelable state) {
-        if (state == null || state == BaseSavedState.EMPTY_STATE) {
-            super.onRestoreInstanceState(state);
-            return;
-        }
-        if (!(state instanceof SavedState)) {
-            // To avoid the IllegalArgumentException, return the BaseSavedState.EMPTY_STATE.
-            super.onRestoreInstanceState(BaseSavedState.EMPTY_STATE);
-            return;
-        }
-        SavedState savedState = (SavedState) state;
-        super.onRestoreInstanceState(savedState.getSuperState());
-        mSavedSpinnerPosition = savedState.getSpinnerPosition();
-        if (mOnItemSelectedListener != null) {
-            mOnItemSelectedListener.onItemSelected(
-                    /* parent= */ null,
-                    /* view= */ null,
-                    savedState.getSpinnerPosition(),
-                    /* id= */ 0);
-        }
-        Log.d(TAG, "onRestoreInstanceState() spinnerPosition=" + savedState.getSpinnerPosition());
-    }
-
-    @VisibleForTesting
-    static class SavedState extends BaseSavedState {
-        private int mSpinnerPosition;
-
-        SavedState(Parcelable superState, int spinnerPosition) {
-            super(superState);
-            mSpinnerPosition = spinnerPosition;
-        }
-
-        int getSpinnerPosition() {
-            return mSpinnerPosition;
-        }
-    }
-
-    private static class SpinnerAdapter extends SettingsSpinnerAdapter<CharSequence> {
-        private final String[] mItems;
-
-        SpinnerAdapter(Context context, String[] items) {
-            super(context);
-            mItems = items;
-        }
-
-        @Override
-        public int getCount() {
-            return mItems.length;
-        }
-
-        @Override
-        public CharSequence getItem(int position) {
-            return mItems[position];
-        }
-    }
-}
diff --git a/src/com/android/settings/network/telephony/NetworkSelectRepository.kt b/src/com/android/settings/network/telephony/NetworkSelectRepository.kt
index 1f5fbc2..d95c90e 100644
--- a/src/com/android/settings/network/telephony/NetworkSelectRepository.kt
+++ b/src/com/android/settings/network/telephony/NetworkSelectRepository.kt
@@ -18,8 +18,10 @@
 
 import android.content.Context
 import android.telephony.AccessNetworkConstants
+import android.telephony.CarrierConfigManager
 import android.telephony.NetworkRegistrationInfo
 import android.telephony.TelephonyManager
+import android.telephony.satellite.SatelliteManager
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.lifecycleScope
@@ -28,9 +30,11 @@
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
-class NetworkSelectRepository(context: Context, subId: Int) {
+class NetworkSelectRepository(context: Context, private val subId: Int) {
     private val telephonyManager =
         context.getSystemService(TelephonyManager::class.java)!!.createForSubscriptionId(subId)
+    private val satelliteManager = context.getSystemService(SatelliteManager::class.java)
+    private val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)
 
     data class NetworkRegistrationAndForbiddenInfo(
         val networkList: List<NetworkRegistrationInfo>,
@@ -55,10 +59,21 @@
         if (telephonyManager.dataState != TelephonyManager.DATA_CONNECTED) return null
         // Try to get the network registration states
         val serviceState = telephonyManager.serviceState ?: return null
-        val networkList = serviceState.getNetworkRegistrationInfoListForTransportType(
+        var networkList = serviceState.getNetworkRegistrationInfoListForTransportType(
             AccessNetworkConstants.TRANSPORT_TYPE_WWAN
         )
         if (networkList.isEmpty()) return null
+
+        val satellitePlmn = getSatellitePlmns()
+        // If connected network is Satellite, filter out
+        if (satellitePlmn.isNotEmpty()) {
+            val filteredNetworkList = networkList.filter {
+                val cellIdentity = it.cellIdentity
+                val plmn = cellIdentity?.plmn
+                plmn != null && !satellitePlmn.contains(plmn)
+            }
+            networkList = filteredNetworkList
+        }
         // Due to the aggregation of cell between carriers, it's possible to get CellIdentity
         // containing forbidden PLMN.
         // Getting current network from ServiceState is no longer a good idea.
@@ -72,4 +87,24 @@
     private fun getForbiddenPlmns(): List<String> {
         return telephonyManager.forbiddenPlmns?.toList() ?: emptyList()
     }
+
+    /**
+     * Update satellite PLMNs from the satellite framework.
+     */
+    private fun getSatellitePlmns(): List<String> {
+        val config = carrierConfigManager.getConfigForSubId(
+            subId,
+            CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL
+        )
+
+        val shouldFilter = config.getBoolean(
+            CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL,
+            true)
+
+        return if (shouldFilter) {
+            satelliteManager.getSatellitePlmnsForCarrier(subId)
+        } else {
+            emptyList();
+        }
+    }
 }
diff --git a/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java b/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java
index 200a47b..778c788 100644
--- a/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java
+++ b/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java
@@ -226,7 +226,10 @@
                         .setArguments(channelArgs)
                         .setUserHandle(UserHandle.of(mAppRow.userId))
                         .setTitleRes(com.android.settings.R.string.notification_channel_title)
-                        .setSourceMetricsCategory(SettingsEnums.DND_APPS_BYPASSING)
+                        .setSourceMetricsCategory(
+                                android.app.Flags.modesUi()
+                                    ? SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APP_CHANNELS
+                                        : SettingsEnums.DND_APPS_BYPASSING)
                         .launch();
                 return true;
             });
diff --git a/src/com/android/settings/notification/app/AppChannelsBypassingDndSettings.java b/src/com/android/settings/notification/app/AppChannelsBypassingDndSettings.java
index 4fab7e2..b5e2b13 100644
--- a/src/com/android/settings/notification/app/AppChannelsBypassingDndSettings.java
+++ b/src/com/android/settings/notification/app/AppChannelsBypassingDndSettings.java
@@ -40,7 +40,9 @@
 
     @Override
     public int getMetricsCategory() {
-        return SettingsEnums.DND_APPS_BYPASSING;
+        return android.app.Flags.modesUi()
+                ? SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APP_CHANNELS
+                : SettingsEnums.DND_APPS_BYPASSING;
     }
 
     @Override
diff --git a/src/com/android/settings/notification/modes/AbstractZenModeHeaderController.java b/src/com/android/settings/notification/modes/AbstractZenModeHeaderController.java
index 81b53cc..7cc67cc 100644
--- a/src/com/android/settings/notification/modes/AbstractZenModeHeaderController.java
+++ b/src/com/android/settings/notification/modes/AbstractZenModeHeaderController.java
@@ -61,13 +61,6 @@
         LayoutPreference preference = checkNotNull(screen.findPreference(getPreferenceKey()));
         preference.setSelectable(false);
 
-        if (mHeaderController == null) {
-            mHeaderController = EntityHeaderController.newInstance(
-                    mFragment.getActivity(),
-                    mFragment,
-                    preference.findViewById(R.id.entity_header));
-        }
-
         ImageView iconView = checkNotNull(preference.findViewById(R.id.entity_header_icon));
         ViewGroup.LayoutParams layoutParams = iconView.getLayoutParams();
         if (layoutParams.width != iconSizePx || layoutParams.height != iconSizePx) {
@@ -75,6 +68,14 @@
             layoutParams.height = iconSizePx;
             iconView.setLayoutParams(layoutParams);
         }
+
+        if (mHeaderController == null) {
+            mHeaderController = EntityHeaderController.newInstance(
+                    mFragment.getActivity(),
+                    mFragment,
+                    preference.findViewById(R.id.entity_header));
+            mHeaderController.done(false); // Make the space for the (unused) name go away.
+        }
     }
 
     protected void updateIcon(Preference preference, @NonNull ZenMode zenMode,
diff --git a/src/com/android/settings/notification/modes/CircularIconsPreference.java b/src/com/android/settings/notification/modes/CircularIconsPreference.java
index 0766ccd..5a89352 100644
--- a/src/com/android/settings/notification/modes/CircularIconsPreference.java
+++ b/src/com/android/settings/notification/modes/CircularIconsPreference.java
@@ -16,233 +16,72 @@
 
 package com.android.settings.notification.modes;
 
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
 import static com.google.common.base.Preconditions.checkNotNull;
 
 import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
 
 import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
 import androidx.preference.PreferenceViewHolder;
 
 import com.android.settings.R;
 import com.android.settingslib.RestrictedPreference;
 
 import com.google.common.base.Equivalence;
-import com.google.common.collect.ImmutableList;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.List;
-import java.util.concurrent.Executor;
 
 public class CircularIconsPreference extends RestrictedPreference {
 
-    private static final float DISABLED_ITEM_ALPHA = 0.3f;
-
-    record LoadedIcons(ImmutableList<Drawable> icons, int extraItems) { }
-
-    private Executor mUiExecutor;
-
-    // Chronologically, fields will be set top-to-bottom.
-    @Nullable private CircularIconSet<?> mIconSet;
-    @Nullable private ListenableFuture<List<Drawable>> mPendingLoadIconsFuture;
-    @Nullable private LoadedIcons mLoadedIcons;
+    private CircularIconSet<?> mIconSet = CircularIconSet.EMPTY;
 
     public CircularIconsPreference(Context context) {
         super(context);
-        init(context);
-    }
-
-    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
-    public CircularIconsPreference(Context context, Executor uiExecutor) {
-        this(context);
-        mUiExecutor = uiExecutor;
+        init();
     }
 
     public CircularIconsPreference(Context context, AttributeSet attrs) {
         super(context, attrs);
-        init(context);
+        init();
     }
 
     public CircularIconsPreference(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        init(context);
+        init();
     }
 
     public CircularIconsPreference(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        init(context);
+        init();
     }
 
-    private void init(Context context) {
-        mUiExecutor = context.getMainExecutor();
+    private void init() {
         setLayoutResource(R.layout.preference_circular_icons);
     }
 
-    <T> void displayIcons(CircularIconSet<T> iconSet) {
-        displayIcons(iconSet, null);
+    <T> void setIcons(CircularIconSet<T> iconSet) {
+        setIcons(iconSet, null);
     }
 
-    <T> void displayIcons(CircularIconSet<T> iconSet, @Nullable Equivalence<T> itemEquivalence) {
-        if (mIconSet != null && mIconSet.hasSameItemsAs(iconSet, itemEquivalence)) {
+    <T> void setIcons(CircularIconSet<T> iconSet, @Nullable Equivalence<T> itemEquivalence) {
+        if (mIconSet.hasSameItemsAs(iconSet, itemEquivalence)) {
             return;
         }
+
         mIconSet = iconSet;
-
-        mLoadedIcons = null;
-        if (mPendingLoadIconsFuture != null) {
-            mPendingLoadIconsFuture.cancel(true);
-            mPendingLoadIconsFuture = null;
-        }
-
         notifyChanged();
     }
 
     @Override
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
+        CircularIconsView iconContainer = checkNotNull(
+                (CircularIconsView) holder.findViewById(R.id.circles_container));
 
-        LinearLayout iconContainer = checkNotNull(
-                (LinearLayout) holder.findViewById(R.id.circles_container));
-        bindIconContainer(iconContainer);
-    }
-
-    private void bindIconContainer(LinearLayout container) {
-        if (mLoadedIcons != null) {
-            // We have the icons ready to display already, show them.
-            setDrawables(container, mLoadedIcons);
-        } else if (mIconSet != null) {
-            // We know what icons we want, but haven't yet loaded them.
-            if (mIconSet.size() == 0) {
-                container.setVisibility(View.GONE);
-                return;
-            }
-            container.setVisibility(View.VISIBLE);
-            if (container.getMeasuredWidth() != 0) {
-                startLoadingIcons(container, mIconSet);
-            } else {
-                container.getViewTreeObserver().addOnGlobalLayoutListener(
-                        new ViewTreeObserver.OnGlobalLayoutListener() {
-                            @Override
-                            public void onGlobalLayout() {
-                                container.getViewTreeObserver().removeOnGlobalLayoutListener(this);
-                                startLoadingIcons(container, mIconSet);
-                            }
-                        }
-                );
-            }
-        }
-    }
-
-    private void startLoadingIcons(LinearLayout container, CircularIconSet<?> iconSet) {
-        Resources res = getContext().getResources();
-        int availableSpace = container.getMeasuredWidth();
-        int iconHorizontalSpace = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter)
-                + res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_margin_between);
-        int numIconsThatFit = availableSpace / iconHorizontalSpace;
-
-        List<ListenableFuture<Drawable>> iconFutures;
-        int extraItems;
-        if (iconSet.size() > numIconsThatFit) {
-            // Reserve one space for the (+xx) textview.
-            int numIconsToShow = numIconsThatFit - 1;
-            if (numIconsToShow < 0) {
-                numIconsToShow = 0;
-            }
-            iconFutures = iconSet.getIcons(numIconsToShow);
-            extraItems = iconSet.size() - numIconsToShow;
-        } else {
-            // Fit exactly or with remaining space.
-            iconFutures = iconSet.getIcons();
-            extraItems = 0;
-        }
-
-        // Display icons when all are ready (more consistent than randomly loading).
-        mPendingLoadIconsFuture = Futures.allAsList(iconFutures);
-        FutureUtil.whenDone(
-                mPendingLoadIconsFuture,
-                icons -> {
-                    mLoadedIcons = new LoadedIcons(ImmutableList.copyOf(icons), extraItems);
-                    notifyChanged(); // So that view is rebound and icons actually shown.
-                },
-                mUiExecutor);
-    }
-
-    private void setDrawables(LinearLayout container, LoadedIcons loadedIcons) {
-        // Rearrange child views until we have <numImages> ImageViews...
-        LayoutInflater inflater = LayoutInflater.from(getContext());
-        int numImages = loadedIcons.icons.size();
-        int numImageViews = getChildCount(container, ImageView.class);
-        if (numImages > numImageViews) {
-            for (int i = 0; i < numImages - numImageViews; i++) {
-                ImageView imageView = (ImageView) inflater.inflate(
-                        R.layout.preference_circular_icons_item, container, false);
-                container.addView(imageView, 0);
-            }
-        } else if (numImageViews > numImages) {
-            for (int i = 0; i < numImageViews - numImages; i++) {
-                container.removeViewAt(0);
-            }
-        }
-        // ... plus 0/1 TextViews at the end.
-        if (loadedIcons.extraItems > 0 && !(getLastChild(container) instanceof TextView)) {
-            TextView plusView = (TextView) inflater.inflate(
-                    R.layout.preference_circular_icons_plus_item, container, false);
-            container.addView(plusView);
-        } else if (loadedIcons.extraItems == 0 && (getLastChild(container) instanceof TextView)) {
-            container.removeViewAt(container.getChildCount() - 1);
-        }
-
-        // Show images (and +n if needed).
-        for (int i = 0; i < numImages; i++) {
-            ImageView imageView = (ImageView) container.getChildAt(i);
-            imageView.setImageDrawable(loadedIcons.icons.get(i));
-        }
-        if (loadedIcons.extraItems > 0) {
-            TextView textView = (TextView) checkNotNull(getLastChild(container));
-            textView.setText(getContext().getString(R.string.zen_mode_plus_n_items,
-                    loadedIcons.extraItems));
-        }
-
-        // Apply enabled/disabled style.
-        for (int i = 0; i < container.getChildCount(); i++) {
-            View child = container.getChildAt(i);
-            child.setAlpha(isEnabled() ? 1.0f : DISABLED_ITEM_ALPHA);
-        }
-    }
-
-    private static int getChildCount(ViewGroup parent, Class<? extends View> childClass) {
-        int count = 0;
-        for (int i = 0; i < parent.getChildCount(); i++) {
-            if (childClass.isInstance(parent.getChildAt(i))) {
-                count++;
-            }
-        }
-        return count;
-    }
-
-    @Nullable
-    private static View getLastChild(ViewGroup parent) {
-        if (parent.getChildCount() == 0) {
-            return null;
-        }
-        return parent.getChildAt(parent.getChildCount() - 1);
-    }
-
-    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
-    @Nullable
-    LoadedIcons getLoadedIcons() {
-        return mLoadedIcons;
+        iconContainer.setVisibility(mIconSet != null && mIconSet.size() == 0 ? GONE : VISIBLE);
+        iconContainer.setEnabled(isEnabled());
+        iconContainer.setIcons(mIconSet);
     }
 }
diff --git a/src/com/android/settings/notification/modes/CircularIconsView.java b/src/com/android/settings/notification/modes/CircularIconsView.java
new file mode 100644
index 0000000..b0e4280
--- /dev/null
+++ b/src/com/android/settings/notification/modes/CircularIconsView.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification.modes;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settings.R;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class CircularIconsView extends LinearLayout {
+
+    private static final float DISABLED_ITEM_ALPHA = 0.3f;
+
+    record Icons(ImmutableList<Drawable> icons, int extraItems) { }
+
+    private Executor mUiExecutor;
+    private int mNumberOfCirclesThatFit;
+
+    // Chronologically, fields will be set top-to-bottom.
+    @Nullable private CircularIconSet<?> mIconSet;
+    @Nullable private ListenableFuture<List<Drawable>> mPendingLoadIconsFuture;
+    @Nullable private Icons mDisplayedIcons;
+
+    public CircularIconsView(Context context) {
+        super(context);
+        setUiExecutor(context.getMainExecutor());
+    }
+
+    public CircularIconsView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setUiExecutor(context.getMainExecutor());
+    }
+
+    public CircularIconsView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        setUiExecutor(context.getMainExecutor());
+    }
+
+    public CircularIconsView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        setUiExecutor(context.getMainExecutor());
+    }
+
+    @VisibleForTesting
+    void setUiExecutor(Executor uiExecutor) {
+        mUiExecutor = uiExecutor;
+    }
+
+    <T> void setIcons(CircularIconSet<T> iconSet) {
+        if (mIconSet != null && mIconSet.equals(iconSet)) {
+            return;
+        }
+
+        mIconSet = checkNotNull(iconSet);
+        cancelPendingTasks();
+        if (getMeasuredWidth() != 0) {
+            startLoadingIcons(iconSet);
+        }
+    }
+
+    private void cancelPendingTasks() {
+        mDisplayedIcons = null;
+        if (mPendingLoadIconsFuture != null) {
+            mPendingLoadIconsFuture.cancel(true);
+            mPendingLoadIconsFuture = null;
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+
+        int numFitting = getNumberOfCirclesThatFit();
+        if (mNumberOfCirclesThatFit != numFitting) {
+            // View has been measured for the first time OR its dimensions have changed since then.
+            // Keep track, because we want to reload stuff if more (or less) items fit.
+            mNumberOfCirclesThatFit = numFitting;
+
+            if (mIconSet != null) {
+                cancelPendingTasks();
+                startLoadingIcons(mIconSet);
+            }
+        }
+    }
+
+    private int getNumberOfCirclesThatFit() {
+        Resources res = getContext().getResources();
+        int availableSpace = getMeasuredWidth();
+        int iconHorizontalSpace = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter)
+                + res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_margin_between);
+        return availableSpace / iconHorizontalSpace;
+    }
+
+    private void startLoadingIcons(CircularIconSet<?> iconSet) {
+        int numCirclesThatFit = getNumberOfCirclesThatFit();
+
+        List<ListenableFuture<Drawable>> iconFutures;
+        int extraItems;
+        if (iconSet.size() > numCirclesThatFit) {
+            // Reserve one space for the (+xx) textview.
+            int numIconsToShow = numCirclesThatFit - 1;
+            if (numIconsToShow < 0) {
+                numIconsToShow = 0;
+            }
+            iconFutures = iconSet.getIcons(numIconsToShow);
+            extraItems = iconSet.size() - numIconsToShow;
+        } else {
+            // Fit exactly or with remaining space.
+            iconFutures = iconSet.getIcons();
+            extraItems = 0;
+        }
+
+        // Display icons when all are ready (more consistent than randomly loading).
+        mPendingLoadIconsFuture = Futures.allAsList(iconFutures);
+        FutureUtil.whenDone(
+                mPendingLoadIconsFuture,
+                icons -> setDrawables(new Icons(ImmutableList.copyOf(icons), extraItems)),
+                mUiExecutor);
+    }
+
+    private void setDrawables(Icons icons) {
+        mDisplayedIcons = icons;
+
+        // Rearrange child views until we have <numImages> ImageViews...
+        LayoutInflater inflater = LayoutInflater.from(getContext());
+        int numImages = icons.icons.size();
+        int numImageViews = getChildCount(ImageView.class);
+        if (numImages > numImageViews) {
+            for (int i = 0; i < numImages - numImageViews; i++) {
+                ImageView imageView = (ImageView) inflater.inflate(
+                        R.layout.preference_circular_icons_item, this, false);
+                addView(imageView, 0);
+            }
+        } else if (numImageViews > numImages) {
+            for (int i = 0; i < numImageViews - numImages; i++) {
+                removeViewAt(0);
+            }
+        }
+        // ... plus 0/1 TextViews at the end.
+        if (icons.extraItems > 0 && !(getLastChild() instanceof TextView)) {
+            TextView plusView = (TextView) inflater.inflate(
+                    R.layout.preference_circular_icons_plus_item, this, false);
+            this.addView(plusView);
+        } else if (icons.extraItems == 0 && (getLastChild() instanceof TextView)) {
+            removeViewAt(getChildCount() - 1);
+        }
+
+        // Show images (and +n if needed).
+        for (int i = 0; i < numImages; i++) {
+            ImageView imageView = (ImageView) getChildAt(i);
+            imageView.setImageDrawable(icons.icons.get(i));
+        }
+        if (icons.extraItems > 0) {
+            TextView textView = (TextView) checkNotNull(getLastChild());
+            textView.setText(getContext().getString(R.string.zen_mode_plus_n_items,
+                    icons.extraItems));
+        }
+
+        applyEnabledDisabledAppearance(isEnabled());
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        super.setEnabled(enabled);
+        applyEnabledDisabledAppearance(isEnabled());
+    }
+
+    private void applyEnabledDisabledAppearance(boolean enabled) {
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            child.setAlpha(enabled ? 1.0f : DISABLED_ITEM_ALPHA);
+        }
+    }
+
+    private int getChildCount(Class<? extends View> childClass) {
+        int count = 0;
+        for (int i = 0; i < getChildCount(); i++) {
+            if (childClass.isInstance(getChildAt(i))) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    @Nullable
+    private View getLastChild() {
+        if (getChildCount() == 0) {
+            return null;
+        }
+        return getChildAt(getChildCount() - 1);
+    }
+
+    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+    @Nullable
+    Icons getDisplayedIcons() {
+        return mDisplayedIcons;
+    }
+}
diff --git a/src/com/android/settings/notification/modes/IconUtil.java b/src/com/android/settings/notification/modes/IconUtil.java
index dc4d875..33d0d96 100644
--- a/src/com/android/settings/notification/modes/IconUtil.java
+++ b/src/com/android/settings/notification/modes/IconUtil.java
@@ -79,6 +79,7 @@
         @Px int innerSizePx = res.getDimensionPixelSize(R.dimen.zen_mode_header_inner_icon_size);
 
         Drawable base = composeIcons(
+                context.getResources(),
                 background,
                 Utils.getColorAttr(context,
                         com.android.internal.R.attr.materialColorSecondaryContainer),
@@ -89,6 +90,7 @@
                 innerSizePx);
 
         Drawable selected = composeIcons(
+                context.getResources(),
                 background,
                 Utils.getColorAttr(context, com.android.internal.R.attr.materialColorPrimary),
                 outerSizePx,
@@ -111,6 +113,7 @@
      */
     static Drawable makeIconPickerHeader(@NonNull Context context, Drawable icon) {
         return composeIconCircle(
+                context.getResources(),
                 Utils.getColorAttr(context,
                         com.android.internal.R.attr.materialColorSecondaryContainer),
                 context.getResources().getDimensionPixelSize(
@@ -129,6 +132,7 @@
      */
     static Drawable makeIconPickerItem(@NonNull Context context, @DrawableRes int iconResId) {
         return composeIconCircle(
+                context.getResources(),
                 context.getColorStateList(R.color.modes_icon_selectable_background),
                 context.getResources().getDimensionPixelSize(
                         R.dimen.zen_mode_icon_list_item_circle_diameter),
@@ -146,6 +150,7 @@
     static Drawable makeCircularIconPreferenceItem(@NonNull Context context,
             @DrawableRes int iconResId) {
         return composeIconCircle(
+                context.getResources(),
                 Utils.getColorAttr(context,
                         com.android.internal.R.attr.materialColorSecondaryContainer),
                 context.getResources().getDimensionPixelSize(
@@ -166,6 +171,7 @@
         Resources res = context.getResources();
         if (Strings.isNullOrEmpty(displayName)) {
             return composeIconCircle(
+                    context.getResources(),
                     Utils.getColorAttr(context,
                             com.android.internal.R.attr.materialColorTertiaryContainer),
                     res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter),
@@ -204,17 +210,17 @@
         return new BitmapDrawable(context.getResources(), bitmap);
     }
 
-    private static Drawable composeIconCircle(ColorStateList circleColor, @Px int circleDiameterPx,
-            Drawable icon, ColorStateList iconColor, @Px int iconSizePx) {
-        return composeIcons(new ShapeDrawable(new OvalShape()), circleColor, circleDiameterPx, icon,
-                iconColor, iconSizePx);
+    private static Drawable composeIconCircle(Resources res, ColorStateList circleColor,
+            @Px int circleDiameterPx, Drawable icon, ColorStateList iconColor, @Px int iconSizePx) {
+        return composeIcons(res, new ShapeDrawable(new OvalShape()), circleColor, circleDiameterPx,
+                icon, iconColor, iconSizePx);
     }
 
-    private static Drawable composeIcons(Drawable outer, ColorStateList outerColor,
+    private static Drawable composeIcons(Resources res, Drawable outer, ColorStateList outerColor,
             @Px int outerSizePx, Drawable icon, ColorStateList iconColor, @Px int iconSizePx) {
-        Drawable background = checkNotNull(outer.getConstantState()).newDrawable().mutate();
+        Drawable background = checkNotNull(outer.getConstantState()).newDrawable(res).mutate();
         background.setTintList(outerColor);
-        Drawable foreground = checkNotNull(icon.getConstantState()).newDrawable().mutate();
+        Drawable foreground = checkNotNull(icon.getConstantState()).newDrawable(res).mutate();
         foreground.setTintList(iconColor);
 
         LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] { background, foreground });
diff --git a/src/com/android/settings/notification/modes/SetupInterstitialActivity.java b/src/com/android/settings/notification/modes/SetupInterstitialActivity.java
index 984fa1c..8408624 100644
--- a/src/com/android/settings/notification/modes/SetupInterstitialActivity.java
+++ b/src/com/android/settings/notification/modes/SetupInterstitialActivity.java
@@ -29,6 +29,7 @@
 
 import android.annotation.SuppressLint;
 import android.app.ActionBar;
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
@@ -197,7 +198,8 @@
             // they happen to go back. Forward the activity result in case we got here (indirectly)
             // from some app that is waiting for the result.
             if (updated) {
-                ZenSubSettingLauncher.forMode(this, modeId)
+                ZenSubSettingLauncher.forModeFragment(this, ZenModeFragment.class, modeId,
+                                SettingsEnums.ZEN_MODE_INTERSTITIAL)
                         .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT).launch();
             }
             finish();
diff --git a/src/com/android/settings/notification/modes/ZenModeAppsFragment.java b/src/com/android/settings/notification/modes/ZenModeAppsFragment.java
index 19035dd..ec72c83 100644
--- a/src/com/android/settings/notification/modes/ZenModeAppsFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeAppsFragment.java
@@ -47,7 +47,6 @@
 
     @Override
     public int getMetricsCategory() {
-        // TODO: b/332937635 - make this the correct metrics category
-        return SettingsEnums.NOTIFICATION_ZEN_MODE_PRIORITY;
+        return SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APPS;
     }
 }
diff --git a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
index 1521a8b..7b17f0c 100644
--- a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
@@ -20,7 +20,10 @@
 import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
 
 import android.app.Application;
+import android.app.settings.SettingsEnums;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -48,6 +51,7 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
+import java.util.function.Function;
 
 /**
  * Preference with a link and summary about what apps can break through the mode
@@ -64,24 +68,26 @@
     private ZenMode mZenMode;
     private CircularIconsPreference mPreference;
     private final Fragment mHost;
+    private final Function<ApplicationInfo, Drawable> mAppIconRetriever;
 
     ZenModeAppsLinkPreferenceController(Context context, String key, Fragment host,
             ZenModesBackend backend, ZenHelperBackend helperBackend) {
         this(context, key, host,
                 ApplicationsState.getInstance((Application) context.getApplicationContext()),
-                backend, helperBackend);
+                backend, helperBackend, appInfo -> Utils.getBadgedIcon(context, appInfo));
     }
 
     @VisibleForTesting
     ZenModeAppsLinkPreferenceController(Context context, String key, Fragment host,
             ApplicationsState applicationsState, ZenModesBackend backend,
-            ZenHelperBackend helperBackend) {
+            ZenHelperBackend helperBackend, Function<ApplicationInfo, Drawable> appIconRetriever) {
         super(context, key, backend);
         mSummaryHelper = new ZenModeSummaryHelper(mContext, helperBackend);
         mHelperBackend = helperBackend;
         mApplicationsState = applicationsState;
         mUserManager = context.getSystemService(UserManager.class);
         mHost = host;
+        mAppIconRetriever = appIconRetriever;
     }
 
     @Override
@@ -93,10 +99,9 @@
     public void updateState(Preference preference, @NonNull ZenMode zenMode) {
         Bundle bundle = new Bundle();
         bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
-        // TODO(b/332937635): Update metrics category
         preference.setIntent(
                 ZenSubSettingLauncher.forModeFragment(mContext, ZenModeAppsFragment.class,
-                        zenMode.getId(), 0).toIntent());
+                        zenMode.getId(), SettingsEnums.ZEN_PRIORITY_MODE).toIntent());
         preference.setEnabled(zenMode.isEnabled());
 
         mZenMode = zenMode;
@@ -104,14 +109,19 @@
 
         if (zenMode.getPolicy().getAllowedChannels() == ZenPolicy.CHANNEL_POLICY_NONE) {
             mPreference.setSummary(R.string.zen_mode_apps_none_apps);
-            mPreference.displayIcons(CircularIconSet.EMPTY);
+            mPreference.setIcons(CircularIconSet.EMPTY);
+            if (mAppSession != null) {
+                mAppSession.deactivateSession();
+            }
         } else {
             if (TextUtils.isEmpty(mPreference.getSummary())) {
                 mPreference.setSummary(R.string.zen_mode_apps_calculating);
             }
-            if (mApplicationsState != null && mHost != null) {
+            if (mAppSession == null) {
                 mAppSession = mApplicationsState.newSession(mAppSessionCallbacks,
                         mHost.getLifecycle());
+            } else {
+                mAppSession.activateSession();
             }
             triggerUpdateAppsBypassingDnd();
         }
@@ -133,12 +143,16 @@
     }
 
     private void displayAppsBypassingDnd(List<AppEntry> allApps) {
+        if (mZenMode.getPolicy().getAllowedChannels() == ZenPolicy.CHANNEL_POLICY_NONE) {
+            // Can get this callback when resuming, if we had CHANNEL_POLICY_PRIORITY and just
+            // switched to CHANNEL_POLICY_NONE.
+            return;
+        }
+
         ImmutableList<AppEntry> apps = getAppsBypassingDndSortedByName(allApps);
-
         mPreference.setSummary(mSummaryHelper.getAppsSummary(mZenMode, apps));
-
-        mPreference.displayIcons(new CircularIconSet<>(apps,
-                app -> Utils.getBadgedIcon(mContext, app.info)),
+        mPreference.setIcons(new CircularIconSet<>(apps,
+                app -> mAppIconRetriever.apply(app.info)),
                 APP_ENTRY_EQUIVALENCE);
     }
 
diff --git a/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java
index 522f191..c44661a 100644
--- a/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java
@@ -106,10 +106,9 @@
         if (mModeId != null) {
             bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, mModeId);
         }
-        // TODO(b/332937635): Update metrics category
         new SubSettingLauncher(mContext)
                 .setDestination(ZenModeSelectBypassingAppsFragment.class.getName())
-                .setSourceMetricsCategory(SettingsEnums.SETTINGS_ZEN_NOTIFICATIONS)
+                .setSourceMetricsCategory(SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APPS)
                 .setArguments(bundle)
                 .launch();
     }
diff --git a/src/com/android/settings/notification/modes/ZenModeCallsFragment.java b/src/com/android/settings/notification/modes/ZenModeCallsFragment.java
index 54072ac..ac05328 100644
--- a/src/com/android/settings/notification/modes/ZenModeCallsFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeCallsFragment.java
@@ -50,7 +50,6 @@
 
     @Override
     public int getMetricsCategory() {
-        // TODO: b/332937635 - make this the correct metrics category
         return SettingsEnums.DND_CALLS;
     }
 
diff --git a/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java
index d885019..efddcf9 100644
--- a/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java
@@ -18,6 +18,7 @@
 
 import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.os.Bundle;
 
@@ -41,10 +42,9 @@
     public void updateState(Preference preference, @NonNull ZenMode zenMode) {
         Bundle bundle = new Bundle();
         bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
-        // TODO(b/332937635): Update metrics category
         preference.setIntent(new SubSettingLauncher(mContext)
                 .setDestination(ZenModeCallsFragment.class.getName())
-                .setSourceMetricsCategory(0)
+                .setSourceMetricsCategory(SettingsEnums.DND_PEOPLE)
                 .setArguments(bundle)
                 .toIntent());
         preference.setSummary(mSummaryHelper.getCallsSettingSummary(zenMode));
diff --git a/src/com/android/settings/notification/modes/ZenModeDisplayFragment.java b/src/com/android/settings/notification/modes/ZenModeDisplayFragment.java
index 38ac8f3..74ed38f 100644
--- a/src/com/android/settings/notification/modes/ZenModeDisplayFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeDisplayFragment.java
@@ -54,7 +54,6 @@
 
     @Override
     public int getMetricsCategory() {
-        // TODO: b/332937635 - make this the correct metrics category
-        return SettingsEnums.DND_PEOPLE;
+        return SettingsEnums.ZEN_MODE_DISPLAY_SETTINGS;
     }
 }
diff --git a/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java
index bba5e34..57dce89 100644
--- a/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java
@@ -18,6 +18,7 @@
 
 import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.os.Bundle;
 
@@ -41,10 +42,9 @@
     void updateState(Preference preference, @NonNull ZenMode zenMode) {
         Bundle bundle = new Bundle();
         bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
-        // TODO(b/332937635): Update metrics category
         preference.setIntent(
                 ZenSubSettingLauncher.forModeFragment(mContext, ZenModeDisplayFragment.class,
-                        zenMode.getId(), 0).toIntent());
+                        zenMode.getId(), SettingsEnums.ZEN_PRIORITY_MODE).toIntent());
         preference.setEnabled(zenMode.isEnabled());
     }
 
diff --git a/src/com/android/settings/notification/modes/ZenModeEditNameIconFragment.java b/src/com/android/settings/notification/modes/ZenModeEditNameIconFragment.java
index a0c2cf1..60f7316 100644
--- a/src/com/android/settings/notification/modes/ZenModeEditNameIconFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeEditNameIconFragment.java
@@ -72,8 +72,7 @@
 
     @Override
     public int getMetricsCategory() {
-        // TODO: b/332937635 - make this the correct metrics category
-        return SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION;
+        return SettingsEnums.ZEN_MODE_EDIT_NAME_ICON;
     }
 
     @Override
diff --git a/src/com/android/settings/notification/modes/ZenModeFragment.java b/src/com/android/settings/notification/modes/ZenModeFragment.java
index 0a80977..3777299 100644
--- a/src/com/android/settings/notification/modes/ZenModeFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeFragment.java
@@ -129,8 +129,7 @@
 
     @Override
     public int getMetricsCategory() {
-        // TODO: b/332937635 - make this the correct metrics category
-        return SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION;
+        return SettingsEnums.ZEN_PRIORITY_MODE;
     }
 
     @Override
@@ -164,9 +163,8 @@
         @Override
         public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {
             if (menuItem.getItemId() == RENAME_MODE) {
-                // TODO: b/332937635 - Update metrics category
                 ZenSubSettingLauncher.forModeFragment(mContext, ZenModeEditNameIconFragment.class,
-                        mZenMode.getId(), 0).launch();
+                        mZenMode.getId(), getMetricsCategory()).launch();
             } else if (menuItem.getItemId() == DELETE_MODE) {
                 new AlertDialog.Builder(mContext)
                         .setTitle(mContext.getString(R.string.zen_mode_delete_mode_confirmation,
diff --git a/src/com/android/settings/notification/modes/ZenModeMessagesFragment.java b/src/com/android/settings/notification/modes/ZenModeMessagesFragment.java
index 8bf574f..709e5da 100644
--- a/src/com/android/settings/notification/modes/ZenModeMessagesFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeMessagesFragment.java
@@ -46,7 +46,6 @@
 
     @Override
     public int getMetricsCategory() {
-        // TODO: b/332937635 - make this the correct metrics category
         return SettingsEnums.DND_MESSAGES;
     }
 
diff --git a/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java
index 4c0b758..50d7958 100644
--- a/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java
@@ -18,6 +18,7 @@
 
 import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.os.Bundle;
 
@@ -40,10 +41,9 @@
     public void updateState(Preference preference, @NonNull ZenMode zenMode) {
         Bundle bundle = new Bundle();
         bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
-        // TODO(b/332937635): Update metrics category
         preference.setIntent(new SubSettingLauncher(mContext)
                 .setDestination(ZenModeMessagesFragment.class.getName())
-                .setSourceMetricsCategory(0)
+                .setSourceMetricsCategory(SettingsEnums.DND_PEOPLE)
                 .setArguments(bundle)
                 .toIntent());
 
diff --git a/src/com/android/settings/notification/modes/ZenModeNewCustomFragment.java b/src/com/android/settings/notification/modes/ZenModeNewCustomFragment.java
index 6086c0c..d7dbaaf 100644
--- a/src/com/android/settings/notification/modes/ZenModeNewCustomFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeNewCustomFragment.java
@@ -16,6 +16,8 @@
 
 package com.android.settings.notification.modes;
 
+import android.app.settings.SettingsEnums;
+
 import androidx.annotation.Nullable;
 
 import com.android.settings.R;
@@ -50,15 +52,15 @@
         if (created != null) {
             // Open the mode view fragment and close the "add mode" fragment, so exiting the mode
             // view goes back to previous screen (which should be the modes list).
-            ZenSubSettingLauncher.forMode(requireContext(), created.getId()).launch();
+            ZenSubSettingLauncher.forModeFragment(requireContext(), ZenModeFragment.class,
+                    created.getId(), getMetricsCategory()).launch();
             finish();
         }
     }
 
     @Override
     public int getMetricsCategory() {
-        // TODO: b/332937635 - make this the correct metrics category
-        return 0;
+        return SettingsEnums.ZEN_MODE_ADD_NEW;
     }
 
     @Override
diff --git a/src/com/android/settings/notification/modes/ZenModeNotifVisFragment.java b/src/com/android/settings/notification/modes/ZenModeNotifVisFragment.java
index 3fdfec6..d1bd493 100644
--- a/src/com/android/settings/notification/modes/ZenModeNotifVisFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeNotifVisFragment.java
@@ -19,6 +19,7 @@
 import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.service.notification.ZenPolicy;
+
 import com.android.settings.R;
 import com.android.settingslib.core.AbstractPreferenceController;
 
@@ -57,7 +58,6 @@
 
     @Override
     public int getMetricsCategory() {
-        // TODO: b/332937635 - make this the correct metrics category
-        return SettingsEnums.DND_PEOPLE;
+        return SettingsEnums.ZEN_CUSTOM_RULE_VIS_EFFECTS;
     }
 }
diff --git a/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java
index 622c4a2..cd1e8c7 100644
--- a/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java
@@ -19,6 +19,7 @@
 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
 import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.os.Bundle;
 
@@ -47,10 +48,9 @@
     public void updateState(Preference preference, @NonNull ZenMode zenMode) {
         Bundle bundle = new Bundle();
         bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
-        // TODO(b/332937635): Update metrics category
         preference.setIntent(new SubSettingLauncher(mContext)
                 .setDestination(ZenModeNotifVisFragment.class.getName())
-                .setSourceMetricsCategory(0)
+                .setSourceMetricsCategory(SettingsEnums.ZEN_MODE_DISPLAY_SETTINGS)
                 .setArguments(bundle)
                 .toIntent());
     }
diff --git a/src/com/android/settings/notification/modes/ZenModeOtherFragment.java b/src/com/android/settings/notification/modes/ZenModeOtherFragment.java
index 1149cd1..28b2e54 100644
--- a/src/com/android/settings/notification/modes/ZenModeOtherFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeOtherFragment.java
@@ -16,14 +16,9 @@
 
 package com.android.settings.notification.modes;
 
-import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_ALARMS;
-import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_EVENTS;
-import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_MEDIA;
-import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REMINDERS;
-import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_SYSTEM;
-
 import android.app.settings.SettingsEnums;
 import android.content.Context;
+
 import com.android.settings.R;
 import com.android.settingslib.core.AbstractPreferenceController;
 
@@ -58,7 +53,6 @@
 
     @Override
     public int getMetricsCategory() {
-        // TODO: b/332937635 - make this the correct metrics category
         return SettingsEnums.NOTIFICATION_ZEN_MODE_PRIORITY;
     }
 }
diff --git a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
index 15e0edc..9613d98 100644
--- a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
@@ -23,6 +23,7 @@
 import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REMINDERS;
 import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_SYSTEM;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.service.notification.ZenPolicy;
 
@@ -65,14 +66,13 @@
 
     @Override
     public void updateState(Preference preference, @NonNull ZenMode zenMode) {
-        // TODO: b/332937635 - Update metrics category
         preference.setIntent(
                 ZenSubSettingLauncher.forModeFragment(mContext, ZenModeOtherFragment.class,
-                        zenMode.getId(), 0).toIntent());
+                        zenMode.getId(), SettingsEnums.ZEN_PRIORITY_MODE).toIntent());
 
         preference.setEnabled(zenMode.isEnabled());
         preference.setSummary(mSummaryHelper.getOtherSoundCategoriesSummary(zenMode));
-        ((CircularIconsPreference) preference).displayIcons(getSoundIcons(zenMode.getPolicy()));
+        ((CircularIconsPreference) preference).setIcons(getSoundIcons(zenMode.getPolicy()));
     }
 
     private CircularIconSet<Integer> getSoundIcons(ZenPolicy policy) {
diff --git a/src/com/android/settings/notification/modes/ZenModePeopleFragment.java b/src/com/android/settings/notification/modes/ZenModePeopleFragment.java
index f541d13..11e4453 100644
--- a/src/com/android/settings/notification/modes/ZenModePeopleFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModePeopleFragment.java
@@ -48,7 +48,6 @@
 
     @Override
     public int getMetricsCategory() {
-        // TODO: b/332937635 - make this the correct metrics category
         return SettingsEnums.DND_PEOPLE;
     }
 }
diff --git a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
index 4610c35ca..bf55471 100644
--- a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
@@ -26,6 +26,7 @@
 import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
 import static android.service.notification.ZenPolicy.STATE_ALLOW;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.content.pm.LauncherApps;
 import android.graphics.drawable.Drawable;
@@ -88,14 +89,14 @@
 
     @Override
     public void updateState(Preference preference, @NonNull ZenMode zenMode) {
-        // TODO(b/332937635): Update metrics category
+        // Passes in source ZenModeFragment metric category.
         preference.setIntent(
                 ZenSubSettingLauncher.forModeFragment(mContext, ZenModePeopleFragment.class,
-                        zenMode.getId(), 0).toIntent());
+                        zenMode.getId(), SettingsEnums.ZEN_PRIORITY_MODE).toIntent());
 
         preference.setEnabled(zenMode.isEnabled());
         preference.setSummary(mSummaryHelper.getPeopleSummary(zenMode.getPolicy()));
-        ((CircularIconsPreference) preference).displayIcons(getPeopleIcons(zenMode.getPolicy()),
+        ((CircularIconsPreference) preference).setIcons(getPeopleIcons(zenMode.getPolicy()),
                 PEOPLE_ITEM_EQUIVALENCE);
     }
 
diff --git a/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java b/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java
index ab5e2d9..11b65bd 100644
--- a/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java
@@ -270,10 +270,9 @@
                 mContext.startActivity(ALL_CONTACTS_INTENT);
             } else if (KEY_ANY_CONVERSATIONS.equals(key)
                     || KEY_IMPORTANT_CONVERSATIONS.equals(key)) {
-                // TODO: b/332937635 - set correct metrics category
                 new SubSettingLauncher(mContext)
                         .setDestination(ConversationListSettings.class.getName())
-                        .setSourceMetricsCategory(SettingsEnums.DND_CONVERSATIONS)
+                        .setSourceMetricsCategory(SettingsEnums.DND_MESSAGES)
                         .launch();
             } else {
                 mContext.startActivity(FALLBACK_INTENT);
diff --git a/src/com/android/settings/notification/modes/ZenModeScheduleChooserDialog.java b/src/com/android/settings/notification/modes/ZenModeScheduleChooserDialog.java
index 6202648..d129aad 100644
--- a/src/com/android/settings/notification/modes/ZenModeScheduleChooserDialog.java
+++ b/src/com/android/settings/notification/modes/ZenModeScheduleChooserDialog.java
@@ -20,6 +20,7 @@
 import static com.google.common.base.Preconditions.checkState;
 
 import android.app.Dialog;
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.net.Uri;
 import android.os.Bundle;
@@ -70,8 +71,7 @@
 
     @Override
     public int getMetricsCategory() {
-        // TODO: b/332937635 - Update metrics category
-        return 0;
+        return SettingsEnums.ZEN_SCHEDULE_CHOOSER_DIALOG;
     }
 
     static void show(DashboardFragment parent, OnScheduleOptionListener optionListener) {
diff --git a/src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java b/src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java
index 8b682b9..1f5438d 100644
--- a/src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java
@@ -74,8 +74,7 @@
 
     @Override
     public int getMetricsCategory() {
-        // TODO(b/332937635): Update metrics category
-        return SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APPS;
+        return SettingsEnums.NOTIFICATION_ZEN_MODE_OVERRIDING_APP;
     }
 
     /**
diff --git a/src/com/android/settings/notification/modes/ZenModeSetCalendarFragment.java b/src/com/android/settings/notification/modes/ZenModeSetCalendarFragment.java
index f0206ef..a266c8b 100644
--- a/src/com/android/settings/notification/modes/ZenModeSetCalendarFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeSetCalendarFragment.java
@@ -46,7 +46,6 @@
 
     @Override
     public int getMetricsCategory() {
-        // TODO: b/332937635 - make this the correct metrics category
         return SettingsEnums.NOTIFICATION_ZEN_MODE_EVENT_RULE;
     }
 }
diff --git a/src/com/android/settings/notification/modes/ZenModeSetScheduleFragment.java b/src/com/android/settings/notification/modes/ZenModeSetScheduleFragment.java
index 4d58097..9119784 100644
--- a/src/com/android/settings/notification/modes/ZenModeSetScheduleFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeSetScheduleFragment.java
@@ -48,7 +48,6 @@
 
     @Override
     public int getMetricsCategory() {
-        // TODO: b/332937635 - make this the correct metrics category
         return SettingsEnums.NOTIFICATION_ZEN_MODE_SCHEDULE_RULE;
     }
 }
diff --git a/src/com/android/settings/notification/modes/ZenModeTimePickerFragment.java b/src/com/android/settings/notification/modes/ZenModeTimePickerFragment.java
index d8e1b38..3fa5394 100644
--- a/src/com/android/settings/notification/modes/ZenModeTimePickerFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeTimePickerFragment.java
@@ -62,7 +62,6 @@
 
     @Override
     public int getMetricsCategory() {
-        // TODO: b/332937635 - set correct metrics category (or decide to keep this one?)
         return SettingsEnums.DIALOG_ZEN_TIMEPICKER;
     }
 
diff --git a/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceController.java b/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceController.java
index 885c4db..1add488 100644
--- a/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeTriggerUpdatePreferenceController.java
@@ -24,6 +24,7 @@
 
 import android.annotation.SuppressLint;
 import android.app.AlertDialog;
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -97,9 +98,9 @@
 
     private void setUpForSystemOwnedTrigger(Preference preference, ZenMode mode) {
         if (mode.getType() == TYPE_SCHEDULE_TIME) {
-            // TODO: b/332937635 - set correct metrics category
             preference.setIntent(ZenSubSettingLauncher.forModeFragment(mContext,
-                    ZenModeSetScheduleFragment.class, mode.getId(), 0).toIntent());
+                    ZenModeSetScheduleFragment.class, mode.getId(),
+                    SettingsEnums.ZEN_PRIORITY_MODE).toIntent());
 
             // [Clock Icon] 9:00 - 17:00 / Sun-Mon
             preference.setIcon(com.android.internal.R.drawable.ic_zen_mode_type_schedule_time);
@@ -115,9 +116,9 @@
                 preference.setSummary(null);
             }
         } else if (mode.getType() == TYPE_SCHEDULE_CALENDAR) {
-            // TODO: b/332937635 - set correct metrics category
             preference.setIntent(ZenSubSettingLauncher.forModeFragment(mContext,
-                    ZenModeSetCalendarFragment.class, mode.getId(), 0).toIntent());
+                    ZenModeSetCalendarFragment.class, mode.getId(),
+                    SettingsEnums.ZEN_PRIORITY_MODE).toIntent());
 
             // [Event Icon] Calendar Events / <Calendar name>
             preference.setIcon(
diff --git a/src/com/android/settings/notification/modes/ZenModesListAddModeTypeChooserDialog.java b/src/com/android/settings/notification/modes/ZenModesListAddModeTypeChooserDialog.java
index 57d3bf9..e7905a8 100644
--- a/src/com/android/settings/notification/modes/ZenModesListAddModeTypeChooserDialog.java
+++ b/src/com/android/settings/notification/modes/ZenModesListAddModeTypeChooserDialog.java
@@ -20,6 +20,7 @@
 import static com.google.common.base.Preconditions.checkState;
 
 import android.app.Dialog;
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.os.Bundle;
 import android.view.LayoutInflater;
@@ -56,8 +57,7 @@
 
     @Override
     public int getMetricsCategory() {
-        // TODO: b/332937635 - Update metrics category
-        return 0;
+        return SettingsEnums.ZEN_MODE_NEW_TYPE_CHOOSER_DIALOG;
     }
 
     static void show(DashboardFragment parent,
diff --git a/src/com/android/settings/notification/modes/ZenModesListFragment.java b/src/com/android/settings/notification/modes/ZenModesListFragment.java
index cab0209..2b58f8e 100644
--- a/src/com/android/settings/notification/modes/ZenModesListFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModesListFragment.java
@@ -74,8 +74,7 @@
 
     @Override
     public int getMetricsCategory() {
-        // TODO: b/332937635 - add new & set metrics categories correctly
-        return SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION;
+        return SettingsEnums.ZEN_PRIORITY_MODES_LIST;
     }
 
     private void onAvailableModeTypesForAdd(List<ModeType> types) {
@@ -97,10 +96,9 @@
             startActivityForResult(type.creationActivityIntent(), REQUEST_NEW_MODE);
         } else {
             // Custom-manual mode -> "add a mode" screen.
-            // TODO: b/332937635 - set metrics categories correctly
             new SubSettingLauncher(requireContext())
                     .setDestination(ZenModeNewCustomFragment.class.getName())
-                    .setSourceMetricsCategory(0)
+                    .setSourceMetricsCategory(SettingsEnums.ZEN_PRIORITY_MODES_LIST)
                     .launch();
         }
     }
@@ -125,7 +123,9 @@
                 .filter(m -> m.getRule().getPackageName().equals(activityInvoked.getPackageName()))
                 .findFirst();
         createdZenMode.ifPresent(
-                mode -> ZenSubSettingLauncher.forMode(mContext, mode.getId()).launch());
+                mode ->
+                        ZenSubSettingLauncher.forModeFragment(mContext, ZenModeFragment.class,
+                                mode.getId(), getMetricsCategory()).launch());
     }
 
     /**
diff --git a/src/com/android/settings/notification/modes/ZenModesListItemPreference.java b/src/com/android/settings/notification/modes/ZenModesListItemPreference.java
index e09d04c..0c96148 100644
--- a/src/com/android/settings/notification/modes/ZenModesListItemPreference.java
+++ b/src/com/android/settings/notification/modes/ZenModesListItemPreference.java
@@ -15,6 +15,7 @@
  */
 package com.android.settings.notification.modes;
 
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.widget.TextView;
 
@@ -63,7 +64,8 @@
 
     @Override
     public void onClick() {
-        ZenSubSettingLauncher.forMode(mContext, mZenMode.getId()).launch();
+        ZenSubSettingLauncher.forModeFragment(mContext, ZenModeFragment.class, mZenMode.getId(),
+                SettingsEnums.ZEN_PRIORITY_MODES_LIST).launch();
     }
 
     public void setZenMode(ZenMode zenMode) {
diff --git a/src/com/android/settings/notification/modes/ZenSubSettingLauncher.java b/src/com/android/settings/notification/modes/ZenSubSettingLauncher.java
index 00c21bb..c02a9d9 100644
--- a/src/com/android/settings/notification/modes/ZenSubSettingLauncher.java
+++ b/src/com/android/settings/notification/modes/ZenSubSettingLauncher.java
@@ -18,7 +18,6 @@
 
 import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
 
-import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.os.Bundle;
 
@@ -26,12 +25,6 @@
 import com.android.settings.dashboard.DashboardFragment;
 
 class ZenSubSettingLauncher {
-
-    static SubSettingLauncher forMode(Context context, String modeId) {
-        return forModeFragment(context, ZenModeFragment.class, modeId,
-                SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION);
-    }
-
     static SubSettingLauncher forModeFragment(Context context,
             Class<? extends DashboardFragment> fragmentClass, String modeId,
             int sourceMetricsCategory) {
diff --git a/src/com/android/settings/users/UserCapabilities.java b/src/com/android/settings/users/UserCapabilities.java
index 590cb0c..60e92a8 100644
--- a/src/com/android/settings/users/UserCapabilities.java
+++ b/src/com/android/settings/users/UserCapabilities.java
@@ -76,6 +76,9 @@
     public void updateAddUserCapabilities(Context context) {
         final UserManager userManager =
                 (UserManager) context.getSystemService(Context.USER_SERVICE);
+        final UserInfo myUserInfo = userManager.getUserInfo(UserHandle.myUserId());
+        mIsAdmin = myUserInfo.isAdmin();
+
         mEnforcedAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(context,
                 UserManager.DISALLOW_ADD_USER, UserHandle.myUserId());
         final boolean hasBaseUserRestriction = RestrictedLockUtilsInternal.hasBaseUserRestriction(
diff --git a/src/com/android/settings/users/UserDetailsSettings.java b/src/com/android/settings/users/UserDetailsSettings.java
index 66c278e..8afab96 100644
--- a/src/com/android/settings/users/UserDetailsSettings.java
+++ b/src/com/android/settings/users/UserDetailsSettings.java
@@ -570,7 +570,9 @@
      *   <li>OR multiple admin support is NOT enabled.</li>
      *   <li>OR the <b>current</b> user has DISALLOW_GRANT_ADMIN restriction applied</li>
      *
-     *   <li>OR the <b>target</b> user ('mUserInfo') is a main user OR a guest user.</li>
+     *   <li>OR the <b>target</b> user ('mUserInfo') is a main user</li>
+     *   <li>OR the <b>target</b> user ('mUserInfo') is not of type
+     *   {@link UserManager#USER_TYPE_FULL_SECONDARY}</li>
      *   <li>OR the <b>target</b> user ('mUserInfo') has DISALLOW_GRANT_ADMIN restriction.</li>
      * </ul>
      *
@@ -582,7 +584,7 @@
                 || mUserManager.hasUserRestriction(UserManager.DISALLOW_GRANT_ADMIN);
 
         boolean targetUserRestricted = mUserInfo.isMain()
-                || mUserInfo.isGuest()
+                || !(UserManager.USER_TYPE_FULL_SECONDARY.equals(mUserInfo.userType))
                 || mUserManager.hasUserRestrictionForUser(UserManager.DISALLOW_GRANT_ADMIN,
                 mUserInfo.getUserHandle());
 
diff --git a/src/com/android/settings/wifi/repository/WifiHotspotRepository.java b/src/com/android/settings/wifi/repository/WifiHotspotRepository.java
index e523831..4dc2e9e 100644
--- a/src/com/android/settings/wifi/repository/WifiHotspotRepository.java
+++ b/src/com/android/settings/wifi/repository/WifiHotspotRepository.java
@@ -397,7 +397,7 @@
      * @return {@code true} if Wi-Fi Hotspot 5 GHz Band is available
      */
     public boolean is5gAvailable() {
-        if (!mBand5g.isUsableChannelsReady && is5GHzBandSupported()) {
+        if (!mBand5g.isChannelsReady && is5GHzBandSupported()) {
             isChannelAvailable(mBand5g);
         }
         return mBand5g.isAvailable();
@@ -439,7 +439,7 @@
      * @return {@code true} if Wi-Fi Hotspot 6 GHz Band is available
      */
     public boolean is6gAvailable() {
-        if (!mBand6g.isUsableChannelsReady && is6GHzBandSupported()) {
+        if (!mBand6g.isChannelsReady && is6GHzBandSupported()) {
             isChannelAvailable(mBand6g);
         }
         return mBand6g.isAvailable();
@@ -475,19 +475,19 @@
             List<WifiAvailableChannel> channels =
                     mWifiManager.getAllowedChannels(sapBand.band, OP_MODE_SAP);
             log("isChannelAvailable(), band:" + sapBand.band + ", channels:" + channels);
-            sapBand.hasUsableChannels = (channels != null && channels.size() > 0);
-            sapBand.isUsableChannelsUnsupported = false;
+            sapBand.hasChannels = (channels != null && channels.size() > 0);
+            sapBand.isChannelsUnsupported = false;
         } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Querying usable SAP channels failed, band:" + sapBand.band);
-            sapBand.hasUsableChannels = false;
-            sapBand.isUsableChannelsUnsupported = true;
+            Log.e(TAG, "Querying SAP channels failed, band:" + sapBand.band);
+            sapBand.hasChannels = false;
+            sapBand.isChannelsUnsupported = true;
         } catch (UnsupportedOperationException e) {
             // This is expected on some hardware.
-            Log.e(TAG, "Querying usable SAP channels is unsupported, band:" + sapBand.band);
-            sapBand.hasUsableChannels = false;
-            sapBand.isUsableChannelsUnsupported = true;
+            Log.e(TAG, "Querying SAP channels is unsupported, band:" + sapBand.band);
+            sapBand.hasChannels = false;
+            sapBand.isChannelsUnsupported = true;
         }
-        sapBand.isUsableChannelsReady = true;
+        sapBand.isChannelsReady = true;
         log("isChannelAvailable(), " + sapBand);
         return sapBand.isAvailable();
     }
@@ -531,8 +531,8 @@
     }
 
     protected void purgeRefreshData() {
-        mBand5g.isUsableChannelsReady = false;
-        mBand6g.isUsableChannelsReady = false;
+        mBand5g.isChannelsReady = false;
+        mBand6g.isChannelsReady = false;
     }
 
     protected void startAutoRefresh() {
@@ -615,15 +615,15 @@
 
     @VisibleForTesting
     void updateCapabilityChanged() {
-        if (mBand5g.isUsableChannelsUnsupported) {
+        if (mBand5g.isChannelsUnsupported) {
             update5gAvailable();
             log("updateCapabilityChanged(), " + mBand5g);
         }
-        if (mBand6g.isUsableChannelsUnsupported) {
+        if (mBand6g.isChannelsUnsupported) {
             update6gAvailable();
             log("updateCapabilityChanged(), " + mBand6g);
         }
-        if (mBand5g.isUsableChannelsUnsupported || mBand6g.isUsableChannelsUnsupported) {
+        if (mBand5g.isChannelsUnsupported || mBand6g.isChannelsUnsupported) {
             updateSpeedType();
         }
     }
@@ -676,9 +676,9 @@
     @VisibleForTesting
     static class SapBand {
         public int band;
-        public boolean isUsableChannelsReady;
-        public boolean hasUsableChannels;
-        public boolean isUsableChannelsUnsupported;
+        public boolean isChannelsReady;
+        public boolean hasChannels;
+        public boolean isChannelsUnsupported;
         public boolean hasCapability;
 
         SapBand(int band) {
@@ -689,7 +689,7 @@
          * Return whether SoftAp band is available or not.
          */
         public boolean isAvailable() {
-            return isUsableChannelsUnsupported ? hasCapability : hasUsableChannels;
+            return isChannelsUnsupported ? hasCapability : hasChannels;
         }
 
         @Override
@@ -697,10 +697,10 @@
         public String toString() {
             return "SapBand{"
                     + "band:" + band
-                    + ",isUsableChannelsReady:" + isUsableChannelsReady
-                    + ",hasUsableChannels:" + hasUsableChannels
-                    + ",isUsableChannelsUnsupported:" + isUsableChannelsUnsupported
-                    + ",hasChannelsCapability:" + hasCapability
+                    + ",isChannelsReady:" + isChannelsReady
+                    + ",hasChannels:" + hasChannels
+                    + ",isChannelsUnsupported:" + isChannelsUnsupported
+                    + ",hasCapability:" + hasCapability
                     + '}';
         }
     }
diff --git a/tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java
index 5fd11f9..6797061 100644
--- a/tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java
@@ -58,8 +58,6 @@
     private ContentResolver mContentResolver;
     private DaltonizerSaturationSeekbarPreferenceController mController;
 
-    private int mOriginalSaturationLevel = -1;
-
     private PreferenceScreen mScreen;
     private LifecycleOwner mLifecycleOwner;
     private Lifecycle mLifecycle;
@@ -73,10 +71,6 @@
     public void setup() {
         Context context = ApplicationProvider.getApplicationContext();
         mContentResolver = context.getContentResolver();
-        mOriginalSaturationLevel = Settings.Secure.getInt(
-                mContentResolver,
-                Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
-                7);
 
         mPreference = new SeekBarPreference(context);
         mPreference.setKey(ToggleDaltonizerPreferenceFragment.KEY_SATURATION);
@@ -92,10 +86,18 @@
 
     @After
     public void cleanup() {
-        Settings.Secure.putInt(
+        Settings.Secure.putString(
+                mContentResolver,
+                Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
+                null);
+        Settings.Secure.putString(
+                mContentResolver,
+                Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
+                null);
+        Settings.Secure.putString(
                 mContentResolver,
                 Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL,
-                mOriginalSaturationLevel);
+                null);
     }
 
     @Test
@@ -113,6 +115,22 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION)
+    public void getAvailabilityStatus_defaultSettings_unavailable() {
+        // By default enabled == false.
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION)
+    public void getAvailabilityStatus_enabledDefaultDisplayMode_available() {
+        setDaltonizerEnabled(1);
+
+        // By default display mode is deuteranomaly.
+        assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION)
     public void getAvailabilityStatus_flagEnabledProtanEnabled_available() {
         setDaltonizerMode(/* enabled= */ 1, /* mode= */ 11);
         assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
@@ -306,10 +324,7 @@
         mLifecycle.addObserver(mController);
         mLifecycle.handleLifecycleEvent(ON_RESUME);
 
-        Settings.Secure.putInt(
-                mContentResolver,
-                Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
-                1);
+        setDaltonizerEnabled(1);
         shadowOf(Looper.getMainLooper()).idle();
 
         assertThat(mPreference.isEnabled()).isTrue();
@@ -324,10 +339,7 @@
         mLifecycle.addObserver(mController);
         mLifecycle.handleLifecycleEvent(ON_RESUME);
 
-        Settings.Secure.putInt(
-                mContentResolver,
-                Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
-                0);
+        setDaltonizerEnabled(0);
         shadowOf(Looper.getMainLooper()).idle();
 
         assertThat(mPreference.isEnabled()).isFalse();
@@ -342,10 +354,7 @@
         mLifecycle.addObserver(mController);
         mLifecycle.handleLifecycleEvent(ON_RESUME);
 
-        Settings.Secure.putInt(
-                mContentResolver,
-                Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
-                0);
+        setDaltonizerDisplay(0);
         shadowOf(Looper.getMainLooper()).idle();
 
         assertThat(mPreference.isEnabled()).isFalse();
@@ -361,23 +370,28 @@
         mLifecycle.handleLifecycleEvent(ON_STOP);
 
         // enabled.
-        Settings.Secure.putInt(
-                mContentResolver,
-                Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
-                1);
+        setDaltonizerEnabled(1);
         shadowOf(Looper.getMainLooper()).idle();
 
         assertThat(mPreference.isEnabled()).isFalse();
     }
 
     private void setDaltonizerMode(int enabled, int mode) {
+        setDaltonizerEnabled(enabled);
+        setDaltonizerDisplay(mode);
+    }
+
+    private void setDaltonizerEnabled(int enabled) {
         Settings.Secure.putInt(
                 mContentResolver,
                 Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
                 enabled);
-        Settings.Secure.putInt(
+    }
+
+    private void setDaltonizerDisplay(int mode) {
+        Settings.Secure.putString(
                 mContentResolver,
                 Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER,
-                mode);
+                Integer.toString(mode));
     }
 }
diff --git a/tests/robotests/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractorTest.kt b/tests/robotests/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractorTest.kt
new file mode 100644
index 0000000..a83b7c2
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/bluetooth/domain/interactor/SpatialAudioInteractorTest.kt
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth.domain.interactor
+
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothProfile
+import android.content.Context
+import android.media.AudioDeviceAttributes
+import android.media.AudioDeviceInfo
+import android.media.AudioManager
+import androidx.test.core.app.ApplicationProvider
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LeAudioProfile
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
+import com.android.settingslib.media.data.repository.SpatializerRepository
+import com.android.settingslib.media.domain.interactor.SpatializerInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.robolectric.RobolectricTestRunner
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(RobolectricTestRunner::class)
+class SpatialAudioInteractorTest {
+    @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+    @Mock private lateinit var audioManager: AudioManager
+    @Mock private lateinit var cachedDevice: CachedBluetoothDevice
+    @Mock private lateinit var bluetoothDevice: BluetoothDevice
+    @Mock private lateinit var spatializerRepository: SpatializerRepository
+    @Mock private lateinit var leAudioProfile: LeAudioProfile
+
+    private lateinit var underTest: SpatialAudioInteractor
+    private val testScope = TestScope()
+
+    @Before
+    fun setUp() {
+        val context = spy(ApplicationProvider.getApplicationContext<Context>())
+        `when`(cachedDevice.device).thenReturn(bluetoothDevice)
+        `when`(cachedDevice.address).thenReturn(BLUETOOTH_ADDRESS)
+        `when`(leAudioProfile.profileId).thenReturn(BluetoothProfile.LE_AUDIO)
+        underTest =
+            SpatialAudioInteractorImpl(
+                context,
+                audioManager,
+                SpatializerInteractor(spatializerRepository),
+                testScope.backgroundScope,
+                testScope.testScheduler)
+    }
+
+    @Test
+    fun getDeviceSetting_noAudioProfile_returnNull() {
+        testScope.runTest {
+            val setting = getLatestValue(underTest.getDeviceSetting(cachedDevice))
+
+            assertThat(setting).isNull()
+            verifyNoInteractions(spatializerRepository)
+        }
+    }
+
+    @Test
+    fun getDeviceSetting_audioProfileNotEnabled_returnNull() {
+        testScope.runTest {
+            `when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
+            `when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(false)
+
+            val setting = getLatestValue(underTest.getDeviceSetting(cachedDevice))
+
+            assertThat(setting).isNull()
+            verifyNoInteractions(spatializerRepository)
+        }
+    }
+
+    @Test
+    fun getDeviceSetting_spatialAudioNotSupported_returnNull() {
+        testScope.runTest {
+            `when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
+            `when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(true)
+            `when`(
+                    spatializerRepository.isSpatialAudioAvailableForDevice(
+                        BLE_AUDIO_DEVICE_ATTRIBUTES))
+                .thenReturn(false)
+
+            val setting = getLatestValue(underTest.getDeviceSetting(cachedDevice))
+
+            assertThat(setting).isNull()
+        }
+    }
+
+    @Test
+    fun getDeviceSetting_spatialAudioSupported_returnTwoToggles() {
+        testScope.runTest {
+            `when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
+            `when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(true)
+            `when`(
+                    spatializerRepository.isSpatialAudioAvailableForDevice(
+                        BLE_AUDIO_DEVICE_ATTRIBUTES))
+                .thenReturn(true)
+            `when`(
+                    spatializerRepository.isHeadTrackingAvailableForDevice(
+                        BLE_AUDIO_DEVICE_ATTRIBUTES))
+                .thenReturn(false)
+            `when`(spatializerRepository.getSpatialAudioCompatibleDevices())
+                .thenReturn(listOf(BLE_AUDIO_DEVICE_ATTRIBUTES))
+            `when`(spatializerRepository.isHeadTrackingEnabled(BLE_AUDIO_DEVICE_ATTRIBUTES))
+                .thenReturn(false)
+
+            val setting =
+                getLatestValue(underTest.getDeviceSetting(cachedDevice))
+                    as DeviceSettingModel.MultiTogglePreference
+
+            assertThat(setting).isNotNull()
+            assertThat(setting.toggles.size).isEqualTo(2)
+            assertThat(setting.state.selectedIndex).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun getDeviceSetting_headTrackingSupported_returnThreeToggles() {
+        testScope.runTest {
+            `when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
+            `when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(true)
+            `when`(
+                    spatializerRepository.isSpatialAudioAvailableForDevice(
+                        BLE_AUDIO_DEVICE_ATTRIBUTES))
+                .thenReturn(true)
+            `when`(
+                    spatializerRepository.isHeadTrackingAvailableForDevice(
+                        BLE_AUDIO_DEVICE_ATTRIBUTES))
+                .thenReturn(true)
+            `when`(spatializerRepository.getSpatialAudioCompatibleDevices())
+                .thenReturn(listOf(BLE_AUDIO_DEVICE_ATTRIBUTES))
+            `when`(spatializerRepository.isHeadTrackingEnabled(BLE_AUDIO_DEVICE_ATTRIBUTES))
+                .thenReturn(true)
+
+            val setting =
+                getLatestValue(underTest.getDeviceSetting(cachedDevice))
+                    as DeviceSettingModel.MultiTogglePreference
+
+            assertThat(setting).isNotNull()
+            assertThat(setting.toggles.size).isEqualTo(3)
+            assertThat(setting.state.selectedIndex).isEqualTo(2)
+        }
+    }
+
+    @Test
+    fun getDeviceSetting_updateState_enableSpatialAudio() {
+        testScope.runTest {
+            `when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
+            `when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(true)
+            `when`(
+                    spatializerRepository.isSpatialAudioAvailableForDevice(
+                        BLE_AUDIO_DEVICE_ATTRIBUTES))
+                .thenReturn(true)
+            `when`(
+                    spatializerRepository.isHeadTrackingAvailableForDevice(
+                        BLE_AUDIO_DEVICE_ATTRIBUTES))
+                .thenReturn(true)
+            `when`(spatializerRepository.getSpatialAudioCompatibleDevices()).thenReturn(listOf())
+            `when`(spatializerRepository.isHeadTrackingEnabled(BLE_AUDIO_DEVICE_ATTRIBUTES))
+                .thenReturn(false)
+
+            val setting =
+                getLatestValue(underTest.getDeviceSetting(cachedDevice))
+                    as DeviceSettingModel.MultiTogglePreference
+            setting.updateState(DeviceSettingStateModel.MultiTogglePreferenceState(2))
+            runCurrent()
+
+            assertThat(setting).isNotNull()
+            verify(spatializerRepository, times(1))
+                .addSpatialAudioCompatibleDevice(BLE_AUDIO_DEVICE_ATTRIBUTES)
+        }
+    }
+
+    @Test
+    fun getDeviceSetting_updateState_enableHeadTracking() {
+        testScope.runTest {
+            `when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
+            `when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(true)
+            `when`(
+                spatializerRepository.isSpatialAudioAvailableForDevice(
+                    BLE_AUDIO_DEVICE_ATTRIBUTES))
+                .thenReturn(true)
+            `when`(
+                spatializerRepository.isHeadTrackingAvailableForDevice(
+                    BLE_AUDIO_DEVICE_ATTRIBUTES))
+                .thenReturn(true)
+            `when`(spatializerRepository.getSpatialAudioCompatibleDevices()).thenReturn(listOf())
+            `when`(spatializerRepository.isHeadTrackingEnabled(BLE_AUDIO_DEVICE_ATTRIBUTES))
+                .thenReturn(false)
+
+            val setting =
+                getLatestValue(underTest.getDeviceSetting(cachedDevice))
+                    as DeviceSettingModel.MultiTogglePreference
+            setting.updateState(DeviceSettingStateModel.MultiTogglePreferenceState(2))
+            runCurrent()
+
+            assertThat(setting).isNotNull()
+            verify(spatializerRepository, times(1))
+                .addSpatialAudioCompatibleDevice(BLE_AUDIO_DEVICE_ATTRIBUTES)
+            verify(spatializerRepository, times(1))
+                .setHeadTrackingEnabled(BLE_AUDIO_DEVICE_ATTRIBUTES, true)
+        }
+    }
+
+    private fun getLatestValue(deviceSettingFlow: Flow<DeviceSettingModel?>): DeviceSettingModel? {
+        var latestValue: DeviceSettingModel? = null
+        deviceSettingFlow.onEach { latestValue = it }.launchIn(testScope.backgroundScope)
+        testScope.runCurrent()
+        return latestValue
+    }
+
+    private companion object {
+        const val BLUETOOTH_ADDRESS = "12:34:56:78:12:34"
+        val BLE_AUDIO_DEVICE_ATTRIBUTES =
+            AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                AudioDeviceInfo.TYPE_BLE_HEADSET,
+                BLUETOOTH_ADDRESS,
+            )
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt b/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt
index 468a2f0..609d767 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt
+++ b/tests/robotests/src/com/android/settings/bluetooth/ui/view/DeviceDetailsFragmentFormatterTest.kt
@@ -19,11 +19,13 @@
 import android.bluetooth.BluetoothAdapter
 import android.content.Context
 import android.graphics.Bitmap
+import android.media.AudioManager
 import androidx.fragment.app.FragmentActivity
 import androidx.preference.Preference
 import androidx.preference.PreferenceManager
 import androidx.preference.PreferenceScreen
 import androidx.test.core.app.ApplicationProvider
+import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor
 import com.android.settings.dashboard.DashboardFragment
 import com.android.settings.testutils.FakeFeatureFactory
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
@@ -31,6 +33,7 @@
 import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
@@ -45,6 +48,7 @@
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
+import org.mockito.Mockito.any
 import org.mockito.Mockito.`when`
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
@@ -59,6 +63,7 @@
     @Mock private lateinit var cachedDevice: CachedBluetoothDevice
     @Mock private lateinit var bluetoothAdapter: BluetoothAdapter
     @Mock private lateinit var repository: DeviceSettingRepository
+    @Mock private lateinit var spatialAudioInteractor: SpatialAudioInteractor
 
     private lateinit var fragment: TestFragment
     private lateinit var underTest: DeviceDetailsFragmentFormatter
@@ -73,6 +78,10 @@
                 featureFactory.bluetoothFeatureProvider.getDeviceSettingRepository(
                     eq(context), eq(bluetoothAdapter), any()))
             .thenReturn(repository)
+        `when`(
+            featureFactory.bluetoothFeatureProvider.getSpatialAudioInteractor(
+                eq(context), any(AudioManager::class.java), any()))
+            .thenReturn(spatialAudioInteractor)
         val fragmentActivity = Robolectric.setupActivity(FragmentActivity::class.java)
         assertThat(fragmentActivity.applicationContext).isNotNull()
         fragment = TestFragment(context)
@@ -186,7 +195,15 @@
                             toggles =
                                 listOf(
                                     ToggleModel(
-                                        "", Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))),
+                                        "", DeviceSettingIcon.BitmapIcon(
+                                            Bitmap.createBitmap(
+                                                1,
+                                                1,
+                                                Bitmap.Config.ARGB_8888
+                                            )
+                                        )
+                                    )
+                                ),
                             isActive = true,
                             state = DeviceSettingStateModel.MultiTogglePreferenceState(0),
                             isAllowedChangingState = true,
diff --git a/tests/robotests/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModelTest.kt b/tests/robotests/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModelTest.kt
index cc462bb..a1fadb8 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModelTest.kt
+++ b/tests/robotests/src/com/android/settings/bluetooth/ui/viewmodel/BluetoothDeviceDetailsViewModelTest.kt
@@ -20,6 +20,7 @@
 import android.content.Context
 import android.graphics.Bitmap
 import androidx.test.core.app.ApplicationProvider
+import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor
 import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout
 import com.android.settings.testutils.FakeFeatureFactory
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
@@ -27,6 +28,7 @@
 import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
@@ -45,6 +47,8 @@
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
@@ -61,6 +65,8 @@
 
     @Mock private lateinit var repository: DeviceSettingRepository
 
+    @Mock private lateinit var spatialAudioInteractor: SpatialAudioInteractor
+
     private lateinit var underTest: BluetoothDeviceDetailsViewModel
     private lateinit var featureFactory: FakeFeatureFactory
     private val testScope = TestScope()
@@ -74,7 +80,8 @@
                     eq(context), eq(bluetoothAdapter), any()))
             .thenReturn(repository)
 
-        underTest = BluetoothDeviceDetailsViewModel(repository, cachedDevice)
+        underTest =
+            BluetoothDeviceDetailsViewModel(repository, spatialAudioInteractor, cachedDevice)
     }
 
     @Test
@@ -92,6 +99,66 @@
     }
 
     @Test
+    fun getDeviceSetting_returnRepositoryResponse() {
+        testScope.runTest {
+            val remoteSettingId1 = 10001
+            val pref = buildMultiTogglePreference(remoteSettingId1)
+            `when`(repository.getDeviceSettingsConfig(cachedDevice))
+                .thenReturn(
+                    DeviceSettingConfigModel(
+                        listOf(
+                            BUILTIN_SETTING_ITEM_1,
+                            buildRemoteSettingItem(remoteSettingId1),
+                        ),
+                        listOf(),
+                        "footer"))
+            `when`(repository.getDeviceSetting(cachedDevice, remoteSettingId1))
+                .thenReturn(flowOf(pref))
+
+            var deviceSetting: DeviceSettingModel? = null
+            underTest
+                .getDeviceSetting(cachedDevice, remoteSettingId1)
+                .onEach { deviceSetting = it }
+                .launchIn(testScope.backgroundScope)
+            runCurrent()
+
+            assertThat(deviceSetting).isSameInstanceAs(pref)
+            verify(repository, times(1)).getDeviceSetting(cachedDevice, remoteSettingId1)
+        }
+    }
+
+    @Test
+    fun getDeviceSetting_spatialAudio_returnSpatialAudioInteractorResponse() {
+        testScope.runTest {
+            val pref =
+                buildMultiTogglePreference(
+                    DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE)
+            `when`(repository.getDeviceSettingsConfig(cachedDevice))
+                .thenReturn(
+                    DeviceSettingConfigModel(
+                        listOf(
+                            BUILTIN_SETTING_ITEM_1,
+                            buildRemoteSettingItem(
+                                DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE),
+                        ),
+                        listOf(),
+                        "footer"))
+            `when`(spatialAudioInteractor.getDeviceSetting(cachedDevice)).thenReturn(flowOf(pref))
+
+            var deviceSetting: DeviceSettingModel? = null
+            underTest
+                .getDeviceSetting(
+                    cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE)
+                .onEach { deviceSetting = it }
+                .launchIn(testScope.backgroundScope)
+            runCurrent()
+
+            assertThat(deviceSetting).isSameInstanceAs(pref)
+            verify(spatialAudioInteractor, times(1)).getDeviceSetting(cachedDevice)
+        }
+    }
+
+    @Test
     fun getLayout_builtinDeviceSettings() {
         testScope.runTest {
             `when`(repository.getDeviceSettingsConfig(cachedDevice))
@@ -163,7 +230,12 @@
             cachedDevice,
             settingId,
             "title",
-            toggles = listOf(ToggleModel("", Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))),
+            toggles =
+                listOf(
+                    ToggleModel(
+                        "toggle1",
+                        DeviceSettingIcon.BitmapIcon(
+                            Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)))),
             isActive = true,
             state = DeviceSettingStateModel.MultiTogglePreferenceState(0),
             isAllowedChangingState = true,
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java
index 4c64808..85fc6e2 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java
@@ -29,6 +29,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
+import android.os.Bundle;
 import android.os.LocaleList;
 import android.text.format.DateUtils;
 
@@ -57,6 +58,7 @@
     private static final String PREF_KEY = "pref_key";
     private static final String PREF_KEY2 = "pref_key2";
     private static final String PREF_SUMMARY = "fake preference summary";
+    private static final String KEY_SPINNER_POSITION = "spinner_position";
     private static final long TIME_LESS_THAN_HALF_MINUTE  = DateUtils.MINUTE_IN_MILLIS / 2  - 1;
 
     @Mock private InstrumentedPreferenceFragment mFragment;
@@ -149,6 +151,15 @@
     }
 
     @Test
+    public void onSaveInstanceState_returnExpectedResult() {
+        mBatteryUsageBreakdownController.mSpinnerPosition = 1;
+        final Bundle savedInstanceState = new Bundle();
+        mBatteryUsageBreakdownController.onSaveInstanceState(savedInstanceState);
+
+        assertThat(savedInstanceState.getInt(KEY_SPINNER_POSITION)).isEqualTo(1);
+    }
+
+    @Test
     public void addAllPreferences_addAllPreferences() {
         final String appLabel = "fake app label";
         doReturn(1).when(mAppListPreferenceGroup).getPreferenceCount();
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/SpinnerPreferenceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/SpinnerPreferenceTest.java
deleted file mode 100644
index 8050984..0000000
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/SpinnerPreferenceTest.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.fuelgauge.batteryusage;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-
-import android.content.Context;
-import android.widget.Spinner;
-
-import androidx.preference.Preference;
-
-import com.android.settings.R;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-@RunWith(RobolectricTestRunner.class)
-public final class SpinnerPreferenceTest {
-
-    private Context mContext;
-    private SpinnerPreference mSpinnerPreference;
-
-    @Mock private Spinner mMockSpinner;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mContext = spy(RuntimeEnvironment.application);
-        mSpinnerPreference = new SpinnerPreference(mContext, /* attrs= */ null);
-    }
-
-    @Test
-    public void constructor_returnExpectedResult() {
-        assertThat(mSpinnerPreference.getLayoutResource()).isEqualTo(R.layout.preference_spinner);
-    }
-
-    @Test
-    public void initializeSpinner_returnExpectedResult() {
-        final String[] items = new String[] {"item1", "item2"};
-        mSpinnerPreference.initializeSpinner(items, null);
-        assertThat(mSpinnerPreference.mItems).isEqualTo(items);
-    }
-
-    @Test
-    public void onSaveInstanceState_returnExpectedResult() {
-        doReturn(1).when(mMockSpinner).getSelectedItemPosition();
-        mSpinnerPreference.mSpinner = mMockSpinner;
-        SpinnerPreference.SavedState savedState =
-                (SpinnerPreference.SavedState) mSpinnerPreference.onSaveInstanceState();
-        assertThat(savedState.getSpinnerPosition()).isEqualTo(1);
-    }
-
-    @Test
-    public void onRestoreInstanceState_returnExpectedResult() {
-        SpinnerPreference.SavedState savedState =
-                new SpinnerPreference.SavedState(Preference.BaseSavedState.EMPTY_STATE, 2);
-        mSpinnerPreference.onRestoreInstanceState(savedState);
-        assertThat(mSpinnerPreference.mSavedSpinnerPosition).isEqualTo(2);
-    }
-}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/CircularIconsPreferenceTest.java b/tests/robotests/src/com/android/settings/notification/modes/CircularIconsPreferenceTest.java
index d145f25..5544832 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/CircularIconsPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/CircularIconsPreferenceTest.java
@@ -62,8 +62,7 @@
 
     private Context mContext;
     private CircularIconsPreference mPreference;
-    private PreferenceViewHolder mViewHolder;
-    private ViewGroup mContainer;
+    private CircularIconsView mContainer;
 
     private int mOneIconWidth;
 
@@ -73,179 +72,211 @@
         mContext = RuntimeEnvironment.application;
         CircularIconSet.sExecutorService = MoreExecutors.newDirectExecutorService();
         mPreference = new TestableCircularIconsPreference(mContext);
-        // Tests should call bindAndMeasureViewHolder() so that icons can be added.
+        // Tests should call bindAndLayoutViewHolder() so that icons can be added.
 
         Resources res = mContext.getResources();
         mOneIconWidth = res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_diameter)
                 + res.getDimensionPixelSize(R.dimen.zen_mode_circular_icon_margin_between);
     }
 
-    private void bindAndMeasureViewHolder(int viewWidth) {
+    private void bindAndLayoutViewHolder(int viewWidth) {
         bindViewHolder();
-        measureViewHolder(viewWidth);
+        layoutViewHolder(viewWidth);
     }
 
     private void bindViewHolder() {
         View preferenceView = LayoutInflater.from(mContext).inflate(mPreference.getLayoutResource(),
                 null);
         mContainer = checkNotNull(preferenceView.findViewById(R.id.circles_container));
-        mViewHolder = PreferenceViewHolder.createInstanceForTests(preferenceView);
-        mPreference.onBindViewHolder(mViewHolder);
+        mContainer.setUiExecutor(MoreExecutors.directExecutor());
+        PreferenceViewHolder viewHolder = PreferenceViewHolder.createInstanceForTests(
+                preferenceView);
+        mPreference.onBindViewHolder(viewHolder);
     }
 
-    private void measureViewHolder(int viewWidth) {
+    private void layoutViewHolder(int viewWidth) {
         checkState(mContainer != null, "Call bindViewHolder() first!");
         mContainer.measure(makeMeasureSpec(viewWidth, View.MeasureSpec.EXACTLY),
                 makeMeasureSpec(1000, View.MeasureSpec.EXACTLY));
-        mContainer.getViewTreeObserver().dispatchOnGlobalLayout();
+        mContainer.layout(0, 0, viewWidth, 1000);
     }
 
     @Test
-    public void displayIcons_loadsIcons() {
+    public void setIcons_loadsIcons() {
         CircularIconSet<Integer> iconSet = new CircularIconSet<>(ImmutableList.of(1, 2),
                 ColorDrawable::new);
 
-        bindAndMeasureViewHolder(VIEW_WIDTH);
-        mPreference.displayIcons(iconSet);
+        bindAndLayoutViewHolder(VIEW_WIDTH);
+        mPreference.setIcons(iconSet);
 
-        assertThat(getIcons(mContainer)).hasSize(2);
-        assertThat(((ColorDrawable) getIcons(mContainer).get(0)).getColor()).isEqualTo(1);
-        assertThat(((ColorDrawable) getIcons(mContainer).get(1)).getColor()).isEqualTo(2);
+        assertThat(getDrawables(mContainer)).hasSize(2);
+        assertThat(((ColorDrawable) getDrawables(mContainer).get(0)).getColor()).isEqualTo(1);
+        assertThat(((ColorDrawable) getDrawables(mContainer).get(1)).getColor()).isEqualTo(2);
         assertThat(getPlusText(mContainer)).isNull();
     }
 
     @Test
-    public void displayIcons_noIcons_hidesRow() {
+    public void setIcons_noIcons_hidesRow() {
         CircularIconSet<Integer> iconSet = new CircularIconSet<>(ImmutableList.of(),
                 ColorDrawable::new);
 
-        bindAndMeasureViewHolder(VIEW_WIDTH);
-        mPreference.displayIcons(iconSet);
+        bindAndLayoutViewHolder(VIEW_WIDTH);
+        mPreference.setIcons(iconSet);
 
         assertThat(mContainer.getVisibility()).isEqualTo(View.GONE);
     }
 
     @Test
-    public void displayIcons_exactlyMaxIcons_loadsAllIcons() throws Exception {
+    public void setIcons_exactlyMaxIcons_loadsAllIcons() throws Exception {
         int width = 300;
         int fittingCircles = width / mOneIconWidth;
         CircularIconSet<Integer> iconSet = new CircularIconSet<>(
                 IntStream.range(0, fittingCircles).boxed().toList(),
                 ColorDrawable::new);
 
-        bindAndMeasureViewHolder(width);
-        mPreference.displayIcons(iconSet);
+        bindAndLayoutViewHolder(width);
+        mPreference.setIcons(iconSet);
 
-        assertThat(getIcons(mContainer)).hasSize(fittingCircles);
-        assertThat(getIcons(mContainer)).containsExactlyElementsIn(
+        assertThat(getDrawables(mContainer)).hasSize(fittingCircles);
+        assertThat(getDrawables(mContainer)).containsExactlyElementsIn(
                 Futures.allAsList(iconSet.getIcons()).get()).inOrder();
         assertThat(getPlusText(mContainer)).isNull();
-
     }
 
     @Test
-    public void displayIcons_tooManyIcons_loadsFirstNAndPlusIcon() throws Exception {
+    public void setIcons_tooManyIcons_loadsFirstNAndPlusIcon() throws Exception {
         int width = 300;
         int fittingCircles = width / mOneIconWidth;
         CircularIconSet<Integer> iconSet = new CircularIconSet<>(
                 IntStream.range(0, fittingCircles + 5).boxed().toList(),
                 ColorDrawable::new);
 
-        bindAndMeasureViewHolder(width);
-        mPreference.displayIcons(iconSet);
+        bindAndLayoutViewHolder(width);
+        mPreference.setIcons(iconSet);
 
         // N-1 icons, plus (+6) text.
-        assertThat(getIcons(mContainer)).hasSize(fittingCircles - 1);
-        assertThat(getIcons(mContainer)).containsExactlyElementsIn(
+        assertThat(getDrawables(mContainer)).hasSize(fittingCircles - 1);
+        assertThat(getDrawables(mContainer)).containsExactlyElementsIn(
                         Futures.allAsList(iconSet.getIcons(fittingCircles - 1)).get())
                 .inOrder();
         assertThat(getPlusText(mContainer)).isEqualTo("+6");
     }
 
     @Test
-    public void displayIcons_teenyTinySpace_showsPlusIcon_noCrash() {
+    public void setIcons_teenyTinySpace_showsPlusIcon_noCrash() {
         CircularIconSet<Integer> iconSet = new CircularIconSet<>(ImmutableList.of(1, 2),
                 ColorDrawable::new);
 
-        bindAndMeasureViewHolder(1);
-        mPreference.displayIcons(iconSet);
+        bindAndLayoutViewHolder(1);
+        mPreference.setIcons(iconSet);
 
-        assertThat(getIcons(mContainer)).isEmpty();
+        assertThat(getDrawables(mContainer)).isEmpty();
         assertThat(getPlusText(mContainer)).isEqualTo("+2");
     }
 
     @Test
-    public void displayIcons_beforeBind_loadsIconsOnBindAndMeasure() {
+    public void setIcons_beforeBind_loadsIconsOnBindAndMeasure() {
         CircularIconSet<Integer> iconSet = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
                 ColorDrawable::new);
 
-        mPreference.displayIcons(iconSet);
-        assertThat(mPreference.getLoadedIcons()).isNull(); // Hold...
+        mPreference.setIcons(iconSet);
+        assertThat(mContainer).isNull(); // Hold...
 
         bindViewHolder();
-        assertThat(mPreference.getLoadedIcons()).isNull(); // Hooooold...
+        assertThat(getDrawables(mContainer)).hasSize(0); // Hooooold...
 
-        measureViewHolder(VIEW_WIDTH);
-        assertThat(mPreference.getLoadedIcons().icons()).hasSize(3);
-        assertThat(getIcons(mContainer)).hasSize(3);
+        layoutViewHolder(VIEW_WIDTH);
+        assertThat(getDrawables(mContainer)).hasSize(3);
     }
 
     @Test
-    public void displayIcons_beforeMeasure_loadsIconsOnMeasure() {
+    public void setIcons_beforeMeasure_loadsIconsOnMeasure() {
         CircularIconSet<Integer> iconSet = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
                 ColorDrawable::new);
         bindViewHolder();
 
-        mPreference.displayIcons(iconSet);
-        assertThat(mPreference.getLoadedIcons()).isNull();
+        mPreference.setIcons(iconSet);
+        assertThat(getDrawables(mContainer)).hasSize(0);
 
-        measureViewHolder(VIEW_WIDTH);
-        assertThat(getIcons(mContainer)).hasSize(3);
+        layoutViewHolder(VIEW_WIDTH);
+        assertThat(getDrawables(mContainer)).hasSize(3);
     }
 
     @Test
-    public void displayIcons_calledAgain_reloadsIcons() {
+    public void setIcons_calledAgain_reloadsIcons() {
         CircularIconSet<Integer> threeIcons = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
                 ColorDrawable::new);
         CircularIconSet<Integer> twoIcons = new CircularIconSet<>(ImmutableList.of(1, 2),
                 ColorDrawable::new);
         CircularIconSet<Integer> fourIcons = new CircularIconSet<>(ImmutableList.of(1, 2, 3, 4),
                 ColorDrawable::new);
-        bindAndMeasureViewHolder(VIEW_WIDTH);
+        bindAndLayoutViewHolder(VIEW_WIDTH);
 
-        mPreference.displayIcons(threeIcons);
-        assertThat(mPreference.getLoadedIcons()).isNotNull();
-        assertThat(getIcons(mContainer)).hasSize(3);
+        mPreference.setIcons(threeIcons);
+        assertThat(getDrawables(mContainer)).hasSize(3);
 
-        mPreference.displayIcons(twoIcons);
-        assertThat(mPreference.getLoadedIcons()).isNotNull();
-        assertThat(getIcons(mContainer)).hasSize(2);
+        mPreference.setIcons(twoIcons);
+        assertThat(getDrawables(mContainer)).hasSize(2);
 
-        mPreference.displayIcons(fourIcons);
-        assertThat(mPreference.getLoadedIcons()).isNotNull();
-        assertThat(getIcons(mContainer)).hasSize(4);
+        mPreference.setIcons(fourIcons);
+        assertThat(getDrawables(mContainer)).hasSize(4);
     }
 
     @Test
-    public void displayIcons_sameSet_doesNotReloadIcons() {
+    public void setIcons_sameSet_doesNotReloadIcons() {
         CircularIconSet<Integer> one = new CircularIconSet<>(ImmutableList.of(1, 2, 3),
                 ColorDrawable::new);
         CircularIconSet<Integer> same = Mockito.spy(new CircularIconSet<>(ImmutableList.of(1, 2, 3),
                 ColorDrawable::new));
         when(same.getIcons()).thenThrow(new RuntimeException("Shouldn't be called!"));
 
-        bindAndMeasureViewHolder(VIEW_WIDTH);
+        bindAndLayoutViewHolder(VIEW_WIDTH);
 
-        mPreference.displayIcons(one);
+        mPreference.setIcons(one);
 
-        mPreference.displayIcons(same); // if no exception, wasn't called.
+        mPreference.setIcons(same); // if no exception, wasn't called.
     }
 
     @Test
+    public void sizeChanged_reloadsIconsIfDifferentFit() {
+        CircularIconSet<Integer> largeIconSet = new CircularIconSet<>(
+                IntStream.range(0, 100).boxed().toList(),
+                ColorDrawable::new);
+        mPreference.setIcons(largeIconSet);
+
+        // Base space -> some icons
+        int firstWidth = 600;
+        int firstFittingCircles = firstWidth / mOneIconWidth;
+        bindAndLayoutViewHolder(firstWidth);
+
+        assertThat(getDrawables(mContainer)).hasSize(firstFittingCircles - 1);
+        assertThat(getPlusText(mContainer)).isEqualTo("+" + (100 - (firstFittingCircles - 1)));
+
+        // More space -> more icons
+        int secondWidth = 1000;
+        int secondFittingCircles = secondWidth / mOneIconWidth;
+        assertThat(secondFittingCircles).isGreaterThan(firstFittingCircles);
+        bindAndLayoutViewHolder(secondWidth);
+
+        assertThat(getDrawables(mContainer)).hasSize(secondFittingCircles - 1);
+        assertThat(getPlusText(mContainer)).isEqualTo("+" + (100 - (secondFittingCircles - 1)));
+
+        // Less space -> fewer icons
+        int thirdWidth = 600;
+        int thirdFittingCircles = thirdWidth / mOneIconWidth;
+        bindAndLayoutViewHolder(thirdWidth);
+
+        assertThat(getDrawables(mContainer)).hasSize(thirdFittingCircles - 1);
+        assertThat(getPlusText(mContainer)).isEqualTo("+" + (100 - (thirdFittingCircles - 1)));
+    }
+
+
+    @Test
     public void onBindViewHolder_withDifferentView_reloadsIconsCorrectly() {
         View preferenceViewOne = LayoutInflater.from(mContext).inflate(
                 mPreference.getLayoutResource(), null);
-        ViewGroup containerOne = preferenceViewOne.findViewById(R.id.circles_container);
+        CircularIconsView containerOne = preferenceViewOne.findViewById(R.id.circles_container);
+        containerOne.setUiExecutor(MoreExecutors.directExecutor());
         PreferenceViewHolder viewHolderOne = PreferenceViewHolder.createInstanceForTests(
                 preferenceViewOne);
         containerOne.measure(makeMeasureSpec(1000, View.MeasureSpec.EXACTLY),
@@ -253,7 +284,8 @@
 
         View preferenceViewTwo = LayoutInflater.from(mContext).inflate(
                 mPreference.getLayoutResource(), null);
-        ViewGroup containerTwo = preferenceViewTwo.findViewById(R.id.circles_container);
+        CircularIconsView containerTwo = preferenceViewTwo.findViewById(R.id.circles_container);
+        containerTwo.setUiExecutor(MoreExecutors.directExecutor());
         PreferenceViewHolder viewHolderTwo = PreferenceViewHolder.createInstanceForTests(
                 preferenceViewTwo);
         containerTwo.measure(makeMeasureSpec(1000, View.MeasureSpec.EXACTLY),
@@ -265,25 +297,25 @@
                 ColorDrawable::new);
 
         mPreference.onBindViewHolder(viewHolderOne);
-        mPreference.displayIcons(iconSetOne);
-        assertThat(getIcons(containerOne)).hasSize(3);
+        mPreference.setIcons(iconSetOne);
+        assertThat(getDrawables(containerOne)).hasSize(3);
 
         mPreference.onBindViewHolder(viewHolderTwo);
-        assertThat(getIcons(containerTwo)).hasSize(3);
+        assertThat(getDrawables(containerTwo)).hasSize(3);
 
-        mPreference.displayIcons(iconSetTwo);
+        mPreference.setIcons(iconSetTwo);
 
         // The second view is updated and the first view is unaffected.
-        assertThat(getIcons(containerTwo)).hasSize(2);
-        assertThat(getIcons(containerOne)).hasSize(3);
+        assertThat(getDrawables(containerTwo)).hasSize(2);
+        assertThat(getDrawables(containerOne)).hasSize(3);
     }
 
     @Test
-    public void setEnabled_afterDisplayIcons_showsEnabledOrDisabledImages() {
+    public void setEnabled_afterSetIcons_showsEnabledOrDisabledImages() {
         CircularIconSet<Integer> iconSet = new CircularIconSet<>(ImmutableList.of(1, 2),
                 ColorDrawable::new);
-        bindAndMeasureViewHolder(VIEW_WIDTH);
-        mPreference.displayIcons(iconSet);
+        bindAndLayoutViewHolder(VIEW_WIDTH);
+        mPreference.setIcons(iconSet);
         assertThat(getViews(mContainer)).hasSize(2);
 
         mPreference.setEnabled(false);
@@ -294,13 +326,13 @@
     }
 
     @Test
-    public void setEnabled_beforeDisplayIcons_showsEnabledOrDisabledImages() {
+    public void setEnabled_beforeSetIcons_showsEnabledOrDisabledImages() {
         CircularIconSet<Integer> iconSet = new CircularIconSet<>(ImmutableList.of(1, 2),
                 ColorDrawable::new);
 
         mPreference.setEnabled(false);
-        bindAndMeasureViewHolder(VIEW_WIDTH);
-        mPreference.displayIcons(iconSet);
+        bindAndLayoutViewHolder(VIEW_WIDTH);
+        mPreference.setIcons(iconSet);
 
         assertThat(getViews(mContainer)).hasSize(2);
         assertThat(getViews(mContainer).get(0).getAlpha()).isLessThan(1f);
@@ -314,7 +346,7 @@
         return views;
     }
 
-    private static List<Drawable> getIcons(ViewGroup container) {
+    private static List<Drawable> getDrawables(ViewGroup container) {
         ArrayList<Drawable> drawables = new ArrayList<>();
         for (int i = 0; i < container.getChildCount(); i++) {
             if (container.getChildAt(i) instanceof ImageView imageView) {
diff --git a/tests/robotests/src/com/android/settings/notification/modes/TestableCircularIconsPreference.java b/tests/robotests/src/com/android/settings/notification/modes/TestableCircularIconsPreference.java
index 6fefcac..6c1b059 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/TestableCircularIconsPreference.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/TestableCircularIconsPreference.java
@@ -20,14 +20,12 @@
 
 import androidx.preference.PreferenceViewHolder;
 
-import com.google.common.util.concurrent.MoreExecutors;
-
 class TestableCircularIconsPreference extends CircularIconsPreference {
 
     private PreferenceViewHolder mLastViewHolder;
 
     TestableCircularIconsPreference(Context context) {
-        super(context, MoreExecutors.directExecutor());
+        super(context);
     }
 
     @Override
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java
index 9263ffd..4148fa3 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java
@@ -19,6 +19,7 @@
 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
 import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
 
+import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -34,10 +35,12 @@
 import static org.robolectric.Shadows.shadowOf;
 
 import android.app.Flags;
+import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.UserInfo;
+import android.graphics.drawable.ColorDrawable;
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -50,6 +53,7 @@
 import androidx.fragment.app.Fragment;
 import androidx.preference.PreferenceViewHolder;
 
+import com.android.settings.R;
 import com.android.settings.SettingsActivity;
 import com.android.settingslib.applications.ApplicationsState;
 import com.android.settingslib.applications.ApplicationsState.AppEntry;
@@ -80,6 +84,7 @@
 
     private ZenModeAppsLinkPreferenceController mController;
     private CircularIconsPreference mPreference;
+    private CircularIconsView mIconsView;
 
     private Context mContext;
     @Mock
@@ -102,15 +107,18 @@
         mContext = RuntimeEnvironment.application;
         CircularIconSet.sExecutorService = MoreExecutors.newDirectExecutorService();
         mPreference = new TestableCircularIconsPreference(mContext);
-
         when(mApplicationsState.newSession(any(), any())).thenReturn(mSession);
+
         mController = new ZenModeAppsLinkPreferenceController(
                 mContext, "controller_key", mock(Fragment.class), mApplicationsState,
-                mZenModesBackend, mHelperBackend);
+                mZenModesBackend, mHelperBackend,
+                /* appIconRetriever= */ appInfo -> new ColorDrawable());
 
         // Ensure the preference view is bound & measured (needed to add child ImageViews).
         View preferenceView = LayoutInflater.from(mContext).inflate(mPreference.getLayoutResource(),
                 null);
+        mIconsView = checkNotNull(preferenceView.findViewById(R.id.circles_container));
+        mIconsView.setUiExecutor(MoreExecutors.directExecutor());
         preferenceView.measure(View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY),
                 View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY));
         PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(preferenceView);
@@ -163,7 +171,7 @@
         assertThat(launcherIntent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
                 .isEqualTo("com.android.settings.notification.modes.ZenModeAppsFragment");
         assertThat(launcherIntent.getIntExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY,
-                -1)).isEqualTo(0);
+                -1)).isEqualTo(SettingsEnums.ZEN_PRIORITY_MODE);
 
         Bundle bundle = launcherIntent.getBundleExtra(
                 SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
@@ -270,7 +278,7 @@
         appEntries.add(createAppEntry("test2", mContext.getUserId()));
         mController.mAppSessionCallbacks.onRebuildComplete(appEntries);
 
-        assertThat(mPreference.getLoadedIcons().icons()).hasSize(2);
+        assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(2);
     }
 
     @Test
@@ -296,6 +304,89 @@
     }
 
     @Test
+    public void updateState_noneToPriority_loadsBypassingAppsAndListensForChanges() {
+        ZenMode zenModeWithNone = new TestModeBuilder()
+                .setZenPolicy(new ZenPolicy.Builder().allowPriorityChannels(false).build())
+                .build();
+        ZenMode zenModeWithPriority = new TestModeBuilder()
+                .setZenPolicy(new ZenPolicy.Builder().allowPriorityChannels(true).build())
+                .build();
+        ArrayList<ApplicationsState.AppEntry> appEntries = new ArrayList<>();
+        appEntries.add(createAppEntry("test", mContext.getUserId()));
+        when(mHelperBackend.getPackagesBypassingDnd(mContext.getUserId(), false))
+                .thenReturn(List.of("test"));
+
+        mController.updateState(mPreference, zenModeWithNone);
+
+        assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(0);
+        verifyNoMoreInteractions(mApplicationsState);
+        verifyNoMoreInteractions(mSession);
+
+        mController.updateState(mPreference, zenModeWithPriority);
+
+        verify(mApplicationsState).newSession(any(), any());
+        verify(mSession).rebuild(any(), any(), anyBoolean());
+        mController.mAppSessionCallbacks.onRebuildComplete(appEntries);
+        assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(1);
+    }
+
+    @Test
+    public void updateState_priorityToNone_clearsBypassingAppsAndStopsListening() {
+        ZenMode zenModeWithNone = new TestModeBuilder()
+                .setZenPolicy(new ZenPolicy.Builder().allowPriorityChannels(false).build())
+                .build();
+        ZenMode zenModeWithPriority = new TestModeBuilder()
+                .setZenPolicy(new ZenPolicy.Builder().allowPriorityChannels(true).build())
+                .build();
+        ArrayList<ApplicationsState.AppEntry> appEntries = new ArrayList<>();
+        appEntries.add(createAppEntry("test", mContext.getUserId()));
+        when(mHelperBackend.getPackagesBypassingDnd(mContext.getUserId(), false))
+                .thenReturn(List.of("test"));
+
+        mController.updateState(mPreference, zenModeWithPriority);
+
+        verify(mApplicationsState).newSession(any(), any());
+        verify(mSession).rebuild(any(), any(), anyBoolean());
+        mController.mAppSessionCallbacks.onRebuildComplete(appEntries);
+        assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(1);
+
+        mController.updateState(mPreference, zenModeWithNone);
+
+        assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(0);
+        verify(mSession).deactivateSession();
+        verifyNoMoreInteractions(mSession);
+        verifyNoMoreInteractions(mApplicationsState);
+
+        // An errant callback (triggered by onResume and received asynchronously after
+        // updateState()) is ignored.
+        mController.mAppSessionCallbacks.onRebuildComplete(appEntries);
+
+        assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(0);
+    }
+
+    @Test
+    public void updateState_priorityToNoneToPriority_restartsListening() {
+        ZenMode zenModeWithNone = new TestModeBuilder()
+                .setZenPolicy(new ZenPolicy.Builder().allowPriorityChannels(false).build())
+                .build();
+        ZenMode zenModeWithPriority = new TestModeBuilder()
+                .setZenPolicy(new ZenPolicy.Builder().allowPriorityChannels(true).build())
+                .build();
+
+        mController.updateState(mPreference, zenModeWithPriority);
+        verify(mApplicationsState).newSession(any(), any());
+        verify(mSession).rebuild(any(), any(), anyBoolean());
+
+        mController.updateState(mPreference, zenModeWithNone);
+        verifyNoMoreInteractions(mApplicationsState);
+        verify(mSession).deactivateSession();
+
+        mController.updateState(mPreference, zenModeWithPriority);
+        verifyNoMoreInteractions(mApplicationsState);
+        verify(mSession).activateSession();
+    }
+
+    @Test
     public void testNoCrashIfAppsReadyBeforeRuleAvailable() {
         mController.mAppSessionCallbacks.onLoadEntriesCompleted();
     }
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java
index 8aa87e6..3db70fa 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java
@@ -95,7 +95,7 @@
 
         mController.updateState(pref, mode);
 
-        verify(pref).displayIcons(argThat(iconSet -> iconSet.size() == 3));
+        verify(pref).setIcons(argThat(iconSet -> iconSet.size() == 3));
     }
 
     @Test
@@ -107,7 +107,7 @@
 
         mController.updateState(pref, mode);
 
-        verify(pref).displayIcons(argThat(iconSet ->
+        verify(pref).setIcons(argThat(iconSet ->
                 iconSet.size() == ZenModeSummaryHelper.OTHER_SOUND_CATEGORIES.size()));
     }
 }
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java
index a4d141e..8555d71 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java
@@ -22,6 +22,7 @@
 import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
 import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
 
+import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -50,6 +51,7 @@
 
 import androidx.preference.PreferenceViewHolder;
 
+import com.android.settings.R;
 import com.android.settings.notification.modes.ZenHelperBackend.Contact;
 import com.android.settingslib.notification.ConversationIconFactory;
 import com.android.settingslib.notification.modes.TestModeBuilder;
@@ -76,6 +78,7 @@
 
     private ZenModePeopleLinkPreferenceController mController;
     private CircularIconsPreference mPreference;
+    private CircularIconsView mIconsView;
 
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -94,6 +97,8 @@
         // Ensure the preference view is bound & measured (needed to add icons).
         View preferenceView = LayoutInflater.from(mContext).inflate(mPreference.getLayoutResource(),
                 null);
+        mIconsView = checkNotNull(preferenceView.findViewById(R.id.circles_container));
+        mIconsView.setUiExecutor(MoreExecutors.directExecutor());
         preferenceView.measure(View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY),
                 View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY));
         PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(preferenceView);
@@ -142,9 +147,9 @@
 
         mController.updateState(mPreference, mode);
 
-        assertThat(mPreference.getLoadedIcons()).isNotNull();
-        assertThat(mPreference.getLoadedIcons().icons()).hasSize(2);
-        assertThat(mPreference.getLoadedIcons().icons().stream()
+        assertThat(mIconsView.getDisplayedIcons()).isNotNull();
+        assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(2);
+        assertThat(mIconsView.getDisplayedIcons().icons().stream()
                 .map(ColorDrawable.class::cast)
                 .map(d -> d.getColor()).toList())
                 .containsExactly(2, 3).inOrder();
@@ -162,9 +167,9 @@
 
         mController.updateState(mPreference, mode);
 
-        assertThat(mPreference.getLoadedIcons()).isNotNull();
-        assertThat(mPreference.getLoadedIcons().icons()).hasSize(4);
-        assertThat(mPreference.getLoadedIcons().icons().stream()
+        assertThat(mIconsView.getDisplayedIcons()).isNotNull();
+        assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(4);
+        assertThat(mIconsView.getDisplayedIcons().icons().stream()
                 .map(ColorDrawable.class::cast)
                 .map(d -> d.getColor()).toList())
                 .containsExactly(1, 2, 3, 4).inOrder();
@@ -182,8 +187,8 @@
 
         mController.updateState(mPreference, mode);
 
-        assertThat(mPreference.getLoadedIcons()).isNotNull();
-        assertThat(mPreference.getLoadedIcons().icons()).hasSize(1);
+        assertThat(mIconsView.getDisplayedIcons()).isNotNull();
+        assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(1);
         verify(mHelperBackend, never()).getContactPhoto(any());
     }
 
@@ -201,8 +206,8 @@
 
         mController.updateState(mPreference, mode);
 
-        assertThat(mPreference.getLoadedIcons()).isNotNull();
-        assertThat(mPreference.getLoadedIcons().icons()).hasSize(3);
+        assertThat(mIconsView.getDisplayedIcons()).isNotNull();
+        assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(3);
         verify(mConversationIconFactory, times(3)).getConversationDrawable((ShortcutInfo) any(),
                 any(), anyInt(), anyBoolean());
     }
diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceControllerTest.java
index 27df890..2569ca3 100644
--- a/tests/robotests/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/zen/ZenModeAddBypassingAppsPreferenceControllerTest.java
@@ -162,7 +162,4 @@
         assertThat(pref.getKey()).isEqualTo(
                 ZenModeAddBypassingAppsPreferenceController.KEY_NO_APPS);
     }
-
-    // TODO(b/331624810): Add tests to verify updateAppList() when the filter is
-    //  ApplicationsState.FILTER_ENABLED_NOT_QUIET
 }
diff --git a/tests/robotests/src/com/android/settings/users/UserCapabilitiesTest.java b/tests/robotests/src/com/android/settings/users/UserCapabilitiesTest.java
index a47703c..bec49e1 100644
--- a/tests/robotests/src/com/android/settings/users/UserCapabilitiesTest.java
+++ b/tests/robotests/src/com/android/settings/users/UserCapabilitiesTest.java
@@ -81,6 +81,17 @@
     }
 
     @Test
+    public void changeAdminStatus_updateUserCapabilities_mIsAdminGetsUpdated() {
+        mUserManager.setIsAdminUser(false);
+        UserCapabilities userCapabilities = UserCapabilities.create(mContext);
+        assertThat(userCapabilities.isAdmin()).isFalse();
+
+        mUserManager.setIsAdminUser(true);
+        userCapabilities.updateAddUserCapabilities(mContext);
+        assertThat(userCapabilities.mIsAdmin).isTrue();
+    }
+
+    @Test
     public void userSwitchEnabled_off() {
         mUserManager.setUserSwitcherEnabled(false);
 
diff --git a/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt b/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt
index 19433f3..e7fc3ed 100644
--- a/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt
+++ b/tests/screenshot/src/com/android/settings/tests/screenshot/biometrics/fingerprint/Injector.kt
@@ -111,9 +111,10 @@
 
   var fingerprintEnrollViewModel =
     FingerprintEnrollViewModel(
-      fingerprintManagerInteractor,
       gatekeeperViewModel,
       navigationViewModel,
+      fingerprintManagerInteractor,
+      fingerprintManagerInteractor,
     )
 
   var fingerprintEnrollEnrollingViewModel =
diff --git a/tests/shared/src/com/android/settings/testutils2/FakeFingerprintManagerInteractor.kt b/tests/shared/src/com/android/settings/testutils2/FakeFingerprintManagerInteractor.kt
index 52df724..f61a3d3 100644
--- a/tests/shared/src/com/android/settings/testutils2/FakeFingerprintManagerInteractor.kt
+++ b/tests/shared/src/com/android/settings/testutils2/FakeFingerprintManagerInteractor.kt
@@ -22,7 +22,14 @@
 import android.hardware.fingerprint.FingerprintEnrollOptions
 import android.hardware.fingerprint.FingerprintSensorProperties
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
-import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.AuthenitcateInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrollFingerprintInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrolledFingerprintsInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RemoveFingerprintInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RenameFingerprintInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
 import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
 import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
 import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
@@ -35,7 +42,15 @@
 import kotlinx.coroutines.flow.flowOf
 
 /** Fake to be used by other classes to easily fake the FingerprintManager implementation. */
-class FakeFingerprintManagerInteractor : FingerprintManagerInteractor {
+class FakeFingerprintManagerInteractor :
+  AuthenitcateInteractor,
+  CanEnrollFingerprintsInteractor,
+  EnrolledFingerprintsInteractor,
+  EnrollFingerprintInteractor,
+  GenerateChallengeInteractor,
+  RemoveFingerprintInteractor,
+  RenameFingerprintInteractor,
+  SensorInteractor {
 
   var enrollableFingerprints: Int = 5
   var enrolledFingerprintsInternal: MutableList<FingerprintData> = mutableListOf()
@@ -67,19 +82,22 @@
   override val enrolledFingerprints: Flow<List<FingerprintData>> = flow {
     emit(enrolledFingerprintsInternal)
   }
-
   override val canEnrollFingerprints: Flow<Boolean> = flow {
     emit(enrolledFingerprintsInternal.size < enrollableFingerprints)
   }
 
-  override val sensorPropertiesInternal: Flow<FingerprintSensor?> = flow { emit(sensorProp) }
+  override fun maxFingerprintsEnrollable(): Int {
+    return enrollableFingerprints
+  }
 
-  override val maxEnrollableFingerprints: Flow<Int> = flow { emit(enrollableFingerprints) }
+  override val sensorPropertiesInternal: Flow<FingerprintSensor?> = flow { emit(sensorProp) }
+  override val hasSideFps: Flow<Boolean> =
+    flowOf(sensorProp.sensorType == FingerprintSensorType.POWER_BUTTON)
 
   override suspend fun enroll(
     hardwareAuthToken: ByteArray?,
     enrollReason: EnrollReason,
-    fingerprintEnrollOptions: FingerprintEnrollOptions
+    fingerprintEnrollOptions: FingerprintEnrollOptions,
   ): Flow<FingerEnrollState> = flowOf(*enrollStateViewModel.toTypedArray())
 
   override suspend fun removeFingerprint(fp: FingerprintData): Boolean {
@@ -92,7 +110,4 @@
     }
   }
 
-  override suspend fun hasSideFps(): Boolean {
-    return sensorProp.sensorType == FingerprintSensorType.POWER_BUTTON
-  }
 }
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/NetworkSelectRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/NetworkSelectRepositoryTest.kt
index 4137de4..0cbfe02 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/NetworkSelectRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/NetworkSelectRepositoryTest.kt
@@ -17,20 +17,21 @@
 package com.android.settings.network.telephony
 
 import android.content.Context
-import android.telephony.AccessNetworkConstants
-import android.telephony.NetworkRegistrationInfo
-import android.telephony.ServiceState
-import android.telephony.TelephonyManager
+import android.os.PersistableBundle
+import android.telephony.*
+import android.telephony.satellite.SatelliteManager
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settings.network.telephony.scan.NetworkScanRepositoryTest
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.spy
 import org.mockito.kotlin.stub
+import org.mockito.kotlin.whenever
 
 @RunWith(AndroidJUnit4::class)
 class NetworkSelectRepositoryTest {
@@ -49,8 +50,16 @@
         on { serviceState } doReturn mockServiceState
     }
 
+    private val mockSatelliteManager = mock<SatelliteManager> {
+        on { getSatellitePlmnsForCarrier(anyInt()) } doReturn SatellitePlmns
+    }
+
+    private var mockCarrierConfigManager = mock<CarrierConfigManager>()
+
     private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
         on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
+        on { getSystemService(SatelliteManager::class.java) } doReturn mockSatelliteManager
+        on { getSystemService(CarrierConfigManager::class.java) } doReturn mockCarrierConfigManager
     }
 
     private val repository = NetworkSelectRepository(context, SUB_ID)
@@ -105,6 +114,14 @@
             on { forbiddenPlmns } doReturn arrayOf(FORBIDDEN_PLMN)
         }
 
+        val config = PersistableBundle()
+        config.putBoolean(
+            CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL,
+            false)
+        whenever(mockCarrierConfigManager.getConfigForSubId(
+            SUB_ID, CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL))
+            .thenReturn(config)
+
         val info = repository.getNetworkRegistrationInfo()
 
         assertThat(info).isEqualTo(
@@ -115,9 +132,76 @@
         )
     }
 
+    @Test
+    fun getNetworkRegistrationInfo_filterSatellitePlmn() {
+
+        val info1 = createTestNetworkRegistrationInfo("310", "260")
+        val info2 = createTestNetworkRegistrationInfo("310", "261")
+        val satelliteInfo = createTestNetworkRegistrationInfo(satelliteMcc, satelliteMnc)
+        val registrationInfos = listOf(info1, info2, satelliteInfo)
+        val filteredRegistrationInfos = listOf(info1, info2)
+
+        mockServiceState.stub {
+            on {
+                getNetworkRegistrationInfoListForTransportType(
+                    AccessNetworkConstants.TRANSPORT_TYPE_WWAN
+                )
+            } doReturn registrationInfos
+        }
+        mockTelephonyManager.stub {
+            on { forbiddenPlmns } doReturn arrayOf(FORBIDDEN_PLMN)
+        }
+
+        // disable satellite plmn filter
+        var config = PersistableBundle()
+        config.putBoolean(
+            CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL,
+            false)
+        whenever(mockCarrierConfigManager.getConfigForSubId(
+            SUB_ID, CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL))
+            .thenReturn(config)
+
+        var infoList = repository.getNetworkRegistrationInfo()
+
+        assertThat(infoList).isEqualTo(
+            NetworkSelectRepository.NetworkRegistrationAndForbiddenInfo(
+                networkList = registrationInfos,
+                forbiddenPlmns = listOf(FORBIDDEN_PLMN),
+            )
+        )
+
+        // enable satellite plmn filter
+        config = PersistableBundle()
+        config.putBoolean(
+            CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL,
+            true)
+        whenever(mockCarrierConfigManager.getConfigForSubId(
+            SUB_ID, CarrierConfigManager.KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL))
+            .thenReturn(config)
+
+        infoList = repository.getNetworkRegistrationInfo()
+
+        assertThat(infoList).isEqualTo(
+            NetworkSelectRepository.NetworkRegistrationAndForbiddenInfo(
+                networkList = filteredRegistrationInfos,
+                forbiddenPlmns = listOf(FORBIDDEN_PLMN),
+            )
+        )
+    }
+
     private companion object {
         const val SUB_ID = 1
         val NetworkRegistrationInfos = listOf(NetworkRegistrationInfo.Builder().build())
         const val FORBIDDEN_PLMN = "Forbidden PLMN"
+        const val satelliteMcc = "310"
+        const val satelliteMnc = "810"
+        val SatellitePlmns = listOf(satelliteMcc + satelliteMnc)
+
+        fun createTestNetworkRegistrationInfo(mcc: String, mnc: String): NetworkRegistrationInfo {
+            val cellInfo = CellIdentityLte(0, 0, 0, 0, IntArray(2) { 0 },
+                0, mcc, mnc, "", "", emptyList(), null)
+
+            return NetworkRegistrationInfo.Builder().setCellIdentity(cellInfo).build()
+        }
     }
 }
diff --git a/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt b/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt
index 67a5957..691b611 100644
--- a/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt
+++ b/tests/unit/src/com/android/settings/fingerprint2/domain/interactor/FingerprintManagerInteractorTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.settings.fingerprint2.domain.interactor
 
-import android.content.Context
 import android.content.Intent
 import android.hardware.biometrics.ComponentInfoInternal
 import android.hardware.biometrics.SensorLocationInternal
@@ -30,23 +29,37 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
 import android.os.CancellationSignal
 import android.os.Handler
-import androidx.test.core.app.ApplicationProvider
 import com.android.settings.biometrics.GatekeeperPasswordProvider
+import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintEnrollmentRepositoryImpl
 import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
-import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractorImpl
-import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
-import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
+import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSettingsRepositoryImpl
+import com.android.settings.biometrics.fingerprint2.data.repository.UserRepo
+import com.android.settings.biometrics.fingerprint2.domain.interactor.AuthenticateInteractorImpl
+import com.android.settings.biometrics.fingerprint2.domain.interactor.CanEnrollFingerprintsInteractorImpl
+import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollFingerprintInteractorImpl
+import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrolledFingerprintsInteractorImpl
+import com.android.settings.biometrics.fingerprint2.domain.interactor.GenerateChallengeInteractorImpl
+import com.android.settings.biometrics.fingerprint2.domain.interactor.RemoveFingerprintsInteractorImpl
+import com.android.settings.biometrics.fingerprint2.domain.interactor.RenameFingerprintsInteractorImpl
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrollFingerprintInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrolledFingerprintsInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RemoveFingerprintInteractor
+import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RenameFingerprintInteractor
 import com.android.settings.biometrics.fingerprint2.lib.model.Default
 import com.android.settings.biometrics.fingerprint2.lib.model.EnrollReason
 import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
 import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintAuthAttemptModel
 import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
+import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintFlow
 import com.android.settings.password.ChooseLockSettingsHelper
 import com.android.systemui.biometrics.shared.model.FingerprintSensor
 import com.android.systemui.biometrics.shared.model.toFingerprintSensor
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.cancelAndJoin
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.last
 import kotlinx.coroutines.launch
@@ -75,13 +88,28 @@
 class FingerprintManagerInteractorTest {
 
   @JvmField @Rule var rule = MockitoJUnit.rule()
-  private lateinit var underTest: FingerprintManagerInteractor
-  private var context: Context = ApplicationProvider.getApplicationContext()
+  private lateinit var enrolledFingerprintsInteractorUnderTest: EnrolledFingerprintsInteractor
+  private lateinit var generateChallengeInteractorUnderTest: GenerateChallengeInteractor
+  private lateinit var removeFingerprintsInteractorUnderTest: RemoveFingerprintInteractor
+  private lateinit var renameFingerprintsInteractorUnderTest: RenameFingerprintInteractor
+  private lateinit var authenticateInteractorImplUnderTest: AuthenticateInteractorImpl
+  private lateinit var canEnrollFingerprintsInteractorUnderTest: CanEnrollFingerprintsInteractor
+  private lateinit var enrollInteractorUnderTest: EnrollFingerprintInteractor
+
+  private val userId = 0
   private var backgroundDispatcher = StandardTestDispatcher()
   @Mock private lateinit var fingerprintManager: FingerprintManager
   @Mock private lateinit var gateKeeperPasswordProvider: GatekeeperPasswordProvider
 
   private var testScope = TestScope(backgroundDispatcher)
+  private var backgroundScope = testScope.backgroundScope
+  private val flow: FingerprintFlow = Default
+  private val maxFingerprints = 5
+  private val currUser = MutableStateFlow(0)
+  private val userRepo =
+    object : UserRepo {
+      override val currentUser: Flow<Int> = currUser
+    }
 
   @Before
   fun setup() {
@@ -89,7 +117,7 @@
       FingerprintSensorPropertiesInternal(
           0 /* sensorId */,
           SensorProperties.STRENGTH_STRONG,
-          5 /* maxEnrollmentsPerUser */,
+          maxFingerprints,
           listOf<ComponentInfoInternal>(),
           FingerprintSensorProperties.TYPE_POWER_BUTTON,
           false /* halControlsIllumination */,
@@ -97,20 +125,37 @@
           listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT),
         )
         .toFingerprintSensor()
+
     val fingerprintSensorRepository =
       object : FingerprintSensorRepository {
         override val fingerprintSensor: Flow<FingerprintSensor> = flowOf(sensor)
+        override val hasSideFps: Flow<Boolean> = flowOf(false)
       }
 
-    underTest =
-      FingerprintManagerInteractorImpl(
-        context,
-        backgroundDispatcher,
+    val settingsRepository = FingerprintSettingsRepositoryImpl(maxFingerprints)
+    val fingerprintEnrollmentRepository =
+      FingerprintEnrollmentRepositoryImpl(
         fingerprintManager,
-        fingerprintSensorRepository,
-        gateKeeperPasswordProvider,
-        FingerprintEnrollInteractorImpl(context, fingerprintManager, Default),
+        userRepo,
+        settingsRepository,
+        backgroundDispatcher,
+        backgroundScope,
       )
+
+    enrolledFingerprintsInteractorUnderTest =
+      EnrolledFingerprintsInteractorImpl(fingerprintManager, userId)
+    generateChallengeInteractorUnderTest =
+      GenerateChallengeInteractorImpl(fingerprintManager, userId, gateKeeperPasswordProvider)
+    removeFingerprintsInteractorUnderTest =
+      RemoveFingerprintsInteractorImpl(fingerprintManager, userId)
+    renameFingerprintsInteractorUnderTest =
+      RenameFingerprintsInteractorImpl(fingerprintManager, userId, backgroundDispatcher)
+    authenticateInteractorImplUnderTest = AuthenticateInteractorImpl(fingerprintManager, userId)
+
+    canEnrollFingerprintsInteractorUnderTest =
+      CanEnrollFingerprintsInteractorImpl(fingerprintEnrollmentRepository)
+
+    enrollInteractorUnderTest = EnrollFingerprintInteractorImpl(userId, fingerprintManager, flow)
   }
 
   @Test
@@ -119,7 +164,8 @@
       whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(emptyList())
 
       val emptyFingerprintList: List<Fingerprint> = emptyList()
-      assertThat(underTest.enrolledFingerprints.last()).isEqualTo(emptyFingerprintList)
+      assertThat(enrolledFingerprintsInteractorUnderTest.enrolledFingerprints.last())
+        .isEqualTo(emptyFingerprintList)
     }
 
   @Test
@@ -129,7 +175,7 @@
       val fingerprintList: List<Fingerprint> = listOf(expected)
       whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(fingerprintList)
 
-      val list = underTest.enrolledFingerprints.last()
+      val list = enrolledFingerprintsInteractorUnderTest.enrolledFingerprints.last()
       assertThat(list!!.size).isEqualTo(fingerprintList.size)
       val actual = list[0]
       assertThat(actual.name).isEqualTo(expected.name)
@@ -138,24 +184,51 @@
     }
 
   @Test
-  fun testCanEnrollFingerprint() =
+  fun testCanEnrollFingerprintSucceeds() =
     testScope.runTest {
-      val fingerprintList1: List<Fingerprint> =
+      val fingerprintList: List<Fingerprint> =
         listOf(
-          Fingerprint("Finger 1,", 2, 3L),
-          Fingerprint("Finger 2,", 3, 3L),
-          Fingerprint("Finger 3,", 4, 3L),
+          Fingerprint("Finger 1", 2, 3L),
+          Fingerprint("Finger 2", 3, 3L),
+          Fingerprint("Finger 3", 4, 3L),
         )
-      val fingerprintList2: List<Fingerprint> =
-        fingerprintList1.plus(
-          listOf(Fingerprint("Finger 4,", 5, 3L), Fingerprint("Finger 5,", 6, 3L))
-        )
-      whenever(fingerprintManager.getEnrolledFingerprints(anyInt()))
-        .thenReturn(fingerprintList1)
-        .thenReturn(fingerprintList2)
+      whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(fingerprintList)
 
-      assertThat(underTest.canEnrollFingerprints.last()).isTrue()
-      assertThat(underTest.canEnrollFingerprints.last()).isFalse()
+      var result: Boolean? = null
+      val job =
+        testScope.launch {
+          canEnrollFingerprintsInteractorUnderTest.canEnrollFingerprints.collect { result = it }
+        }
+
+      runCurrent()
+      job.cancelAndJoin()
+
+      assertThat(result).isTrue()
+    }
+
+  @Test
+  fun testCanEnrollFingerprintFails() =
+    testScope.runTest {
+      val fingerprintList: List<Fingerprint> =
+        listOf(
+          Fingerprint("Finger 1", 2, 3L),
+          Fingerprint("Finger 2", 3, 3L),
+          Fingerprint("Finger 3", 4, 3L),
+          Fingerprint("Finger 4", 5, 3L),
+          Fingerprint("Finger 5", 6, 3L),
+        )
+      whenever(fingerprintManager.getEnrolledFingerprints(anyInt())).thenReturn(fingerprintList)
+
+      var result: Boolean? = null
+      val job =
+        testScope.launch {
+          canEnrollFingerprintsInteractorUnderTest.canEnrollFingerprints.collect { result = it }
+        }
+
+      runCurrent()
+      job.cancelAndJoin()
+
+      assertThat(result).isFalse()
     }
 
   @Test
@@ -178,7 +251,8 @@
         argumentCaptor()
 
       var result: Pair<Long, ByteArray?>? = null
-      val job = testScope.launch { result = underTest.generateChallenge(1L) }
+      val job =
+        testScope.launch { result = generateChallengeInteractorUnderTest.generateChallenge(1L) }
       runCurrent()
 
       verify(fingerprintManager).generateChallenge(anyInt(), capture(generateChallengeCallback))
@@ -201,7 +275,10 @@
 
       var result: Boolean? = null
       val job =
-        testScope.launch { result = underTest.removeFingerprint(fingerprintViewModelToRemove) }
+        testScope.launch {
+          result =
+            removeFingerprintsInteractorUnderTest.removeFingerprint(fingerprintViewModelToRemove)
+        }
       runCurrent()
 
       verify(fingerprintManager)
@@ -224,7 +301,10 @@
 
       var result: Boolean? = null
       val job =
-        testScope.launch { result = underTest.removeFingerprint(fingerprintViewModelToRemove) }
+        testScope.launch {
+          result =
+            removeFingerprintsInteractorUnderTest.removeFingerprint(fingerprintViewModelToRemove)
+        }
       runCurrent()
 
       verify(fingerprintManager)
@@ -246,7 +326,7 @@
     testScope.runTest {
       val fingerprintToRename = FingerprintData("Finger 2", 1, 2L)
 
-      underTest.renameFingerprint(fingerprintToRename, "Woo")
+      renameFingerprintsInteractorUnderTest.renameFingerprint(fingerprintToRename, "Woo")
 
       verify(fingerprintManager).rename(eq(fingerprintToRename.fingerId), anyInt(), safeEq("Woo"))
     }
@@ -257,7 +337,7 @@
       val fingerprint = Fingerprint("Woooo", 100, 101L)
 
       var result: FingerprintAuthAttemptModel? = null
-      val job = launch { result = underTest.authenticate() }
+      val job = launch { result = authenticateInteractorImplUnderTest.authenticate() }
 
       val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> = argumentCaptor()
 
@@ -284,7 +364,7 @@
   fun testAuth_lockout() =
     testScope.runTest {
       var result: FingerprintAuthAttemptModel? = null
-      val job = launch { result = underTest.authenticate() }
+      val job = launch { result = authenticateInteractorImplUnderTest.authenticate() }
 
       val authCallback: ArgumentCaptor<FingerprintManager.AuthenticationCallback> = argumentCaptor()
 
@@ -314,7 +394,7 @@
       val token = byteArrayOf(5, 3, 2)
       var result: FingerEnrollState? = null
       val job = launch {
-        underTest
+        enrollInteractorUnderTest
           .enroll(token, EnrollReason.FindSensor, FingerprintEnrollOptions.Builder().build())
           .collect { result = it }
       }
@@ -343,7 +423,7 @@
       val token = byteArrayOf(5, 3, 2)
       var result: FingerEnrollState? = null
       val job = launch {
-        underTest
+        enrollInteractorUnderTest
           .enroll(token, EnrollReason.FindSensor, FingerprintEnrollOptions.Builder().build())
           .collect { result = it }
       }
@@ -372,7 +452,7 @@
       val token = byteArrayOf(5, 3, 2)
       var result: FingerEnrollState? = null
       val job = launch {
-        underTest
+        enrollInteractorUnderTest
           .enroll(token, EnrollReason.FindSensor, FingerprintEnrollOptions.Builder().build())
           .collect { result = it }
       }
diff --git a/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt b/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt
index 9662c39..04cece8 100644
--- a/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt
+++ b/tests/unit/src/com/android/settings/fingerprint2/enrollment/viewmodel/FingerprintEnrollFindSensorViewModelV2Test.kt
@@ -99,9 +99,10 @@
     backgroundViewModel.inForeground()
     enrollViewModel =
       FingerprintEnrollViewModel(
-        fakeFingerprintManagerInteractor,
         gatekeeperViewModel,
         navigationViewModel,
+        fakeFingerprintManagerInteractor,
+        fakeFingerprintManagerInteractor,
       )
     accessibilityInteractor =
       object : AccessibilityInteractor {
diff --git a/tests/unit/src/com/android/settings/fingerprint2/ui/enrollment/modules/enrolling/rfps/viewmodel/RFPSIconTouchViewModelTest.kt b/tests/unit/src/com/android/settings/fingerprint2/ui/enrollment/modules/enrolling/rfps/viewmodel/RFPSIconTouchViewModelTest.kt
index 46e883a..53f4726 100644
--- a/tests/unit/src/com/android/settings/fingerprint2/ui/enrollment/modules/enrolling/rfps/viewmodel/RFPSIconTouchViewModelTest.kt
+++ b/tests/unit/src/com/android/settings/fingerprint2/ui/enrollment/modules/enrolling/rfps/viewmodel/RFPSIconTouchViewModelTest.kt
@@ -49,8 +49,7 @@
   fun setup() {
     Dispatchers.setMain(backgroundDispatcher)
     testScope = TestScope(backgroundDispatcher)
-    rfpsIconTouchViewModel =
-      RFPSIconTouchViewModel()
+    rfpsIconTouchViewModel = RFPSIconTouchViewModel()
   }
 
   @After
diff --git a/tests/unit/src/com/android/settings/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModelTest.kt b/tests/unit/src/com/android/settings/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModelTest.kt
index c475cc4..cf2deec 100644
--- a/tests/unit/src/com/android/settings/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModelTest.kt
+++ b/tests/unit/src/com/android/settings/fingerprint2/ui/enrollment/viewmodel/FingerprintEnrollEnrollingViewModelTest.kt
@@ -88,9 +88,10 @@
     backgroundViewModel.inForeground()
     val fingerprintEnrollViewModel =
       FingerprintEnrollViewModel(
-        fakeFingerprintManagerInteractor,
         gateKeeperViewModel,
         navigationViewModel,
+        fakeFingerprintManagerInteractor,
+        fakeFingerprintManagerInteractor,
       )
     enrollEnrollingViewModel =
       FingerprintEnrollEnrollingViewModel(fingerprintEnrollViewModel, backgroundViewModel)
diff --git a/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsNavigationViewModelTest.kt b/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsNavigationViewModelTest.kt
index 201fffa..88f76dd 100644
--- a/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsNavigationViewModelTest.kt
+++ b/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsNavigationViewModelTest.kt
@@ -67,10 +67,11 @@
     underTest =
       FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
           defaultUserId,
-          fakeFingerprintManagerInteractor,
           backgroundDispatcher,
           null,
           null,
+          fakeFingerprintManagerInteractor,
+          fakeFingerprintManagerInteractor,
         )
         .create(FingerprintSettingsNavigationViewModel::class.java)
   }
@@ -272,10 +273,11 @@
       underTest =
         FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
             defaultUserId,
-            fakeFingerprintManagerInteractor,
             backgroundDispatcher,
             token,
             challenge,
+            fakeFingerprintManagerInteractor,
+            fakeFingerprintManagerInteractor,
           )
           .create(FingerprintSettingsNavigationViewModel::class.java)
 
@@ -299,10 +301,11 @@
       underTest =
         FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
             defaultUserId,
-            fakeFingerprintManagerInteractor,
             backgroundDispatcher,
             token,
             challenge,
+            fakeFingerprintManagerInteractor,
+            fakeFingerprintManagerInteractor,
           )
           .create(FingerprintSettingsNavigationViewModel::class.java)
 
@@ -331,10 +334,11 @@
       underTest =
         FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
             defaultUserId,
-            fakeFingerprintManagerInteractor,
             backgroundDispatcher,
             token,
             challenge,
+            fakeFingerprintManagerInteractor,
+            fakeFingerprintManagerInteractor,
           )
           .create(FingerprintSettingsNavigationViewModel::class.java)
 
diff --git a/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsViewModelTest.kt b/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsViewModelTest.kt
index 1618e16..79163d9 100644
--- a/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsViewModelTest.kt
+++ b/tests/unit/src/com/android/settings/fingerprint2/ui/settings/FingerprintSettingsViewModelTest.kt
@@ -73,19 +73,25 @@
     navigationViewModel =
       FingerprintSettingsNavigationViewModel.FingerprintSettingsNavigationModelFactory(
           defaultUserId,
-          fakeFingerprintManagerInteractor,
           backgroundDispatcher,
           null,
           null,
+          fakeFingerprintManagerInteractor,
+          fakeFingerprintManagerInteractor,
         )
         .create(FingerprintSettingsNavigationViewModel::class.java)
 
     underTest =
       FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
           defaultUserId,
-          fakeFingerprintManagerInteractor,
           backgroundDispatcher,
           navigationViewModel,
+          fakeFingerprintManagerInteractor,
+          fakeFingerprintManagerInteractor,
+          fakeFingerprintManagerInteractor,
+          fakeFingerprintManagerInteractor,
+          fakeFingerprintManagerInteractor,
+          fakeFingerprintManagerInteractor,
         )
         .create(FingerprintSettingsViewModel::class.java)
   }
@@ -114,14 +120,7 @@
       fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
         mutableListOf(FingerprintData("a", 1, 3L))
 
-      underTest =
-        FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
-            defaultUserId,
-            fakeFingerprintManagerInteractor,
-            backgroundDispatcher,
-            navigationViewModel,
-          )
-          .create(FingerprintSettingsViewModel::class.java)
+      recreateSettingsViewModel()
 
       var authAttempt: FingerprintAuthAttemptModel? = null
       val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
@@ -156,14 +155,7 @@
       fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
         mutableListOf(FingerprintData("a", 1, 3L))
 
-      underTest =
-        FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
-            defaultUserId,
-            fakeFingerprintManagerInteractor,
-            backgroundDispatcher,
-            navigationViewModel,
-          )
-          .create(FingerprintSettingsViewModel::class.java)
+      recreateSettingsViewModel()
 
       var authAttempt: FingerprintAuthAttemptModel? = null
       val job = launch { underTest.authFlow.take(5).collectLatest { authAttempt = it } }
@@ -198,14 +190,7 @@
       val success = FingerprintAuthAttemptModel.Success(1)
       fakeFingerprintManagerInteractor.authenticateAttempt = success
 
-      underTest =
-        FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
-            defaultUserId,
-            fakeFingerprintManagerInteractor,
-            backgroundDispatcher,
-            navigationViewModel,
-          )
-          .create(FingerprintSettingsViewModel::class.java)
+      recreateSettingsViewModel()
 
       var authAttempt: FingerprintAuthAttemptModel? = null
 
@@ -225,14 +210,7 @@
     fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
       mutableListOf(fingerprintToDelete)
 
-    underTest =
-      FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
-          defaultUserId,
-          fakeFingerprintManagerInteractor,
-          backgroundDispatcher,
-          navigationViewModel,
-        )
-        .create(FingerprintSettingsViewModel::class.java)
+    recreateSettingsViewModel()
 
     var dialog: PreferenceViewModel? = null
     val dialogJob = launch { underTest.isShowingDialog.collect { dialog = it } }
@@ -261,14 +239,7 @@
     fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
       mutableListOf(fingerprintToRename)
 
-    underTest =
-      FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
-          defaultUserId,
-          fakeFingerprintManagerInteractor,
-          backgroundDispatcher,
-          navigationViewModel,
-        )
-        .create(FingerprintSettingsViewModel::class.java)
+    recreateSettingsViewModel()
 
     var dialog: PreferenceViewModel? = null
     val dialogJob = launch { underTest.isShowingDialog.collect { dialog = it } }
@@ -299,14 +270,7 @@
     fakeFingerprintManagerInteractor.enrolledFingerprintsInternal =
       mutableListOf(fingerprintToDelete)
 
-    underTest =
-      FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
-          defaultUserId,
-          fakeFingerprintManagerInteractor,
-          backgroundDispatcher,
-          navigationViewModel,
-        )
-        .create(FingerprintSettingsViewModel::class.java)
+    recreateSettingsViewModel()
 
     var dialog: PreferenceViewModel? = null
     val dialogJob = launch { underTest.isShowingDialog.collect { dialog = it } }
@@ -390,6 +354,22 @@
       assertThat(authAttempt).isEqualTo(null)
     }
 
+  private fun recreateSettingsViewModel() {
+    underTest =
+      FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
+          defaultUserId,
+          backgroundDispatcher,
+          navigationViewModel,
+          fakeFingerprintManagerInteractor,
+          fakeFingerprintManagerInteractor,
+          fakeFingerprintManagerInteractor,
+          fakeFingerprintManagerInteractor,
+          fakeFingerprintManagerInteractor,
+          fakeFingerprintManagerInteractor,
+        )
+        .create(FingerprintSettingsViewModel::class.java)
+  }
+
   private fun setupAuth(): MutableList<FingerprintData> {
     fakeFingerprintManagerInteractor.sensorProp =
       FingerprintSensorPropertiesInternal(
@@ -409,14 +389,7 @@
     val success = FingerprintAuthAttemptModel.Success(1)
     fakeFingerprintManagerInteractor.authenticateAttempt = success
 
-    underTest =
-      FingerprintSettingsViewModel.FingerprintSettingsViewModelFactory(
-          defaultUserId,
-          fakeFingerprintManagerInteractor,
-          backgroundDispatcher,
-          navigationViewModel,
-        )
-        .create(FingerprintSettingsViewModel::class.java)
+    recreateSettingsViewModel()
 
     return fingerprints
   }
diff --git a/tests/unit/src/com/android/settings/wifi/repository/WifiHotspotRepositoryTest.java b/tests/unit/src/com/android/settings/wifi/repository/WifiHotspotRepositoryTest.java
index 3571a36..4765d18 100644
--- a/tests/unit/src/com/android/settings/wifi/repository/WifiHotspotRepositoryTest.java
+++ b/tests/unit/src/com/android/settings/wifi/repository/WifiHotspotRepositoryTest.java
@@ -118,12 +118,12 @@
         mRepository.mSecurityType = mSecurityType;
         mRepository.mSpeedType = mSpeedType;
         mRepository.mIsDualBand = true;
-        mRepository.mBand5g.isUsableChannelsReady = true;
-        mRepository.mBand5g.isUsableChannelsUnsupported = false;
-        mRepository.mBand5g.hasUsableChannels = true;
-        mRepository.mBand6g.isUsableChannelsReady = true;
-        mRepository.mBand6g.isUsableChannelsUnsupported = false;
-        mRepository.mBand6g.hasUsableChannels = true;
+        mRepository.mBand5g.isChannelsReady = true;
+        mRepository.mBand5g.isChannelsUnsupported = false;
+        mRepository.mBand5g.hasChannels = true;
+        mRepository.mBand6g.isChannelsReady = true;
+        mRepository.mBand6g.isChannelsUnsupported = false;
+        mRepository.mBand6g.hasChannels = true;
     }
 
     @Test
@@ -382,7 +382,7 @@
     @Test
     public void updateSpeedType_singleBand5gPreferredBut5gUnavailable_get2gSpeedType() {
         mRepository.mIsDualBand = false;
-        mRepository.mBand5g.hasUsableChannels = false;
+        mRepository.mBand5g.hasChannels = false;
         SoftApConfiguration config = new SoftApConfiguration.Builder()
                 .setBand(WIFI_5GHZ_BAND_PREFERRED).build();
         when(mWifiManager.getSoftApConfiguration()).thenReturn(config);
@@ -407,7 +407,7 @@
     @Test
     public void updateSpeedType_singleBand6gPreferredBut6gUnavailable_get5gSpeedType() {
         mRepository.mIsDualBand = false;
-        mRepository.mBand6g.hasUsableChannels = false;
+        mRepository.mBand6g.hasChannels = false;
         SoftApConfiguration config = new SoftApConfiguration.Builder()
                 .setBand(WIFI_6GHZ_BAND_PREFERRED).build();
         when(mWifiManager.getSoftApConfiguration()).thenReturn(config);
@@ -420,8 +420,8 @@
     @Test
     public void updateSpeedType_singleBand6gPreferredBut5gAnd6gUnavailable_get2gSpeedType() {
         mRepository.mIsDualBand = false;
-        mRepository.mBand5g.hasUsableChannels = false;
-        mRepository.mBand6g.hasUsableChannels = false;
+        mRepository.mBand5g.hasChannels = false;
+        mRepository.mBand6g.hasChannels = false;
         SoftApConfiguration config = new SoftApConfiguration.Builder()
                 .setBand(WIFI_6GHZ_BAND_PREFERRED).build();
         when(mWifiManager.getSoftApConfiguration()).thenReturn(config);
@@ -446,7 +446,7 @@
     @Test
     public void updateSpeedType_dualBand2gAnd5gBut5gUnavailable_get2gSpeedType() {
         mRepository.mIsDualBand = true;
-        mRepository.mBand5g.hasUsableChannels = false;
+        mRepository.mBand5g.hasChannels = false;
         SoftApConfiguration config = new SoftApConfiguration.Builder()
                 .setBand(WIFI_5GHZ_BAND_PREFERRED).build();
         when(mWifiManager.getSoftApConfiguration()).thenReturn(config);
@@ -562,19 +562,19 @@
     }
 
     @Test
-    public void is5gAvailable_hasUsableChannels_returnTrue() {
+    public void is5gAvailable_hasChannels_returnTrue() {
         mRepository.mIs5gBandSupported = true;
         // Reset m5gBand to trigger an update
-        mRepository.mBand5g.isUsableChannelsReady = false;
+        mRepository.mBand5g.isChannelsReady = false;
 
         assertThat(mRepository.is5gAvailable()).isTrue();
     }
 
     @Test
-    public void is5gAvailable_noUsableChannels_returnFalse() {
+    public void is5gAvailable_noChannels_returnFalse() {
         mRepository.mIs5gBandSupported = true;
         // Reset m5gBand to trigger an update
-        mRepository.mBand5g.isUsableChannelsReady = false;
+        mRepository.mBand5g.isChannelsReady = false;
         when(mWifiManager.getAllowedChannels(WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS, OP_MODE_SAP))
                 .thenReturn(null);
 
@@ -585,7 +585,7 @@
     @UiThreadTest
     public void get5gAvailable_shouldNotReturnNull() {
         // Reset m5gBand to trigger an update
-        mRepository.mBand5g.isUsableChannelsReady = false;
+        mRepository.mBand5g.isChannelsReady = false;
 
         assertThat(mRepository.get5gAvailable()).isNotNull();
     }
@@ -606,19 +606,19 @@
     }
 
     @Test
-    public void is6gAvailable_hasUsableChannels_returnTrue() {
+    public void is6gAvailable_hasChannels_returnTrue() {
         mRepository.mIs6gBandSupported = true;
         // Reset m6gBand to trigger an update
-        mRepository.mBand6g.isUsableChannelsReady = false;
+        mRepository.mBand6g.isChannelsReady = false;
 
         assertThat(mRepository.is6gAvailable()).isTrue();
     }
 
     @Test
-    public void is6gAvailable_noUsableChannels_returnFalse() {
+    public void is6gAvailable_noChannels_returnFalse() {
         mRepository.mIs6gBandSupported = true;
         // Reset m6gBand to trigger an update
-        mRepository.mBand6g.isUsableChannelsReady = false;
+        mRepository.mBand6g.isChannelsReady = false;
         when(mWifiManager.getAllowedChannels(WifiScanner.WIFI_BAND_6_GHZ, OP_MODE_SAP))
                 .thenReturn(null);
 
@@ -658,33 +658,33 @@
     }
 
     @Test
-    public void isChannelAvailable_throwIllegalArgumentException_hasUsableChannelsFalse() {
+    public void isChannelAvailable_throwIllegalArgumentException_hasChannelsFalse() {
         doThrow(IllegalArgumentException.class).when(mWifiManager)
                 .getAllowedChannels(WifiScanner.WIFI_BAND_6_GHZ, OP_MODE_SAP);
 
         mRepository.isChannelAvailable(mRepository.mBand6g);
 
-        assertThat(mRepository.mBand6g.hasUsableChannels).isFalse();
-        assertThat(mRepository.mBand6g.isUsableChannelsUnsupported).isTrue();
+        assertThat(mRepository.mBand6g.hasChannels).isFalse();
+        assertThat(mRepository.mBand6g.isChannelsUnsupported).isTrue();
     }
 
     @Test
-    public void isChannelAvailable_throwUnsupportedOperationException_hasUsableChannelsFalse() {
+    public void isChannelAvailable_throwUnsupportedOperationException_hasChannelsFalse() {
         doThrow(UnsupportedOperationException.class).when(mWifiManager)
                 .getAllowedChannels(WifiScanner.WIFI_BAND_6_GHZ, OP_MODE_SAP);
 
         mRepository.isChannelAvailable(mRepository.mBand6g);
 
-        assertThat(mRepository.mBand6g.hasUsableChannels).isFalse();
-        assertThat(mRepository.mBand6g.isUsableChannelsUnsupported).isTrue();
+        assertThat(mRepository.mBand6g.hasChannels).isFalse();
+        assertThat(mRepository.mBand6g.isChannelsUnsupported).isTrue();
     }
 
     @Test
-    public void isChannelAvailable_noExceptionAndHasUsableChannels_hasUsableChannelsTrue() {
+    public void isChannelAvailable_noExceptionAndHasChannels_hasChannelsTrue() {
         mRepository.isChannelAvailable(mRepository.mBand6g);
 
-        assertThat(mRepository.mBand6g.hasUsableChannels).isTrue();
-        assertThat(mRepository.mBand6g.isUsableChannelsUnsupported).isFalse();
+        assertThat(mRepository.mBand6g.hasChannels).isTrue();
+        assertThat(mRepository.mBand6g.isChannelsUnsupported).isFalse();
     }
 
     @Test
@@ -744,9 +744,9 @@
     }
 
     @Test
-    public void updateCapabilityChanged_band5gUsableChannelsUnsupported_update5gAvailable() {
+    public void updateCapabilityChanged_band5gChannelsUnsupported_update5gAvailable() {
         mRepository = spy(new WifiHotspotRepository(mContext, mWifiManager, mTetheringManager));
-        mRepository.mBand5g.isUsableChannelsUnsupported = true;
+        mRepository.mBand5g.isChannelsUnsupported = true;
 
         mRepository.updateCapabilityChanged();
 
@@ -755,9 +755,9 @@
     }
 
     @Test
-    public void updateCapabilityChanged_band6gUsableChannelsUnsupported_update5gAvailable() {
+    public void updateCapabilityChanged_band6gChannelsUnsupported_update5gAvailable() {
         mRepository = spy(new WifiHotspotRepository(mContext, mWifiManager, mTetheringManager));
-        mRepository.mBand6g.isUsableChannelsUnsupported = true;
+        mRepository.mBand6g.isChannelsUnsupported = true;
 
         mRepository.updateCapabilityChanged();
 
@@ -766,18 +766,18 @@
     }
 
     @Test
-    public void isAvailable_isUsableChannelsUnsupportedFalse_returnHasUsableChannels() {
-        mRepository.mBand6g.isUsableChannelsUnsupported = false;
-        mRepository.mBand6g.hasUsableChannels = false;
+    public void isAvailable_isChannelsUnsupportedFalse_returnHasChannels() {
+        mRepository.mBand6g.isChannelsUnsupported = false;
+        mRepository.mBand6g.hasChannels = false;
         mRepository.mBand6g.hasCapability = true;
 
         assertThat(mRepository.mBand6g.isAvailable()).isFalse();
     }
 
     @Test
-    public void isAvailable_isUsableChannelsUnsupportedTrue_returnHasCapability() {
-        mRepository.mBand6g.isUsableChannelsUnsupported = true;
-        mRepository.mBand6g.hasUsableChannels = false;
+    public void isAvailable_isChannelsUnsupportedTrue_returnHasCapability() {
+        mRepository.mBand6g.isChannelsUnsupported = true;
+        mRepository.mBand6g.hasChannels = false;
         mRepository.mBand6g.hasCapability = true;
 
         assertThat(mRepository.mBand6g.isAvailable()).isTrue();