Reland sfps enroll improvement feature with feature provider

Prior cl, ag/24720067, was reverted due to platinum failed,
this cl fixes the fails (NPE on UDFPS).

Bug: 288155127
Bug: 305132251
Test: Manually check on Lynx and Felix
Test: atest BiometricsE2eTests:FingerprintEnrollSuccessTest
Test: abtd for platinum, see b/305048300#comment14
Change-Id: I90f4ea14853edf03abd8ffe0b7874894eb2d4f81
diff --git a/aconfig/Android.bp b/aconfig/Android.bp
index 6daa90c..7b571e0 100644
--- a/aconfig/Android.bp
+++ b/aconfig/Android.bp
@@ -12,6 +12,7 @@
         "settings_experience_flag_declarations.aconfig",
         "settings_onboarding_experience_flag_declarations.aconfig",
         "settings_telephony_flag_declarations.aconfig",
+        "settings_biometrics_integration_declarations.aconfig",
     ],
 }
 
diff --git a/aconfig/settings_biometrics_integration_declarations.aconfig b/aconfig/settings_biometrics_integration_declarations.aconfig
new file mode 100644
index 0000000..529e126
--- /dev/null
+++ b/aconfig/settings_biometrics_integration_declarations.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.settings.flags"
+
+flag {
+  name: "sfps_enroll_refinement"
+  namespace: "biometrics_integration"
+  description: "This flag controls whether the sfps pause enrollment feature should be enabled"
+  bug: "288155127"
+}
+
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
index be2a948..063d55d 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
@@ -68,10 +68,13 @@
 import com.android.settings.biometrics.BiometricEnrollSidecar;
 import com.android.settings.biometrics.BiometricUtils;
 import com.android.settings.biometrics.BiometricsEnrollEnrolling;
+import com.android.settings.biometrics.fingerprint.feature.SfpsEnrollmentFeature;
 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.display.DisplayDensityUtils;
 
 import com.airbnb.lottie.LottieAnimationView;
+import com.airbnb.lottie.LottieComposition;
 import com.airbnb.lottie.LottieCompositionFactory;
 import com.airbnb.lottie.LottieProperty;
 import com.airbnb.lottie.model.KeyPath;
@@ -84,6 +87,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.List;
+import java.util.function.Function;
 
 /**
  * Activity which handles the actual enrolling for fingerprint.
@@ -99,27 +103,22 @@
 
     private static final int PROGRESS_BAR_MAX = 10000;
 
-    private static final int STAGE_UNKNOWN = -1;
+    public static final int STAGE_UNKNOWN = -1;
     private static final int STAGE_CENTER = 0;
     private static final int STAGE_GUIDED = 1;
     private static final int STAGE_FINGERTIP = 2;
     private static final int STAGE_LEFT_EDGE = 3;
     private static final int STAGE_RIGHT_EDGE = 4;
 
-    @VisibleForTesting
-    protected static final int SFPS_STAGE_NO_ANIMATION = 0;
+    public static final int SFPS_STAGE_NO_ANIMATION = 0;
 
-    @VisibleForTesting
-    protected static final int SFPS_STAGE_CENTER = 1;
+    public static final int SFPS_STAGE_CENTER = 1;
 
-    @VisibleForTesting
-    protected static final int SFPS_STAGE_FINGERTIP = 2;
+    public static final int SFPS_STAGE_FINGERTIP = 2;
 
-    @VisibleForTesting
-    protected static final int SFPS_STAGE_LEFT_EDGE = 3;
+    public static final int SFPS_STAGE_LEFT_EDGE = 3;
 
-    @VisibleForTesting
-    protected static final int SFPS_STAGE_RIGHT_EDGE = 4;
+    public static final int SFPS_STAGE_RIGHT_EDGE = 4;
 
     @IntDef({STAGE_UNKNOWN, STAGE_CENTER, STAGE_GUIDED, STAGE_FINGERTIP, STAGE_LEFT_EDGE,
             STAGE_RIGHT_EDGE})
@@ -196,6 +195,9 @@
     private OrientationEventListener mOrientationEventListener;
     private int mPreviousRotation = 0;
 
+    @NonNull
+    private SfpsEnrollmentFeature mSfpsEnrollmentFeature = new EmptySfpsEnrollmentFeature();
+
     @VisibleForTesting
     protected boolean shouldShowLottie() {
         DisplayDensityUtils displayDensity = new DisplayDensityUtils(getApplicationContext());
@@ -244,6 +246,8 @@
             setContentView(layout);
             setDescriptionText(R.string.security_settings_udfps_enroll_start_message);
         } else if (mCanAssumeSfps) {
+            mSfpsEnrollmentFeature = FeatureFactory.getFeatureFactory()
+                    .getFingerprintFeatureProvider().getSfpsEnrollmentFeature();
             setContentView(R.layout.sfps_enroll_enrolling);
             setHelpAnimation();
         } else {
@@ -599,7 +603,8 @@
         }
         switch (getCurrentSfpsStage()) {
             case SFPS_STAGE_NO_ANIMATION:
-                setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title);
+                setHeaderText(mSfpsEnrollmentFeature
+                        .getFeaturedStageHeaderResource(SFPS_STAGE_NO_ANIMATION));
                 if (!mHaveShownSfpsNoAnimationLottie && mIllustrationLottie != null) {
                     mHaveShownSfpsNoAnimationLottie = true;
                     mIllustrationLottie.setContentDescription(
@@ -608,39 +613,48 @@
                                     0
                             )
                     );
-                    configureEnrollmentStage(R.raw.sfps_lottie_no_animation);
+                    configureEnrollmentStage(mSfpsEnrollmentFeature
+                            .getSfpsEnrollLottiePerStage(SFPS_STAGE_NO_ANIMATION));
                 }
                 break;
 
             case SFPS_STAGE_CENTER:
-                setHeaderText(R.string.security_settings_sfps_enroll_finger_center_title);
+                setHeaderText(mSfpsEnrollmentFeature
+                        .getFeaturedStageHeaderResource(SFPS_STAGE_CENTER));
                 if (!mHaveShownSfpsCenterLottie && mIllustrationLottie != null) {
                     mHaveShownSfpsCenterLottie = true;
-                    configureEnrollmentStage(R.raw.sfps_lottie_pad_center);
+                    configureEnrollmentStage(mSfpsEnrollmentFeature
+                            .getSfpsEnrollLottiePerStage(SFPS_STAGE_CENTER));
                 }
                 break;
 
             case SFPS_STAGE_FINGERTIP:
-                setHeaderText(R.string.security_settings_sfps_enroll_fingertip_title);
+                setHeaderText(mSfpsEnrollmentFeature
+                        .getFeaturedStageHeaderResource(SFPS_STAGE_FINGERTIP));
                 if (!mHaveShownSfpsTipLottie && mIllustrationLottie != null) {
                     mHaveShownSfpsTipLottie = true;
-                    configureEnrollmentStage(R.raw.sfps_lottie_tip);
+                    configureEnrollmentStage(mSfpsEnrollmentFeature
+                            .getSfpsEnrollLottiePerStage(SFPS_STAGE_FINGERTIP));
                 }
                 break;
 
             case SFPS_STAGE_LEFT_EDGE:
-                setHeaderText(R.string.security_settings_sfps_enroll_left_edge_title);
+                setHeaderText(mSfpsEnrollmentFeature
+                        .getFeaturedStageHeaderResource(SFPS_STAGE_LEFT_EDGE));
                 if (!mHaveShownSfpsLeftEdgeLottie && mIllustrationLottie != null) {
                     mHaveShownSfpsLeftEdgeLottie = true;
-                    configureEnrollmentStage(R.raw.sfps_lottie_left_edge);
+                    configureEnrollmentStage(mSfpsEnrollmentFeature
+                            .getSfpsEnrollLottiePerStage(SFPS_STAGE_LEFT_EDGE));
                 }
                 break;
 
             case SFPS_STAGE_RIGHT_EDGE:
-                setHeaderText(R.string.security_settings_sfps_enroll_right_edge_title);
+                setHeaderText(mSfpsEnrollmentFeature
+                        .getFeaturedStageHeaderResource(SFPS_STAGE_RIGHT_EDGE));
                 if (!mHaveShownSfpsRightEdgeLottie && mIllustrationLottie != null) {
                     mHaveShownSfpsRightEdgeLottie = true;
-                    configureEnrollmentStage(R.raw.sfps_lottie_right_edge);
+                    configureEnrollmentStage(mSfpsEnrollmentFeature
+                            .getSfpsEnrollLottiePerStage(SFPS_STAGE_RIGHT_EDGE));
                 }
                 break;
 
@@ -665,11 +679,16 @@
             setDescriptionText("");
         }
         LottieCompositionFactory.fromRawRes(this, lottie)
-                .addListener((c) -> {
-                    mIllustrationLottie.setComposition(c);
-                    mIllustrationLottie.setVisibility(View.VISIBLE);
-                    mIllustrationLottie.playAnimation();
-                });
+                .addListener((c) -> onLottieComposition(mIllustrationLottie, c));
+    }
+
+    private void onLottieComposition(LottieAnimationView view, LottieComposition composition) {
+        if (view == null || composition == null) {
+            return;
+        }
+        view.setComposition(composition);
+        view.setVisibility(View.VISIBLE);
+        view.playAnimation();
     }
 
     @EnrollStage
@@ -699,17 +718,8 @@
         }
 
         final int progressSteps = mSidecar.getEnrollmentSteps() - mSidecar.getEnrollmentRemaining();
-        if (progressSteps < getStageThresholdSteps(0)) {
-            return SFPS_STAGE_NO_ANIMATION;
-        } else if (progressSteps < getStageThresholdSteps(1)) {
-            return SFPS_STAGE_CENTER;
-        } else if (progressSteps < getStageThresholdSteps(2)) {
-            return SFPS_STAGE_FINGERTIP;
-        } else if (progressSteps < getStageThresholdSteps(3)) {
-            return SFPS_STAGE_LEFT_EDGE;
-        } else {
-            return SFPS_STAGE_RIGHT_EDGE;
-        }
+        return mSfpsEnrollmentFeature
+                .getCurrentSfpsEnrollStage(progressSteps, this::getStageThresholdSteps);
     }
 
     private boolean isStageHalfCompleted() {
@@ -740,22 +750,31 @@
             Log.w(TAG, "getStageThresholdSteps: Enrollment not started yet");
             return 1;
         }
-        return Math.round(mSidecar.getEnrollmentSteps()
-                * mFingerprintManager.getEnrollStageThreshold(index));
+        final float threshold = mCanAssumeSfps
+                ? mSfpsEnrollmentFeature.getEnrollStageThreshold(this, index)
+                : mFingerprintManager.getEnrollStageThreshold(index);
+        return Math.round(mSidecar.getEnrollmentSteps() * threshold);
     }
 
     @Override
     public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
-        if (!TextUtils.isEmpty(helpString)) {
+        final CharSequence featuredString = mCanAssumeSfps
+                ? mSfpsEnrollmentFeature.getFeaturedVendorString(this, helpMsgId, helpString)
+                : helpString;
+
+        if (!TextUtils.isEmpty(featuredString)) {
             if (!(mCanAssumeUdfps || mCanAssumeSfps)) {
                 mErrorText.removeCallbacks(mTouchAgainRunnable);
             }
-            showError(helpString);
+            showError(featuredString);
 
             if (mUdfpsEnrollHelper != null) mUdfpsEnrollHelper.onEnrollmentHelp();
         }
 
         dismissTouchDialogIfSfps();
+        if (mCanAssumeSfps) {
+            mSfpsEnrollmentFeature.handleOnEnrollmentHelp(helpMsgId, featuredString, () -> this);
+        }
     }
 
     @Override
@@ -1170,4 +1189,28 @@
             return SettingsEnums.DIALOG_FINGERPRINT_ICON_TOUCH;
         }
     }
-}
\ No newline at end of file
+
+    private static class EmptySfpsEnrollmentFeature implements SfpsEnrollmentFeature {
+        private final String exceptionStr = "Assume sfps but no SfpsEnrollmentFeature impl.";
+
+        @Override
+        public int getCurrentSfpsEnrollStage(int progressSteps, Function<Integer, Integer> mapper) {
+            throw new IllegalStateException(exceptionStr);
+        }
+
+        @Override
+        public int getFeaturedStageHeaderResource(int stage) {
+            throw new IllegalStateException(exceptionStr);
+        }
+
+        @Override
+        public int getSfpsEnrollLottiePerStage(int stage) {
+            throw new IllegalStateException(exceptionStr);
+        }
+
+        @Override
+        public float getEnrollStageThreshold(@NonNull Context context, int index) {
+            throw new IllegalStateException(exceptionStr);
+        }
+    }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProvider.java b/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProvider.java
new file mode 100644
index 0000000..906f95a
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProvider.java
@@ -0,0 +1,27 @@
+/*
+ * 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.fingerprint;
+
+import com.android.settings.biometrics.fingerprint.feature.SfpsEnrollmentFeature;
+
+public interface FingerprintFeatureProvider {
+    /**
+     * Gets the feature implementation of SFPS enrollment.
+     * @return the feature implementation
+     */
+    SfpsEnrollmentFeature getSfpsEnrollmentFeature();
+}
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProviderImpl.java b/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProviderImpl.java
new file mode 100644
index 0000000..9745ca3
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProviderImpl.java
@@ -0,0 +1,36 @@
+/*
+ * 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.fingerprint;
+
+import androidx.annotation.Nullable;
+
+import com.android.settings.biometrics.fingerprint.feature.SfpsEnrollmentFeature;
+import com.android.settings.biometrics.fingerprint.feature.SfpsEnrollmentFeatureImpl;
+
+public class FingerprintFeatureProviderImpl implements FingerprintFeatureProvider {
+
+    @Nullable
+    private SfpsEnrollmentFeature mSfpsEnrollmentFeatureImpl = null;
+
+    @Override
+    public SfpsEnrollmentFeature getSfpsEnrollmentFeature() {
+        if (mSfpsEnrollmentFeatureImpl == null) {
+            mSfpsEnrollmentFeatureImpl = new SfpsEnrollmentFeatureImpl();
+        }
+        return mSfpsEnrollmentFeatureImpl;
+    }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeature.java b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeature.java
new file mode 100644
index 0000000..a1a18e5
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeature.java
@@ -0,0 +1,79 @@
+/*
+ * 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.fingerprint.feature;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public interface SfpsEnrollmentFeature {
+
+    /**
+     * Gets current SFPS enrollment stage.
+     * @param progressSteps current step of enrollment
+     * @param mapper a mapper to map each stage to its threshold
+     * @return current enrollment stage
+     */
+    int getCurrentSfpsEnrollStage(int progressSteps, Function<Integer, Integer> mapper);
+
+    /**
+     * Gets the vendor string by feature.
+     * @param context Context
+     * @param id An integer identifying the error message
+     * @param msg A human-readable string that can be shown in UI
+     * @return A human-readable string of specific feature
+     */
+    default CharSequence getFeaturedVendorString(Context context, int id, CharSequence msg) {
+        return msg;
+    }
+
+    /**
+     * Gets the stage header string by feature.
+     * @param stage the specific stage
+     * @return the resource id of the header text of the specific stage
+     */
+    int getFeaturedStageHeaderResource(int stage);
+
+    /**
+     * Gets the enrollment lottie resource id per stage
+     * @param stage current enrollment stage
+     * @return enrollment lottie resource id
+     */
+    int getSfpsEnrollLottiePerStage(int stage);
+
+    /**
+     * Handles extra stuffs on receiving enrollment help.
+     * @param helpMsgId help message id
+     * @param helpString help message
+     * @param enrollingSupplier supplier of enrolling context
+     */
+    default void handleOnEnrollmentHelp(int helpMsgId, CharSequence helpString,
+            Supplier<FingerprintEnrollEnrolling> enrollingSupplier) {}
+
+    /**
+     * Gets the fingerprint enrollment threshold.
+     * @param context context
+     * @param index the enrollment stage index
+     * @return threshold
+     */
+    float getEnrollStageThreshold(@NonNull Context context, int index);
+}
diff --git a/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeatureImpl.java b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeatureImpl.java
new file mode 100644
index 0000000..5a97537
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/feature/SfpsEnrollmentFeatureImpl.java
@@ -0,0 +1,91 @@
+/*
+ * 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.fingerprint.feature;
+
+import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_CENTER;
+import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_FINGERTIP;
+import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_LEFT_EDGE;
+import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_NO_ANIMATION;
+import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.SFPS_STAGE_RIGHT_EDGE;
+import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.STAGE_UNKNOWN;
+
+import android.content.Context;
+import android.hardware.fingerprint.FingerprintManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settings.R;
+
+import java.util.function.Function;
+
+public class SfpsEnrollmentFeatureImpl implements SfpsEnrollmentFeature {
+
+    @Nullable
+    private FingerprintManager mFingerprintManager = null;
+
+    @Override
+    public int getCurrentSfpsEnrollStage(int progressSteps, Function<Integer, Integer> mapper) {
+        if (mapper == null) {
+            return STAGE_UNKNOWN;
+        }
+        if (progressSteps < mapper.apply(0)) {
+            return SFPS_STAGE_NO_ANIMATION;
+        } else if (progressSteps < mapper.apply(1)) {
+            return SFPS_STAGE_CENTER;
+        } else if (progressSteps < mapper.apply(2)) {
+            return SFPS_STAGE_FINGERTIP;
+        } else if (progressSteps < mapper.apply(3)) {
+            return SFPS_STAGE_LEFT_EDGE;
+        } else {
+            return SFPS_STAGE_RIGHT_EDGE;
+        }
+    }
+
+    @Override
+    public int getFeaturedStageHeaderResource(int stage) {
+        return switch (stage) {
+            case SFPS_STAGE_NO_ANIMATION
+                    -> R.string.security_settings_fingerprint_enroll_repeat_title;
+            case SFPS_STAGE_CENTER -> R.string.security_settings_sfps_enroll_finger_center_title;
+            case SFPS_STAGE_FINGERTIP -> R.string.security_settings_sfps_enroll_fingertip_title;
+            case SFPS_STAGE_LEFT_EDGE -> R.string.security_settings_sfps_enroll_left_edge_title;
+            case SFPS_STAGE_RIGHT_EDGE -> R.string.security_settings_sfps_enroll_right_edge_title;
+            default -> throw new IllegalArgumentException("Invalid stage: " + stage);
+        };
+    }
+
+    @Override
+    public int getSfpsEnrollLottiePerStage(int stage) {
+        return switch (stage) {
+            case SFPS_STAGE_NO_ANIMATION -> R.raw.sfps_lottie_no_animation;
+            case SFPS_STAGE_CENTER -> R.raw.sfps_lottie_pad_center;
+            case SFPS_STAGE_FINGERTIP -> R.raw.sfps_lottie_tip;
+            case SFPS_STAGE_LEFT_EDGE -> R.raw.sfps_lottie_left_edge;
+            case SFPS_STAGE_RIGHT_EDGE -> R.raw.sfps_lottie_right_edge;
+            default -> throw new IllegalArgumentException("Invalid stage: " + stage);
+        };
+    }
+
+    @Override
+    public float getEnrollStageThreshold(@NonNull Context context, int index) {
+        if (mFingerprintManager == null) {
+            mFingerprintManager = context.getSystemService(FingerprintManager.class);
+        }
+        return mFingerprintManager.getEnrollStageThreshold(index);
+    }
+}
diff --git a/src/com/android/settings/overlay/FeatureFactory.kt b/src/com/android/settings/overlay/FeatureFactory.kt
index 7645076..ac689d9 100644
--- a/src/com/android/settings/overlay/FeatureFactory.kt
+++ b/src/com/android/settings/overlay/FeatureFactory.kt
@@ -21,6 +21,7 @@
 import com.android.settings.accounts.AccountFeatureProvider
 import com.android.settings.applications.ApplicationFeatureProvider
 import com.android.settings.biometrics.face.FaceFeatureProvider
+import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider
 import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider
 import com.android.settings.bluetooth.BluetoothFeatureProvider
 import com.android.settings.connecteddevice.fastpair.FastPairFeatureProvider
@@ -104,9 +105,17 @@
      */
     abstract val bluetoothFeatureProvider: BluetoothFeatureProvider
 
+    /**
+     * Retrieves implementation for Face feature.
+     */
     abstract val faceFeatureProvider: FaceFeatureProvider
 
     /**
+     * Retrieves implementation for Fingerprint feature.
+     */
+    abstract val fingerprintFeatureProvider: FingerprintFeatureProvider
+
+    /**
      * Gets implementation for Biometrics repository provider.
      */
     abstract val biometricsRepositoryProvider: BiometricsRepositoryProvider
diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.kt b/src/com/android/settings/overlay/FeatureFactoryImpl.kt
index 0afe9f4..7f991b7 100644
--- a/src/com/android/settings/overlay/FeatureFactoryImpl.kt
+++ b/src/com/android/settings/overlay/FeatureFactoryImpl.kt
@@ -29,6 +29,8 @@
 import com.android.settings.applications.ApplicationFeatureProviderImpl
 import com.android.settings.biometrics.face.FaceFeatureProvider
 import com.android.settings.biometrics.face.FaceFeatureProviderImpl
+import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider
+import com.android.settings.biometrics.fingerprint.FingerprintFeatureProviderImpl
 import com.android.settings.biometrics2.factory.BiometricsRepositoryProviderImpl
 import com.android.settings.bluetooth.BluetoothFeatureProvider
 import com.android.settings.bluetooth.BluetoothFeatureProviderImpl
@@ -145,6 +147,10 @@
 
     override val faceFeatureProvider: FaceFeatureProvider by lazy { FaceFeatureProviderImpl() }
 
+    override val fingerprintFeatureProvider: FingerprintFeatureProvider by lazy {
+        FingerprintFeatureProviderImpl()
+    }
+
     override val biometricsRepositoryProvider by lazy { BiometricsRepositoryProviderImpl() }
 
     override val wifiTrackerLibProvider: WifiTrackerLibProvider by lazy {
diff --git a/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java b/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java
index 52a5f24..9156cae 100644
--- a/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java
+++ b/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java
@@ -24,6 +24,7 @@
 import com.android.settings.accounts.AccountFeatureProvider;
 import com.android.settings.applications.ApplicationFeatureProvider;
 import com.android.settings.biometrics.face.FaceFeatureProvider;
+import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider;
 import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider;
 import com.android.settings.bluetooth.BluetoothFeatureProvider;
 import com.android.settings.connecteddevice.fastpair.FastPairFeatureProvider;
@@ -80,6 +81,7 @@
     public final AccountFeatureProvider mAccountFeatureProvider;
     public final BluetoothFeatureProvider mBluetoothFeatureProvider;
     public final FaceFeatureProvider mFaceFeatureProvider;
+    public final FingerprintFeatureProvider mFingerprintFeatureProvider;
     public final BiometricsRepositoryProvider mBiometricsRepositoryProvider;
 
     public PanelFeatureProvider panelFeatureProvider;
@@ -132,6 +134,7 @@
         panelFeatureProvider = mock(PanelFeatureProvider.class);
         mBluetoothFeatureProvider = mock(BluetoothFeatureProvider.class);
         mFaceFeatureProvider = mock(FaceFeatureProvider.class);
+        mFingerprintFeatureProvider = mock(FingerprintFeatureProvider.class);
         mBiometricsRepositoryProvider = mock(BiometricsRepositoryProvider.class);
         wifiTrackerLibProvider = mock(WifiTrackerLibProvider.class);
         securitySettingsFeatureProvider = mock(SecuritySettingsFeatureProvider.class);
@@ -257,6 +260,11 @@
     }
 
     @Override
+    public FingerprintFeatureProvider getFingerprintFeatureProvider() {
+        return mFingerprintFeatureProvider;
+    }
+
+    @Override
     public BiometricsRepositoryProvider getBiometricsRepositoryProvider() {
         return mBiometricsRepositoryProvider;
     }
diff --git a/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt b/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
index 95f25ad..54299eb 100644
--- a/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
+++ b/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
@@ -22,6 +22,7 @@
 import com.android.settings.accounts.AccountFeatureProvider
 import com.android.settings.applications.ApplicationFeatureProvider
 import com.android.settings.biometrics.face.FaceFeatureProvider
+import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider
 import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider
 import com.android.settings.bluetooth.BluetoothFeatureProvider
 import com.android.settings.connecteddevice.fastpair.FastPairFeatureProvider
@@ -120,6 +121,8 @@
         get() = TODO("Not yet implemented")
     override val faceFeatureProvider: FaceFeatureProvider
         get() = TODO("Not yet implemented")
+    override val fingerprintFeatureProvider: FingerprintFeatureProvider
+        get() = TODO("Not yet implemented")
     override val biometricsRepositoryProvider: BiometricsRepositoryProvider
         get() = TODO("Not yet implemented")
     override val wifiTrackerLibProvider: WifiTrackerLibProvider
diff --git a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
index a3a92a3..b5062a0 100644
--- a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
+++ b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
@@ -24,6 +24,7 @@
 import com.android.settings.accounts.AccountFeatureProvider;
 import com.android.settings.applications.ApplicationFeatureProvider;
 import com.android.settings.biometrics.face.FaceFeatureProvider;
+import com.android.settings.biometrics.fingerprint.FingerprintFeatureProvider;
 import com.android.settings.biometrics2.factory.BiometricsRepositoryProvider;
 import com.android.settings.bluetooth.BluetoothFeatureProvider;
 import com.android.settings.connecteddevice.fastpair.FastPairFeatureProvider;
@@ -79,6 +80,7 @@
     public final AccountFeatureProvider mAccountFeatureProvider;
     public final BluetoothFeatureProvider mBluetoothFeatureProvider;
     public final FaceFeatureProvider mFaceFeatureProvider;
+    public final FingerprintFeatureProvider mFingerprintFeatureProvider;
     public final BiometricsRepositoryProvider mBiometricsRepositoryProvider;
 
     public PanelFeatureProvider panelFeatureProvider;
@@ -131,6 +133,7 @@
         panelFeatureProvider = mock(PanelFeatureProvider.class);
         mBluetoothFeatureProvider = mock(BluetoothFeatureProvider.class);
         mFaceFeatureProvider = mock(FaceFeatureProvider.class);
+        mFingerprintFeatureProvider = mock(FingerprintFeatureProvider.class);
         mBiometricsRepositoryProvider = mock(BiometricsRepositoryProvider.class);
         wifiTrackerLibProvider = mock(WifiTrackerLibProvider.class);
         securitySettingsFeatureProvider = mock(SecuritySettingsFeatureProvider.class);
@@ -256,6 +259,11 @@
     }
 
     @Override
+    public FingerprintFeatureProvider getFingerprintFeatureProvider() {
+        return mFingerprintFeatureProvider;
+    }
+
+    @Override
     public BiometricsRepositoryProvider getBiometricsRepositoryProvider() {
         return mBiometricsRepositoryProvider;
     }