Support configurable multi-stage UDFPS enrollment

Implements multi-stage enrollment for UDFPS. This implementation
supports both highlighting the progress bar when a help message is
received and configuring the progress thresholds between enroll stages
via an XML resource.

Test: Manual

Bug: 198928407
Bug: 200076382
Change-Id: Ied4dbcb4a523f477c5ca1a9146d24751c6473f35
This commit is contained in:
Curtis Belmonte
2021-09-17 17:13:31 -07:00
parent 949b349912
commit 73855e0a59

View File

@@ -18,6 +18,7 @@ package com.android.settings.biometrics.fingerprint;
import android.animation.Animator; import android.animation.Animator;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.annotation.IntDef;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.app.Dialog; import android.app.Dialog;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
@@ -58,6 +59,8 @@ import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton; import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupcompat.util.WizardManagerHelper; import com.google.android.setupcompat.util.WizardManagerHelper;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List; import java.util.List;
/** /**
@@ -69,11 +72,16 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
static final String TAG_SIDECAR = "sidecar"; static final String TAG_SIDECAR = "sidecar";
private static final int PROGRESS_BAR_MAX = 10000; private static final int PROGRESS_BAR_MAX = 10000;
private static final int FINISH_DELAY = 250;
/** private static final int STAGE_UNKNOWN = -1;
* Enroll with two center touches before going to guided enrollment. private static final int STAGE_CENTER = 0;
*/ private static final int STAGE_GUIDED = 1;
private static final int NUM_CENTER_TOUCHES = 2; private static final int STAGE_FINGERTIP = 2;
private static final int STAGE_EDGES = 3;
@IntDef({STAGE_UNKNOWN, STAGE_CENTER, STAGE_GUIDED, STAGE_FINGERTIP, STAGE_EDGES})
@Retention(RetentionPolicy.SOURCE)
private @interface EnrollStage {}
/** /**
* If we don't see progress during this time, we show an error message to remind the users that * If we don't see progress during this time, we show an error message to remind the users that
@@ -100,6 +108,7 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
.build(); .build();
private FingerprintManager mFingerprintManager;
private boolean mCanAssumeUdfps; private boolean mCanAssumeUdfps;
@Nullable private ProgressBar mProgressBar; @Nullable private ProgressBar mProgressBar;
private ObjectAnimator mProgressAnim; private ObjectAnimator mProgressAnim;
@@ -125,9 +134,9 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
final FingerprintManager fingerprintManager = getSystemService(FingerprintManager.class); mFingerprintManager = getSystemService(FingerprintManager.class);
final List<FingerprintSensorPropertiesInternal> props = final List<FingerprintSensorPropertiesInternal> props =
fingerprintManager.getSensorPropertiesInternal(); mFingerprintManager.getSensorPropertiesInternal();
mCanAssumeUdfps = props.size() == 1 && props.get(0).isAnyUdfpsType(); mCanAssumeUdfps = props.size() == 1 && props.get(0).isAnyUdfpsType();
mAccessibilityManager = getSystemService(AccessibilityManager.class); mAccessibilityManager = getSystemService(AccessibilityManager.class);
@@ -273,7 +282,7 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
// UDFPS animations are owned by SystemUI // UDFPS animations are owned by SystemUI
if (progress >= PROGRESS_BAR_MAX) { if (progress >= PROGRESS_BAR_MAX) {
// Wait for any animations in SysUI to finish, then proceed to next page // Wait for any animations in SysUI to finish, then proceed to next page
getMainThreadHandler().postDelayed(mDelayedFinishRunnable, FINISH_DELAY); getMainThreadHandler().postDelayed(mDelayedFinishRunnable, getFinishDelay());
} }
return; return;
} }
@@ -300,8 +309,55 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
} }
private void updateTitleAndDescription() { private void updateTitleAndDescription() {
if (mSidecar == null || mSidecar.getEnrollmentSteps() == -1) {
if (mCanAssumeUdfps) { if (mCanAssumeUdfps) {
updateTitleAndDescriptionForUdfps();
return;
}
if (mSidecar == null || mSidecar.getEnrollmentSteps() == -1) {
setDescriptionText(R.string.security_settings_fingerprint_enroll_start_message);
} else {
setDescriptionText(R.string.security_settings_fingerprint_enroll_repeat_message);
}
}
private void updateTitleAndDescriptionForUdfps() {
switch (getCurrentStage()) {
case STAGE_CENTER:
setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title);
setDescriptionText(R.string.security_settings_udfps_enroll_start_message);
break;
case STAGE_GUIDED:
setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title);
if (mIsAccessibilityEnabled) {
setDescriptionText(R.string.security_settings_udfps_enroll_repeat_a11y_message);
} else {
setDescriptionText(R.string.security_settings_udfps_enroll_repeat_message);
}
break;
case STAGE_FINGERTIP:
setHeaderText(R.string.security_settings_udfps_enroll_fingertip_title);
if (isStageHalfCompleted()) {
setDescriptionText(R.string.security_settings_fingerprint_enroll_repeat_title);
} else {
setDescriptionText("");
}
break;
case STAGE_EDGES:
setHeaderText(R.string.security_settings_udfps_enroll_edge_title);
if (isStageHalfCompleted()) {
setDescriptionText(
R.string.security_settings_fingerprint_enroll_repeat_message);
} else {
setDescriptionText(R.string.security_settings_udfps_enroll_edge_message);
}
break;
case STAGE_UNKNOWN:
default:
// setHeaderText(R.string.security_settings_fingerprint_enroll_udfps_title); // setHeaderText(R.string.security_settings_fingerprint_enroll_udfps_title);
// Don't use BiometricEnrollBase#setHeaderText, since that invokes setTitle, // Don't use BiometricEnrollBase#setHeaderText, since that invokes setTitle,
// which gets announced for a11y upon entering the page. For UDFPS, we want to // which gets announced for a11y upon entering the page. For UDFPS, we want to
@@ -309,41 +365,61 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
getLayout().setHeaderText( getLayout().setHeaderText(
R.string.security_settings_fingerprint_enroll_udfps_title); R.string.security_settings_fingerprint_enroll_udfps_title);
setDescriptionText(R.string.security_settings_udfps_enroll_start_message); setDescriptionText(R.string.security_settings_udfps_enroll_start_message);
final CharSequence description = getString( final CharSequence description = getString(
R.string.security_settings_udfps_enroll_a11y); R.string.security_settings_udfps_enroll_a11y);
getLayout().getHeaderTextView().setContentDescription(description); getLayout().getHeaderTextView().setContentDescription(description);
setTitle(description); setTitle(description);
} else { break;
setDescriptionText(R.string.security_settings_fingerprint_enroll_start_message);
}
} else if (mCanAssumeUdfps && !isCenterEnrollmentComplete()) {
if (mIsSetupWizard) {
setHeaderText(R.string.security_settings_udfps_enroll_title_one_more_time);
} else {
setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title);
}
setDescriptionText(R.string.security_settings_udfps_enroll_start_message);
} else {
if (mCanAssumeUdfps) {
setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title);
if (mIsAccessibilityEnabled) {
setDescriptionText(R.string.security_settings_udfps_enroll_repeat_a11y_message);
} else {
setDescriptionText(R.string.security_settings_udfps_enroll_repeat_message);
}
} else {
setDescriptionText(R.string.security_settings_fingerprint_enroll_repeat_message);
}
} }
} }
private boolean isCenterEnrollmentComplete() { @EnrollStage
private int getCurrentStage() {
if (mSidecar == null || mSidecar.getEnrollmentSteps() == -1) {
return STAGE_UNKNOWN;
}
final int progressSteps = mSidecar.getEnrollmentSteps() - mSidecar.getEnrollmentRemaining();
if (progressSteps < getStageThresholdSteps(0)) {
return STAGE_CENTER;
} else if (progressSteps < getStageThresholdSteps(1)) {
return STAGE_GUIDED;
} else if (progressSteps < getStageThresholdSteps(2)) {
return STAGE_FINGERTIP;
} else {
return STAGE_EDGES;
}
}
private boolean isStageHalfCompleted() {
// Prior to first enrollment step.
if (mSidecar == null || mSidecar.getEnrollmentSteps() == -1) { if (mSidecar == null || mSidecar.getEnrollmentSteps() == -1) {
return false; return false;
} }
final int stepsEnrolled = mSidecar.getEnrollmentSteps() - mSidecar.getEnrollmentRemaining();
return stepsEnrolled >= NUM_CENTER_TOUCHES; final int progressSteps = mSidecar.getEnrollmentSteps() - mSidecar.getEnrollmentRemaining();
int prevThresholdSteps = 0;
for (int i = 0; i < mFingerprintManager.getEnrollStageCount(); i++) {
final int thresholdSteps = getStageThresholdSteps(i);
if (progressSteps >= prevThresholdSteps && progressSteps < thresholdSteps) {
final int adjustedProgress = progressSteps - prevThresholdSteps;
final int adjustedThreshold = thresholdSteps - prevThresholdSteps;
return adjustedProgress >= adjustedThreshold / 2;
}
prevThresholdSteps = thresholdSteps;
}
// After last enrollment step.
return true;
}
private int getStageThresholdSteps(int index) {
if (mSidecar == null || mSidecar.getEnrollmentSteps() == -1) {
Log.w(TAG, "getStageThresholdSteps: Enrollment not started yet");
return 1;
}
return Math.round(mSidecar.getEnrollmentSteps()
* mFingerprintManager.getEnrollStageThreshold(index));
} }
@Override @Override
@@ -489,8 +565,8 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
mOrientationEventListener = null; mOrientationEventListener = null;
} }
private final Animator.AnimatorListener mProgressAnimationListener private final Animator.AnimatorListener mProgressAnimationListener =
= new Animator.AnimatorListener() { new Animator.AnimatorListener() {
@Override @Override
public void onAnimationStart(Animator animation) { } public void onAnimationStart(Animator animation) { }
@@ -501,7 +577,7 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
@Override @Override
public void onAnimationEnd(Animator animation) { public void onAnimationEnd(Animator animation) {
if (mProgressBar.getProgress() >= PROGRESS_BAR_MAX) { if (mProgressBar.getProgress() >= PROGRESS_BAR_MAX) {
mProgressBar.postDelayed(mDelayedFinishRunnable, FINISH_DELAY); mProgressBar.postDelayed(mDelayedFinishRunnable, getFinishDelay());
} }
} }
@@ -509,6 +585,10 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
public void onAnimationCancel(Animator animation) { } public void onAnimationCancel(Animator animation) { }
}; };
private long getFinishDelay() {
return mCanAssumeUdfps ? 400L : 250L;
}
// Give the user a chance to see progress completed before jumping to the next stage. // Give the user a chance to see progress completed before jumping to the next stage.
private final Runnable mDelayedFinishRunnable = new Runnable() { private final Runnable mDelayedFinishRunnable = new Runnable() {
@Override @Override