Merge "Support configurable multi-stage UDFPS enrollment" into sc-v2-dev
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
index 714e210..c2bcee3 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
@@ -18,6 +18,7 @@
 
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.app.Dialog;
 import android.app.settings.SettingsEnums;
@@ -58,6 +59,8 @@
 import com.google.android.setupcompat.template.FooterButton;
 import com.google.android.setupcompat.util.WizardManagerHelper;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 
 /**
@@ -69,11 +72,16 @@
     static final String TAG_SIDECAR = "sidecar";
 
     private static final int PROGRESS_BAR_MAX = 10000;
-    private static final int FINISH_DELAY = 250;
-    /**
-     * Enroll with two center touches before going to guided enrollment.
-     */
-    private static final int NUM_CENTER_TOUCHES = 2;
+
+    private 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_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
@@ -100,6 +108,7 @@
                     .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
                     .build();
 
+    private FingerprintManager mFingerprintManager;
     private boolean mCanAssumeUdfps;
     @Nullable private ProgressBar mProgressBar;
     private ObjectAnimator mProgressAnim;
@@ -125,9 +134,9 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        final FingerprintManager fingerprintManager = getSystemService(FingerprintManager.class);
+        mFingerprintManager = getSystemService(FingerprintManager.class);
         final List<FingerprintSensorPropertiesInternal> props =
-                fingerprintManager.getSensorPropertiesInternal();
+                mFingerprintManager.getSensorPropertiesInternal();
         mCanAssumeUdfps = props.size() == 1 && props.get(0).isAnyUdfpsType();
 
         mAccessibilityManager = getSystemService(AccessibilityManager.class);
@@ -273,7 +282,7 @@
             // UDFPS animations are owned by SystemUI
             if (progress >= PROGRESS_BAR_MAX) {
                 // Wait for any animations in SysUI to finish, then proceed to next page
-                getMainThreadHandler().postDelayed(mDelayedFinishRunnable, FINISH_DELAY);
+                getMainThreadHandler().postDelayed(mDelayedFinishRunnable, getFinishDelay());
             }
             return;
         }
@@ -300,8 +309,55 @@
     }
 
     private void updateTitleAndDescription() {
+        if (mCanAssumeUdfps) {
+            updateTitleAndDescriptionForUdfps();
+            return;
+        }
+
         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);
                 // Don't use BiometricEnrollBase#setHeaderText, since that invokes setTitle,
                 // which gets announced for a11y upon entering the page. For UDFPS, we want to
@@ -309,41 +365,61 @@
                 getLayout().setHeaderText(
                         R.string.security_settings_fingerprint_enroll_udfps_title);
                 setDescriptionText(R.string.security_settings_udfps_enroll_start_message);
-
                 final CharSequence description = getString(
                         R.string.security_settings_udfps_enroll_a11y);
                 getLayout().getHeaderTextView().setContentDescription(description);
                 setTitle(description);
-            } else {
-                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);
-            }
+                break;
         }
     }
 
-    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) {
             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
@@ -489,25 +565,29 @@
         mOrientationEventListener = null;
     }
 
-    private final Animator.AnimatorListener mProgressAnimationListener
-            = new Animator.AnimatorListener() {
+    private final Animator.AnimatorListener mProgressAnimationListener =
+            new Animator.AnimatorListener() {
 
-        @Override
-        public void onAnimationStart(Animator animation) { }
+                @Override
+                public void onAnimationStart(Animator animation) { }
 
-        @Override
-        public void onAnimationRepeat(Animator animation) { }
+                @Override
+                public void onAnimationRepeat(Animator animation) { }
 
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            if (mProgressBar.getProgress() >= PROGRESS_BAR_MAX) {
-                mProgressBar.postDelayed(mDelayedFinishRunnable, FINISH_DELAY);
-            }
-        }
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    if (mProgressBar.getProgress() >= PROGRESS_BAR_MAX) {
+                        mProgressBar.postDelayed(mDelayedFinishRunnable, getFinishDelay());
+                    }
+                }
 
-        @Override
-        public void onAnimationCancel(Animator animation) { }
-    };
+                @Override
+                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.
     private final Runnable mDelayedFinishRunnable = new Runnable() {