• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.settings.biometrics.fingerprint;
18 
19 import static android.text.Layout.HYPHENATION_FREQUENCY_NONE;
20 
21 import android.animation.Animator;
22 import android.animation.ObjectAnimator;
23 import android.annotation.IntDef;
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.annotation.RawRes;
27 import android.app.Dialog;
28 import android.app.settings.SettingsEnums;
29 import android.content.Context;
30 import android.content.DialogInterface;
31 import android.content.Intent;
32 import android.content.res.ColorStateList;
33 import android.content.res.Configuration;
34 import android.content.res.Resources;
35 import android.graphics.PorterDuff;
36 import android.graphics.PorterDuffColorFilter;
37 import android.graphics.drawable.Animatable2;
38 import android.graphics.drawable.AnimatedVectorDrawable;
39 import android.graphics.drawable.Drawable;
40 import android.graphics.drawable.LayerDrawable;
41 import android.hardware.fingerprint.FingerprintManager;
42 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
43 import android.os.Bundle;
44 import android.os.Process;
45 import android.os.VibrationAttributes;
46 import android.os.VibrationEffect;
47 import android.os.Vibrator;
48 import android.text.TextUtils;
49 import android.util.Log;
50 import android.view.MotionEvent;
51 import android.view.OrientationEventListener;
52 import android.view.Surface;
53 import android.view.View;
54 import android.view.accessibility.AccessibilityEvent;
55 import android.view.accessibility.AccessibilityManager;
56 import android.view.animation.AccelerateDecelerateInterpolator;
57 import android.view.animation.AnimationUtils;
58 import android.view.animation.Interpolator;
59 import android.widget.ProgressBar;
60 import android.widget.RelativeLayout;
61 import android.widget.TextView;
62 import android.widget.Toast;
63 
64 import androidx.annotation.IdRes;
65 import androidx.appcompat.app.AlertDialog;
66 
67 import com.android.internal.annotations.VisibleForTesting;
68 import com.android.settings.R;
69 import com.android.settings.biometrics.BiometricEnrollSidecar;
70 import com.android.settings.biometrics.BiometricUtils;
71 import com.android.settings.biometrics.BiometricsEnrollEnrolling;
72 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
73 import com.android.settingslib.display.DisplayDensityUtils;
74 
75 import com.airbnb.lottie.LottieAnimationView;
76 import com.airbnb.lottie.LottieCompositionFactory;
77 import com.airbnb.lottie.LottieProperty;
78 import com.airbnb.lottie.model.KeyPath;
79 import com.google.android.setupcompat.template.FooterBarMixin;
80 import com.google.android.setupcompat.template.FooterButton;
81 import com.google.android.setupcompat.util.WizardManagerHelper;
82 import com.google.android.setupdesign.template.DescriptionMixin;
83 import com.google.android.setupdesign.template.HeaderMixin;
84 
85 import java.lang.annotation.Retention;
86 import java.lang.annotation.RetentionPolicy;
87 import java.util.List;
88 
89 /**
90  * Activity which handles the actual enrolling for fingerprint.
91  */
92 public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
93 
94     private static final String TAG = "FingerprintEnrollEnrolling";
95     static final String TAG_SIDECAR = "sidecar";
96     static final String TAG_UDFPS_HELPER = "udfps_helper";
97     static final String ICON_TOUCH_DIALOG = "fps_icon_touch_dialog";
98     static final String KEY_STATE_CANCELED = "is_canceled";
99     static final String KEY_STATE_PREVIOUS_ROTATION = "previous_rotation";
100 
101     private static final int PROGRESS_BAR_MAX = 10000;
102 
103     private static final int STAGE_UNKNOWN = -1;
104     private static final int STAGE_CENTER = 0;
105     private static final int STAGE_GUIDED = 1;
106     private static final int STAGE_FINGERTIP = 2;
107     private static final int STAGE_LEFT_EDGE = 3;
108     private static final int STAGE_RIGHT_EDGE = 4;
109 
110     @VisibleForTesting
111     protected static final int SFPS_STAGE_NO_ANIMATION = 0;
112 
113     @VisibleForTesting
114     protected static final int SFPS_STAGE_CENTER = 1;
115 
116     @VisibleForTesting
117     protected static final int SFPS_STAGE_FINGERTIP = 2;
118 
119     @VisibleForTesting
120     protected static final int SFPS_STAGE_LEFT_EDGE = 3;
121 
122     @VisibleForTesting
123     protected static final int SFPS_STAGE_RIGHT_EDGE = 4;
124 
125     @IntDef({STAGE_UNKNOWN, STAGE_CENTER, STAGE_GUIDED, STAGE_FINGERTIP, STAGE_LEFT_EDGE,
126             STAGE_RIGHT_EDGE})
127     @Retention(RetentionPolicy.SOURCE)
128     private @interface EnrollStage {}
129 
130 
131     @VisibleForTesting
132     @IntDef({STAGE_UNKNOWN, SFPS_STAGE_NO_ANIMATION, SFPS_STAGE_CENTER, SFPS_STAGE_FINGERTIP,
133             SFPS_STAGE_LEFT_EDGE, SFPS_STAGE_RIGHT_EDGE})
134     @Retention(RetentionPolicy.SOURCE)
135     protected @interface SfpsEnrollStage {}
136 
137     /**
138      * If we don't see progress during this time, we show an error message to remind the users that
139      * they need to lift the finger and touch again.
140      */
141     private static final int HINT_TIMEOUT_DURATION = 2500;
142 
143     /**
144      * How long the user needs to touch the icon until we show the dialog.
145      */
146     private static final long ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN = 500;
147 
148     /**
149      * How many times the user needs to touch the icon until we show the dialog that this is not the
150      * fingerprint sensor.
151      */
152     private static final int ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN = 3;
153 
154     private static final VibrationEffect VIBRATE_EFFECT_ERROR =
155             VibrationEffect.createWaveform(new long[] {0, 5, 55, 60}, -1);
156     private static final VibrationAttributes FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES =
157             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ACCESSIBILITY);
158 
159     private FingerprintManager mFingerprintManager;
160     private boolean mCanAssumeUdfps;
161     private boolean mCanAssumeSfps;
162     @Nullable private ProgressBar mProgressBar;
163     @VisibleForTesting
164     @Nullable
165     UdfpsEnrollHelper mUdfpsEnrollHelper;
166     private ObjectAnimator mProgressAnim;
167     private TextView mErrorText;
168     private Interpolator mFastOutSlowInInterpolator;
169     private Interpolator mLinearOutSlowInInterpolator;
170     private Interpolator mFastOutLinearInInterpolator;
171     private int mIconTouchCount;
172     private boolean mAnimationCancelled;
173     @Nullable private AnimatedVectorDrawable mIconAnimationDrawable;
174     @Nullable private AnimatedVectorDrawable mIconBackgroundBlinksDrawable;
175     private boolean mRestoring;
176     private Vibrator mVibrator;
177     private boolean mIsSetupWizard;
178     @VisibleForTesting
179     boolean mIsCanceled;
180     private AccessibilityManager mAccessibilityManager;
181     private boolean mIsAccessibilityEnabled;
182     private LottieAnimationView mIllustrationLottie;
183     private boolean mHaveShownUdfpsTipLottie;
184     private boolean mHaveShownUdfpsLeftEdgeLottie;
185     private boolean mHaveShownUdfpsRightEdgeLottie;
186     private boolean mHaveShownUdfpsCenterLottie;
187     private boolean mHaveShownUdfpsGuideLottie;
188     private boolean mHaveShownSfpsNoAnimationLottie;
189     private boolean mHaveShownSfpsCenterLottie;
190     private boolean mHaveShownSfpsTipLottie;
191     private boolean mHaveShownSfpsLeftEdgeLottie;
192     private boolean mHaveShownSfpsRightEdgeLottie;
193     private boolean mShouldShowLottie;
194 
195     private ObjectAnimator mHelpAnimation;
196 
197     private OrientationEventListener mOrientationEventListener;
198     private int mPreviousRotation = 0;
199 
200     @VisibleForTesting
shouldShowLottie()201     protected boolean shouldShowLottie() {
202         DisplayDensityUtils displayDensity = new DisplayDensityUtils(getApplicationContext());
203         int currentDensityIndex = displayDensity.getCurrentIndexForDefaultDisplay();
204         final int currentDensity = displayDensity.getDefaultDisplayDensityValues()
205                 [currentDensityIndex];
206         final int defaultDensity = displayDensity.getDefaultDensityForDefaultDisplay();
207         return defaultDensity == currentDensity;
208     }
209 
210     @Override
onApplyThemeResource(Resources.Theme theme, int resid, boolean first)211     protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) {
212         theme.applyStyle(R.style.SetupWizardPartnerResource, true);
213         super.onApplyThemeResource(theme, resid, first);
214     }
215 
216     @Override
onCreate(Bundle savedInstanceState)217     protected void onCreate(Bundle savedInstanceState) {
218         super.onCreate(savedInstanceState);
219 
220         if (isInMultiWindowMode()) {
221             final Toast splitUnsupportedToast = Toast.makeText(this,
222                     R.string.dock_multi_instances_not_supported_text, Toast.LENGTH_SHORT);
223             splitUnsupportedToast.show();
224             finish();
225             return;
226         }
227 
228         if (savedInstanceState != null) {
229             restoreSavedState(savedInstanceState);
230         }
231         mFingerprintManager = getSystemService(FingerprintManager.class);
232         final List<FingerprintSensorPropertiesInternal> props =
233                 mFingerprintManager.getSensorPropertiesInternal();
234         mCanAssumeUdfps = props != null && props.size() == 1 && props.get(0).isAnyUdfpsType();
235         mCanAssumeSfps = props != null && props.size() == 1 && props.get(0).isAnySidefpsType();
236 
237         mAccessibilityManager = getSystemService(AccessibilityManager.class);
238         mIsAccessibilityEnabled = mAccessibilityManager.isEnabled();
239 
240         listenOrientationEvent();
241 
242         if (mCanAssumeUdfps) {
243             final UdfpsEnrollEnrollingView layout =
244                     (UdfpsEnrollEnrollingView) getLayoutInflater().inflate(
245                             R.layout.udfps_enroll_enrolling, null, false);
246             setUdfpsEnrollHelper();
247             layout.initView(props.get(0), mUdfpsEnrollHelper, mAccessibilityManager);
248 
249             setContentView(layout);
250             setDescriptionText(R.string.security_settings_udfps_enroll_start_message);
251         } else if (mCanAssumeSfps) {
252             setContentView(R.layout.sfps_enroll_enrolling);
253             setHelpAnimation();
254         } else {
255             setContentView(R.layout.fingerprint_enroll_enrolling);
256             setDescriptionText(R.string.security_settings_fingerprint_enroll_start_message);
257         }
258 
259         mIsSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent());
260         if (mCanAssumeUdfps || mCanAssumeSfps) {
261             updateTitleAndDescription();
262         } else {
263             setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title);
264         }
265 
266         mShouldShowLottie = shouldShowLottie();
267         // On non-SFPS devices, only show the lottie if the current display density is the default
268         // density. Otherwise, the lottie will overlap with the settings header text.
269         boolean isLandscape = BiometricUtils.isReverseLandscape(getApplicationContext())
270                 || BiometricUtils.isLandscape(getApplicationContext());
271 
272         updateOrientation((isLandscape
273                 ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT));
274 
275         mErrorText = findViewById(R.id.error_text);
276         mProgressBar = findViewById(R.id.fingerprint_progress_bar);
277         mVibrator = getSystemService(Vibrator.class);
278 
279         mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class);
280         mFooterBarMixin.setSecondaryButton(
281                 new FooterButton.Builder(this)
282                         .setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
283                         .setListener(this::onSkipButtonClick)
284                         .setButtonType(FooterButton.ButtonType.SKIP)
285                         .setTheme(R.style.SudGlifButton_Secondary)
286                         .build()
287         );
288 
289         // If it's udfps, set the background color only for secondary button if necessary.
290         if (mCanAssumeUdfps) {
291             mShouldSetFooterBarBackground = false;
292             ((UdfpsEnrollEnrollingView) getLayout()).setSecondaryButtonBackground(
293                     getBackgroundColor());
294         }
295 
296         final LayerDrawable fingerprintDrawable = mProgressBar != null
297                 ? (LayerDrawable) mProgressBar.getBackground() : null;
298         if (fingerprintDrawable != null) {
299             mIconAnimationDrawable = (AnimatedVectorDrawable)
300                     fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_animation);
301             mIconBackgroundBlinksDrawable = (AnimatedVectorDrawable)
302                     fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_background);
303             mIconAnimationDrawable.registerAnimationCallback(mIconAnimationCallback);
304         }
305 
306         mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(
307                 this, android.R.interpolator.fast_out_slow_in);
308         mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
309                 this, android.R.interpolator.linear_out_slow_in);
310         mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(
311                 this, android.R.interpolator.fast_out_linear_in);
312         if (mProgressBar != null) {
313             mProgressBar.setProgressBackgroundTintMode(PorterDuff.Mode.SRC);
314             mProgressBar.setOnTouchListener((v, event) -> {
315                 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
316                     mIconTouchCount++;
317                     if (mIconTouchCount == ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN) {
318                         showIconTouchDialog();
319                     } else {
320                         mProgressBar.postDelayed(mShowDialogRunnable,
321                                 ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN);
322                     }
323                 } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL
324                         || event.getActionMasked() == MotionEvent.ACTION_UP) {
325                     mProgressBar.removeCallbacks(mShowDialogRunnable);
326                 }
327                 return true;
328             });
329         }
330 
331         final Configuration config = getApplicationContext().getResources().getConfiguration();
332         maybeHideSfpsText(config);
333     }
334 
setHelpAnimation()335     private void setHelpAnimation() {
336         final float translationX = 40;
337         final int duration = 550;
338         final RelativeLayout progressLottieLayout = findViewById(R.id.progress_lottie);
339         mHelpAnimation = ObjectAnimator.ofFloat(progressLottieLayout,
340                 "translationX" /* propertyName */,
341                 0, translationX, -1 * translationX, translationX, 0f);
342         mHelpAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
343         mHelpAnimation.setDuration(duration);
344         mHelpAnimation.setAutoCancel(false);
345     }
346     @Override
getSidecar()347     protected BiometricEnrollSidecar getSidecar() {
348         final FingerprintEnrollSidecar sidecar = new FingerprintEnrollSidecar(this,
349                 FingerprintManager.ENROLL_ENROLL);
350         return sidecar;
351     }
352 
353     @Override
shouldStartAutomatically()354     protected boolean shouldStartAutomatically() {
355         if (mCanAssumeUdfps) {
356             // Continue enrollment if restoring (e.g. configuration changed). Otherwise, wait
357             // for the entry animation to complete before starting.
358             return mRestoring && !mIsCanceled;
359         }
360         return true;
361     }
362 
363     @Override
onSaveInstanceState(Bundle outState)364     protected void onSaveInstanceState(Bundle outState) {
365         super.onSaveInstanceState(outState);
366         outState.putBoolean(KEY_STATE_CANCELED, mIsCanceled);
367         outState.putInt(KEY_STATE_PREVIOUS_ROTATION, mPreviousRotation);
368     }
369 
restoreSavedState(Bundle savedInstanceState)370     private void restoreSavedState(Bundle savedInstanceState) {
371         mRestoring = true;
372         mIsCanceled = savedInstanceState.getBoolean(KEY_STATE_CANCELED, false);
373         mPreviousRotation = savedInstanceState.getInt(KEY_STATE_PREVIOUS_ROTATION,
374                 getDisplay().getRotation());
375     }
376 
377     @Override
onStart()378     protected void onStart() {
379         super.onStart();
380         updateProgress(false /* animate */);
381         updateTitleAndDescription();
382         if (mRestoring) {
383             startIconAnimation();
384         }
385     }
386 
387     @Override
onEnterAnimationComplete()388     public void onEnterAnimationComplete() {
389         super.onEnterAnimationComplete();
390 
391         if (mCanAssumeUdfps) {
392             startEnrollment();
393         }
394 
395         mAnimationCancelled = false;
396         startIconAnimation();
397     }
398 
startIconAnimation()399     private void startIconAnimation() {
400         if (mIconAnimationDrawable != null) {
401             mIconAnimationDrawable.start();
402         }
403     }
404 
stopIconAnimation()405     private void stopIconAnimation() {
406         mAnimationCancelled = true;
407         if (mIconAnimationDrawable != null) {
408             mIconAnimationDrawable.stop();
409         }
410     }
411 
412     @VisibleForTesting
onCancelEnrollment(@dRes int errorMsgId)413     void onCancelEnrollment(@IdRes int errorMsgId) {
414         // showErrorDialog() will cause onWindowFocusChanged(false), set mIsCanceled to false
415         // before showErrorDialog() to prevent that another error dialog is triggered again.
416         mIsCanceled = true;
417         FingerprintErrorDialog.showErrorDialog(this, errorMsgId,
418                 this instanceof SetupFingerprintEnrollEnrolling);
419         cancelEnrollment();
420         stopIconAnimation();
421         stopListenOrientationEvent();
422         if (!mCanAssumeUdfps) {
423             mErrorText.removeCallbacks(mTouchAgainRunnable);
424         }
425     }
426 
427     @Override
onStop()428     protected void onStop() {
429         if (!isChangingConfigurations()) {
430             if (!WizardManagerHelper.isAnySetupWizard(getIntent())
431                     && !BiometricUtils.isAnyMultiBiometricFlow(this)
432                     && !mFromSettingsSummary) {
433                 setResult(RESULT_TIMEOUT);
434             }
435             finish();
436         }
437         stopIconAnimation();
438 
439         super.onStop();
440     }
441 
442     @Override
shouldFinishWhenBackgrounded()443     protected boolean shouldFinishWhenBackgrounded() {
444         // Prevent super.onStop() from finishing, since we handle this in our onStop().
445         return false;
446     }
447 
448     @Override
onDestroy()449     protected void onDestroy() {
450         stopListenOrientationEvent();
451         super.onDestroy();
452     }
453 
animateProgress(int progress)454     private void animateProgress(int progress) {
455         if (mCanAssumeUdfps) {
456             // UDFPS animations are owned by SystemUI
457             if (progress >= PROGRESS_BAR_MAX) {
458                 // Wait for any animations in SysUI to finish, then proceed to next page
459                 getMainThreadHandler().postDelayed(mDelayedFinishRunnable, getFinishDelay());
460             }
461             return;
462         }
463         if (mProgressAnim != null) {
464             mProgressAnim.cancel();
465         }
466         ObjectAnimator anim = ObjectAnimator.ofInt(mProgressBar, "progress",
467                 mProgressBar.getProgress(), progress);
468         anim.addListener(mProgressAnimationListener);
469         anim.setInterpolator(mFastOutSlowInInterpolator);
470         anim.setDuration(250);
471         anim.start();
472         mProgressAnim = anim;
473     }
474 
animateFlash()475     private void animateFlash() {
476         if (mIconBackgroundBlinksDrawable != null) {
477             mIconBackgroundBlinksDrawable.start();
478         }
479     }
480 
getFinishIntent()481     protected Intent getFinishIntent() {
482         return new Intent(this, FingerprintEnrollFinish.class);
483     }
484 
updateTitleAndDescription()485     private void updateTitleAndDescription() {
486         if (mCanAssumeUdfps) {
487             updateTitleAndDescriptionForUdfps();
488             return;
489         } else if (mCanAssumeSfps) {
490             updateTitleAndDescriptionForSfps();
491             return;
492         }
493 
494         if (mSidecar == null || mSidecar.getEnrollmentSteps() == -1) {
495             setDescriptionText(R.string.security_settings_fingerprint_enroll_start_message);
496         } else {
497             setDescriptionText(R.string.security_settings_fingerprint_enroll_repeat_message);
498         }
499     }
500 
updateTitleAndDescriptionForUdfps()501     private void updateTitleAndDescriptionForUdfps() {
502         switch (getCurrentStage()) {
503             case STAGE_CENTER:
504                 setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title);
505                 if (mIsAccessibilityEnabled || mIllustrationLottie == null) {
506                     setDescriptionText(R.string.security_settings_udfps_enroll_start_message);
507                 } else if (!mHaveShownUdfpsCenterLottie && mIllustrationLottie != null) {
508                     mHaveShownUdfpsCenterLottie = true;
509                     // Note: Update string reference when differentiate in between udfps & sfps
510                     mIllustrationLottie.setContentDescription(
511                             getString(R.string.security_settings_sfps_enroll_finger_center_title)
512                     );
513                     configureEnrollmentStage(R.raw.udfps_center_hint_lottie);
514                 }
515                 break;
516 
517             case STAGE_GUIDED:
518                 setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title);
519                 if (mIsAccessibilityEnabled || mIllustrationLottie == null) {
520                     setDescriptionText(R.string.security_settings_udfps_enroll_repeat_a11y_message);
521                 } else if (!mHaveShownUdfpsGuideLottie && mIllustrationLottie != null) {
522                     mHaveShownUdfpsGuideLottie = true;
523                     mIllustrationLottie.setContentDescription(
524                             getString(R.string.security_settings_fingerprint_enroll_repeat_message)
525                     );
526                     // TODO(b/228100413) Could customize guided lottie animation
527                     configureEnrollmentStage(R.raw.udfps_center_hint_lottie);
528                 }
529                 break;
530             case STAGE_FINGERTIP:
531                 setHeaderText(R.string.security_settings_udfps_enroll_fingertip_title);
532                 if (!mHaveShownUdfpsTipLottie && mIllustrationLottie != null) {
533                     mHaveShownUdfpsTipLottie = true;
534                     mIllustrationLottie.setContentDescription(
535                             getString(R.string.security_settings_udfps_tip_fingerprint_help)
536                     );
537                     configureEnrollmentStage(R.raw.udfps_tip_hint_lottie);
538                 }
539                 break;
540             case STAGE_LEFT_EDGE:
541                 setHeaderText(R.string.security_settings_udfps_enroll_left_edge_title);
542                 if (!mHaveShownUdfpsLeftEdgeLottie && mIllustrationLottie != null) {
543                     mHaveShownUdfpsLeftEdgeLottie = true;
544                     mIllustrationLottie.setContentDescription(
545                             getString(R.string.security_settings_udfps_side_fingerprint_help)
546                     );
547                     configureEnrollmentStage(R.raw.udfps_left_edge_hint_lottie);
548                 } else if (mIllustrationLottie == null) {
549                     if (isStageHalfCompleted()) {
550                         setDescriptionText(
551                                 R.string.security_settings_fingerprint_enroll_repeat_message);
552                     } else {
553                         setDescriptionText(R.string.security_settings_udfps_enroll_edge_message);
554                     }
555                 }
556                 break;
557             case STAGE_RIGHT_EDGE:
558                 setHeaderText(R.string.security_settings_udfps_enroll_right_edge_title);
559                 if (!mHaveShownUdfpsRightEdgeLottie && mIllustrationLottie != null) {
560                     mHaveShownUdfpsRightEdgeLottie = true;
561                     mIllustrationLottie.setContentDescription(
562                             getString(R.string.security_settings_udfps_side_fingerprint_help)
563                     );
564                     configureEnrollmentStage(R.raw.udfps_right_edge_hint_lottie);
565 
566                 } else if (mIllustrationLottie == null) {
567                     if (isStageHalfCompleted()) {
568                         setDescriptionText(
569                                 R.string.security_settings_fingerprint_enroll_repeat_message);
570                     } else {
571                         setDescriptionText(R.string.security_settings_udfps_enroll_edge_message);
572                     }
573                 }
574                 break;
575 
576             case STAGE_UNKNOWN:
577             default:
578                 // setHeaderText(R.string.security_settings_fingerprint_enroll_udfps_title);
579                 // Don't use BiometricEnrollBase#setHeaderText, since that invokes setTitle,
580                 // which gets announced for a11y upon entering the page. For UDFPS, we want to
581                 // announce a different string for a11y upon entering the page.
582                 getLayout().setHeaderText(
583                         R.string.security_settings_fingerprint_enroll_udfps_title);
584                 setDescriptionText(R.string.security_settings_udfps_enroll_start_message);
585                 final CharSequence description = getString(
586                         R.string.security_settings_udfps_enroll_a11y);
587                 getLayout().getHeaderTextView().setContentDescription(description);
588                 setTitle(description);
589                 break;
590 
591         }
592     }
593 
594     // Interrupt any existing talkback speech to prevent stacking talkback messages
clearTalkback()595     private void clearTalkback() {
596         AccessibilityManager.getInstance(getApplicationContext()).interrupt();
597     }
598 
updateTitleAndDescriptionForSfps()599     private void updateTitleAndDescriptionForSfps() {
600         if (mIsAccessibilityEnabled) {
601             clearTalkback();
602             getLayout().getDescriptionTextView().setAccessibilityLiveRegion(
603                     View.ACCESSIBILITY_LIVE_REGION_POLITE);
604         }
605         switch (getCurrentSfpsStage()) {
606             case SFPS_STAGE_NO_ANIMATION:
607                 setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title);
608                 if (!mHaveShownSfpsNoAnimationLottie && mIllustrationLottie != null) {
609                     mHaveShownSfpsNoAnimationLottie = true;
610                     mIllustrationLottie.setContentDescription(
611                             getString(
612                                     R.string.security_settings_sfps_animation_a11y_label,
613                                     0
614                             )
615                     );
616                     configureEnrollmentStage(R.raw.sfps_lottie_no_animation);
617                 }
618                 break;
619 
620             case SFPS_STAGE_CENTER:
621                 setHeaderText(R.string.security_settings_sfps_enroll_finger_center_title);
622                 if (!mHaveShownSfpsCenterLottie && mIllustrationLottie != null) {
623                     mHaveShownSfpsCenterLottie = true;
624                     configureEnrollmentStage(R.raw.sfps_lottie_pad_center);
625                 }
626                 break;
627 
628             case SFPS_STAGE_FINGERTIP:
629                 setHeaderText(R.string.security_settings_sfps_enroll_fingertip_title);
630                 if (!mHaveShownSfpsTipLottie && mIllustrationLottie != null) {
631                     mHaveShownSfpsTipLottie = true;
632                     configureEnrollmentStage(R.raw.sfps_lottie_tip);
633                 }
634                 break;
635 
636             case SFPS_STAGE_LEFT_EDGE:
637                 setHeaderText(R.string.security_settings_sfps_enroll_left_edge_title);
638                 if (!mHaveShownSfpsLeftEdgeLottie && mIllustrationLottie != null) {
639                     mHaveShownSfpsLeftEdgeLottie = true;
640                     configureEnrollmentStage(R.raw.sfps_lottie_left_edge);
641                 }
642                 break;
643 
644             case SFPS_STAGE_RIGHT_EDGE:
645                 setHeaderText(R.string.security_settings_sfps_enroll_right_edge_title);
646                 if (!mHaveShownSfpsRightEdgeLottie && mIllustrationLottie != null) {
647                     mHaveShownSfpsRightEdgeLottie = true;
648                     configureEnrollmentStage(R.raw.sfps_lottie_right_edge);
649                 }
650                 break;
651 
652             case STAGE_UNKNOWN:
653             default:
654                 // Don't use BiometricEnrollBase#setHeaderText, since that invokes setTitle,
655                 // which gets announced for a11y upon entering the page. For SFPS, we want to
656                 // announce a different string for a11y upon entering the page.
657                 getLayout().setHeaderText(
658                         R.string.security_settings_sfps_enroll_find_sensor_title);
659                 final CharSequence description = getString(
660                         R.string.security_settings_sfps_enroll_find_sensor_message);
661                 getLayout().getHeaderTextView().setContentDescription(description);
662                 setTitle(description);
663                 break;
664 
665         }
666     }
667 
configureEnrollmentStage(@awRes int lottie)668     @VisibleForTesting void configureEnrollmentStage(@RawRes int lottie) {
669         if (!mCanAssumeSfps) {
670             setDescriptionText("");
671         }
672         LottieCompositionFactory.fromRawRes(this, lottie)
673                 .addListener((c) -> {
674                     mIllustrationLottie.setComposition(c);
675                     mIllustrationLottie.setVisibility(View.VISIBLE);
676                     mIllustrationLottie.playAnimation();
677                 });
678     }
679 
680     @EnrollStage
getCurrentStage()681     private int getCurrentStage() {
682         if (mSidecar == null || mSidecar.getEnrollmentSteps() == -1) {
683             return STAGE_UNKNOWN;
684         }
685 
686         final int progressSteps = mSidecar.getEnrollmentSteps() - mSidecar.getEnrollmentRemaining();
687         if (progressSteps < getStageThresholdSteps(0)) {
688             return STAGE_CENTER;
689         } else if (progressSteps < getStageThresholdSteps(1)) {
690             return STAGE_GUIDED;
691         } else if (progressSteps < getStageThresholdSteps(2)) {
692             return STAGE_FINGERTIP;
693         } else if (progressSteps < getStageThresholdSteps(3)) {
694             return STAGE_LEFT_EDGE;
695         } else {
696             return STAGE_RIGHT_EDGE;
697         }
698     }
699 
700     @SfpsEnrollStage
getCurrentSfpsStage()701     private int getCurrentSfpsStage() {
702         if (mSidecar == null) {
703             return STAGE_UNKNOWN;
704         }
705 
706         final int progressSteps = mSidecar.getEnrollmentSteps() - mSidecar.getEnrollmentRemaining();
707         if (progressSteps < getStageThresholdSteps(0)) {
708             return SFPS_STAGE_NO_ANIMATION;
709         } else if (progressSteps < getStageThresholdSteps(1)) {
710             return SFPS_STAGE_CENTER;
711         } else if (progressSteps < getStageThresholdSteps(2)) {
712             return SFPS_STAGE_FINGERTIP;
713         } else if (progressSteps < getStageThresholdSteps(3)) {
714             return SFPS_STAGE_LEFT_EDGE;
715         } else {
716             return SFPS_STAGE_RIGHT_EDGE;
717         }
718     }
719 
isStageHalfCompleted()720     private boolean isStageHalfCompleted() {
721         // Prior to first enrollment step.
722         if (mSidecar == null || mSidecar.getEnrollmentSteps() == -1) {
723             return false;
724         }
725 
726         final int progressSteps = mSidecar.getEnrollmentSteps() - mSidecar.getEnrollmentRemaining();
727         int prevThresholdSteps = 0;
728         for (int i = 0; i < mFingerprintManager.getEnrollStageCount(); i++) {
729             final int thresholdSteps = getStageThresholdSteps(i);
730             if (progressSteps >= prevThresholdSteps && progressSteps < thresholdSteps) {
731                 final int adjustedProgress = progressSteps - prevThresholdSteps;
732                 final int adjustedThreshold = thresholdSteps - prevThresholdSteps;
733                 return adjustedProgress >= adjustedThreshold / 2;
734             }
735             prevThresholdSteps = thresholdSteps;
736         }
737 
738         // After last enrollment step.
739         return true;
740     }
741 
742     @VisibleForTesting
getStageThresholdSteps(int index)743     protected int getStageThresholdSteps(int index) {
744         if (mSidecar == null || mSidecar.getEnrollmentSteps() == -1) {
745             Log.w(TAG, "getStageThresholdSteps: Enrollment not started yet");
746             return 1;
747         }
748         return Math.round(mSidecar.getEnrollmentSteps()
749                 * mFingerprintManager.getEnrollStageThreshold(index));
750     }
751 
752     @Override
onEnrollmentHelp(int helpMsgId, CharSequence helpString)753     public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
754         if (!TextUtils.isEmpty(helpString)) {
755             if (!(mCanAssumeUdfps || mCanAssumeSfps)) {
756                 mErrorText.removeCallbacks(mTouchAgainRunnable);
757             }
758             showError(helpString);
759 
760             if (mUdfpsEnrollHelper != null) mUdfpsEnrollHelper.onEnrollmentHelp();
761         }
762 
763         dismissTouchDialogIfSfps();
764     }
765 
766     @Override
onEnrollmentError(int errMsgId, CharSequence errString)767     public void onEnrollmentError(int errMsgId, CharSequence errString) {
768         onCancelEnrollment(errMsgId);
769         dismissTouchDialogIfSfps();
770     }
771 
announceEnrollmentProgress(CharSequence announcement)772     private void announceEnrollmentProgress(CharSequence announcement) {
773         AccessibilityEvent e = AccessibilityEvent.obtain();
774         e.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT);
775         e.setClassName(getClass().getName());
776         e.setPackageName(getPackageName());
777         e.getText().add(announcement);
778         mAccessibilityManager.sendAccessibilityEvent(e);
779     }
780 
781     @Override
onEnrollmentProgressChange(int steps, int remaining)782     public void onEnrollmentProgressChange(int steps, int remaining) {
783         updateProgress(true /* animate */);
784         final int percent = (int) (((float) (steps - remaining) / (float) steps) * 100);
785         if (mCanAssumeSfps && mIsAccessibilityEnabled) {
786             CharSequence announcement = getString(
787                     R.string.security_settings_sfps_enroll_progress_a11y_message, percent);
788             announceEnrollmentProgress(announcement);
789             if (mIllustrationLottie != null) {
790                 mIllustrationLottie.setContentDescription(
791                         getString(
792                                 R.string.security_settings_sfps_animation_a11y_label,
793                                 percent)
794                 );
795             }
796         }
797         updateTitleAndDescription();
798         animateFlash();
799         if (mCanAssumeUdfps) {
800             if (mIsAccessibilityEnabled) {
801                 CharSequence announcement = getString(
802                         R.string.security_settings_udfps_enroll_progress_a11y_message, percent);
803                 announceEnrollmentProgress(announcement);
804             }
805         } else if (!mCanAssumeSfps) {
806             mErrorText.removeCallbacks(mTouchAgainRunnable);
807             mErrorText.postDelayed(mTouchAgainRunnable, HINT_TIMEOUT_DURATION);
808         }
809         dismissTouchDialogIfSfps();
810     }
811 
dismissTouchDialogIfSfps()812     private void dismissTouchDialogIfSfps() {
813         if (!mCanAssumeSfps) {
814             return;
815         }
816         final IconTouchDialog dialog = (IconTouchDialog)
817                 getSupportFragmentManager().findFragmentByTag(ICON_TOUCH_DIALOG);
818         if (dialog != null && dialog.isResumed()) {
819             dialog.dismiss();
820         }
821     }
822 
823     @Override
onAcquired(boolean isAcquiredGood)824     public void onAcquired(boolean isAcquiredGood) {
825         if (mUdfpsEnrollHelper != null) {
826             mUdfpsEnrollHelper.onAcquired(isAcquiredGood);
827         }
828     }
829 
830     @Override
onUdfpsPointerDown(int sensorId)831     public void onUdfpsPointerDown(int sensorId) {
832         if (mUdfpsEnrollHelper != null) {
833             mUdfpsEnrollHelper.onPointerDown(sensorId);
834         }
835     }
836 
837     @Override
onUdfpsPointerUp(int sensorId)838     public void onUdfpsPointerUp(int sensorId) {
839         if (mUdfpsEnrollHelper != null) {
840             mUdfpsEnrollHelper.onPointerUp(sensorId);
841         }
842     }
843 
844     @Override
onUdfpsOverlayShown()845     public void onUdfpsOverlayShown() {
846         if (mCanAssumeUdfps) {
847             findViewById(R.id.udfps_animation_view).setVisibility(View.VISIBLE);
848         }
849     }
850 
updateProgress(boolean animate)851     private void updateProgress(boolean animate) {
852         if (mSidecar == null || !mSidecar.isEnrolling()) {
853             Log.d(TAG, "Enrollment not started yet");
854             return;
855         }
856 
857         int progress = getProgress(
858                 mSidecar.getEnrollmentSteps(), mSidecar.getEnrollmentRemaining());
859         // Only clear the error when progress has been made.
860         // TODO (b/234772728) Add tests.
861         if (mProgressBar != null && mProgressBar.getProgress() < progress) {
862             clearError();
863         }
864 
865         if (mUdfpsEnrollHelper != null) {
866             mUdfpsEnrollHelper.onEnrollmentProgress(mSidecar.getEnrollmentSteps(),
867                     mSidecar.getEnrollmentRemaining());
868         }
869 
870         if (animate) {
871             animateProgress(progress);
872         } else {
873             if (mProgressBar != null) {
874                 mProgressBar.setProgress(progress);
875             }
876             if (progress >= PROGRESS_BAR_MAX) {
877                 mDelayedFinishRunnable.run();
878             }
879         }
880     }
881 
getProgress(int steps, int remaining)882     private int getProgress(int steps, int remaining) {
883         if (steps == -1) {
884             return 0;
885         }
886         int progress = Math.max(0, steps + 1 - remaining);
887         return PROGRESS_BAR_MAX * progress / (steps + 1);
888     }
889 
showIconTouchDialog()890     private void showIconTouchDialog() {
891         mIconTouchCount = 0;
892         new IconTouchDialog().show(getSupportFragmentManager(), ICON_TOUCH_DIALOG);
893     }
894 
showError(CharSequence error)895     private void showError(CharSequence error) {
896         if (mCanAssumeSfps) {
897             setHeaderText(error);
898             if (!mHelpAnimation.isRunning()) {
899                 mHelpAnimation.start();
900             }
901             applySfpsErrorDynamicColors(getApplicationContext(), true);
902         } else if (mCanAssumeUdfps) {
903             setHeaderText(error);
904             // Show nothing for subtitle when getting an error message.
905             setDescriptionText("");
906         } else {
907             mErrorText.setText(error);
908             if (mErrorText.getVisibility() == View.INVISIBLE) {
909                 mErrorText.setVisibility(View.VISIBLE);
910                 mErrorText.setTranslationY(getResources().getDimensionPixelSize(
911                         R.dimen.fingerprint_error_text_appear_distance));
912                 mErrorText.setAlpha(0f);
913                 mErrorText.animate()
914                         .alpha(1f)
915                         .translationY(0f)
916                         .setDuration(200)
917                         .setInterpolator(mLinearOutSlowInInterpolator)
918                         .start();
919             } else {
920                 mErrorText.animate().cancel();
921                 mErrorText.setAlpha(1f);
922                 mErrorText.setTranslationY(0f);
923             }
924         }
925         if (isResumed() && mIsAccessibilityEnabled && !mCanAssumeUdfps) {
926             mVibrator.vibrate(Process.myUid(), getApplicationContext().getOpPackageName(),
927                     VIBRATE_EFFECT_ERROR, getClass().getSimpleName() + "::showError",
928                     FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES);
929         }
930     }
931 
clearError()932     private void clearError() {
933         if (mCanAssumeSfps) {
934             applySfpsErrorDynamicColors(getApplicationContext(), false);
935         }
936         if ((!(mCanAssumeUdfps || mCanAssumeSfps)) && mErrorText.getVisibility() == View.VISIBLE) {
937             mErrorText.animate()
938                     .alpha(0f)
939                     .translationY(getResources().getDimensionPixelSize(
940                             R.dimen.fingerprint_error_text_disappear_distance))
941                     .setDuration(100)
942                     .setInterpolator(mFastOutLinearInInterpolator)
943                     .withEndAction(() -> mErrorText.setVisibility(View.INVISIBLE))
944                     .start();
945         }
946     }
947 
948     /**
949      * Applies dynamic colors corresponding to showing or clearing errors on the progress bar
950      * and finger lottie for SFPS
951      */
applySfpsErrorDynamicColors(Context context, boolean isError)952     private void applySfpsErrorDynamicColors(Context context, boolean isError) {
953         applyProgressBarDynamicColor(context, isError);
954         if (mIllustrationLottie != null) {
955             applyLottieDynamicColor(context, isError);
956         }
957     }
958 
applyProgressBarDynamicColor(Context context, boolean isError)959     private void applyProgressBarDynamicColor(Context context, boolean isError) {
960         if (mProgressBar != null) {
961             int error_color = context.getColor(R.color.sfps_enrollment_progress_bar_error_color);
962             int progress_bar_fill_color = context.getColor(
963                     R.color.sfps_enrollment_progress_bar_fill_color);
964             ColorStateList fillColor = ColorStateList.valueOf(
965                     isError ? error_color : progress_bar_fill_color);
966             mProgressBar.setProgressTintList(fillColor);
967             mProgressBar.setProgressTintMode(PorterDuff.Mode.SRC);
968             mProgressBar.invalidate();
969         }
970     }
971 
applyLottieDynamicColor(Context context, boolean isError)972     private void applyLottieDynamicColor(Context context, boolean isError) {
973         int error_color = context.getColor(R.color.sfps_enrollment_fp_error_color);
974         int fp_captured_color = context.getColor(R.color.sfps_enrollment_fp_captured_color);
975         int color = isError ? error_color : fp_captured_color;
976         mIllustrationLottie.addValueCallback(
977                 new KeyPath(".blue100", "**"),
978                 LottieProperty.COLOR_FILTER,
979                 frameInfo -> new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
980         );
981         mIllustrationLottie.invalidate();
982     }
983 
listenOrientationEvent()984     private void listenOrientationEvent() {
985         mOrientationEventListener = new OrientationEventListener(this) {
986             @Override
987             public void onOrientationChanged(int orientation) {
988                 final int currentRotation = getDisplay().getRotation();
989                 if ((mPreviousRotation == Surface.ROTATION_90
990                         && currentRotation == Surface.ROTATION_270) || (
991                         mPreviousRotation == Surface.ROTATION_270
992                                 && currentRotation == Surface.ROTATION_90)) {
993                     mPreviousRotation = currentRotation;
994                     recreate();
995                 }
996             }
997         };
998         mOrientationEventListener.enable();
999         mPreviousRotation = getDisplay().getRotation();
1000     }
1001 
stopListenOrientationEvent()1002     private void stopListenOrientationEvent() {
1003         if (mOrientationEventListener != null) {
1004             mOrientationEventListener.disable();
1005         }
1006         mOrientationEventListener = null;
1007     }
1008 
1009     private final Animator.AnimatorListener mProgressAnimationListener =
1010             new Animator.AnimatorListener() {
1011 
1012                 @Override
1013                 public void onAnimationStart(Animator animation) {
1014                     startIconAnimation();
1015                 }
1016 
1017                 @Override
1018                 public void onAnimationRepeat(Animator animation) { }
1019 
1020                 @Override
1021                 public void onAnimationEnd(Animator animation) {
1022                     stopIconAnimation();
1023 
1024                     if (mProgressBar.getProgress() >= PROGRESS_BAR_MAX) {
1025                         mProgressBar.postDelayed(mDelayedFinishRunnable, getFinishDelay());
1026                     }
1027                 }
1028 
1029                 @Override
1030                 public void onAnimationCancel(Animator animation) { }
1031             };
1032 
getFinishDelay()1033     private long getFinishDelay() {
1034         return mCanAssumeUdfps ? 400L : 250L;
1035     }
1036 
1037     // Give the user a chance to see progress completed before jumping to the next stage.
1038     private final Runnable mDelayedFinishRunnable = new Runnable() {
1039         @Override
1040         public void run() {
1041             launchFinish(mToken);
1042         }
1043     };
1044 
1045     private final Animatable2.AnimationCallback mIconAnimationCallback =
1046             new Animatable2.AnimationCallback() {
1047         @Override
1048         public void onAnimationEnd(Drawable d) {
1049             if (mAnimationCancelled) {
1050                 return;
1051             }
1052 
1053             // Start animation after it has ended.
1054             mProgressBar.post(new Runnable() {
1055                 @Override
1056                 public void run() {
1057                     startIconAnimation();
1058                 }
1059             });
1060         }
1061     };
1062 
1063     private final Runnable mShowDialogRunnable = new Runnable() {
1064         @Override
1065         public void run() {
1066             showIconTouchDialog();
1067         }
1068     };
1069 
1070     private final Runnable mTouchAgainRunnable = new Runnable() {
1071         @Override
1072         public void run() {
1073             showError(getString(R.string.security_settings_fingerprint_enroll_lift_touch_again));
1074         }
1075     };
1076 
1077     @Override
getMetricsCategory()1078     public int getMetricsCategory() {
1079         return SettingsEnums.FINGERPRINT_ENROLLING;
1080     }
1081 
updateOrientation(int orientation)1082     private void updateOrientation(int orientation) {
1083         if (mCanAssumeSfps) {
1084             mIllustrationLottie = findViewById(R.id.illustration_lottie);
1085         } else {
1086             switch(orientation) {
1087                 case Configuration.ORIENTATION_LANDSCAPE: {
1088                     mIllustrationLottie = null;
1089                     break;
1090                 }
1091                 case Configuration.ORIENTATION_PORTRAIT: {
1092                     if (mShouldShowLottie) {
1093                         mIllustrationLottie = findViewById(R.id.illustration_lottie);
1094                     }
1095                     break;
1096                 }
1097                 default:
1098                     Log.e(TAG, "Error unhandled configuration change");
1099                     break;
1100             }
1101         }
1102     }
1103 
1104     @Override
onConfigurationChanged(@onNull Configuration newConfig)1105     public void onConfigurationChanged(@NonNull Configuration newConfig) {
1106         super.onConfigurationChanged(newConfig);
1107         maybeHideSfpsText(newConfig);
1108         switch(newConfig.orientation) {
1109             case Configuration.ORIENTATION_LANDSCAPE: {
1110                 updateOrientation(Configuration.ORIENTATION_LANDSCAPE);
1111                 break;
1112             }
1113             case Configuration.ORIENTATION_PORTRAIT: {
1114                 updateOrientation(Configuration.ORIENTATION_PORTRAIT);
1115                 break;
1116             }
1117             default:
1118                 Log.e(TAG, "Error unhandled configuration change");
1119                 break;
1120         }
1121     }
1122 
maybeHideSfpsText(@onNull Configuration newConfig)1123     private void maybeHideSfpsText(@NonNull Configuration newConfig) {
1124         final HeaderMixin headerMixin = getLayout().getMixin(HeaderMixin.class);
1125         final DescriptionMixin descriptionMixin = getLayout().getMixin(DescriptionMixin.class);
1126         final boolean isLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE;
1127 
1128         if (mCanAssumeSfps) {
1129             // hide the description
1130             descriptionMixin.getTextView().setVisibility(View.GONE);
1131             headerMixin.getTextView().setHyphenationFrequency(HYPHENATION_FREQUENCY_NONE);
1132             if (isLandscape) {
1133                 headerMixin.setAutoTextSizeEnabled(true);
1134                 headerMixin.getTextView().setMinLines(0);
1135                 headerMixin.getTextView().setMaxLines(10);
1136             } else {
1137                 headerMixin.setAutoTextSizeEnabled(false);
1138                 headerMixin.getTextView().setLines(4);
1139             }
1140         }
1141     }
1142 
setUdfpsEnrollHelper()1143     private void setUdfpsEnrollHelper() {
1144         mUdfpsEnrollHelper = (UdfpsEnrollHelper) getSupportFragmentManager().findFragmentByTag(
1145                 FingerprintEnrollEnrolling.TAG_UDFPS_HELPER);
1146         if (mUdfpsEnrollHelper == null) {
1147             mUdfpsEnrollHelper = new UdfpsEnrollHelper(getApplicationContext(),
1148                     mFingerprintManager);
1149             getSupportFragmentManager().beginTransaction()
1150                     .add(mUdfpsEnrollHelper, FingerprintEnrollEnrolling.TAG_UDFPS_HELPER)
1151                     .commitAllowingStateLoss();
1152         }
1153     }
1154 
1155     public static class IconTouchDialog extends InstrumentedDialogFragment {
1156 
1157         @Override
onCreateDialog(Bundle savedInstanceState)1158         public Dialog onCreateDialog(Bundle savedInstanceState) {
1159             AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(),
1160                     R.style.Theme_AlertDialog);
1161             builder.setTitle(R.string.security_settings_fingerprint_enroll_touch_dialog_title)
1162                     .setMessage(R.string.security_settings_fingerprint_enroll_touch_dialog_message)
1163                     .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok,
1164                             new DialogInterface.OnClickListener() {
1165                                 @Override
1166                                 public void onClick(DialogInterface dialog, int which) {
1167                                     dialog.dismiss();
1168                                 }
1169                             });
1170             return builder.create();
1171         }
1172 
1173         @Override
getMetricsCategory()1174         public int getMetricsCategory() {
1175             return SettingsEnums.DIALOG_FINGERPRINT_ICON_TOUCH;
1176         }
1177     }
1178 }