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:
@@ -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 (mCanAssumeUdfps) {
|
||||||
|
updateTitleAndDescriptionForUdfps();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (mSidecar == null || mSidecar.getEnrollmentSteps() == -1) {
|
if (mSidecar == null || mSidecar.getEnrollmentSteps() == -1) {
|
||||||
if (mCanAssumeUdfps) {
|
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,25 +565,29 @@ 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) { }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAnimationRepeat(Animator animation) { }
|
public void onAnimationRepeat(Animator animation) { }
|
||||||
|
|
||||||
@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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
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() {
|
||||||
|
Reference in New Issue
Block a user