[Biometric Onboarding & Edu] Support check enrolled fingerprint
- Add a new PreferenceItem for check enrolled fingerprint
- Create a new DialogFragment for the check enrolled
fingerprint with functions:
- request an authentication to FingerprintManager
- highlight the item when a authentication successes
- show error text when authentication fails
- close the authentication
Bug: 370940762
Test: atest FingerprintSettingsFragmentTest
Flag: com.android.settings.flags.biometrics_onboarding_education
Change-Id: I90637e4ec20ea46e6f530ffd7ba79df9c31eda6b
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 7386eab..2ef34a7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -921,6 +921,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. -->
@@ -974,7 +976,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 d8a14f1..223900c 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;
@@ -41,6 +42,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;
@@ -50,8 +52,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;
@@ -77,6 +86,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;
@@ -236,6 +246,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 =
@@ -697,6 +710,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;
@@ -922,6 +946,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);
}
@@ -974,6 +1000,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;
@@ -1350,6 +1386,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<>();