Merge "[Biometric Onboarding & Edu] Support check enrolled fingerprint" into main
diff --git a/res/drawable/ic_check_list_24dp.xml b/res/drawable/ic_check_list_24dp.xml
new file mode 100644
index 0000000..4d8955c
--- /dev/null
+++ b/res/drawable/ic_check_list_24dp.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="960"
+        android:viewportHeight="960"
+        android:tint="?android:attr/colorControlNormal">
+    <path
+        android:pathData="M222,760L80,618L136,562L221,647L391,477L447,534L222,760ZM222,440L80,298L136,242L221,327L391,157L447,214L222,440ZM520,680L520,600L880,600L880,680L520,680ZM520,360L520,280L880,280L880,360L520,360Z"
+        android:fillColor="#000000"/>
+</vector>
diff --git a/res/layout/fingerprint_check_enrolled_dialog.xml b/res/layout/fingerprint_check_enrolled_dialog.xml
new file mode 100644
index 0000000..5565829
--- /dev/null
+++ b/res/layout/fingerprint_check_enrolled_dialog.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <com.android.settings.biometrics.fingerprint.UdfpsCheckEnrolledView
+        android:id="@+id/udfps_check_enrolled_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentTop="true"
+        android:layout_centerHorizontal="true">
+
+        <!-- Fingerprint -->
+        <ImageView
+            android:id="@+id/udfps_fingerprint_sensor_view"
+            android:contentDescription="@string/accessibility_fingerprint_label"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <TextView
+            android:id="@+id/udfps_fingerprint_sensor_message"
+            android:layout_marginTop="80dp"
+            android:layout_below="@id/udfps_fingerprint_sensor_view"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:textSize="18sp"
+            android:textColor="@android:color/white"
+            android:text="@string/fingerprint_check_enroll_touch_sensor"/>
+
+    </com.android.settings.biometrics.fingerprint.UdfpsCheckEnrolledView>
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 236a661..bd78f0b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -938,6 +938,8 @@
     <string name="security_settings_fingerprint_settings_preferences_category">When using Fingerprint Unlock</string>
     <!-- Title shown for work menu item that launches fingerprint settings or enrollment [CHAR LIMIT=22] -->
     <string name="security_settings_work_fingerprint_preference_title">Fingerprint for work</string>
+    <!-- Preference to check enrolled fingerprints -->
+    <string name="fingerprint_check_enrolled_title">Check enrolled fingerprints</string>
     <!-- Preference to add another fingerprint -->
     <string name="fingerprint_add_title">Add fingerprint</string>
     <!-- Message showing the current number of fingerprints set up. Shown for a menu item that launches fingerprint settings or enrollment. -->
@@ -991,7 +993,10 @@
     <string name="security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_6">For best results, use a screen protector that\u2019s Made for Google certified. With other screen protectors, your child\u2019s fingerprint may not work.</string>
     <!-- Introduction detail message shown in fingerprint enrollment introduction to learn more about fingerprint [CHAR LIMIT=NONE]-->
     <string name="security_settings_fingerprint_v2_enroll_introduction_message_learn_more"></string>
-
+    <!-- Description shown on the check enrolled fingerprint dialog -->
+    <string name="fingerprint_check_enroll_touch_sensor">Touch the fingerprint sensor</string>
+    <!-- Description shown on the check enrolled fingerprint dialog -->
+    <string name="fingerprint_check_enroll_not_recognized">Fingerprint not recognized</string>
     <!-- Watch unlock enrollment and settings --><skip />
     <!-- Title shown for menu item that launches watch unlock settings. [CHAR LIMIT=40] -->
     <string name ="security_settings_activeunlock_preference_title">Watch Unlock</string>
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
index f649a40..8d6f3cb 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
@@ -21,6 +21,7 @@
 import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE;
 import static android.app.admin.DevicePolicyResources.UNDEFINED;
 import static android.hardware.biometrics.Flags.screenOffUnlockUdfps;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
 import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
 import static com.android.settings.Utils.isPrivateProfile;
@@ -40,6 +41,7 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -49,8 +51,15 @@
 import android.text.Spanned;
 import android.text.TextUtils;
 import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowInsetsController;
+import android.view.WindowManager;
 import android.widget.ImeAwareEditText;
+import android.widget.TextView;
 import android.widget.Toast;
 
 import androidx.annotation.NonNull;
@@ -76,6 +85,7 @@
 import com.android.settings.core.SettingsBaseActivity;
 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
 import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.flags.Flags;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.password.ChooseLockGeneric;
 import com.android.settings.password.ChooseLockSettingsHelper;
@@ -235,6 +245,9 @@
 
         private static final String TAG = "FingerprintSettings";
         private static final String KEY_FINGERPRINT_ITEM_PREFIX = "key_fingerprint_item";
+
+        private static final String KEY_FINGERPRINT_CHECK_ENROLLED =
+                "key_fingerprint_check_enrolled";
         @VisibleForTesting
         static final String KEY_FINGERPRINT_ADD = "key_fingerprint_add";
         private static final String KEY_FINGERPRINT_ENABLE_KEYGUARD_TOGGLE =
@@ -691,6 +704,17 @@
                 mFingerprintsEnrolledCategory.addPreference(pref);
                 pref.setOnPreferenceChangeListener(this);
             }
+            if (Flags.biometricsOnboardingEducation() && isUdfps() && fingerprintCount > 0) {
+                // Setup check enrolled fingerprints preference
+                Preference pref = new Preference(root.getContext());
+                pref.setKey(KEY_FINGERPRINT_CHECK_ENROLLED);
+                pref.setTitle(root.getContext().getString(
+                        R.string.fingerprint_check_enrolled_title));
+                pref.setIcon(R.drawable.ic_check_list_24dp);
+                pref.setVisible(true);
+                mFingerprintsEnrolledCategory.addPreference(pref);
+                pref.setOnPreferenceChangeListener(this);
+            }
             mAddFingerprintPreference = findPreference(KEY_FINGERPRINT_ADD);
             setupAddFingerprintPreference();
             return keyToReturn;
@@ -916,6 +940,8 @@
                 FingerprintPreference fpref = (FingerprintPreference) pref;
                 final Fingerprint fp = fpref.getFingerprint();
                 showRenameDialog(fp);
+            } else if (KEY_FINGERPRINT_CHECK_ENROLLED.equals(key)) {
+                showCheckEnrolledDialog();
             }
             return super.onPreferenceTreeClick(pref);
         }
@@ -968,6 +994,16 @@
             mAuthenticateSidecar.stopAuthentication();
         }
 
+        private void showCheckEnrolledDialog() {
+            final CheckEnrolledDialog checkEnrolledDialog = new CheckEnrolledDialog();
+            final Bundle args = new Bundle();
+            args.putInt(CheckEnrolledDialog.KEY_USER_ID, mUserId);
+            args.putParcelable(CheckEnrolledDialog.KEY_SENSOR_PROPERTIES, mSensorProperties.get(0));
+            checkEnrolledDialog.setArguments(args);
+            checkEnrolledDialog.setTargetFragment(this, 0);
+            checkEnrolledDialog.show(getFragmentManager(), CheckEnrolledDialog.class.getName());
+        }
+
         @Override
         public boolean onPreferenceChange(Preference preference, Object value) {
             boolean result = true;
@@ -1344,6 +1380,121 @@
             return new InputFilter[]{filter};
         }
 
+        public static class CheckEnrolledDialog extends InstrumentedDialogFragment {
+
+            private static final String KEY_USER_ID = "user_id";
+            private static final String KEY_SENSOR_PROPERTIES = "sensor_properties";
+            private int mUserId;
+            private @Nullable CancellationSignal mCancellationSignal;
+            private @Nullable FingerprintSensorPropertiesInternal mSensorPropertiesInternal;
+
+            @Override
+            public @NonNull View onCreateView(
+                    @NonNull LayoutInflater inflater,
+                    @Nullable ViewGroup container,
+                    @Nullable Bundle savedInstanceState) {
+                return inflater.inflate(
+                        R.layout.fingerprint_check_enrolled_dialog, container, false);
+            }
+
+            @Override
+            public @NonNull Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+                final Dialog dialog = super.onCreateDialog(savedInstanceState);
+                if (dialog != null) {
+                    mUserId = getArguments().getInt(KEY_USER_ID);
+                    mSensorPropertiesInternal =
+                            getArguments().getParcelable(KEY_SENSOR_PROPERTIES);
+
+                    // Remove the default dialog title bar
+                    dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE);
+
+                    dialog.setOnShowListener(dialogInterface -> {
+                        final UdfpsCheckEnrolledView v =
+                                dialog.findViewById(R.id.udfps_check_enrolled_view);
+                        v.setSensorProperties(mSensorPropertiesInternal);
+                    });
+                }
+
+                return dialog;
+            }
+
+            @Override
+            public void onStart() {
+                super.onStart();
+                if (getDialog() == null) {
+                    return;
+                }
+
+                final Dialog dialog = getDialog();
+                Window window = dialog.getWindow();
+                WindowManager.LayoutParams params = window.getAttributes();
+
+                // Make the dialog fullscreen
+                params.width = WindowManager.LayoutParams.MATCH_PARENT;
+                params.height = WindowManager.LayoutParams.MATCH_PARENT;
+                params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+                params.setFitInsetsTypes(0);
+                window.setAttributes(params);
+                window.getDecorView().getWindowInsetsController().hide(
+                        WindowInsets.Type.statusBars());
+                window.getDecorView().getWindowInsetsController().setSystemBarsBehavior(
+                        WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
+                window.setBackgroundDrawableResource(android.R.color.black);
+
+                final TextView message =
+                        dialog.findViewById(R.id.udfps_fingerprint_sensor_message);
+                final Vibrator vibrator = getContext().getSystemService(Vibrator.class);
+                final FingerprintManager fpm = Utils.getFingerprintManagerOrNull(getContext());
+                mCancellationSignal = new CancellationSignal();
+                fpm.authenticate(
+                        null /* crypto */,
+                        mCancellationSignal,
+                        new FingerprintManager.AuthenticationCallback() {
+                            @Override
+                            public void onAuthenticationError(
+                                    int errorCode, @NonNull CharSequence errString) {
+                                dialog.dismiss();
+                            }
+
+                            @Override
+                            public void onAuthenticationSucceeded(
+                                    @NonNull FingerprintManager.AuthenticationResult result) {
+                                int fingerId = result.getFingerprint().getBiometricId();
+                                FingerprintSettingsFragment parent =
+                                        (FingerprintSettingsFragment) getTargetFragment();
+                                parent.highlightFingerprintItem(fingerId);
+                                dialog.dismiss();
+                            }
+
+                            @Override
+                            public void onAuthenticationFailed() {
+                                vibrator.vibrate(
+                                        VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK));
+                                message.setText(R.string.fingerprint_check_enroll_not_recognized);
+                                message.postDelayed(() -> {
+                                    message.setText(R.string.fingerprint_check_enroll_touch_sensor);
+                                }, 2000);
+                            }
+                        },
+                        null /* handler */,
+                        mUserId);
+            }
+
+            @Override
+            public void onDismiss(@NonNull DialogInterface dialog) {
+                super.onDismiss(dialog);
+                if (mCancellationSignal != null) {
+                    mCancellationSignal.cancel();
+                    mCancellationSignal = null;
+                }
+            }
+
+            @Override
+            public int getMetricsCategory() {
+                return 0;
+            }
+        }
+
         public static class RenameDialog extends InstrumentedDialogFragment {
 
             private Fingerprint mFp;
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsCheckEnrolledView.java b/src/com/android/settings/biometrics/fingerprint/UdfpsCheckEnrolledView.java
new file mode 100644
index 0000000..52a28c7
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsCheckEnrolledView.java
@@ -0,0 +1,186 @@
+/*
+ * 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.fingerprint;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.RotationUtils;
+import android.view.DisplayInfo;
+import android.view.Surface;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settings.R;
+import com.android.systemui.biometrics.UdfpsUtils;
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
+
+/**
+ * View corresponding with fingerprint_check_enrolled_dialog.xml
+ */
+public class UdfpsCheckEnrolledView extends RelativeLayout {
+    private static final String TAG = "UdfpsCheckEnrolledView";
+    @NonNull
+    private final UdfpsFingerprintDrawable mFingerprintDrawable;
+    private ImageView mFingerprintView;
+    private UdfpsUtils mUdfpsUtils;
+
+    private @Nullable Rect mSensorRect;
+    private @Nullable UdfpsOverlayParams mOverlayParams;
+    private @Nullable FingerprintSensorPropertiesInternal mSensorProperties;
+
+
+    public UdfpsCheckEnrolledView(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        mFingerprintDrawable = new UdfpsFingerprintDrawable(mContext, attrs);
+        mUdfpsUtils = new UdfpsUtils();
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mFingerprintView = findViewById(R.id.udfps_fingerprint_sensor_view);
+        mFingerprintView.setImageDrawable(mFingerprintDrawable);
+    }
+
+    /**
+     * setup SensorProperties
+     */
+    public void setSensorProperties(@Nullable FingerprintSensorPropertiesInternal properties) {
+        mSensorProperties = properties;
+        updateOverlayParams();
+    }
+
+    private void onSensorRectUpdated() {
+        updateDimensions();
+
+        if (mSensorRect == null || mOverlayParams == null) {
+            Log.e(TAG, "Fail to onSensorRectUpdated, mSensorRect/mOverlayParams null");
+            return;
+        }
+
+        // Updates sensor rect in relation to the overlay view
+        mSensorRect.set(0, 0,
+                mOverlayParams.getSensorBounds().width(),
+                mOverlayParams.getSensorBounds().height());
+        mFingerprintDrawable.onSensorRectUpdated(new RectF(mSensorRect));
+    }
+
+    private void updateDimensions() {
+        if (mOverlayParams == null) {
+            Log.e(TAG, "Fail to updateDimensions for " + this + ", mOverlayParams null");
+            return;
+        }
+        // Original sensorBounds assume portrait mode.
+        final Rect rotatedBounds = new Rect(mOverlayParams.getSensorBounds());
+        int rotation = mOverlayParams.getRotation();
+        if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
+            RotationUtils.rotateBounds(
+                    rotatedBounds,
+                    mOverlayParams.getNaturalDisplayWidth(),
+                    mOverlayParams.getNaturalDisplayHeight(),
+                    rotation
+            );
+        }
+
+        RelativeLayout parent = ((RelativeLayout) getParent());
+        if (parent == null) {
+            Log.e(TAG, "Fail to updateDimensions for " + this + ", parent null");
+            return;
+        }
+        final int[] coords = parent.getLocationOnScreen();
+        final int parentLeft = coords[0];
+        final int parentTop = coords[1];
+        final int parentRight = parentLeft + parent.getWidth();
+
+        // Update container view LayoutParams
+        RelativeLayout.LayoutParams checkEnrolledViewLp =
+                new RelativeLayout.LayoutParams(getWidth(), getHeight());
+        checkEnrolledViewLp.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+        if (rotation == Surface.ROTATION_90) {
+            checkEnrolledViewLp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
+            checkEnrolledViewLp.width =
+                    rotatedBounds.width() + 2 * (parentRight - rotatedBounds.right);
+        } else {
+            checkEnrolledViewLp.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
+            checkEnrolledViewLp.width = rotatedBounds.width() + 2 * rotatedBounds.left;
+        }
+        setLayoutParams(checkEnrolledViewLp);
+
+        // Update fingerprint view LayoutParams
+        RelativeLayout.LayoutParams fingerprintViewLp = new RelativeLayout.LayoutParams(
+                rotatedBounds.width(), rotatedBounds.height());
+        fingerprintViewLp.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+        fingerprintViewLp.topMargin = rotatedBounds.top - parentTop;
+        if (rotation == Surface.ROTATION_90) {
+            fingerprintViewLp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
+            fingerprintViewLp.rightMargin = parentRight - rotatedBounds.right;
+        } else {
+            fingerprintViewLp.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
+            fingerprintViewLp.leftMargin = rotatedBounds.left - parentLeft;
+        }
+        mFingerprintView.setLayoutParams(fingerprintViewLp);
+    }
+
+    private void updateOverlayParams() {
+
+        if (mSensorProperties == null) {
+            android.util.Log.e(TAG, "There is no sensor info!");
+            return;
+        }
+
+        DisplayInfo displayInfo = new DisplayInfo();
+        if (getDisplay() == null) {
+            android.util.Log.e(TAG, "Can not get display");
+            return;
+        }
+        getDisplay().getDisplayInfo(displayInfo);
+        Rect udfpsBounds = mSensorProperties.getLocation().getRect();
+        float scaleFactor = mUdfpsUtils.getScaleFactor(displayInfo);
+        udfpsBounds.scale(scaleFactor);
+
+        final Rect overlayBounds = new Rect(
+                0, /* left */
+                displayInfo.getNaturalHeight() / 2, /* top */
+                displayInfo.getNaturalWidth(), /* right */
+                displayInfo.getNaturalHeight() /* botom */);
+
+        mOverlayParams = new UdfpsOverlayParams(
+                udfpsBounds,
+                overlayBounds,
+                displayInfo.getNaturalWidth(),
+                displayInfo.getNaturalHeight(),
+                scaleFactor,
+                displayInfo.rotation,
+                mSensorProperties.sensorType);
+
+        post(() -> {
+            if (mOverlayParams == null) {
+                Log.e(TAG, "Fail to updateOverlayParams, mOverlayParams null");
+                return;
+            }
+            mSensorRect = new Rect(mOverlayParams.getSensorBounds());
+            onSensorRectUpdated();
+        });
+    }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsFingerprintDrawable.java b/src/com/android/settings/biometrics/fingerprint/UdfpsFingerprintDrawable.java
new file mode 100644
index 0000000..e5ed6e1
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsFingerprintDrawable.java
@@ -0,0 +1,138 @@
+/*
+ * 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.fingerprint;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.PathShape;
+import android.util.AttributeSet;
+import android.util.PathParser;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settings.R;
+
+/**
+ * UDFPS fingerprint drawable
+ */
+public class UdfpsFingerprintDrawable extends Drawable {
+    private static final String TAG = "UdfpsFingerprintDrawable";
+
+    private static final float DEFAULT_STROKE_WIDTH = 3f;
+
+    @NonNull
+    private final Paint mSensorOutlinePaint;
+    @NonNull
+    private final ShapeDrawable mFingerprintDrawable;
+    private int mAlpha;
+
+    @Nullable
+    private RectF mSensorRect;
+    private int mEnrollIcon;
+    private int mOutlineColor;
+
+    UdfpsFingerprintDrawable(@NonNull Context context, @Nullable AttributeSet attrs) {
+        mFingerprintDrawable = defaultFactory(context);
+
+        loadResources(context, attrs);
+        mSensorOutlinePaint = new Paint(0 /* flags */);
+        mSensorOutlinePaint.setAntiAlias(true);
+        mSensorOutlinePaint.setColor(mOutlineColor);
+        mSensorOutlinePaint.setStyle(Paint.Style.FILL);
+
+        mFingerprintDrawable.setTint(mEnrollIcon);
+
+        setAlpha(255);
+    }
+
+    /** The [sensorRect] coordinates for the sensor area. */
+    void onSensorRectUpdated(@NonNull RectF sensorRect) {
+        int margin = ((int) sensorRect.height()) / 8;
+        Rect bounds = new Rect((int) (sensorRect.left) + margin, (int) (sensorRect.top) + margin,
+                (int) (sensorRect.right) - margin, (int) (sensorRect.bottom) - margin);
+        updateFingerprintIconBounds(bounds);
+        mSensorRect = sensorRect;
+    }
+
+    void updateFingerprintIconBounds(@NonNull Rect bounds) {
+        mFingerprintDrawable.setBounds(bounds);
+        invalidateSelf();
+    }
+
+    @Override
+    public void draw(@NonNull Canvas canvas) {
+        if (mSensorRect != null) {
+            canvas.drawOval(mSensorRect, mSensorOutlinePaint);
+        }
+        mFingerprintDrawable.draw(canvas);
+        mFingerprintDrawable.setAlpha(getAlpha());
+        mSensorOutlinePaint.setAlpha(getAlpha());
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mAlpha = alpha;
+        mFingerprintDrawable.setAlpha(alpha);
+        mSensorOutlinePaint.setAlpha(alpha);
+        invalidateSelf();
+    }
+
+    @Override
+    public int getAlpha() {
+        return mAlpha;
+    }
+
+    @Override
+    public void setColorFilter(@Nullable ColorFilter colorFilter) {
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.OPAQUE;
+    }
+
+    private ShapeDrawable defaultFactory(Context context) {
+        String fpPath = context.getResources().getString(R.string.config_udfpsIcon);
+        ShapeDrawable drawable = new ShapeDrawable(
+                new PathShape(PathParser.createPathFromPathData(fpPath), 72f, 72f)
+        );
+        drawable.mutate();
+        drawable.getPaint().setStyle(Paint.Style.STROKE);
+        drawable.getPaint().setStrokeCap(Paint.Cap.ROUND);
+        drawable.getPaint().setStrokeWidth(DEFAULT_STROKE_WIDTH);
+        return drawable;
+    }
+
+    private void loadResources(Context context, @Nullable AttributeSet attrs) {
+        final TypedArray ta = context.obtainStyledAttributes(attrs,
+                R.styleable.BiometricsEnrollView, R.attr.biometricsEnrollStyle,
+                R.style.BiometricsEnrollStyle);
+        mEnrollIcon = ta.getColor(R.styleable.BiometricsEnrollView_biometricsEnrollIcon, 0);
+        mOutlineColor = ta.getColor(
+                R.styleable.BiometricsEnrollView_biometricsMovingTargetFill, 0);
+        ta.recycle();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java
index 1086f85..a570baa 100644
--- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsFragmentTest.java
@@ -17,6 +17,7 @@
 package com.android.settings.biometrics.fingerprint;
 
 import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
+import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR;
 import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
 
 import static com.android.settings.biometrics.BiometricEnrollBase.BIOMETRIC_AUTH_REQUEST;
@@ -354,6 +355,48 @@
         assertThat(addPref.isEnabled()).isTrue();
     }
 
+    @Test
+    @EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+    public void testCheckEnrolledShown_whenAtLeastOneFingerprintEnrolled_Udfps() {
+        final Fingerprint fingerprint = new Fingerprint("Test", 0, 0);
+        doReturn(List.of(fingerprint)).when(mFingerprintManager).getEnrolledFingerprints(anyInt());
+        setUpFragment(false, PRIMARY_USER_ID, TYPE_UDFPS_OPTICAL, 5);
+
+        shadowOf(Looper.getMainLooper()).idle();
+
+        final Preference checkEnrolledPerf =
+                mFragment.findPreference("key_fingerprint_check_enrolled");
+        assertThat(checkEnrolledPerf).isNotNull();
+        assertThat(checkEnrolledPerf.isVisible()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+    public void testCheckEnrolledHide_whenNoFingerprintEnrolled_Udfps() {
+        doReturn(List.of()).when(mFingerprintManager).getEnrolledFingerprints(anyInt());
+        setUpFragment(false, PRIMARY_USER_ID, TYPE_UDFPS_OPTICAL, 5);
+
+        shadowOf(Looper.getMainLooper()).idle();
+
+        final Preference checkEnrolledPerf =
+                mFragment.findPreference("key_fingerprint_check_enrolled");
+        assertThat(checkEnrolledPerf).isNull();
+    }
+
+    @Test
+    @EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+    public void testCheckEnrolledHide_nonUdfps() {
+        final Fingerprint fingerprint = new Fingerprint("Test", 0, 0);
+        doReturn(List.of(fingerprint)).when(mFingerprintManager).getEnrolledFingerprints(anyInt());
+        setUpFragment(false, PRIMARY_USER_ID, TYPE_REAR, 5);
+
+        shadowOf(Looper.getMainLooper()).idle();
+
+        final Preference checkEnrolledPerf =
+                mFragment.findPreference("key_fingerprint_check_enrolled");
+        assertThat(checkEnrolledPerf).isNull();
+    }
+
     private void setSensor(@FingerprintSensorProperties.SensorType int sensorType,
             int maxFingerprints) {
         final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();