Merge "Get the application context and pass into to a background executor." into main
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/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 4b30dc1..ab883ee 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -8851,9 +8851,13 @@
     <string name="nls_feature_reply_summary">It can reply to messages and take action on buttons in notifications, including snoozing or dismissing notifications and answering calls.</string>
     <string name="nls_feature_settings_title">Change settings</string>
     <string name="nls_feature_settings_summary">It can turn Do Not Disturb on or off and change related settings.</string>
+    <string name="nls_feature_modes_settings_summary">It can manage and activate Priority Modes, and change related settings.</string>
     <string name="notification_listener_disable_warning_summary">
         If you turn off notification access for <xliff:g id="notification_listener_name">%1$s</xliff:g>, Do Not Disturb access may also be turned off.
     </string>
+    <string name="notification_listener_disable_modes_warning_summary">
+        If you turn off notification access for <xliff:g id="notification_listener_name">%1$s</xliff:g>, Priority Modes access may also be turned off.
+    </string>
     <string name="notification_listener_disable_warning_confirm">Turn off</string>
     <string name="notification_listener_disable_warning_cancel">Cancel</string>
     <string name="notif_type_ongoing">Real-time</string>
@@ -9030,6 +9034,15 @@
     <!-- Sound & notification > Do Not Disturb access > Text to display when the list is empty. [CHAR LIMIT=NONE] -->
     <string name="zen_access_empty_text">No installed apps have requested Do Not Disturb access</string>
 
+    <!-- Special App Access: Title for managing Priority Modes access option. [CHAR LIMIT=40] -->
+    <string name="manage_zen_modes_access_title">Priority Modes access</string>
+
+    <!-- Button title that grants 'Priority Modes' permission to an app [CHAR_LIMIT=60]-->
+    <string name="zen_modes_access_detail_switch">Allow Priority Modes access</string>
+
+    <!-- Special App Access > Do Not Disturb access > Text to display when the list is empty. [CHAR LIMIT=NONE] -->
+    <string name="zen_modes_access_empty_text">No installed apps have requested Priority Modes access</string>
+
     <!-- [CHAR LIMIT=NONE] Text appearing when app notifications are off -->
     <string name="app_notifications_off_desc">You haven\'t allowed notifications from this app</string>
 
@@ -9535,6 +9548,23 @@
     <!-- Priority Modes: Blurb for modes that are not of a specific type (OTHER, UNKNOWN). [CHAR LIMIT=NONE] -->
     <string name="zen_mode_blurb_generic">Minimize interruptions by only allowing important people and apps to reach you</string>
 
+    <!-- Priority Modes: Inspirational text for a mode that activates during a fixed time schedule (e.g. 9:00-17:00). [CHAR LIMIT=NONE] -->
+    <string name="zen_mode_inspiration_schedule_time">Set a mode that follows a regular schedule</string>
+    <!-- Priority Modes: Inspirational text for a mode that activates during calendar events. [CHAR LIMIT=NONE] -->
+    <string name="zen_mode_inspiration_schedule_calendar">Keep your device in sync with your day’s events</string>
+    <!-- Priority Modes: Inspirational text for a mode of type BEDTIME. [CHAR LIMIT=NONE] -->
+    <string name="zen_mode_inspiration_bedtime">Wake up feeling like 100%</string>
+    <!-- Priority Modes: Inspirational text for a mode of type DRIVING. [CHAR LIMIT=NONE] -->
+    <string name="zen_mode_inspiration_driving">Put safety first while on the road</string>
+    <!-- Priority Modes: Inspirational text for a mode of type IMMERSIVE. [CHAR LIMIT=NONE] -->
+    <string name="zen_mode_inspiration_immersive">Gain focus to get in the zone</string>
+    <!-- Priority Modes: Inspirational text for a mode of type THEATER. [CHAR LIMIT=NONE] -->
+    <string name="zen_mode_inspiration_theater">For moments when courtesy counts</string>
+    <!-- Priority Modes: Inspirational text for a mode of type MANAGED. [CHAR LIMIT=NONE] -->
+    <string name="zen_mode_inspiration_managed">Guided usage to help you stay in good hands</string>
+    <!-- Priority Modes: Inspirational text for modes that are not of a specific type (OTHER, UNKNOWN). [CHAR LIMIT=NONE] -->
+    <string name="zen_mode_inspiration_generic">Take control of your attention</string>
+
     <!-- Content description for help icon button [CHAR LIMIT=20] -->
     <string name="warning_button_text">Warning</string>
 
@@ -10134,6 +10164,18 @@
     <!-- Zen mode access settings - summary for warning dialog when revoking access [CHAR LIMIT=NONE] -->
     <string name="zen_access_revoke_warning_dialog_summary">All Do Not Disturb rules created by this app will be removed.</string>
 
+    <!-- Priority modes access settings - title for warning dialog when enabling access [CHAR LIMIT=NONE] -->
+    <string name="zen_modes_access_warning_dialog_title">Allow access to Priority Modes for <xliff:g id="app" example="Tasker">%1$s</xliff:g>?</string>
+
+    <!-- Priority modes access settings - summary for warning dialog when enabling access [CHAR LIMIT=NONE] -->
+    <string name="zen_modes_access_warning_dialog_summary">The app will be able to turn on/off Do Not Disturb, manage and activate Priority Modes, and make changes to related settings.</string>
+
+    <!-- Priority modes access settings - title for warning dialog when revoking access [CHAR LIMIT=NONE] -->
+    <string name="zen_modes_access_revoke_warning_dialog_title">Revoke access Priority Modes for <xliff:g id="app" example="Tasker">%1$s</xliff:g>?</string>
+
+    <!-- Priority modes access settings - summary for warning dialog when revoking access [CHAR LIMIT=NONE] -->
+    <string name="zen_modes_access_revoke_warning_dialog_summary">All modes created by this app will be removed.</string>
+
     <!-- Ignore battery optimizations on label [CHAR LIMIT=30] -->
     <string name="ignore_optimizations_on">Don\u2019t optimize</string>
 
diff --git a/res/xml/mobile_network_settings.xml b/res/xml/mobile_network_settings.xml
index eb80ac8..825d72c 100644
--- a/res/xml/mobile_network_settings.xml
+++ b/res/xml/mobile_network_settings.xml
@@ -91,9 +91,11 @@
             settings:searchable="false"
             settings:controller="com.android.settings.network.telephony.RoamingPreferenceController"/>
 
+        <!-- Settings search is handled by DataUsageSearchItem. -->
         <Preference
             android:key="data_usage_summary"
             android:title="@string/app_cellular_data_usage"
+            settings:searchable="false"
             settings:controller="com.android.settings.network.telephony.DataUsagePreferenceController"/>
 
         <com.android.settings.datausage.BillingCyclePreference
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/applications/specialaccess/notificationaccess/FriendlyWarningDialogFragment.java b/src/com/android/settings/applications/specialaccess/notificationaccess/FriendlyWarningDialogFragment.java
index 3577946..c92f734 100644
--- a/src/com/android/settings/applications/specialaccess/notificationaccess/FriendlyWarningDialogFragment.java
+++ b/src/com/android/settings/applications/specialaccess/notificationaccess/FriendlyWarningDialogFragment.java
@@ -16,6 +16,7 @@
 package com.android.settings.applications.specialaccess.notificationaccess;
 
 import android.app.Dialog;
+import android.app.Flags;
 import android.app.settings.SettingsEnums;
 import android.content.ComponentName;
 import android.content.DialogInterface;
@@ -55,7 +56,10 @@
         NotificationAccessDetails parent = (NotificationAccessDetails) getTargetFragment();
 
         final String summary = getResources().getString(
-                R.string.notification_listener_disable_warning_summary, label);
+                Flags.modesApi() && Flags.modesUi()
+                        ? R.string.notification_listener_disable_modes_warning_summary
+                        : R.string.notification_listener_disable_warning_summary,
+                label);
         return new AlertDialog.Builder(getContext())
                 .setMessage(summary)
                 .setCancelable(true)
diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/ScaryWarningDialogFragment.java b/src/com/android/settings/applications/specialaccess/notificationaccess/ScaryWarningDialogFragment.java
index 747a125..53181fd 100644
--- a/src/com/android/settings/applications/specialaccess/notificationaccess/ScaryWarningDialogFragment.java
+++ b/src/com/android/settings/applications/specialaccess/notificationaccess/ScaryWarningDialogFragment.java
@@ -16,6 +16,7 @@
 package com.android.settings.applications.specialaccess.notificationaccess;
 
 import android.app.Dialog;
+import android.app.Flags;
 import android.app.settings.SettingsEnums;
 import android.content.ComponentName;
 import android.content.Context;
@@ -96,6 +97,11 @@
                 R.string.nls_warning_prompt, label);
         ((TextView) content.findViewById(R.id.prompt)).setText(prompt);
 
+        ((TextView) content.findViewById(R.id.settings_description)).setText(
+                Flags.modesApi() && Flags.modesUi()
+                        ? R.string.nls_feature_modes_settings_summary
+                        : R.string.nls_feature_settings_summary);
+
         Button allowButton = content.findViewById(R.id.allow_button);
         allowButton.setOnClickListener((view) -> {
             parent.enable(cn);
diff --git a/src/com/android/settings/applications/specialaccess/zenaccess/FriendlyWarningDialogFragment.java b/src/com/android/settings/applications/specialaccess/zenaccess/FriendlyWarningDialogFragment.java
index 5da2990..38317ed 100644
--- a/src/com/android/settings/applications/specialaccess/zenaccess/FriendlyWarningDialogFragment.java
+++ b/src/com/android/settings/applications/specialaccess/zenaccess/FriendlyWarningDialogFragment.java
@@ -17,6 +17,7 @@
 package com.android.settings.applications.specialaccess.zenaccess;
 
 import android.app.Dialog;
+import android.app.Flags;
 import android.app.settings.SettingsEnums;
 import android.os.Bundle;
 import android.text.TextUtils;
@@ -58,9 +59,14 @@
         final String label = args.getString(KEY_LABEL);
 
         final String title = getResources().getString(
-                R.string.zen_access_revoke_warning_dialog_title, label);
+                Flags.modesApi() && Flags.modesUi()
+                        ? R.string.zen_modes_access_revoke_warning_dialog_title
+                        : R.string.zen_access_revoke_warning_dialog_title,
+                label);
         final String summary = getResources()
-                .getString(R.string.zen_access_revoke_warning_dialog_summary);
+                .getString(Flags.modesApi() && Flags.modesUi()
+                        ? R.string.zen_modes_access_revoke_warning_dialog_summary
+                        : R.string.zen_access_revoke_warning_dialog_summary);
 
         ZenAccessDetails parent = (ZenAccessDetails) getTargetFragment();
         return new AlertDialog.Builder(getContext())
diff --git a/src/com/android/settings/applications/specialaccess/zenaccess/ScaryWarningDialogFragment.java b/src/com/android/settings/applications/specialaccess/zenaccess/ScaryWarningDialogFragment.java
index e4ef48b..b489602 100644
--- a/src/com/android/settings/applications/specialaccess/zenaccess/ScaryWarningDialogFragment.java
+++ b/src/com/android/settings/applications/specialaccess/zenaccess/ScaryWarningDialogFragment.java
@@ -17,6 +17,7 @@
 package com.android.settings.applications.specialaccess.zenaccess;
 
 import android.app.Dialog;
+import android.app.Flags;
 import android.app.settings.SettingsEnums;
 import android.os.Bundle;
 import android.text.TextUtils;
@@ -55,10 +56,15 @@
         final String pkg = args.getString(KEY_PKG);
         final String label = args.getString(KEY_LABEL);
 
-        final String title = getResources().getString(R.string.zen_access_warning_dialog_title,
+        final String title = getResources().getString(
+                Flags.modesApi() && Flags.modesUi()
+                        ? R.string.zen_modes_access_warning_dialog_title
+                        : R.string.zen_access_warning_dialog_title,
                 label);
         final String summary = getResources()
-                .getString(R.string.zen_access_warning_dialog_summary);
+                .getString(Flags.modesApi() && Flags.modesUi()
+                        ? R.string.zen_modes_access_warning_dialog_summary
+                        : R.string.zen_access_warning_dialog_summary);
 
         ZenAccessDetails parent = (ZenAccessDetails) getTargetFragment();
 
diff --git a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java
index 6f4137c..cfeeb0d 100644
--- a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java
+++ b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessController.java
@@ -18,6 +18,7 @@
 
 import android.app.ActivityManager;
 import android.app.AppGlobals;
+import android.app.Flags;
 import android.app.NotificationManager;
 import android.app.settings.SettingsEnums;
 import android.content.Context;
@@ -28,7 +29,10 @@
 import android.util.Log;
 
 import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
 
+import com.android.settings.R;
 import com.android.settings.core.BasePreferenceController;
 import com.android.settings.overlay.FeatureFactory;
 
@@ -48,6 +52,16 @@
         return AVAILABLE;
     }
 
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        Preference preference = screen.findPreference(getPreferenceKey());
+        if (preference != null) {
+            preference.setTitle(Flags.modesApi() && Flags.modesUi()
+                    ? R.string.manage_zen_modes_access_title
+                    : R.string.manage_zen_access_title);
+        }
+    }
+
     public static Set<String> getPackagesRequestingNotificationPolicyAccess() {
         final String[] PERM = {
                 android.Manifest.permission.ACCESS_NOTIFICATION_POLICY
diff --git a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessDetails.java b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessDetails.java
index ffe13e6..74903c0 100644
--- a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessDetails.java
+++ b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessDetails.java
@@ -16,9 +16,11 @@
 
 package com.android.settings.applications.specialaccess.zenaccess;
 
+import android.app.Flags;
 import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.os.Bundle;
+import android.os.UserManager;
 
 import androidx.appcompat.app.AlertDialog;
 import androidx.preference.TwoStatePreference;
@@ -42,6 +44,9 @@
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         addPreferencesFromResource(R.xml.zen_access_permission_details);
+        requireActivity().setTitle(Flags.modesApi() && Flags.modesUi()
+                ? R.string.manage_zen_modes_access_title
+                : R.string.manage_zen_access_title);
         getSettingsLifecycle().addObserver(
                 new ZenAccessSettingObserverMixin(getContext(), this /* listener */));
     }
@@ -49,6 +54,11 @@
     @Override
     protected boolean refreshUi() {
         final Context context = getContext();
+        // don't show for managed profiles
+        if (UserManager.get(context).isManagedProfile(context.getUserId())
+            && !ZenAccessController.hasAccess(context, mPackageName)) {
+            finish();
+        }
         // If this app didn't declare this permission in their manifest, don't bother showing UI.
         final Set<String> needAccessApps =
                 ZenAccessController.getPackagesRequestingNotificationPolicyAccess();
@@ -74,6 +84,9 @@
             preference.setSummary(getString(R.string.zen_access_disabled_package_warning));
             return;
         }
+        preference.setTitle(Flags.modesApi() && Flags.modesUi()
+                ? R.string.zen_modes_access_detail_switch
+                : R.string.zen_access_detail_switch);
         preference.setChecked(ZenAccessController.hasAccess(context, mPackageName));
         preference.setOnPreferenceChangeListener((p, newValue) -> {
             final boolean access = (Boolean) newValue;
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/connecteddevice/audiosharing/audiostreams/AudioStreamConfirmDialog.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamConfirmDialog.java
index 4c17a7c..958740b 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamConfirmDialog.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamConfirmDialog.java
@@ -129,6 +129,10 @@
     private Dialog getUnsupportedDialog() {
         return new AudioStreamsDialogFragment.DialogBuilder(getActivity())
                 .setTitle(getString(R.string.audio_streams_dialog_cannot_listen))
+                .setSubTitle1(
+                        mBroadcastMetadata != null
+                                ? AudioStreamsHelper.getBroadcastName(mBroadcastMetadata)
+                                : "")
                 .setSubTitle2(getString(R.string.audio_streams_dialog_unsupported_device_subtitle))
                 .setRightButtonText(getString(R.string.audio_streams_dialog_close))
                 .setRightButtonOnClickListener(
diff --git a/src/com/android/settings/development/TouchpadVisualizerPreferenceController.java b/src/com/android/settings/development/TouchpadVisualizerPreferenceController.java
index f918d26..04ae92b 100644
--- a/src/com/android/settings/development/TouchpadVisualizerPreferenceController.java
+++ b/src/com/android/settings/development/TouchpadVisualizerPreferenceController.java
@@ -17,8 +17,10 @@
 package com.android.settings.development;
 
 import android.content.Context;
-import android.provider.Settings;
+import android.hardware.input.InputSettings;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 import androidx.preference.SwitchPreference;
@@ -26,23 +28,13 @@
 import com.android.settings.core.PreferenceControllerMixin;
 import com.android.settingslib.development.DeveloperOptionsPreferenceController;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import android.hardware.input.InputSettings;
-
-/** PreferenceController that controls the "Touchpad visualizer" developer option. */
+/** PreferenceController that controls the "Show touchpad input" developer option. */
 public class TouchpadVisualizerPreferenceController extends
         DeveloperOptionsPreferenceController implements
         Preference.OnPreferenceChangeListener, PreferenceControllerMixin {
 
     private static final String TOUCHPAD_VISUALIZER_KEY = "touchpad_visualizer";
 
-    @VisibleForTesting
-    static final int SETTING_VALUE_ON = 1;
-    @VisibleForTesting
-    static final int SETTING_VALUE_OFF = 0;
-
     public TouchpadVisualizerPreferenceController(@NonNull Context context) {
         super(context);
     }
@@ -60,24 +52,22 @@
     @Override
     public boolean onPreferenceChange(@NonNull Preference preference, @Nullable Object newValue) {
         final boolean isEnabled = newValue != null ? (Boolean) newValue : false;
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.TOUCHPAD_VISUALIZER,
-                isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF);
+        InputSettings.setTouchpadVisualizer(mContext, isEnabled);
+
         return true;
     }
 
     @Override
     public void updateState(@NonNull Preference preference) {
-        int touchpadVisualizer = Settings.System.getInt(mContext.getContentResolver(),
-                Settings.System.TOUCHPAD_VISUALIZER, SETTING_VALUE_OFF);
-        ((SwitchPreference) mPreference).setChecked(touchpadVisualizer != SETTING_VALUE_OFF);
+        boolean touchpadVisualizerEnabled = InputSettings.useTouchpadVisualizer(mContext);
+        ((SwitchPreference) mPreference).setChecked(touchpadVisualizerEnabled);
     }
 
     @Override
     protected void onDeveloperOptionsSwitchDisabled() {
         super.onDeveloperOptionsSwitchDisabled();
-        Settings.System.putInt(mContext.getContentResolver(), Settings.System.TOUCHPAD_VISUALIZER,
-                SETTING_VALUE_OFF);
+        InputSettings.setTouchpadVisualizer(mContext, false);
+
         ((SwitchPreference) mPreference).setChecked(false);
     }
 }
\ No newline at end of file
diff --git a/src/com/android/settings/network/SimOnboardingService.kt b/src/com/android/settings/network/SimOnboardingService.kt
index 59dc35e..e4f17e2 100644
--- a/src/com/android/settings/network/SimOnboardingService.kt
+++ b/src/com/android/settings/network/SimOnboardingService.kt
@@ -24,10 +24,10 @@
 import android.telephony.SubscriptionManager
 import android.telephony.TelephonyManager
 import android.telephony.UiccCardInfo
-import android.telephony.UiccSlotInfo
 import android.util.Log
 import com.android.settings.network.SimOnboardingActivity.Companion.CallbackType
 import com.android.settings.network.telephony.MobileDataRepository
+import com.android.settings.network.telephony.UiccSlotRepository
 import com.android.settings.sim.SimActivationNotifier
 import com.android.settings.spa.network.setDefaultData
 import com.android.settings.spa.network.setDefaultSms
@@ -46,7 +46,6 @@
     var targetSubInfo: SubscriptionInfo? = null
     var availableSubInfoList: List<SubscriptionInfo> = listOf()
     var activeSubInfoList: List<SubscriptionInfo> = listOf()
-    var slotInfoList: List<UiccSlotInfo> = listOf()
     var uiccCardInfoList: List<UiccCardInfo> = listOf()
     var targetPrimarySimCalls: Int = INVALID_SUBSCRIPTION_ID
     var targetPrimarySimTexts: Int = INVALID_SUBSCRIPTION_ID
@@ -73,14 +72,6 @@
             }
             return  uiccCardInfoList.any { it.isMultipleEnabledProfilesSupported }
         }
-    var isRemovablePsimProfileEnabled: Boolean = false
-        get() {
-            if(slotInfoList.isEmpty()) {
-                Log.w(TAG, "UICC Slot info list is empty.")
-                return false
-            }
-            return UiccSlotUtil.isRemovableSimEnabled(slotInfoList)
-        }
     var isEsimProfileEnabled: Boolean = false
         get() {
             activeSubInfoList.stream().anyMatch { it.isEmbedded }
@@ -137,19 +128,11 @@
             return telephonyManager?.doesSwitchMultiSimConfigTriggerReboot() ?: false
         }
 
-    fun isValid(): Boolean {
-        return targetSubId != INVALID_SUBSCRIPTION_ID
-            && targetSubInfo != null
-            && activeSubInfoList.isNotEmpty()
-            && slotInfoList.isNotEmpty()
-    }
-
     fun clear() {
         targetSubId = -1
         targetSubInfo = null
         availableSubInfoList = listOf()
         activeSubInfoList = listOf()
-        slotInfoList = listOf()
         uiccCardInfoList = listOf()
         targetPrimarySimCalls = -1
         targetPrimarySimTexts = -1
@@ -181,8 +164,6 @@
                 availableSubInfoList.find { subInfo -> subInfo.subscriptionId == targetSubId }
             targetSubInfo?.let { userSelectedSubInfoList.add(it) }
             Log.d(TAG, "targetSubId: $targetSubId , targetSubInfo: $targetSubInfo")
-            slotInfoList = telephonyManager?.uiccSlotsInfo?.toList() ?: listOf()
-            Log.d(TAG, "slotInfoList: $slotInfoList.")
             uiccCardInfoList = telephonyManager?.uiccCardsInfo!!
             Log.d(TAG, "uiccCardInfoList: $uiccCardInfoList")
 
@@ -192,7 +173,6 @@
 
             Log.d(
                 TAG,"doesTargetSimHaveEsimOperation: $doesTargetSimHaveEsimOperation" +
-                    ", isRemovableSimEnabled: $isRemovablePsimProfileEnabled" +
                     ", isMultipleEnabledProfilesSupported: $isMultipleEnabledProfilesSupported" +
                     ", targetPrimarySimCalls: $targetPrimarySimCalls" +
                     ", targetPrimarySimTexts: $targetPrimarySimTexts" +
@@ -317,14 +297,15 @@
             return true
         }
 
-        if (doesTargetSimHaveEsimOperation && isRemovablePsimProfileEnabled) {
-            Log.d(TAG,
-                "eSIM operation and removable PSIM is enabled. DSDS condition satisfied."
-            )
-            return true
-        }
-
-        if (!doesTargetSimHaveEsimOperation && isEsimProfileEnabled) {
+        if (doesTargetSimHaveEsimOperation) {
+            if (UiccSlotRepository(telephonyManager).anyRemovablePhysicalSimEnabled()) {
+                Log.d(
+                    TAG,
+                    "eSIM operation and removable PSIM is enabled. DSDS condition satisfied."
+                )
+                return true
+            }
+        } else if (isEsimProfileEnabled) {
             Log.d(TAG,
                 "Removable SIM operation and eSIM profile is enabled. DSDS condition"
                         + " satisfied."
diff --git a/src/com/android/settings/network/SwitchToEuiccSubscriptionSidecar.java b/src/com/android/settings/network/SwitchToEuiccSubscriptionSidecar.java
index a26aa8a..8f6c32a 100644
--- a/src/com/android/settings/network/SwitchToEuiccSubscriptionSidecar.java
+++ b/src/com/android/settings/network/SwitchToEuiccSubscriptionSidecar.java
@@ -30,8 +30,6 @@
 import com.android.settings.SidecarFragment;
 import com.android.settings.network.telephony.EuiccOperationSidecar;
 
-import com.google.common.collect.ImmutableList;
-
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.List;
@@ -205,10 +203,10 @@
     }
 
     private int getLogicalSlotIndex(int physicalSlotIndex, int portIndex) {
-        ImmutableList<UiccSlotInfo> slotInfos = UiccSlotUtil.getSlotInfos(mTelephonyManager);
-        if (slotInfos != null && physicalSlotIndex >= 0 && physicalSlotIndex < slotInfos.size()
-                && slotInfos.get(physicalSlotIndex) != null) {
-            for (UiccPortInfo portInfo : slotInfos.get(physicalSlotIndex).getPorts()) {
+        UiccSlotInfo[] slotInfos = mTelephonyManager.getUiccSlotsInfo();
+        if (slotInfos != null && physicalSlotIndex >= 0 && physicalSlotIndex < slotInfos.length
+                && slotInfos[physicalSlotIndex] != null) {
+            for (UiccPortInfo portInfo : slotInfos[physicalSlotIndex].getPorts()) {
                 if (portInfo.getPortIndex() == portIndex) {
                     return portInfo.getLogicalSlotIndex();
                 }
diff --git a/src/com/android/settings/network/UiccSlotUtil.java b/src/com/android/settings/network/UiccSlotUtil.java
index 5ae0a36..8a5a22c 100644
--- a/src/com/android/settings/network/UiccSlotUtil.java
+++ b/src/com/android/settings/network/UiccSlotUtil.java
@@ -33,8 +33,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.utils.ThreadUtils;
 
-import com.google.common.collect.ImmutableList;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -106,18 +104,6 @@
     }
 
     /**
-     * Returns an immutable list of all UICC slots. If TelephonyManager#getUiccSlotsInfo returns, it
-     * returns an empty list instead.
-     */
-    public static ImmutableList<UiccSlotInfo> getSlotInfos(TelephonyManager telMgr) {
-        UiccSlotInfo[] slotInfos = telMgr.getUiccSlotsInfo();
-        if (slotInfos == null) {
-            return ImmutableList.of();
-        }
-        return ImmutableList.copyOf(slotInfos);
-    }
-
-    /**
      * Switches to the removable slot. It waits for SIM_STATE_LOADED after switch. If slotId is
      * INVALID_PHYSICAL_SLOT_ID, the method will use the first detected inactive removable slot.
      *
@@ -219,14 +205,13 @@
      */
     public static int getEsimSlotId(Context context, int subId) {
         TelephonyManager telMgr = context.getSystemService(TelephonyManager.class);
-        List<UiccCardInfo> uiccCardInfos = telMgr.getUiccCardsInfo();
-        ImmutableList<UiccSlotInfo> slotInfos = UiccSlotUtil.getSlotInfos(telMgr);
         SubscriptionManager subscriptionManager = context.getSystemService(
                 SubscriptionManager.class).createForAllUserProfiles();
         SubscriptionInfo subInfo = SubscriptionUtil.getSubById(subscriptionManager, subId);
 
         // checking whether this is the removable esim. If it is, then return the removable slot id.
         if (subInfo != null && subInfo.isEmbedded()) {
+            List<UiccCardInfo> uiccCardInfos = telMgr.getUiccCardsInfo();
             for (UiccCardInfo uiccCardInfo : uiccCardInfos) {
                 if (uiccCardInfo.getCardId() == subInfo.getCardId()
                         && uiccCardInfo.getCardId() > TelephonyManager.UNSUPPORTED_CARD_ID
@@ -238,10 +223,12 @@
             }
         }
 
-        int firstEsimSlot = IntStream.range(0, slotInfos.size())
+        UiccSlotInfo[] slotInfos = telMgr.getUiccSlotsInfo();
+        if (slotInfos == null) return -1;
+        int firstEsimSlot = IntStream.range(0, slotInfos.length)
                 .filter(
                         index -> {
-                            UiccSlotInfo slotInfo = slotInfos.get(index);
+                            UiccSlotInfo slotInfo = slotInfos[index];
                             if (slotInfo == null) {
                                 return false;
                             }
@@ -421,41 +408,6 @@
                 .orElse(INVALID_LOGICAL_SLOT_ID);
     }
 
-    /**
-     * Return whether the removable psim is enabled.
-     *
-     * @param telMgr is a TelephonyManager.
-     * @return whether the removable psim is enabled.
-     */
-    public static boolean isRemovableSimEnabled(TelephonyManager telMgr) {
-        if (telMgr == null) {
-            return false;
-        }
-        List<UiccSlotInfo> slotInfos = UiccSlotUtil.getSlotInfos(telMgr);
-        return isRemovableSimEnabled(slotInfos);
-    }
-
-    /**
-     * Return whether the removable psim is enabled.
-     *
-     * @param slotInfos is a List of UiccSlotInfo.
-     * @return whether the removable psim is enabled.
-     */
-    public static boolean isRemovableSimEnabled(List<UiccSlotInfo> slotInfos) {
-        boolean isRemovableSimEnabled =
-                slotInfos.stream()
-                        .anyMatch(
-                                slot -> slot != null
-                                        && slot.isRemovable()
-                                        && !slot.getIsEuicc()
-                                        && slot.getPorts().stream()
-                                                .anyMatch(port -> port.isActive())
-                                        && slot.getCardStateInfo()
-                                        == UiccSlotInfo.CARD_STATE_INFO_PRESENT);
-        Log.i(TAG, "isRemovableSimEnabled: " + isRemovableSimEnabled);
-        return isRemovableSimEnabled;
-    }
-
     private static boolean isMultipleEnabledProfilesSupported(TelephonyManager telMgr) {
         List<UiccCardInfo> cardInfos = telMgr.getUiccCardsInfo();
         if (cardInfos == null) {
diff --git a/src/com/android/settings/network/telephony/DataUsagePreferenceController.kt b/src/com/android/settings/network/telephony/DataUsagePreferenceController.kt
index d47a246..aa113b6 100644
--- a/src/com/android/settings/network/telephony/DataUsagePreferenceController.kt
+++ b/src/com/android/settings/network/telephony/DataUsagePreferenceController.kt
@@ -29,35 +29,32 @@
 import androidx.preference.Preference
 import androidx.preference.PreferenceScreen
 import com.android.settings.R
+import com.android.settings.core.BasePreferenceController
 import com.android.settings.datausage.DataUsageUtils
 import com.android.settings.datausage.lib.DataUsageFormatter.FormattedDataUsage
 import com.android.settings.datausage.lib.DataUsageLib
 import com.android.settings.datausage.lib.NetworkCycleDataRepository
 import com.android.settings.datausage.lib.NetworkStatsRepository.Companion.AllTimeRange
+import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchItem
+import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchResult
 import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
-/**
- * Preference controller for "Data usage"
- */
+/** Preference controller for "Data usage" */
 class DataUsagePreferenceController(context: Context, key: String) :
-    TelephonyBasePreferenceController(context, key) {
+    BasePreferenceController(context, key) {
 
+    private var subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID
     private lateinit var preference: Preference
     private var networkTemplate: NetworkTemplate? = null
 
     fun init(subId: Int) {
-        mSubId = subId
+        this.subId = subId
     }
 
-    override fun getAvailabilityStatus(subId: Int): Int = when {
-        SubscriptionManager.isValidSubscriptionId(subId) &&
-            DataUsageUtils.hasMobileData(mContext) -> AVAILABLE
-
-        else -> AVAILABLE_UNSEARCHABLE
-    }
+    override fun getAvailabilityStatus() = AVAILABLE
 
     override fun displayPreference(screen: PreferenceScreen) {
         super.displayPreference(screen)
@@ -75,11 +72,12 @@
 
     override fun handlePreferenceTreeClick(preference: Preference): Boolean {
         if (preference.key != preferenceKey || networkTemplate == null) return false
-        val intent = Intent(Settings.ACTION_MOBILE_DATA_USAGE).apply {
-            setPackage(mContext.packageName)
-            putExtra(Settings.EXTRA_NETWORK_TEMPLATE, networkTemplate)
-            putExtra(Settings.EXTRA_SUB_ID, mSubId)
-        }
+        val intent =
+            Intent(Settings.ACTION_MOBILE_DATA_USAGE).apply {
+                setPackage(mContext.packageName)
+                putExtra(Settings.EXTRA_NETWORK_TEMPLATE, networkTemplate)
+                putExtra(Settings.EXTRA_SUB_ID, subId)
+            }
         mContext.startActivity(intent)
         return true
     }
@@ -93,13 +91,10 @@
         preference.summary = summary?.displayText
     }
 
-    private fun getNetworkTemplate(): NetworkTemplate? = when {
-        SubscriptionManager.isValidSubscriptionId(mSubId) -> {
-            DataUsageLib.getMobileTemplate(mContext, mSubId)
-        }
-
-        else -> null
-    }
+    private fun getNetworkTemplate(): NetworkTemplate? =
+        if (SubscriptionManager.isValidSubscriptionId(subId)) {
+            DataUsageLib.getMobileTemplate(mContext, subId)
+        } else null
 
     @VisibleForTesting
     fun createNetworkCycleDataRepository(): NetworkCycleDataRepository? =
@@ -118,4 +113,16 @@
         val allTimeUsage = repository.queryUsage(AllTimeRange)
         return allTimeUsage.getDataUsedString(mContext) to (allTimeUsage.usage > 0)
     }
+
+    companion object {
+        class DataUsageSearchItem(private val context: Context) : MobileNetworkSettingsSearchItem {
+            override fun getSearchResult(subId: Int): MobileNetworkSettingsSearchResult? {
+                if (!DataUsageUtils.hasMobileData(context)) return null
+                return MobileNetworkSettingsSearchResult(
+                    key = "data_usage_summary",
+                    title = context.getString(R.string.app_cellular_data_usage),
+                )
+            }
+        }
+    }
 }
diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettingsSearchIndex.kt b/src/com/android/settings/network/telephony/MobileNetworkSettingsSearchIndex.kt
index 58661f0..55cb1fa 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkSettingsSearchIndex.kt
+++ b/src/com/android/settings/network/telephony/MobileNetworkSettingsSearchIndex.kt
@@ -21,6 +21,7 @@
 import android.telephony.SubscriptionInfo
 import com.android.settings.R
 import com.android.settings.network.SubscriptionUtil
+import com.android.settings.network.telephony.DataUsagePreferenceController.Companion.DataUsageSearchItem
 import com.android.settings.network.telephony.MmsMessagePreferenceController.Companion.MmsMessageSearchItem
 import com.android.settings.network.telephony.NrAdvancedCallingPreferenceController.Companion.NrAdvancedCallingSearchItem
 import com.android.settings.network.telephony.RoamingPreferenceController.Companion.RoamingSearchItem
@@ -114,6 +115,7 @@
 
         fun createSearchItems(context: Context): List<MobileNetworkSettingsSearchItem> =
             listOf(
+                DataUsageSearchItem(context),
                 MmsMessageSearchItem(context),
                 NrAdvancedCallingSearchItem(context),
                 PreferredNetworkModeSearchItem(context),
diff --git a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java
index 6f4d3c3..981e5bb 100644
--- a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java
+++ b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java
@@ -583,7 +583,7 @@
     }
 
     private boolean isRemovableSimEnabled() {
-        return UiccSlotUtil.isRemovableSimEnabled(mTelMgr);
+        return new UiccSlotRepository(mTelMgr).anyRemovablePhysicalSimEnabled();
     }
 
     private boolean isMultipleEnabledProfilesSupported() {
diff --git a/src/com/android/settings/network/telephony/UiccSlotRepository.kt b/src/com/android/settings/network/telephony/UiccSlotRepository.kt
new file mode 100644
index 0000000..3a83805
--- /dev/null
+++ b/src/com/android/settings/network/telephony/UiccSlotRepository.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony
+
+import android.telephony.TelephonyManager
+import android.telephony.UiccSlotInfo
+import android.util.Log
+
+class UiccSlotRepository(private val telephonyManager: TelephonyManager?) {
+
+    /** Returns whether any removable physical sim is enabled. */
+    fun anyRemovablePhysicalSimEnabled(): Boolean {
+        val result =
+            telephonyManager?.uiccSlotsInfo?.any { uiccSlotInfo: UiccSlotInfo? ->
+                uiccSlotInfo.isRemovablePhysicalSimEnabled()
+            } ?: false
+        Log.i(TAG, "anyRemovablePhysicalSimEnabled: $result")
+        return result
+    }
+
+    private fun UiccSlotInfo?.isRemovablePhysicalSimEnabled(): Boolean {
+        return this != null &&
+            isRemovable &&
+            !isEuicc &&
+            ports.any { port -> port.isActive } &&
+            cardStateInfo == UiccSlotInfo.CARD_STATE_INFO_PRESENT
+    }
+
+    companion object {
+        private const val TAG = "UiccRepository"
+    }
+}
diff --git a/src/com/android/settings/network/telephony/ims/ImsFeatureRepository.kt b/src/com/android/settings/network/telephony/ims/ImsFeatureRepository.kt
new file mode 100644
index 0000000..ba33257
--- /dev/null
+++ b/src/com/android/settings/network/telephony/ims/ImsFeatureRepository.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony.ims
+
+import android.content.Context
+import android.telephony.AccessNetworkConstants.TransportType
+import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability
+import android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech
+import com.android.settings.network.telephony.subscriptionsChangedFlow
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+
+/**
+ * A repository for the IMS feature.
+ *
+ * @throws IllegalArgumentException if the [subId] is invalid.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+class ImsFeatureRepository(
+    private val context: Context,
+    private val subId: Int,
+    private val provisioningRepository: ProvisioningRepository = ProvisioningRepository(context),
+    private val imsMmTelRepository: ImsMmTelRepository = ImsMmTelRepositoryImpl(context, subId)
+) {
+    /**
+     * A cold flow that determines the provisioning status for the specified IMS MmTel capability,
+     * and whether or not the requested MmTel capability is supported by the carrier on the
+     * specified network transport.
+     *
+     * @return true if the feature is provisioned and supported, false otherwise.
+     */
+    fun isReadyFlow(
+        @MmTelCapability capability: Int,
+        @ImsRegistrationTech tech: Int,
+        @TransportType transportType: Int,
+    ): Flow<Boolean> =
+        context.subscriptionsChangedFlow().flatMapLatest {
+            combine(
+                provisioningRepository.imsFeatureProvisionedFlow(subId, capability, tech),
+                imsMmTelRepository.isSupportedFlow(capability, transportType),
+            ) { imsFeatureProvisioned, isSupported ->
+                imsFeatureProvisioned && isSupported
+            }
+        }
+}
diff --git a/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt b/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt
index 9bc10e5..c5d1200 100644
--- a/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt
+++ b/src/com/android/settings/network/telephony/ims/ImsMmTelRepository.kt
@@ -36,6 +36,7 @@
 import kotlinx.coroutines.flow.catch
 import kotlinx.coroutines.flow.conflate
 import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withContext
 
@@ -47,6 +48,11 @@
 
     fun imsReadyFlow(): Flow<Boolean>
 
+    fun isSupportedFlow(
+        @MmTelFeature.MmTelCapabilities.MmTelCapability capability: Int,
+        @AccessNetworkConstants.TransportType transportType: Int,
+    ): Flow<Boolean>
+
     suspend fun isSupported(
         @MmTelFeature.MmTelCapabilities.MmTelCapability capability: Int,
         @AccessNetworkConstants.TransportType transportType: Int,
@@ -55,6 +61,11 @@
     suspend fun setCrossSimCallingEnabled(enabled: Boolean)
 }
 
+/**
+ * A repository for the IMS MMTel.
+ *
+ * @throws IllegalArgumentException if the [subId] is invalid.
+ */
 class ImsMmTelRepositoryImpl(
     context: Context,
     private val subId: Int,
@@ -126,8 +137,12 @@
         awaitClose { imsMmTelManager.unregisterImsStateCallback(callback) }
     }.catch { e ->
         Log.w(TAG, "[$subId] error while imsReadyFlow", e)
+        emit(false)
     }.conflate().flowOn(Dispatchers.Default)
 
+    override fun isSupportedFlow(capability: Int, transportType: Int): Flow<Boolean> =
+        imsReadyFlow().map { imsReady -> imsReady && isSupported(capability, transportType) }
+
     override suspend fun isSupported(
         @MmTelFeature.MmTelCapabilities.MmTelCapability capability: Int,
         @AccessNetworkConstants.TransportType transportType: Int,
diff --git a/src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt b/src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt
index 04e687c..6af0559 100644
--- a/src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt
+++ b/src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt
@@ -20,24 +20,17 @@
 import android.telephony.AccessNetworkConstants
 import android.telephony.CarrierConfigManager
 import android.telephony.CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL
-import android.telephony.SubscriptionManager
 import android.telephony.ims.ImsMmTelManager.WiFiCallingMode
 import android.telephony.ims.feature.MmTelFeature
 import android.telephony.ims.stub.ImsRegistrationImplBase
 import androidx.lifecycle.LifecycleOwner
+import com.android.settings.network.telephony.ims.ImsFeatureRepository
 import com.android.settings.network.telephony.ims.ImsMmTelRepository
 import com.android.settings.network.telephony.ims.ImsMmTelRepositoryImpl
-import com.android.settings.network.telephony.ims.ProvisioningRepository
-import com.android.settings.network.telephony.subscriptionsChangedFlow
 import com.android.settings.network.telephony.telephonyManager
 import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.withContext
 
 interface IWifiCallingRepository {
@@ -50,11 +43,11 @@
 constructor(
     private val context: Context,
     private val subId: Int,
-    private val imsMmTelRepository: ImsMmTelRepository = ImsMmTelRepositoryImpl(context, subId)
+    private val imsFeatureRepository: ImsFeatureRepository = ImsFeatureRepository(context, subId),
+    private val imsMmTelRepository: ImsMmTelRepository = ImsMmTelRepositoryImpl(context, subId),
 ) : IWifiCallingRepository {
     private val telephonyManager = context.telephonyManager(subId)
 
-    private val provisioningRepository = ProvisioningRepository(context)
     private val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)!!
 
     @WiFiCallingMode
@@ -76,28 +69,12 @@
         wifiCallingReadyFlow().collectLatestWithLifecycle(lifecycleOwner, action = action)
     }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
-    fun wifiCallingReadyFlow(): Flow<Boolean> {
-        if (!SubscriptionManager.isValidSubscriptionId(subId)) return flowOf(false)
-        return context.subscriptionsChangedFlow().flatMapLatest {
-            combine(
-                provisioningRepository.imsFeatureProvisionedFlow(
-                    subId = subId,
-                    capability = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
-                    tech = ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
-                ),
-                isWifiCallingSupportedFlow(),
-            ) { imsFeatureProvisioned, isWifiCallingSupported ->
-                imsFeatureProvisioned && isWifiCallingSupported
-            }
-        }
-    }
-
-    private fun isWifiCallingSupportedFlow(): Flow<Boolean> {
-        return imsMmTelRepository.imsReadyFlow().map { imsReady ->
-            imsReady && isWifiCallingSupported()
-        }
-    }
+    fun wifiCallingReadyFlow(): Flow<Boolean> =
+        imsFeatureRepository.isReadyFlow(
+            capability = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
+            tech = ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
+            transportType = AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+        )
 
     suspend fun isWifiCallingSupported(): Boolean = withContext(Dispatchers.Default) {
         imsMmTelRepository.isSupported(
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 ccf7f52..5a89352 100644
--- a/src/com/android/settings/notification/modes/CircularIconsPreference.java
+++ b/src/com/android/settings/notification/modes/CircularIconsPreference.java
@@ -16,236 +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) {
-        static final LoadedIcons EMPTY = new LoadedIcons(ImmutableList.of(), 0);
-    }
-
-    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);
-                mLoadedIcons = LoadedIcons.EMPTY;
-                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);
-                                notifyChanged();
-                            }
-                        }
-                );
-            }
-        }
-    }
-
-    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 830baaf..8408624 100644
--- a/src/com/android/settings/notification/modes/SetupInterstitialActivity.java
+++ b/src/com/android/settings/notification/modes/SetupInterstitialActivity.java
@@ -21,9 +21,13 @@
 import static android.app.AutomaticZenRule.TYPE_IMMERSIVE;
 import static android.app.AutomaticZenRule.TYPE_MANAGED;
 import static android.app.AutomaticZenRule.TYPE_OTHER;
+import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR;
+import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
 import static android.app.AutomaticZenRule.TYPE_THEATER;
+import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
 import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
 
+import android.annotation.SuppressLint;
 import android.app.ActionBar;
 import android.app.settings.SettingsEnums;
 import android.content.Context;
@@ -39,6 +43,7 @@
 
 import androidx.activity.EdgeToEdge;
 import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
 import androidx.annotation.VisibleForTesting;
 import androidx.fragment.app.FragmentActivity;
 
@@ -124,6 +129,11 @@
             title.setText(mode.getName());
         }
 
+        TextView subtitle = findViewById(R.id.mode_name_subtitle);
+        if (subtitle != null) {
+            subtitle.setText(getSubtitle(mode));
+        }
+
         ImageView img = findViewById(R.id.image);
         if (img != null) {
             setImage(img, mode);
@@ -135,6 +145,27 @@
         }
     }
 
+    @StringRes
+    @SuppressLint("SwitchIntDef")
+    private static int getSubtitle(ZenMode mode) {
+        if (mode.isSystemOwned()) {
+            return switch (mode.getType()) {
+                case TYPE_SCHEDULE_TIME -> R.string.zen_mode_inspiration_schedule_time;
+                case TYPE_SCHEDULE_CALENDAR -> R.string.zen_mode_inspiration_schedule_calendar;
+                default -> R.string.zen_mode_inspiration_generic; // Custom Manual
+            };
+        } else {
+            return switch (mode.getType()) {
+                case TYPE_BEDTIME -> R.string.zen_mode_inspiration_bedtime;
+                case TYPE_DRIVING -> R.string.zen_mode_inspiration_driving;
+                case TYPE_IMMERSIVE -> R.string.zen_mode_inspiration_immersive;
+                case TYPE_THEATER -> R.string.zen_mode_inspiration_theater;
+                case TYPE_MANAGED -> R.string.zen_mode_inspiration_managed;
+                default -> R.string.zen_mode_inspiration_generic; // Including OTHER, UNKNOWN.
+            };
+        }
+    }
+
     private void setImage(@NonNull ImageView img, @NonNull ZenMode mode) {
         int drawableRes = switch (mode.getType()) {
             case TYPE_BEDTIME -> R.drawable.modes_interstitial_bedtime;
@@ -142,7 +173,9 @@
             case TYPE_IMMERSIVE -> R.drawable.modes_interstitial_immersive;
             case TYPE_THEATER -> R.drawable.modes_interstitial_theater;
             case TYPE_MANAGED -> R.drawable.modes_interstitial_managed;
-            case TYPE_OTHER -> R.drawable.modes_interstitial_other;
+            case TYPE_OTHER, TYPE_SCHEDULE_CALENDAR, TYPE_SCHEDULE_TIME ->
+                    R.drawable.modes_interstitial_other;
+            case TYPE_UNKNOWN -> R.drawable.modes_interstitial_unknown;
             default -> R.drawable.modes_interstitial_unknown;
         };
 
diff --git a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
index 45287ab..7b17f0c 100644
--- a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
@@ -109,7 +109,7 @@
 
         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();
             }
@@ -151,7 +151,7 @@
 
         ImmutableList<AppEntry> apps = getAppsBypassingDndSortedByName(allApps);
         mPreference.setSummary(mSummaryHelper.getAppsSummary(mZenMode, apps));
-        mPreference.displayIcons(new CircularIconSet<>(apps,
+        mPreference.setIcons(new CircularIconSet<>(apps,
                 app -> mAppIconRetriever.apply(app.info)),
                 APP_ENTRY_EQUIVALENCE);
     }
diff --git a/src/com/android/settings/notification/modes/ZenModeFragment.java b/src/com/android/settings/notification/modes/ZenModeFragment.java
index 3777299..13aabd3 100644
--- a/src/com/android/settings/notification/modes/ZenModeFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeFragment.java
@@ -110,9 +110,10 @@
         if (mode == null || mode.getStatus() != DISABLED_BY_OTHER) {
             return false;
         }
+
+        mContext.startActivity(SetupInterstitialActivity.getIntent(mContext, mode));
         // don't come back here from the interstitial
         finish();
-        mContext.startActivity(SetupInterstitialActivity.getIntent(mContext, mode));
         return true;
     }
 
diff --git a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
index 5b26364..9613d98 100644
--- a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
@@ -72,7 +72,7 @@
 
         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/ZenModePeopleLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
index 9aad460..bf55471 100644
--- a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
@@ -96,7 +96,7 @@
 
         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/zen/ZenAccessSettings.java b/src/com/android/settings/notification/zen/ZenAccessSettings.java
index f765d6d..4b598db 100644
--- a/src/com/android/settings/notification/zen/ZenAccessSettings.java
+++ b/src/com/android/settings/notification/zen/ZenAccessSettings.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.notification.zen;
 
+import android.app.Flags;
 import android.app.NotificationManager;
 import android.app.settings.SettingsEnums;
 import android.content.Context;
@@ -68,6 +69,9 @@
         mContext = getActivity();
         mPkgMan = mContext.getPackageManager();
         mNoMan = mContext.getSystemService(NotificationManager.class);
+        requireActivity().setTitle(Flags.modesApi() && Flags.modesUi()
+                ? R.string.manage_zen_modes_access_title
+                : R.string.manage_zen_access_title);
         getSettingsLifecycle().addObserver(
                 new ZenAccessSettingObserverMixin(getContext(), this /* listener */));
     }
@@ -75,7 +79,9 @@
     @Override
     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
-        setEmptyText(R.string.zen_access_empty_text);
+        setEmptyText(Flags.modesApi() && Flags.modesUi()
+                ? R.string.zen_modes_access_empty_text
+                : R.string.zen_access_empty_text);
     }
 
     @Override
@@ -139,7 +145,9 @@
             pref.setOnPreferenceClickListener(preference -> {
                 AppInfoBase.startAppInfoFragment(
                         ZenAccessDetails.class  /* fragment */,
-                        getString(R.string.manage_zen_access_title) /* titleRes */,
+                        getString(Flags.modesApi() && Flags.modesUi()
+                                ? R.string.manage_zen_modes_access_title
+                                : R.string.manage_zen_access_title),
                         pkg,
                         app.uid,
                         this /* source */,
@@ -154,7 +162,7 @@
 
     /**
      * @return the summary for the current state of whether the app associated with the given
-     * {@param packageName} is allowed to enter picture-in-picture.
+     * {@param packageName} is allowed to manage DND / Priority Modes.
      */
     private int getPreferenceSummary(String packageName) {
         final boolean enabled = ZenAccessController.hasAccess(getContext(), packageName);
diff --git a/src/com/android/settings/spa/search/SpaSearchLandingActivity.kt b/src/com/android/settings/spa/search/SpaSearchLandingActivity.kt
index cb5f745..2c0955b 100644
--- a/src/com/android/settings/spa/search/SpaSearchLandingActivity.kt
+++ b/src/com/android/settings/spa/search/SpaSearchLandingActivity.kt
@@ -27,9 +27,6 @@
 import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
 import com.android.settings.password.PasswordUtils
 import com.android.settings.spa.SpaDestination
-import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingKey
-import com.google.protobuf.ByteString
-import com.google.protobuf.InvalidProtocolBufferException
 
 class SpaSearchLandingActivity : Activity() {
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -48,22 +45,17 @@
     companion object {
         @VisibleForTesting
         fun tryLaunch(context: Context, keyString: String) {
-            val key =
-                try {
-                    SpaSearchLandingKey.parseFrom(ByteString.copyFromUtf8(keyString))
-                } catch (e: InvalidProtocolBufferException) {
-                    Log.w(TAG, "arg key ($keyString) invalid", e)
-                    return
-                }
-
+            val key = decodeToSpaSearchLandingKey(keyString) ?: return
             if (key.hasSpaPage()) {
                 val destination = key.spaPage.destination
                 if (destination.isNotEmpty()) {
+                    Log.d(TAG, "Launch SPA search result: ${key.spaPage}")
                     SpaDestination(destination = destination, highlightMenuKey = null)
                         .startFromExportedActivity(context)
                 }
             }
             if (key.hasFragment()) {
+                Log.d(TAG, "Launch fragment search result: ${key.fragment}")
                 val arguments =
                     Bundle().apply {
                         key.fragment.argumentsMap.forEach { (k, v) ->
diff --git a/src/com/android/settings/spa/search/SpaSearchLandingKeyExt.kt b/src/com/android/settings/spa/search/SpaSearchLandingKeyExt.kt
new file mode 100644
index 0000000..9540d9b
--- /dev/null
+++ b/src/com/android/settings/spa/search/SpaSearchLandingKeyExt.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.spa.search
+
+import android.util.Base64
+import android.util.Log
+import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingKey
+
+private const val TAG = "SpaSearchLandingKeyExt"
+
+fun SpaSearchLandingKey.encodeToString(): String =
+    Base64.encodeToString(toByteArray(), Base64.DEFAULT)
+
+fun decodeToSpaSearchLandingKey(input: String): SpaSearchLandingKey? =
+    try {
+        SpaSearchLandingKey.parseFrom(Base64.decode(input, Base64.DEFAULT))
+    } catch (e: Exception) {
+        Log.w(TAG, "SpaSearchLandingKey ($input) invalid", e)
+        null
+    }
diff --git a/src/com/android/settings/spa/search/SpaSearchRepository.kt b/src/com/android/settings/spa/search/SpaSearchRepository.kt
index e5334dd..b1003ae 100644
--- a/src/com/android/settings/spa/search/SpaSearchRepository.kt
+++ b/src/com/android/settings/spa/search/SpaSearchRepository.kt
@@ -96,7 +96,7 @@
             keywords: String? = null,
         ) =
             SearchIndexableRaw(context).apply {
-                key = spaSearchLandingKey.toByteString().toStringUtf8()
+                key = spaSearchLandingKey.encodeToString()
                 title = itemTitle
                 this.keywords = keywords
                 intentAction = SEARCH_LANDING_ACTION
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/development/TouchpadVisualizerPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/TouchpadVisualizerPreferenceControllerTest.java
index 98c774e..826bae9 100644
--- a/tests/robotests/src/com/android/settings/development/TouchpadVisualizerPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/development/TouchpadVisualizerPreferenceControllerTest.java
@@ -22,22 +22,35 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
-import android.provider.Settings;
+import android.hardware.input.InputSettings;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.preference.PreferenceScreen;
 import androidx.preference.SwitchPreference;
 
+import com.android.hardware.input.Flags;
+import com.android.settings.testutils.shadow.ShadowSystemSettings;
+
 import org.junit.Before;
+import org.junit.Rule;
 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;
+import org.robolectric.annotation.Config;
 
 @RunWith(RobolectricTestRunner.class)
+@Config(shadows = {
+        ShadowSystemSettings.class,
+})
 public class TouchpadVisualizerPreferenceControllerTest {
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Mock
     private PreferenceScreen mScreen;
     @Mock
@@ -57,55 +70,52 @@
     }
 
     @Test
+    @EnableFlags({Flags.FLAG_TOUCHPAD_VISUALIZER})
     public void updateState_touchpadVisualizerEnabled_shouldCheckedPreference() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.TOUCHPAD_VISUALIZER, ShowTapsPreferenceController.SETTING_VALUE_ON);
-
+        InputSettings.setTouchpadVisualizer(mContext, true);
         mController.updateState(mPreference);
 
         verify(mPreference).setChecked(true);
     }
 
     @Test
+    @EnableFlags({Flags.FLAG_TOUCHPAD_VISUALIZER})
     public void updateState_touchpadVisualizerDisabled_shouldUncheckedPreference() {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.TOUCHPAD_VISUALIZER,
-                ShowTapsPreferenceController.SETTING_VALUE_OFF);
-
+        InputSettings.setTouchpadVisualizer(mContext, false);
         mController.updateState(mPreference);
 
         verify(mPreference).setChecked(false);
     }
 
     @Test
+    @EnableFlags({Flags.FLAG_TOUCHPAD_VISUALIZER})
     public void onPreferenceChange_preferenceChecked_shouldEnableTouchpadVisualizer() {
         mController.onPreferenceChange(mPreference, true /* new value */);
 
-        final int touchpadVisualizer = Settings.System.getInt(mContext.getContentResolver(),
-                Settings.System.TOUCHPAD_VISUALIZER, -1 /* default */);
+        final boolean touchpadVisualizer = InputSettings.useTouchpadVisualizer(mContext);
 
-        assertThat(touchpadVisualizer).isEqualTo(ShowTapsPreferenceController.SETTING_VALUE_ON);
+        assertThat(touchpadVisualizer).isTrue();
     }
 
     @Test
+    @EnableFlags({Flags.FLAG_TOUCHPAD_VISUALIZER})
     public void onPreferenceChange_preferenceUnchecked_shouldDisableTouchpadVisualizer() {
         mController.onPreferenceChange(mPreference, false /* new value */);
 
-        final int showTapsMode = Settings.System.getInt(mContext.getContentResolver(),
-                Settings.System.TOUCHPAD_VISUALIZER, -1 /* default */);
+        final boolean touchpadVisualizer = InputSettings.useTouchpadVisualizer(mContext);
 
-        assertThat(showTapsMode).isEqualTo(ShowTapsPreferenceController.SETTING_VALUE_OFF);
+        assertThat(touchpadVisualizer).isFalse();
     }
 
     @Test
+    @EnableFlags({Flags.FLAG_TOUCHPAD_VISUALIZER})
     public void onDeveloperOptionsSwitchDisabled_preferenceShouldBeEnabled() {
         mController.onDeveloperOptionsSwitchDisabled();
 
-        final int showTapsMode = Settings.System.getInt(mContext.getContentResolver(),
-                Settings.System.TOUCHPAD_VISUALIZER, -1 /* default */);
+        final boolean touchpadVisualizer = InputSettings.useTouchpadVisualizer(mContext);
 
-        assertThat(showTapsMode).isEqualTo(ShowTapsPreferenceController.SETTING_VALUE_OFF);
+        assertThat(touchpadVisualizer).isFalse();
         verify(mPreference).setEnabled(false);
         verify(mPreference).setChecked(false);
     }
-}
\ No newline at end of file
+}
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 29e9cf9..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;
@@ -52,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;
@@ -82,6 +84,7 @@
 
     private ZenModeAppsLinkPreferenceController mController;
     private CircularIconsPreference mPreference;
+    private CircularIconsView mIconsView;
 
     private Context mContext;
     @Mock
@@ -114,6 +117,8 @@
         // 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);
@@ -273,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
@@ -313,7 +318,7 @@
 
         mController.updateState(mPreference, zenModeWithNone);
 
-        assertThat(mPreference.getLoadedIcons().icons()).hasSize(0);
+        assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(0);
         verifyNoMoreInteractions(mApplicationsState);
         verifyNoMoreInteractions(mSession);
 
@@ -322,7 +327,7 @@
         verify(mApplicationsState).newSession(any(), any());
         verify(mSession).rebuild(any(), any(), anyBoolean());
         mController.mAppSessionCallbacks.onRebuildComplete(appEntries);
-        assertThat(mPreference.getLoadedIcons().icons()).hasSize(1);
+        assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(1);
     }
 
     @Test
@@ -343,11 +348,11 @@
         verify(mApplicationsState).newSession(any(), any());
         verify(mSession).rebuild(any(), any(), anyBoolean());
         mController.mAppSessionCallbacks.onRebuildComplete(appEntries);
-        assertThat(mPreference.getLoadedIcons().icons()).hasSize(1);
+        assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(1);
 
         mController.updateState(mPreference, zenModeWithNone);
 
-        assertThat(mPreference.getLoadedIcons().icons()).hasSize(0);
+        assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(0);
         verify(mSession).deactivateSession();
         verifyNoMoreInteractions(mSession);
         verifyNoMoreInteractions(mApplicationsState);
@@ -356,7 +361,7 @@
         // updateState()) is ignored.
         mController.mAppSessionCallbacks.onRebuildComplete(appEntries);
 
-        assertThat(mPreference.getLoadedIcons().icons()).hasSize(0);
+        assertThat(mIconsView.getDisplayedIcons().icons()).hasSize(0);
     }
 
     @Test
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/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/DataUsagePreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/DataUsagePreferenceControllerTest.kt
index 7124b6a..f4974e9 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/DataUsagePreferenceControllerTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/DataUsagePreferenceControllerTest.kt
@@ -29,7 +29,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.dx.mockito.inline.extended.ExtendedMockito
 import com.android.settings.core.BasePreferenceController.AVAILABLE
-import com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE
 import com.android.settings.datausage.DataUsageUtils
 import com.android.settings.datausage.lib.DataUsageLib
 import com.android.settings.datausage.lib.NetworkCycleDataRepository
@@ -77,7 +76,6 @@
     @Before
     fun setUp() {
         mockSession = ExtendedMockito.mockitoSession()
-            .initMocks(this)
             .spyStatic(DataUsageUtils::class.java)
             .spyStatic(DataUsageLib::class.java)
             .strictness(Strictness.LENIENT)
@@ -101,19 +99,11 @@
     }
 
     @Test
-    fun getAvailabilityStatus_validSubId_returnAvailable() {
+    fun getAvailabilityStatus_returnAvailable() {
         assertThat(controller.availabilityStatus).isEqualTo(AVAILABLE)
     }
 
     @Test
-    fun getAvailabilityStatus_invalidSubId_returnUnsearchable() {
-        val availabilityStatus =
-            controller.getAvailabilityStatus(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
-
-        assertThat(availabilityStatus).isEqualTo(AVAILABLE_UNSEARCHABLE)
-    }
-
-    @Test
     fun handlePreferenceTreeClick_startActivity() = runBlocking {
         val usageData = NetworkUsageData(START_TIME, END_TIME, 1L)
         repository.stub {
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkSettingsSearchIndexTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkSettingsSearchIndexTest.kt
index bf51208..ad50433 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkSettingsSearchIndexTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/MobileNetworkSettingsSearchIndexTest.kt
@@ -30,8 +30,8 @@
 import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingFragment
 import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingKey
 import com.android.settings.spa.search.SpaSearchLandingActivity
+import com.android.settings.spa.search.decodeToSpaSearchLandingKey
 import com.google.common.truth.Truth.assertThat
-import com.google.protobuf.ByteString
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -104,7 +104,7 @@
             searchIndexableData.searchIndexProvider.getDynamicRawDataToIndex(context, true)
         assertThat(dynamicRawDataToIndex).hasSize(1)
         val rawData = dynamicRawDataToIndex[0]
-        val key = SpaSearchLandingKey.parseFrom(ByteString.copyFromUtf8(rawData.key))
+        val key = decodeToSpaSearchLandingKey(rawData.key)
         assertThat(key)
             .isEqualTo(
                 SpaSearchLandingKey.newBuilder()
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/UiccSlotRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/UiccSlotRepositoryTest.kt
new file mode 100644
index 0000000..96aa151
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/UiccSlotRepositoryTest.kt
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.network.telephony
+
+import android.telephony.TelephonyManager
+import android.telephony.UiccPortInfo
+import android.telephony.UiccSlotInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class UiccSlotRepositoryTest {
+
+    private val mockTelephonyManager = mock<TelephonyManager>()
+
+    private val repository = UiccSlotRepository(mockTelephonyManager)
+
+    @Test
+    fun anyRemovablePhysicalSimEnabled_oneSimSlotDeviceActiveEsim_returnsFalse() {
+        mockTelephonyManager.stub {
+            on { uiccSlotsInfo } doReturn
+                arrayOf(
+                    createUiccSlotInfo(
+                        isEuicc = true, isRemovable = false, logicalSlotIdx = 1, isActive = true),
+                )
+        }
+
+        val result = repository.anyRemovablePhysicalSimEnabled()
+
+        assertThat(result).isFalse()
+    }
+
+    @Test
+    fun anyRemovablePhysicalSimEnabled_activeRemovableEsimAndInactivePsim_returnsFalse() {
+        mockTelephonyManager.stub {
+            on { uiccSlotsInfo } doReturn
+                arrayOf(
+                    createUiccSlotInfo(
+                        isEuicc = true, isRemovable = true, logicalSlotIdx = 0, isActive = true),
+                    createUiccSlotInfo(
+                        isEuicc = false, isRemovable = true, logicalSlotIdx = -1, isActive = false),
+                )
+        }
+
+        val result = repository.anyRemovablePhysicalSimEnabled()
+
+        assertThat(result).isFalse()
+    }
+
+    @Test
+    fun anyRemovablePhysicalSimEnabled_activeRemovableEsimAndActivePsim_returnsTrue() {
+        mockTelephonyManager.stub {
+            on { uiccSlotsInfo } doReturn
+                arrayOf(
+                    createUiccSlotInfo(
+                        isEuicc = false, isRemovable = true, logicalSlotIdx = 0, isActive = true),
+                    createUiccSlotInfo(
+                        isEuicc = true, isRemovable = true, logicalSlotIdx = 1, isActive = true),
+                )
+        }
+
+        val result = repository.anyRemovablePhysicalSimEnabled()
+
+        assertThat(result).isTrue()
+    }
+
+    @Test
+    fun anyRemovablePhysicalSimEnabled_inactiveRemovableEsimAndActivePsim_returnsTrue() {
+        mockTelephonyManager.stub {
+            on { uiccSlotsInfo } doReturn
+                arrayOf(
+                    createUiccSlotInfo(
+                        isEuicc = true, isRemovable = true, logicalSlotIdx = -1, isActive = false),
+                    createUiccSlotInfo(
+                        isEuicc = false, isRemovable = true, logicalSlotIdx = 0, isActive = true),
+                )
+        }
+
+        val result = repository.anyRemovablePhysicalSimEnabled()
+
+        assertThat(result).isTrue()
+    }
+
+    @Test
+    fun anyRemovablePhysicalSimEnabled_twoActiveRemovableEsimsAndInactivePsim_returnsFalse() {
+        mockTelephonyManager.stub {
+            on { uiccSlotsInfo } doReturn
+                arrayOf(
+                    createUiccSlotInfoForRemovableEsimMep(
+                        logicalSlotIdx1 = 0,
+                        isActiveEsim1 = true,
+                        logicalSlotIdx2 = 1,
+                        isActiveEsim2 = true,
+                    ),
+                    createUiccSlotInfo(
+                        isEuicc = false, isRemovable = true, logicalSlotIdx = -1, isActive = false),
+                )
+        }
+
+        val result = repository.anyRemovablePhysicalSimEnabled()
+
+        assertThat(result).isFalse()
+    }
+
+    @Test
+    fun anyRemovablePhysicalSimEnabled_oneActiveOneInactiveRemovableEsimActivePsim_returnsTrue() {
+        mockTelephonyManager.stub {
+            on { uiccSlotsInfo } doReturn
+                arrayOf(
+                    createUiccSlotInfoForRemovableEsimMep(
+                        logicalSlotIdx1 = 1,
+                        isActiveEsim1 = true,
+                        logicalSlotIdx2 = -1,
+                        isActiveEsim2 = false,
+                    ),
+                    createUiccSlotInfo(
+                        isEuicc = false, isRemovable = true, logicalSlotIdx = 0, isActive = true),
+                )
+        }
+
+        val result = repository.anyRemovablePhysicalSimEnabled()
+
+        assertThat(result).isTrue()
+    }
+
+    @Test
+    fun anyRemovablePhysicalSimEnabled_activePsim_returnsTrue() {
+        mockTelephonyManager.stub {
+            on { uiccSlotsInfo } doReturn
+                arrayOf(
+                    createUiccSlotInfo(
+                        isEuicc = false, isRemovable = true, logicalSlotIdx = 0, isActive = true),
+                )
+        }
+
+        val result = repository.anyRemovablePhysicalSimEnabled()
+
+        assertThat(result).isTrue()
+    }
+
+    @Test
+    fun anyRemovablePhysicalSimEnabled_inactivePsim_returnsFalse() {
+        mockTelephonyManager.stub {
+            on { uiccSlotsInfo } doReturn
+                arrayOf(
+                    createUiccSlotInfo(
+                        isEuicc = false, isRemovable = true, logicalSlotIdx = -1, isActive = false),
+                )
+        }
+
+        val result = repository.anyRemovablePhysicalSimEnabled()
+
+        assertThat(result).isFalse()
+    }
+
+    @Test
+    fun anyRemovablePhysicalSimEnabled_activeEsimAndActivePsim_returnsTrue() {
+        mockTelephonyManager.stub {
+            on { uiccSlotsInfo } doReturn
+                arrayOf(
+                    createUiccSlotInfo(
+                        isEuicc = false, isRemovable = true, logicalSlotIdx = 0, isActive = true),
+                    createUiccSlotInfo(
+                        isEuicc = true, isRemovable = false, logicalSlotIdx = 1, isActive = true),
+                )
+        }
+
+        val result = repository.anyRemovablePhysicalSimEnabled()
+
+        assertThat(result).isTrue()
+    }
+
+    @Test
+    fun anyRemovablePhysicalSimEnabled_activeEsimAndInactivePsim_returnsFalse() {
+        mockTelephonyManager.stub {
+            on { uiccSlotsInfo } doReturn
+                arrayOf(
+                    createUiccSlotInfo(
+                        isEuicc = false, isRemovable = true, logicalSlotIdx = 0, isActive = false),
+                    createUiccSlotInfo(
+                        isEuicc = true, isRemovable = false, logicalSlotIdx = 1, isActive = true),
+                )
+        }
+
+        val result = repository.anyRemovablePhysicalSimEnabled()
+
+        assertThat(result).isFalse()
+    }
+
+    @Test
+    fun anyRemovablePhysicalSimEnabled_uiccSlotInfoIsNull_returnsFalse() {
+        mockTelephonyManager.stub { on { uiccSlotsInfo } doReturn arrayOf(null) }
+
+        val result = repository.anyRemovablePhysicalSimEnabled()
+
+        assertThat(result).isFalse()
+    }
+
+    private companion object {
+        fun createUiccSlotInfo(
+            isEuicc: Boolean,
+            isRemovable: Boolean,
+            logicalSlotIdx: Int,
+            isActive: Boolean
+        ) =
+            UiccSlotInfo(
+                isEuicc,
+                /* cardId = */ "123",
+                /* cardStateInfo = */ UiccSlotInfo.CARD_STATE_INFO_PRESENT,
+                /* isExtendedApduSupported = */ true,
+                isRemovable,
+                /* portList = */ listOf(
+                    UiccPortInfo(/* iccId= */ "", /* portIndex= */ 0, logicalSlotIdx, isActive),
+                ),
+            )
+
+        fun createUiccSlotInfoForRemovableEsimMep(
+            logicalSlotIdx1: Int,
+            isActiveEsim1: Boolean,
+            logicalSlotIdx2: Int,
+            isActiveEsim2: Boolean,
+        ) =
+            UiccSlotInfo(
+                /* isEuicc = */ true,
+                /* cardId = */ "123",
+                /* cardStateInfo = */ UiccSlotInfo.CARD_STATE_INFO_PRESENT,
+                /* isExtendedApduSupported = */ true,
+                /* isRemovable = */ true,
+                /* portList = */ listOf(
+                    UiccPortInfo(
+                        /* iccId = */ "",
+                        /* portIndex = */ 0,
+                        /* logicalSlotIndex = */ logicalSlotIdx1,
+                        /* isActive = */ isActiveEsim1),
+                    UiccPortInfo(
+                        /* iccId = */ "",
+                        /* portIndex = */ 1,
+                        /* logicalSlotIndex = */ logicalSlotIdx2,
+                        /* isActive = */ isActiveEsim2),
+                ),
+            )
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsFeatureRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsFeatureRepositoryTest.kt
new file mode 100644
index 0000000..3f72b2c
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/ims/ImsFeatureRepositoryTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony.ims
+
+import android.content.Context
+import android.telephony.AccessNetworkConstants
+import android.telephony.ims.feature.MmTelFeature
+import android.telephony.ims.stub.ImsRegistrationImplBase
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@RunWith(AndroidJUnit4::class)
+class ImsFeatureRepositoryTest {
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    private val mockProvisioningRepository = mock<ProvisioningRepository>()
+    private val mockImsMmTelRepository = mock<ImsMmTelRepository>()
+
+    @Test
+    fun isReadyFlow_notProvisioned_returnFalse() = runBlocking {
+        mockProvisioningRepository.stub {
+            onBlocking { imsFeatureProvisionedFlow(SUB_ID, CAPABILITY, TECH) } doReturn
+                flowOf(false)
+        }
+
+        val repository =
+            ImsFeatureRepository(
+                context = context,
+                subId = SUB_ID,
+                provisioningRepository = mockProvisioningRepository,
+            )
+
+        val isReady = repository.isReadyFlow(CAPABILITY, TECH, TRANSPORT_TYPE).first()
+
+        assertThat(isReady).isFalse()
+    }
+
+    @Test
+    fun isReadyFlow_notSupported_returnFalse() = runBlocking {
+        mockImsMmTelRepository.stub {
+            onBlocking { isSupportedFlow(CAPABILITY, TRANSPORT_TYPE) } doReturn flowOf(false)
+        }
+
+        val repository =
+            ImsFeatureRepository(
+                context = context,
+                subId = SUB_ID,
+                imsMmTelRepository = mockImsMmTelRepository,
+            )
+
+        val isReady = repository.isReadyFlow(CAPABILITY, TECH, TRANSPORT_TYPE).first()
+
+        assertThat(isReady).isFalse()
+    }
+
+    @Test
+    fun isReadyFlow_provisionedAndSupported_returnFalse() = runBlocking {
+        mockProvisioningRepository.stub {
+            onBlocking { imsFeatureProvisionedFlow(SUB_ID, CAPABILITY, TECH) } doReturn flowOf(true)
+        }
+        mockImsMmTelRepository.stub {
+            onBlocking { isSupportedFlow(CAPABILITY, TRANSPORT_TYPE) } doReturn flowOf(true)
+        }
+
+        val repository =
+            ImsFeatureRepository(
+                context = context,
+                subId = SUB_ID,
+                provisioningRepository = mockProvisioningRepository,
+                imsMmTelRepository = mockImsMmTelRepository,
+            )
+
+        val isReady = repository.isReadyFlow(CAPABILITY, TECH, TRANSPORT_TYPE).first()
+
+        assertThat(isReady).isTrue()
+    }
+
+    private companion object {
+        const val SUB_ID = 10
+        const val CAPABILITY = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE
+        const val TECH = ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN
+        const val TRANSPORT_TYPE = AccessNetworkConstants.TRANSPORT_TYPE_WLAN
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/wificalling/WifiCallingRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/wificalling/WifiCallingRepositoryTest.kt
index 0144f66..f0a23eb 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/wificalling/WifiCallingRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/wificalling/WifiCallingRepositoryTest.kt
@@ -55,7 +55,8 @@
         on { getWiFiCallingMode(any()) } doReturn ImsMmTelManager.WIFI_MODE_UNKNOWN
     }
 
-    private val repository = WifiCallingRepository(context, SUB_ID, mockImsMmTelRepository)
+    private val repository =
+        WifiCallingRepository(context, SUB_ID, imsMmTelRepository = mockImsMmTelRepository)
 
     @Test
     fun getWiFiCallingMode_roamingAndNotUseWfcHomeModeForRoaming_returnRoamingSetting() {
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/InstantAppDomainsPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/InstantAppDomainsPreferenceTest.kt
index e5f8fb0..a952763 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/InstantAppDomainsPreferenceTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/InstantAppDomainsPreferenceTest.kt
@@ -22,9 +22,9 @@
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.assertIsEnabled
 import androidx.compose.ui.test.assertIsNotDisplayed
 import androidx.compose.ui.test.hasText
+import androidx.compose.ui.test.isEnabled
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.onRoot
@@ -96,10 +96,9 @@
     fun title_displayed() {
         setContent()
 
-        composeTestRule
-            .onNodeWithText(context.getString(R.string.app_launch_supported_domain_urls_title))
-            .assertIsDisplayed()
-            .assertIsEnabled()
+        composeTestRule.waitUntilExists(
+            hasText(context.getString(R.string.app_launch_supported_domain_urls_title)) and
+                isEnabled())
     }
 
     @Test
diff --git a/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingLabelSimTest.kt b/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingLabelSimTest.kt
index ad2ba55..1910153 100644
--- a/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingLabelSimTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingLabelSimTest.kt
@@ -19,23 +19,17 @@
 import android.content.Context
 import android.telephony.SubscriptionInfo
 import android.telephony.SubscriptionManager
-import android.view.KeyEvent.ACTION_DOWN
-import android.view.KeyEvent.KEYCODE_FORWARD_DEL
 import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.ui.input.key.KeyEvent
-import androidx.compose.ui.input.key.NativeKeyEvent
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.semantics.SemanticsProperties
 import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.assertIsEnabled
 import androidx.compose.ui.test.assertIsNotEnabled
 import androidx.compose.ui.test.hasText
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
-import androidx.compose.ui.test.performKeyPress
 import androidx.compose.ui.test.performTextClearance
 import androidx.compose.ui.test.performTextInput
 import androidx.lifecycle.testing.TestLifecycleOwner
@@ -80,7 +74,6 @@
         on { targetSubInfo }.doReturn(null)
         on { availableSubInfoList }.doReturn(listOf())
         on { activeSubInfoList }.doReturn(listOf())
-        on { slotInfoList }.doReturn(listOf())
         on { uiccCardInfoList }.doReturn(listOf())
 
         on { targetPrimarySimCalls }.doReturn(PRIMARY_SIM_ASK_EVERY_TIME)
diff --git a/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingPageProviderTest.kt b/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingPageProviderTest.kt
index 1395ed4..fcd3e24 100644
--- a/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingPageProviderTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingPageProviderTest.kt
@@ -45,7 +45,6 @@
         on { targetSubInfo }.doReturn(null)
         on { availableSubInfoList }.doReturn(listOf())
         on { activeSubInfoList }.doReturn(listOf())
-        on { slotInfoList }.doReturn(listOf())
         on { uiccCardInfoList }.doReturn(listOf())
 
         on { targetPrimarySimCalls }.doReturn(PRIMARY_SIM_ASK_EVERY_TIME)
diff --git a/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingPrimarySimTest.kt b/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingPrimarySimTest.kt
index d9c762d..ef86ac5 100644
--- a/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingPrimarySimTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingPrimarySimTest.kt
@@ -46,7 +46,6 @@
         on { targetSubInfo }.doReturn(null)
         on { availableSubInfoList }.doReturn(listOf())
         on { activeSubInfoList }.doReturn(listOf())
-        on { slotInfoList }.doReturn(listOf())
         on { uiccCardInfoList }.doReturn(listOf())
 
         on { targetPrimarySimCalls }.doReturn(PRIMARY_SIM_ASK_EVERY_TIME)
diff --git a/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingSelectSimTest.kt b/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingSelectSimTest.kt
index 5b7778e..385bc42 100644
--- a/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingSelectSimTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/network/SimOnboardingSelectSimTest.kt
@@ -69,7 +69,6 @@
         on { targetSubInfo }.doReturn(null)
         on { availableSubInfoList }.doReturn(listOf())
         on { activeSubInfoList }.doReturn(listOf())
-        on { slotInfoList }.doReturn(listOf())
         on { uiccCardInfoList }.doReturn(listOf())
 
         on { targetPrimarySimCalls }.doReturn(PRIMARY_SIM_ASK_EVERY_TIME)
diff --git a/tests/spa_unit/src/com/android/settings/spa/search/SpaSearchLandingActivityTest.kt b/tests/spa_unit/src/com/android/settings/spa/search/SpaSearchLandingActivityTest.kt
index 7410bb4..6fb4b84 100644
--- a/tests/spa_unit/src/com/android/settings/spa/search/SpaSearchLandingActivityTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/search/SpaSearchLandingActivityTest.kt
@@ -52,7 +52,7 @@
                 .setSpaPage(SpaSearchLandingSpaPage.newBuilder().setDestination(DESTINATION))
                 .build()
 
-        SpaSearchLandingActivity.tryLaunch(context, key.toByteString().toStringUtf8())
+        SpaSearchLandingActivity.tryLaunch(context, key.encodeToString())
 
         verify(context).startActivity(argThat { getStringExtra(KEY_DESTINATION) == DESTINATION })
     }
@@ -70,7 +70,7 @@
                             BundleValue.newBuilder().setIntValue(ARGUMENT_VALUE).build()))
                 .build()
 
-        SpaSearchLandingActivity.tryLaunch(context, key.toByteString().toStringUtf8())
+        SpaSearchLandingActivity.tryLaunch(context, key.encodeToString())
 
         val intent = argumentCaptor<Intent> { verify(context).startActivity(capture()) }.firstValue
         assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
diff --git a/tests/spa_unit/src/com/android/settings/spa/search/SpaSearchLandingKeyExtTest.kt b/tests/spa_unit/src/com/android/settings/spa/search/SpaSearchLandingKeyExtTest.kt
new file mode 100644
index 0000000..f4a9ce6
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/search/SpaSearchLandingKeyExtTest.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.spa.search
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.spa.SpaSearchLanding.BundleValue
+import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingFragment
+import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingKey
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SpaSearchLandingKeyExtTest {
+
+    @Test
+    fun encodeToString_thenDecode_shouldDecodeCorrectly() {
+        val encoded = KEY.encodeToString()
+
+        val decoded = decodeToSpaSearchLandingKey(encoded)
+
+        assertThat(decoded).isEqualTo(KEY)
+    }
+
+    @Test
+    fun decodeToSpaSearchLandingKey_badString_shouldReturnNull() {
+        val decoded = decodeToSpaSearchLandingKey("bad")
+
+        assertThat(decoded).isNull()
+    }
+
+    private companion object {
+        val KEY: SpaSearchLandingKey =
+            SpaSearchLandingKey.newBuilder()
+                .setFragment(
+                    SpaSearchLandingFragment.newBuilder()
+                        .setFragmentName("Destination")
+                        .setPreferenceKey("preference_key")
+                        .putArguments(
+                            "argument_key", BundleValue.newBuilder().setIntValue(123).build()))
+                .build()
+    }
+}
diff --git a/tests/spa_unit/src/com/android/settings/spa/search/SpaSearchRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/spa/search/SpaSearchRepositoryTest.kt
index c38f22f..f97abe4 100644
--- a/tests/spa_unit/src/com/android/settings/spa/search/SpaSearchRepositoryTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/search/SpaSearchRepositoryTest.kt
@@ -23,7 +23,6 @@
 import com.android.settings.spa.search.SpaSearchRepository.Companion.createSearchIndexableData
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.google.common.truth.Truth.assertThat
-import com.google.protobuf.ByteString
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.mock
@@ -46,7 +45,7 @@
         assertThat(searchIndexableData.targetClass).isEqualTo(pageProvider::class.java)
         assertThat(dynamicRawDataToIndex).hasSize(1)
         val rawData = dynamicRawDataToIndex[0]
-        val key = SpaSearchLandingKey.parseFrom(ByteString.copyFromUtf8(rawData.key))
+        val key = decodeToSpaSearchLandingKey(rawData.key)
         assertThat(key)
             .isEqualTo(
                 SpaSearchLandingKey.newBuilder()
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/network/UiccSlotUtilTest.java b/tests/unit/src/com/android/settings/network/UiccSlotUtilTest.java
index 2aa1573..83a6fed 100644
--- a/tests/unit/src/com/android/settings/network/UiccSlotUtilTest.java
+++ b/tests/unit/src/com/android/settings/network/UiccSlotUtilTest.java
@@ -38,8 +38,6 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import com.google.common.collect.ImmutableList;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -47,7 +45,6 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
@@ -82,25 +79,6 @@
     }
 
     @Test
-    public void getSlotInfos_oneSimSlotDevice_returnTheCorrectSlotInfoList() {
-        when(mTelephonyManager.getUiccSlotsInfo()).thenReturn(oneSimSlotDeviceActivePsim());
-        ImmutableList<UiccSlotInfo> testUiccSlotInfos =
-                UiccSlotUtil.getSlotInfos(mTelephonyManager);
-
-        assertThat(testUiccSlotInfos.size()).isEqualTo(1);
-    }
-
-    @Test
-    public void getSlotInfos_twoSimSlotsDevice_returnTheCorrectSlotInfoList() {
-        when(mTelephonyManager.getUiccSlotsInfo()).thenReturn(
-                twoSimSlotsDeviceActivePsimActiveEsim());
-        ImmutableList<UiccSlotInfo> testUiccSlotInfos =
-                UiccSlotUtil.getSlotInfos(mTelephonyManager);
-
-        assertThat(testUiccSlotInfos.size()).isEqualTo(2);
-    }
-
-    @Test
     public void getEsimSlotId_twoSimSlotsDeviceAndEsimIsSlot0_returnTheCorrectEsimSlot() {
         when(mTelephonyManager.getUiccSlotsInfo()).thenReturn(
                 twoSimSlotsDeviceActiveEsimActivePsim());
@@ -643,105 +621,7 @@
         assertThat(testExcludedLogicalSlotIndex).isEqualTo(verifyExcludedLogicalSlotIndex);
     }
 
-    @Test
-    public void isRemovableSimEnabled_noPsim_returnsFalse() {
-        when(mTelephonyManager.getUiccSlotsInfo()).thenReturn(
-                oneSimSlotDeviceActiveEsim());
 
-        boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager);
-
-        assertThat(testSlot).isFalse();
-    }
-
-    @Test
-    public void isRemovableSimEnabled_activeRemovableEsimAndInactivePsim_returnsFalse() {
-        when(mTelephonyManager.getUiccSlotsInfo()).thenReturn(
-                twoSimSlotsDeviceActiveRemovableEsimInactivePsim());
-
-        boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager);
-
-        assertThat(testSlot).isFalse();
-    }
-
-    @Test
-    public void isRemovableSimEnabled_activeRemovableEsimAndActivePsim_returnsTrue() {
-        when(mTelephonyManager.getUiccSlotsInfo()).thenReturn(
-                twoSimSlotsDeviceActivePsimActiveRemovableEsim());
-
-        boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager);
-
-        assertThat(testSlot).isTrue();
-    }
-
-    @Test
-    public void isRemovableSimEnabled_inactiveRemovableEsimAndActivePsim_returnsTrue() {
-        when(mTelephonyManager.getUiccSlotsInfo()).thenReturn(
-                twoSimSlotsDeviceInactiveRemovableEsimActivePsim());
-
-        boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager);
-
-        assertThat(testSlot).isTrue();
-    }
-
-    @Test
-    public void isRemovableSimEnabled_twoActiveRemovableEsimsAndInactivePsim_returnsFalse() {
-        when(mTelephonyManager.getUiccSlotsInfo()).thenReturn(
-                twoSimSlotsDeviceTwoActiveRemovableEsimsInactivePsim());
-
-        boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager);
-
-        assertThat(testSlot).isFalse();
-    }
-
-    @Test
-    public void isRemovableSimEnabled_oneActiveOneInactiveRemovableEsimActivePsim_returnsTrue() {
-        when(mTelephonyManager.getUiccSlotsInfo()).thenReturn(
-                twoSimSlotsDeviceOneActiveOneInactiveRemovableEsimsActivePsim());
-
-        boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager);
-
-        assertThat(testSlot).isTrue();
-    }
-
-    @Test
-    public void isRemovableSimEnabled_activePsim_returnsTrue() {
-        when(mTelephonyManager.getUiccSlotsInfo()).thenReturn(
-                oneSimSlotDeviceActivePsim());
-
-        boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager);
-
-        assertThat(testSlot).isTrue();
-    }
-
-    @Test
-    public void isRemovableSimEnabled_inactivePsim_returnsFalse() {
-        when(mTelephonyManager.getUiccSlotsInfo()).thenReturn(
-                oneSimSlotDeviceinactivePsim());
-
-        boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager);
-
-        assertThat(testSlot).isFalse();
-    }
-
-    @Test
-    public void isRemovableSimEnabled_activeEsimAndActivePsim_returnsTrue() {
-        when(mTelephonyManager.getUiccSlotsInfo()).thenReturn(
-                twoSimSlotsDeviceActivePsimActiveEsim());
-
-        boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager);
-
-        assertThat(testSlot).isTrue();
-    }
-
-    @Test
-    public void isRemovableSimEnabled_activeEsimAndInactivePsim_returnsFalse() {
-        when(mTelephonyManager.getUiccSlotsInfo()).thenReturn(
-                twoSimSlotsDeviceInactivePsimActiveEsim());
-
-        boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager);
-
-        assertThat(testSlot).isFalse();
-    }
 
     @Test
     public void performSwitchToSlot_setSimSlotMapping() throws UiccSlotsException {
@@ -856,13 +736,6 @@
         return slotMap;
     }
 
-    private List<UiccSlotMapping> createUiccSlotMappingSsModeEsimPort1Active() {
-        List<UiccSlotMapping> slotMap = new ArrayList<>();
-        slotMap.add(new UiccSlotMapping(1, ESIM_PHYSICAL_SLOT, 0));
-
-        return slotMap;
-    }
-
     private List<UiccSlotMapping> createUiccSlotMappingPsimAndPort0() {
         List<UiccSlotMapping> slotMap = new ArrayList<>();
         slotMap.add(new UiccSlotMapping(0, PSIM_PHYSICAL_SLOT, 0));
@@ -915,14 +788,6 @@
         return new UiccSlotInfo[]{createUiccSlotInfo(false, true, 0, true)};
     }
 
-    private UiccSlotInfo[] oneSimSlotDeviceActiveEsim() {
-        return new UiccSlotInfo[]{createUiccSlotInfo(true, false, 1, true)};
-    }
-
-    private UiccSlotInfo[] oneSimSlotDeviceinactivePsim() {
-        return new UiccSlotInfo[]{createUiccSlotInfo(false, true, -1, false)};
-    }
-
     private UiccSlotInfo[] twoSimSlotsDeviceActivePsimActiveEsim() {
         return new UiccSlotInfo[]{
                 createUiccSlotInfo(false, true, 0, true),
@@ -941,61 +806,12 @@
                 createUiccSlotInfo(true, true, 1, true)};
     }
 
-    private UiccSlotInfo[] twoSimSlotsDeviceActiveRemovableEsimInactivePsim() {
-        return new UiccSlotInfo[]{
-                createUiccSlotInfo(true, true, 0, true),
-                createUiccSlotInfo(false, true, -1, false)};
-    }
-
-    private UiccSlotInfo[] twoSimSlotsDeviceInactiveRemovableEsimActivePsim() {
-        return new UiccSlotInfo[]{
-                createUiccSlotInfo(true, true, -1, false),
-                createUiccSlotInfo(false, true, 0, true)};
-    }
-
-    private UiccSlotInfo[] twoSimSlotsDeviceTwoActiveRemovableEsimsInactivePsim() {
-        return new UiccSlotInfo[]{
-                createUiccSlotInfoForRemovableEsimMep(0, true, 1, true),
-                createUiccSlotInfo(false, true, -1, false)};
-    }
-
-    private UiccSlotInfo[] twoSimSlotsDeviceOneActiveOneInactiveRemovableEsimsActivePsim() {
-        return new UiccSlotInfo[]{
-                createUiccSlotInfoForRemovableEsimMep(1, true, -1, false),
-                createUiccSlotInfo(false, true, 0, true)};
-    }
-
     private UiccSlotInfo[] twoSimSlotsDeviceActiveEsimActivePsim() {
         return new UiccSlotInfo[]{
                 createUiccSlotInfo(true, false, 0, true),
                 createUiccSlotInfo(false, true, 1, true)};
     }
 
-    private UiccSlotInfo[] twoSimSlotsDeviceTwoActiveEsims() {
-        // device supports MEP, so device can enable two esims.
-        // If device has psim slot, the UiccSlotInfo of psim always be in UiccSlotInfo[].
-        return new UiccSlotInfo[]{
-                createUiccSlotInfo(false, true, -1, true),
-                createUiccSlotInfoForEsimMep(0, true, 1, true)};
-    }
-
-    private UiccSlotInfo[] twoSimSlotsDeviceActivePsimInactiveEsim() {
-        return new UiccSlotInfo[]{
-                createUiccSlotInfo(false, true, 0, true),
-                createUiccSlotInfo(true, false, -1, false)};
-    }
-
-    private UiccSlotInfo[] twoSimSlotsDeviceInactivePsimActiveEsim() {
-        return new UiccSlotInfo[]{
-                createUiccSlotInfo(false, true, 0, false),
-                createUiccSlotInfo(true, false, 1, true)};
-    }
-
-    private UiccSlotInfo[] twoSimSlotsDeviceNoInsertPsimActiveEsim() {
-        return new UiccSlotInfo[]{
-                createUiccSlotInfo(false, true, -1, false),
-                createUiccSlotInfo(true, false, 1, true)};
-    }
     //ToDo: add more cases.
 
     private UiccSlotInfo createUiccSlotInfo(boolean isEuicc, boolean isRemovable,
@@ -1011,36 +827,4 @@
                                 logicalSlotIdx /* logicalSlotIdx */, isActive /* isActive */))
         );
     }
-
-    private UiccSlotInfo createUiccSlotInfoForEsimMep(int logicalSlotIdx1, boolean isActiveEsim1,
-            int logicalSlotIdx2, boolean isActiveEsim2) {
-        return new UiccSlotInfo(
-                true, /* isEuicc */
-                "123", /* cardId */
-                CARD_STATE_INFO_PRESENT, /* cardStateInfo */
-                true, /* isExtendApduSupported */
-                false, /* isRemovable */
-                Arrays.asList(
-                        new UiccPortInfo("" /* iccId */, 0 /* portIdx */,
-                                logicalSlotIdx1 /* logicalSlotIdx */, isActiveEsim1 /* isActive */),
-                        new UiccPortInfo("" /* iccId */, 1 /* portIdx */,
-                                logicalSlotIdx2 /* logicalSlotIdx */,
-                                isActiveEsim2 /* isActive */)));
-    }
-
-    private UiccSlotInfo createUiccSlotInfoForRemovableEsimMep(int logicalSlotIdx1,
-            boolean isActiveEsim1, int logicalSlotIdx2, boolean isActiveEsim2) {
-        return new UiccSlotInfo(
-                true, /* isEuicc */
-                "123", /* cardId */
-                CARD_STATE_INFO_PRESENT, /* cardStateInfo */
-                true, /* isExtendApduSupported */
-                true, /* isRemovable */
-                Arrays.asList(
-                        new UiccPortInfo("" /* iccId */, 0 /* portIdx */,
-                                logicalSlotIdx1 /* logicalSlotIdx */, isActiveEsim1 /* isActive */),
-                        new UiccPortInfo("" /* iccId */, 1 /* portIdx */,
-                                logicalSlotIdx2 /* logicalSlotIdx */,
-                                isActiveEsim2 /* isActive */)));
-    }
 }
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();