fingerprint pre-enroll calibration

Add calibration interface and dialog

Test: Manually doing enrollment without any exception
Bug: 301226085
Change-Id: Ibe0d205e87f75bb1d5bd1c646deb8501d96a4a5a
diff --git a/aconfig/settings_biometrics_integration_declarations.aconfig b/aconfig/settings_biometrics_integration_declarations.aconfig
index 529e126..bc437f2 100644
--- a/aconfig/settings_biometrics_integration_declarations.aconfig
+++ b/aconfig/settings_biometrics_integration_declarations.aconfig
@@ -7,3 +7,10 @@
   bug: "288155127"
 }
 
+flag {
+  name: "udfps_enroll_calibration"
+  namespace: "biometrics_integration"
+  description: "This flag controls whether the fps enroll calibration feature should be enabled"
+  bug: "301226085"
+}
+
diff --git a/src/com/android/settings/biometrics/BiometricEnrollBase.java b/src/com/android/settings/biometrics/BiometricEnrollBase.java
index c9c8cff..7df1fe1 100644
--- a/src/com/android/settings/biometrics/BiometricEnrollBase.java
+++ b/src/com/android/settings/biometrics/BiometricEnrollBase.java
@@ -67,6 +67,7 @@
     public static final String EXTRA_FINISHED_ENROLL_FACE = "finished_enrolling_face";
     public static final String EXTRA_FINISHED_ENROLL_FINGERPRINT = "finished_enrolling_fingerprint";
     public static final String EXTRA_LAUNCHED_POSTURE_GUIDANCE = "launched_posture_guidance";
+    public static final String KEY_CALIBRATOR_UUID = "calibrator_uuid";
 
     /**
      * Used by the choose fingerprint wizard to indicate the wizard is
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
index cfd57d7..276845c 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
@@ -25,21 +25,27 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.util.Log;
 import android.view.OrientationEventListener;
 import android.view.Surface;
 import android.view.View;
-import android.view.View.OnClickListener;
 import android.view.accessibility.AccessibilityManager;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.lifecycle.Observer;
 
 import com.android.settings.R;
 import com.android.settings.Utils;
 import com.android.settings.biometrics.BiometricEnrollBase;
 import com.android.settings.biometrics.BiometricEnrollSidecar;
 import com.android.settings.biometrics.BiometricUtils;
+import com.android.settings.biometrics.fingerprint.UdfpsEnrollCalibrator.Result;
+import com.android.settings.biometrics.fingerprint.UdfpsEnrollCalibrator.Status;
+import com.android.settings.flags.Flags;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.password.ChooseLockSettingsHelper;
 import com.android.settingslib.widget.LottieColorUtils;
 import com.android.systemui.unfold.compat.ScreenSizeFoldProvider;
@@ -50,6 +56,7 @@
 import com.google.android.setupcompat.template.FooterButton;
 
 import java.util.List;
+import java.util.UUID;
 
 /**
  * Activity explaining the fingerprint sensor location for fingerprint enrollment.
@@ -76,6 +83,10 @@
     private ScreenSizeFoldProvider mScreenSizeFoldProvider;
     private boolean mIsFolded;
     private boolean mIsReverseDefaultRotation;
+    @Nullable
+    private UdfpsEnrollCalibrator mCalibrator;
+    @Nullable
+    private Observer<Status> mCalibratorStatusObserver;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -107,14 +118,6 @@
         if (mCanAssumeUdfps) {
             setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title);
             setDescriptionText(R.string.security_settings_udfps_enroll_find_sensor_message);
-            mFooterBarMixin.setPrimaryButton(
-                    new FooterButton.Builder(this)
-                    .setText(R.string.security_settings_udfps_enroll_find_sensor_start_button)
-                    .setListener(this::onStartButtonClick)
-                    .setButtonType(FooterButton.ButtonType.NEXT)
-                    .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
-                    .build()
-            );
 
             mIllustrationLottie = findViewById(R.id.illustration_lottie);
             AccessibilityManager am = getSystemService(AccessibilityManager.class);
@@ -167,12 +170,20 @@
 
         mAnimation = null;
         if (mCanAssumeUdfps) {
-            mIllustrationLottie.setOnClickListener(new OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    onStartButtonClick(v);
+            if (Flags.udfpsEnrollCalibration()) {
+                mCalibrator = FeatureFactory.getFeatureFactory().getFingerprintFeatureProvider()
+                        .getUdfpsEnrollCalibrator(
+                                (savedInstanceState != null)
+                                ? savedInstanceState.getParcelable(KEY_CALIBRATOR_UUID, UUID.class)
+                                : getIntent().getSerializableExtra(KEY_CALIBRATOR_UUID, UUID.class)
+                        );
+                if (mCalibrator == null
+                        || mCalibrator.getStatusLiveData().getValue() == Status.FINISHED) {
+                    enableUdfpsLottieAndNextButton();
                 }
-            });
+            } else {
+                enableUdfpsLottieAndNextButton();
+            }
         } else if (!mCanAssumeSfps) {
             View animationView = findViewById(R.id.fingerprint_sensor_location_animation);
             if (animationView instanceof FingerprintFindSensorAnimation) {
@@ -181,6 +192,20 @@
         }
     }
 
+    private void enableUdfpsLottieAndNextButton() {
+        mFooterBarMixin.setPrimaryButton(
+                new FooterButton.Builder(this)
+                        .setText(R.string.security_settings_udfps_enroll_find_sensor_start_button)
+                        .setListener(this::onStartButtonClick)
+                        .setButtonType(FooterButton.ButtonType.NEXT)
+                        .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
+                        .build()
+        );
+        if (mIllustrationLottie != null) {
+            mIllustrationLottie.setOnClickListener(this::onStartButtonClick);
+        }
+    }
+
     private int getRotationFromDefault(int rotation) {
         if (mIsReverseDefaultRotation) {
             return (rotation + 1) % 4;
@@ -258,6 +283,11 @@
     protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
         outState.putBoolean(SAVED_STATE_IS_NEXT_CLICKED, mNextClicked);
+        if (Flags.udfpsEnrollCalibration()) {
+            if (mCalibrator != null) {
+                outState.putSerializable(KEY_CALIBRATOR_UUID, mCalibrator.getUuid());
+            }
+        }
     }
 
     @Override
@@ -287,6 +317,39 @@
         if (mAnimation != null) {
             mAnimation.startAnimation();
         }
+        if (Flags.udfpsEnrollCalibration()) {
+            if (mCalibrator != null) {
+                final Status current = mCalibrator.getStatusLiveData().getValue();
+                if (current == Status.PROCESSING) {
+                    if (mCalibratorStatusObserver == null) {
+                        mCalibratorStatusObserver = status -> {
+                            if (status == Status.GOT_RESULT) {
+                                onGotCalibrationResult();
+                            }
+                        };
+                    }
+                    mCalibrator.getStatusLiveData().observe(this, mCalibratorStatusObserver);
+                } else if (current == Status.GOT_RESULT) {
+                    onGotCalibrationResult();
+                }
+            }
+        }
+    }
+
+    private void onGotCalibrationResult() {
+        if (Flags.udfpsEnrollCalibration()) {
+            if (mCalibrator != null) {
+                mCalibrator.setFinished();
+                if (mCalibrator.getResult() == Result.NEED_CALIBRATION) {
+                    UdfpsEnrollCalibrationDialog.newInstance(
+                            mCalibrator.getCalibrationDialogTitleTextId(),
+                            mCalibrator.getCalibrationDialogMessageTextId(),
+                            mCalibrator.getCalibrationDialogDismissButtonTextId()
+                    ).show(getSupportFragmentManager(), "findsensor-calibration-dialog");
+                }
+            }
+            new Handler(Looper.getMainLooper()).post(this::enableUdfpsLottieAndNextButton);
+        }
     }
 
     private void stopLookingForFingerprint() {
@@ -344,6 +407,12 @@
         if (mAnimation != null) {
             mAnimation.pauseAnimation();
         }
+        if (Flags.udfpsEnrollCalibration()) {
+            if (mCalibrator != null && mCalibratorStatusObserver != null) {
+                mCalibrator.getStatusLiveData().removeObserver(mCalibratorStatusObserver);
+                mCalibratorStatusObserver = null;
+            }
+        }
     }
 
     @Override
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
index df23a5c..dc3c65e 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
@@ -45,6 +45,8 @@
 import com.android.settings.biometrics.BiometricUtils;
 import com.android.settings.biometrics.GatekeeperPasswordProvider;
 import com.android.settings.biometrics.MultiBiometricEnrollHelper;
+import com.android.settings.flags.Flags;
+import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.password.ChooseLockSettingsHelper;
 import com.android.settingslib.HelpUtils;
 import com.android.settingslib.RestrictedLockUtilsInternal;
@@ -55,6 +57,7 @@
 import com.google.android.setupdesign.util.DeviceHelper;
 
 import java.util.List;
+import java.util.UUID;
 
 public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction {
 
@@ -67,6 +70,8 @@
 
     private DevicePolicyManager mDevicePolicyManager;
     private boolean mCanAssumeUdfps;
+    @Nullable
+    private UdfpsEnrollCalibrator mCalibrator;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -85,6 +90,16 @@
 
         mDevicePolicyManager = getSystemService(DevicePolicyManager.class);
 
+        if (Flags.udfpsEnrollCalibration()) {
+            mCalibrator = FeatureFactory.getFeatureFactory().getFingerprintFeatureProvider()
+                    .getUdfpsEnrollCalibrator(
+                            (savedInstanceState != null)
+                                    ? savedInstanceState.getParcelable(
+                                        KEY_CALIBRATOR_UUID, UUID.class)
+                                    : null
+                        );
+        }
+
         final ImageView iconFingerprint = findViewById(R.id.icon_fingerprint);
         final ImageView iconDeviceLocked = findViewById(R.id.icon_device_locked);
         final ImageView iconTrashCan = findViewById(R.id.icon_trash_can);
@@ -156,6 +171,16 @@
     }
 
     @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        if (Flags.udfpsEnrollCalibration()) {
+            if (mCalibrator != null) {
+                outState.putSerializable(KEY_CALIBRATOR_UUID, mCalibrator.getUuid());
+            }
+        }
+    }
+
+    @Override
     protected void initViews() {
         setDescriptionText(getString(
                 R.string.security_settings_fingerprint_enroll_introduction_v3_message,
@@ -364,6 +389,11 @@
             intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
                     BiometricUtils.getGatekeeperPasswordHandle(getIntent()));
         }
+        if (Flags.udfpsEnrollCalibration()) {
+            if (mCalibrator != null) {
+                intent.putExtra(KEY_CALIBRATOR_UUID, mCalibrator.getUuid());
+            }
+        }
         return intent;
     }
 
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProvider.java b/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProvider.java
index 906f95a..5a2bf8b 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProvider.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProvider.java
@@ -16,12 +16,24 @@
 
 package com.android.settings.biometrics.fingerprint;
 
+import androidx.annotation.Nullable;
+
 import com.android.settings.biometrics.fingerprint.feature.SfpsEnrollmentFeature;
 
+import java.util.UUID;
+
 public interface FingerprintFeatureProvider {
     /**
      * Gets the feature implementation of SFPS enrollment.
      * @return the feature implementation
      */
     SfpsEnrollmentFeature getSfpsEnrollmentFeature();
+
+    /**
+     * Gets calibrator to calibrate the FPS before enrolling udfps
+     * @param uuid unique id for passed between different activities
+     * @return udfps calibrator
+     */
+    @Nullable
+    UdfpsEnrollCalibrator getUdfpsEnrollCalibrator(@Nullable UUID uuid);
 }
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProviderImpl.java b/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProviderImpl.java
index 9745ca3..1baabc6 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProviderImpl.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintFeatureProviderImpl.java
@@ -21,6 +21,8 @@
 import com.android.settings.biometrics.fingerprint.feature.SfpsEnrollmentFeature;
 import com.android.settings.biometrics.fingerprint.feature.SfpsEnrollmentFeatureImpl;
 
+import java.util.UUID;
+
 public class FingerprintFeatureProviderImpl implements FingerprintFeatureProvider {
 
     @Nullable
@@ -33,4 +35,10 @@
         }
         return mSfpsEnrollmentFeatureImpl;
     }
+
+    @Nullable
+    @Override
+    public UdfpsEnrollCalibrator getUdfpsEnrollCalibrator(@Nullable UUID uuid) {
+        return null;
+    }
 }
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrationDialog.kt b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrationDialog.kt
new file mode 100644
index 0000000..892996a
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrationDialog.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 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.app.Dialog
+import android.content.DialogInterface
+import android.os.Bundle
+import androidx.annotation.StringRes
+import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.DialogFragment
+import com.android.settings.R
+
+class UdfpsEnrollCalibrationDialog : DialogFragment() {
+
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
+            AlertDialog.Builder(requireActivity(), R.style.Theme_AlertDialog)
+                    .setTitle(arguments!!.getInt(KEY_TITLE_TEXT_ID))
+                    .setMessage(arguments!!.getInt(KEY_MESSAGE_TEXT_ID))
+                    .setPositiveButton(arguments!!.getInt(KEY_DISMISS_BUTTON_TEXT_ID)) {
+                        dialog: DialogInterface?, _: Int -> dialog?.dismiss()
+                    }
+                    .create().also {
+                        isCancelable = false
+                    }
+
+    companion object {
+
+        private const val KEY_TITLE_TEXT_ID = "title_text_id"
+        private const val KEY_MESSAGE_TEXT_ID = "message_text_id"
+        private const val KEY_DISMISS_BUTTON_TEXT_ID = "dismiss_button_text_id"
+
+        @JvmStatic
+        fun newInstance(
+                @StringRes titleTextId: Int,
+                @StringRes messageTextId: Int,
+                @StringRes dismissButtonTextId: Int
+        ) = UdfpsEnrollCalibrationDialog().apply {
+            arguments = Bundle().apply {
+                putInt(KEY_TITLE_TEXT_ID, titleTextId)
+                putInt(KEY_MESSAGE_TEXT_ID, messageTextId)
+                putInt(KEY_DISMISS_BUTTON_TEXT_ID, dismissButtonTextId)
+            }
+        }
+    }
+}
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt
new file mode 100644
index 0000000..c0626d3
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt
@@ -0,0 +1,36 @@
+package com.android.settings.biometrics.fingerprint
+
+import androidx.annotation.StringRes
+import androidx.lifecycle.LiveData
+import java.util.UUID
+
+interface UdfpsEnrollCalibrator {
+
+    enum class Status {
+        PROCESSING,
+        GOT_RESULT,
+        FINISHED,
+    }
+
+    enum class Result {
+        NEED_CALIBRATION,
+        NO_NEED_CALIBRATION,
+    }
+
+    val uuid: UUID
+
+    val statusLiveData: LiveData<Status>
+
+    val result: Result?
+
+    fun setFinished()
+
+    @get:StringRes
+    val calibrationDialogTitleTextId: Int
+
+    @get:StringRes
+    val calibrationDialogMessageTextId: Int
+
+    @get:StringRes
+    val calibrationDialogDismissButtonTextId: Int
+}
\ No newline at end of file