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 android.animation.Animator;
20 import android.animation.ObjectAnimator;
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.app.Dialog;
25 import android.app.settings.SettingsEnums;
26 import android.content.DialogInterface;
27 import android.content.Intent;
28 import android.content.res.Configuration;
29 import android.graphics.drawable.Animatable2;
30 import android.graphics.drawable.AnimatedVectorDrawable;
31 import android.graphics.drawable.Drawable;
32 import android.graphics.drawable.LayerDrawable;
33 import android.hardware.fingerprint.FingerprintManager;
34 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
35 import android.media.AudioAttributes;
36 import android.os.Bundle;
37 import android.os.VibrationEffect;
38 import android.os.Vibrator;
39 import android.text.TextUtils;
40 import android.util.Log;
41 import android.view.MotionEvent;
42 import android.view.OrientationEventListener;
43 import android.view.Surface;
44 import android.view.View;
45 import android.view.accessibility.AccessibilityEvent;
46 import android.view.accessibility.AccessibilityManager;
47 import android.view.animation.AnimationUtils;
48 import android.view.animation.Interpolator;
49 import android.widget.ProgressBar;
50 import android.widget.TextView;
51 
52 import androidx.appcompat.app.AlertDialog;
53 
54 import com.android.settings.R;
55 import com.android.settings.biometrics.BiometricEnrollSidecar;
56 import com.android.settings.biometrics.BiometricUtils;
57 import com.android.settings.biometrics.BiometricsEnrollEnrolling;
58 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
59 import com.android.settingslib.display.DisplayDensityUtils;
60 
61 import com.airbnb.lottie.LottieAnimationView;
62 import com.google.android.setupcompat.template.FooterBarMixin;
63 import com.google.android.setupcompat.template.FooterButton;
64 import com.google.android.setupcompat.util.WizardManagerHelper;
65 
66 import java.lang.annotation.Retention;
67 import java.lang.annotation.RetentionPolicy;
68 import java.util.List;
69 
70 /**
71  * Activity which handles the actual enrolling for fingerprint.
72  */
73 public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
74 
75     private static final String TAG = "FingerprintEnrollEnrolling";
76     static final String TAG_SIDECAR = "sidecar";
77 
78     private static final int PROGRESS_BAR_MAX = 10000;
79 
80     private static final int STAGE_UNKNOWN = -1;
81     private static final int STAGE_CENTER = 0;
82     private static final int STAGE_GUIDED = 1;
83     private static final int STAGE_FINGERTIP = 2;
84     private static final int STAGE_EDGES = 3;
85 
86     @IntDef({STAGE_UNKNOWN, STAGE_CENTER, STAGE_GUIDED, STAGE_FINGERTIP, STAGE_EDGES})
87     @Retention(RetentionPolicy.SOURCE)
88     private @interface EnrollStage {}
89 
90     /**
91      * If we don't see progress during this time, we show an error message to remind the users that
92      * they need to lift the finger and touch again.
93      */
94     private static final int HINT_TIMEOUT_DURATION = 2500;
95 
96     /**
97      * How long the user needs to touch the icon until we show the dialog.
98      */
99     private static final long ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN = 500;
100 
101     /**
102      * How many times the user needs to touch the icon until we show the dialog that this is not the
103      * fingerprint sensor.
104      */
105     private static final int ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN = 3;
106 
107     private static final VibrationEffect VIBRATE_EFFECT_ERROR =
108             VibrationEffect.createWaveform(new long[] {0, 5, 55, 60}, -1);
109     private static final AudioAttributes FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES =
110             new AudioAttributes.Builder()
111                     .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
112                     .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
113                     .build();
114 
115     private FingerprintManager mFingerprintManager;
116     private boolean mCanAssumeUdfps;
117     @Nullable private ProgressBar mProgressBar;
118     private ObjectAnimator mProgressAnim;
119     private TextView mDescriptionText;
120     private TextView mErrorText;
121     private Interpolator mFastOutSlowInInterpolator;
122     private Interpolator mLinearOutSlowInInterpolator;
123     private Interpolator mFastOutLinearInInterpolator;
124     private int mIconTouchCount;
125     private boolean mAnimationCancelled;
126     @Nullable private AnimatedVectorDrawable mIconAnimationDrawable;
127     @Nullable private AnimatedVectorDrawable mIconBackgroundBlinksDrawable;
128     private boolean mRestoring;
129     private Vibrator mVibrator;
130     private boolean mIsSetupWizard;
131     private AccessibilityManager mAccessibilityManager;
132     private boolean mIsAccessibilityEnabled;
133     private LottieAnimationView mIllustrationLottie;
134     private boolean mHaveShownUdfpsTipLottie;
135     private boolean mHaveShownUdfpsSideLottie;
136     private boolean mShouldShowLottie;
137 
138     private OrientationEventListener mOrientationEventListener;
139     private int mPreviousRotation = 0;
140 
141     private boolean mShowingNewUdfpsEnroll = false;
142 
143     @Override
onCreate(Bundle savedInstanceState)144     protected void onCreate(Bundle savedInstanceState) {
145         super.onCreate(savedInstanceState);
146 
147         mFingerprintManager = getSystemService(FingerprintManager.class);
148         final List<FingerprintSensorPropertiesInternal> props =
149                 mFingerprintManager.getSensorPropertiesInternal();
150         mCanAssumeUdfps = props.size() == 1 && props.get(0).isAnyUdfpsType();
151 
152         mAccessibilityManager = getSystemService(AccessibilityManager.class);
153         mIsAccessibilityEnabled = mAccessibilityManager.isEnabled();
154 
155         listenOrientationEvent();
156 
157         if (mCanAssumeUdfps) {
158             if (BiometricUtils.isReverseLandscape(getApplicationContext())) {
159                 setContentView(R.layout.udfps_enroll_enrolling_land);
160             } else {
161                 setContentView(R.layout.udfps_enroll_enrolling);
162             }
163             setDescriptionText(R.string.security_settings_udfps_enroll_start_message);
164         } else {
165             setContentView(R.layout.fingerprint_enroll_enrolling);
166             setDescriptionText(R.string.security_settings_fingerprint_enroll_start_message);
167         }
168 
169         mIsSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent());
170         if (mCanAssumeUdfps) {
171             updateTitleAndDescription();
172         } else {
173             setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title);
174         }
175 
176         DisplayDensityUtils displayDensity =
177                 new DisplayDensityUtils(getApplicationContext());
178         int currentDensityIndex = displayDensity.getCurrentIndex();
179         final int currentDensity = displayDensity.getValues()[currentDensityIndex];
180         final int defaultDensity = displayDensity.getDefaultDensity();
181         mShouldShowLottie = defaultDensity == currentDensity;
182 
183         mShowingNewUdfpsEnroll = getApplicationContext().getResources().getBoolean(
184                 com.android.internal.R.bool.config_udfpsSupportsNewUi);
185         // Only show the lottie if the current display density is the default density.
186         // Otherwise, the lottie will overlap with the settings header text.
187         boolean isLandscape = BiometricUtils.isReverseLandscape(getApplicationContext())
188                 || BiometricUtils.isLandscape(getApplicationContext());
189 
190         if (mShowingNewUdfpsEnroll) {
191             updateOrientation((isLandscape
192                     ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT));
193         }
194 
195         mErrorText = findViewById(R.id.error_text);
196         mProgressBar = findViewById(R.id.fingerprint_progress_bar);
197         mVibrator = getSystemService(Vibrator.class);
198 
199         mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class);
200         mFooterBarMixin.setSecondaryButton(
201                 new FooterButton.Builder(this)
202                         .setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
203                         .setListener(this::onSkipButtonClick)
204                         .setButtonType(FooterButton.ButtonType.SKIP)
205                         .setTheme(R.style.SudGlifButton_Secondary)
206                         .build()
207         );
208 
209         final LayerDrawable fingerprintDrawable = mProgressBar != null
210                 ? (LayerDrawable) mProgressBar.getBackground() : null;
211         if (fingerprintDrawable != null) {
212             mIconAnimationDrawable = (AnimatedVectorDrawable)
213                     fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_animation);
214             mIconBackgroundBlinksDrawable = (AnimatedVectorDrawable)
215                     fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_background);
216             mIconAnimationDrawable.registerAnimationCallback(mIconAnimationCallback);
217         }
218 
219         mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(
220                 this, android.R.interpolator.fast_out_slow_in);
221         mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
222                 this, android.R.interpolator.linear_out_slow_in);
223         mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(
224                 this, android.R.interpolator.fast_out_linear_in);
225         if (mProgressBar != null) {
226             mProgressBar.setOnTouchListener((v, event) -> {
227                 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
228                     mIconTouchCount++;
229                     if (mIconTouchCount == ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN) {
230                         showIconTouchDialog();
231                     } else {
232                         mProgressBar.postDelayed(mShowDialogRunnable,
233                                 ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN);
234                     }
235                 } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL
236                         || event.getActionMasked() == MotionEvent.ACTION_UP) {
237                     mProgressBar.removeCallbacks(mShowDialogRunnable);
238                 }
239                 return true;
240             });
241         }
242         mRestoring = savedInstanceState != null;
243     }
244 
245     @Override
getSidecar()246     protected BiometricEnrollSidecar getSidecar() {
247         final FingerprintEnrollSidecar sidecar = new FingerprintEnrollSidecar();
248         sidecar.setEnrollReason(FingerprintManager.ENROLL_ENROLL);
249         return sidecar;
250     }
251 
252     @Override
shouldStartAutomatically()253     protected boolean shouldStartAutomatically() {
254         if (mCanAssumeUdfps) {
255             // Continue enrollment if restoring (e.g. configuration changed). Otherwise, wait
256             // for the entry animation to complete before starting.
257             return mRestoring;
258         }
259         return true;
260     }
261 
262     @Override
onStart()263     protected void onStart() {
264         super.onStart();
265         updateProgress(false /* animate */);
266         updateTitleAndDescription();
267         if (mRestoring) {
268             startIconAnimation();
269         }
270     }
271 
272     @Override
onEnterAnimationComplete()273     public void onEnterAnimationComplete() {
274         super.onEnterAnimationComplete();
275 
276         if (mCanAssumeUdfps) {
277             startEnrollment();
278         }
279 
280         mAnimationCancelled = false;
281         startIconAnimation();
282     }
283 
startIconAnimation()284     private void startIconAnimation() {
285         if (mIconAnimationDrawable != null) {
286             mIconAnimationDrawable.start();
287         }
288     }
289 
stopIconAnimation()290     private void stopIconAnimation() {
291         mAnimationCancelled = true;
292         if (mIconAnimationDrawable != null) {
293             mIconAnimationDrawable.stop();
294         }
295     }
296 
297     @Override
onStop()298     protected void onStop() {
299         super.onStop();
300         stopIconAnimation();
301     }
302 
303     @Override
onDestroy()304     protected void onDestroy() {
305         stopListenOrientationEvent();
306         super.onDestroy();
307     }
308 
animateProgress(int progress)309     private void animateProgress(int progress) {
310         if (mCanAssumeUdfps) {
311             // UDFPS animations are owned by SystemUI
312             if (progress >= PROGRESS_BAR_MAX) {
313                 // Wait for any animations in SysUI to finish, then proceed to next page
314                 getMainThreadHandler().postDelayed(mDelayedFinishRunnable, getFinishDelay());
315             }
316             return;
317         }
318         if (mProgressAnim != null) {
319             mProgressAnim.cancel();
320         }
321         ObjectAnimator anim = ObjectAnimator.ofInt(mProgressBar, "progress",
322                 mProgressBar.getProgress(), progress);
323         anim.addListener(mProgressAnimationListener);
324         anim.setInterpolator(mFastOutSlowInInterpolator);
325         anim.setDuration(250);
326         anim.start();
327         mProgressAnim = anim;
328     }
329 
animateFlash()330     private void animateFlash() {
331         if (mIconBackgroundBlinksDrawable != null) {
332             mIconBackgroundBlinksDrawable.start();
333         }
334     }
335 
getFinishIntent()336     protected Intent getFinishIntent() {
337         return new Intent(this, FingerprintEnrollFinish.class);
338     }
339 
updateTitleAndDescription()340     private void updateTitleAndDescription() {
341         if (mCanAssumeUdfps) {
342             updateTitleAndDescriptionForUdfps();
343             return;
344         }
345 
346         if (mSidecar == null || mSidecar.getEnrollmentSteps() == -1) {
347             setDescriptionText(R.string.security_settings_fingerprint_enroll_start_message);
348         } else {
349             setDescriptionText(R.string.security_settings_fingerprint_enroll_repeat_message);
350         }
351     }
352 
updateTitleAndDescriptionForUdfps()353     private void updateTitleAndDescriptionForUdfps() {
354         switch (getCurrentStage()) {
355             case STAGE_CENTER:
356                 setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title);
357                 setDescriptionText(R.string.security_settings_udfps_enroll_start_message);
358                 break;
359 
360             case STAGE_GUIDED:
361                 setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title);
362                 if (mIsAccessibilityEnabled) {
363                     setDescriptionText(R.string.security_settings_udfps_enroll_repeat_a11y_message);
364                 } else {
365                     setDescriptionText(R.string.security_settings_udfps_enroll_repeat_message);
366                 }
367                 break;
368 
369             case STAGE_FINGERTIP:
370                 setHeaderText(R.string.security_settings_udfps_enroll_fingertip_title);
371                 if (mShowingNewUdfpsEnroll) {
372                     if (!mHaveShownUdfpsTipLottie && mIllustrationLottie != null) {
373                         mHaveShownUdfpsTipLottie = true;
374                         setDescriptionText("");
375                         mIllustrationLottie.setAnimation(R.raw.udfps_tip_hint_lottie);
376                         mIllustrationLottie.setVisibility(View.VISIBLE);
377                         mIllustrationLottie.playAnimation();
378                         mIllustrationLottie.setContentDescription(
379                                 getString(R.string.security_settings_udfps_tip_fingerprint_help));
380                     }
381                 } else {
382                     if (isStageHalfCompleted()) {
383                         setDescriptionText(
384                                 R.string.security_settings_fingerprint_enroll_repeat_title);
385                     } else {
386                         setDescriptionText("");
387                     }
388                 }
389                 break;
390 
391             case STAGE_EDGES:
392                 setHeaderText(R.string.security_settings_udfps_enroll_edge_title);
393                 if (mShowingNewUdfpsEnroll) {
394                     if (!mHaveShownUdfpsSideLottie && mIllustrationLottie != null) {
395                         mHaveShownUdfpsSideLottie = true;
396                         setDescriptionText("");
397                         mIllustrationLottie.setAnimation(R.raw.udfps_edge_hint_lottie);
398                         mIllustrationLottie.setVisibility(View.VISIBLE);
399                         mIllustrationLottie.playAnimation();
400                         mIllustrationLottie.setContentDescription(
401                                 getString(R.string.security_settings_udfps_side_fingerprint_help));
402                     } else if (mIllustrationLottie == null) {
403                         if (isStageHalfCompleted()) {
404                             setDescriptionText(
405                                     R.string.security_settings_fingerprint_enroll_repeat_message);
406                         } else {
407                             setDescriptionText(
408                                     R.string.security_settings_udfps_enroll_edge_message);
409                         }
410                     }
411                 } else {
412                     if (isStageHalfCompleted()) {
413                         setDescriptionText(
414                                 R.string.security_settings_fingerprint_enroll_repeat_message);
415                     } else {
416                         setDescriptionText(R.string.security_settings_udfps_enroll_edge_message);
417                     }
418                 }
419                 break;
420 
421             case STAGE_UNKNOWN:
422             default:
423                 // setHeaderText(R.string.security_settings_fingerprint_enroll_udfps_title);
424                 // Don't use BiometricEnrollBase#setHeaderText, since that invokes setTitle,
425                 // which gets announced for a11y upon entering the page. For UDFPS, we want to
426                 // announce a different string for a11y upon entering the page.
427                 getLayout().setHeaderText(
428                         R.string.security_settings_fingerprint_enroll_udfps_title);
429                 setDescriptionText(R.string.security_settings_udfps_enroll_start_message);
430                 final CharSequence description = getString(
431                         R.string.security_settings_udfps_enroll_a11y);
432                 getLayout().getHeaderTextView().setContentDescription(description);
433                 setTitle(description);
434                 break;
435         }
436     }
437 
438     @EnrollStage
getCurrentStage()439     private int getCurrentStage() {
440         if (mSidecar == null || mSidecar.getEnrollmentSteps() == -1) {
441             return STAGE_UNKNOWN;
442         }
443 
444         final int progressSteps = mSidecar.getEnrollmentSteps() - mSidecar.getEnrollmentRemaining();
445         if (progressSteps < getStageThresholdSteps(0)) {
446             return STAGE_CENTER;
447         } else if (progressSteps < getStageThresholdSteps(1)) {
448             return STAGE_GUIDED;
449         } else if (progressSteps < getStageThresholdSteps(2)) {
450             return STAGE_FINGERTIP;
451         } else {
452             return STAGE_EDGES;
453         }
454     }
455 
isStageHalfCompleted()456     private boolean isStageHalfCompleted() {
457         // Prior to first enrollment step.
458         if (mSidecar == null || mSidecar.getEnrollmentSteps() == -1) {
459             return false;
460         }
461 
462         final int progressSteps = mSidecar.getEnrollmentSteps() - mSidecar.getEnrollmentRemaining();
463         int prevThresholdSteps = 0;
464         for (int i = 0; i < mFingerprintManager.getEnrollStageCount(); i++) {
465             final int thresholdSteps = getStageThresholdSteps(i);
466             if (progressSteps >= prevThresholdSteps && progressSteps < thresholdSteps) {
467                 final int adjustedProgress = progressSteps - prevThresholdSteps;
468                 final int adjustedThreshold = thresholdSteps - prevThresholdSteps;
469                 return adjustedProgress >= adjustedThreshold / 2;
470             }
471             prevThresholdSteps = thresholdSteps;
472         }
473 
474         // After last enrollment step.
475         return true;
476     }
477 
getStageThresholdSteps(int index)478     private int getStageThresholdSteps(int index) {
479         if (mSidecar == null || mSidecar.getEnrollmentSteps() == -1) {
480             Log.w(TAG, "getStageThresholdSteps: Enrollment not started yet");
481             return 1;
482         }
483         return Math.round(mSidecar.getEnrollmentSteps()
484                 * mFingerprintManager.getEnrollStageThreshold(index));
485     }
486 
487     @Override
onEnrollmentHelp(int helpMsgId, CharSequence helpString)488     public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
489         if (!TextUtils.isEmpty(helpString)) {
490             if (!mCanAssumeUdfps) {
491                 mErrorText.removeCallbacks(mTouchAgainRunnable);
492             }
493             showError(helpString);
494         }
495     }
496 
497     @Override
onEnrollmentError(int errMsgId, CharSequence errString)498     public void onEnrollmentError(int errMsgId, CharSequence errString) {
499         FingerprintErrorDialog.showErrorDialog(this, errMsgId);
500         stopIconAnimation();
501         if (!mCanAssumeUdfps) {
502             mErrorText.removeCallbacks(mTouchAgainRunnable);
503         }
504     }
505 
506     @Override
onEnrollmentProgressChange(int steps, int remaining)507     public void onEnrollmentProgressChange(int steps, int remaining) {
508         updateProgress(true /* animate */);
509         updateTitleAndDescription();
510         clearError();
511         animateFlash();
512         if (!mCanAssumeUdfps) {
513             mErrorText.removeCallbacks(mTouchAgainRunnable);
514             mErrorText.postDelayed(mTouchAgainRunnable, HINT_TIMEOUT_DURATION);
515         } else {
516             if (mIsAccessibilityEnabled) {
517                 final int percent = (int) (((float)(steps - remaining) / (float) steps) * 100);
518                 CharSequence cs = getString(
519                         R.string.security_settings_udfps_enroll_progress_a11y_message, percent);
520                 AccessibilityEvent e = AccessibilityEvent.obtain();
521                 e.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT);
522                 e.setClassName(getClass().getName());
523                 e.setPackageName(getPackageName());
524                 e.getText().add(cs);
525                 mAccessibilityManager.sendAccessibilityEvent(e);
526             }
527         }
528     }
529 
updateProgress(boolean animate)530     private void updateProgress(boolean animate) {
531         if (mSidecar == null || !mSidecar.isEnrolling()) {
532             Log.d(TAG, "Enrollment not started yet");
533             return;
534         }
535 
536         int progress = getProgress(
537                 mSidecar.getEnrollmentSteps(), mSidecar.getEnrollmentRemaining());
538         if (animate) {
539             animateProgress(progress);
540         } else {
541             if (mProgressBar != null) {
542                 mProgressBar.setProgress(progress);
543             }
544             if (progress >= PROGRESS_BAR_MAX) {
545                 mDelayedFinishRunnable.run();
546             }
547         }
548     }
549 
getProgress(int steps, int remaining)550     private int getProgress(int steps, int remaining) {
551         if (steps == -1) {
552             return 0;
553         }
554         int progress = Math.max(0, steps + 1 - remaining);
555         return PROGRESS_BAR_MAX * progress / (steps + 1);
556     }
557 
showIconTouchDialog()558     private void showIconTouchDialog() {
559         mIconTouchCount = 0;
560         new IconTouchDialog().show(getSupportFragmentManager(), null /* tag */);
561     }
562 
showError(CharSequence error)563     private void showError(CharSequence error) {
564         if (mCanAssumeUdfps) {
565             setHeaderText(error);
566             // Show nothing for subtitle when getting an error message.
567             setDescriptionText("");
568         } else {
569             mErrorText.setText(error);
570             if (mErrorText.getVisibility() == View.INVISIBLE) {
571                 mErrorText.setVisibility(View.VISIBLE);
572                 mErrorText.setTranslationY(getResources().getDimensionPixelSize(
573                         R.dimen.fingerprint_error_text_appear_distance));
574                 mErrorText.setAlpha(0f);
575                 mErrorText.animate()
576                         .alpha(1f)
577                         .translationY(0f)
578                         .setDuration(200)
579                         .setInterpolator(mLinearOutSlowInInterpolator)
580                         .start();
581             } else {
582                 mErrorText.animate().cancel();
583                 mErrorText.setAlpha(1f);
584                 mErrorText.setTranslationY(0f);
585             }
586         }
587         if (isResumed()) {
588             mVibrator.vibrate(VIBRATE_EFFECT_ERROR, FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES);
589         }
590     }
591 
clearError()592     private void clearError() {
593         if (!mCanAssumeUdfps && mErrorText.getVisibility() == View.VISIBLE) {
594             mErrorText.animate()
595                     .alpha(0f)
596                     .translationY(getResources().getDimensionPixelSize(
597                             R.dimen.fingerprint_error_text_disappear_distance))
598                     .setDuration(100)
599                     .setInterpolator(mFastOutLinearInInterpolator)
600                     .withEndAction(() -> mErrorText.setVisibility(View.INVISIBLE))
601                     .start();
602         }
603     }
604 
listenOrientationEvent()605     private void listenOrientationEvent() {
606         mOrientationEventListener = new OrientationEventListener(this) {
607             @Override
608             public void onOrientationChanged(int orientation) {
609                 final int currentRotation = getDisplay().getRotation();
610                 if ((mPreviousRotation == Surface.ROTATION_90
611                         && currentRotation == Surface.ROTATION_270) || (
612                         mPreviousRotation == Surface.ROTATION_270
613                                 && currentRotation == Surface.ROTATION_90)) {
614                     mPreviousRotation = currentRotation;
615                     recreate();
616                 }
617             }
618         };
619         mOrientationEventListener.enable();
620         mPreviousRotation = getDisplay().getRotation();
621     }
622 
stopListenOrientationEvent()623     private void stopListenOrientationEvent() {
624         if (mOrientationEventListener != null) {
625             mOrientationEventListener.disable();
626         }
627         mOrientationEventListener = null;
628     }
629 
630     private final Animator.AnimatorListener mProgressAnimationListener =
631             new Animator.AnimatorListener() {
632 
633                 @Override
634                 public void onAnimationStart(Animator animation) { }
635 
636                 @Override
637                 public void onAnimationRepeat(Animator animation) { }
638 
639                 @Override
640                 public void onAnimationEnd(Animator animation) {
641                     if (mProgressBar.getProgress() >= PROGRESS_BAR_MAX) {
642                         mProgressBar.postDelayed(mDelayedFinishRunnable, getFinishDelay());
643                     }
644                 }
645 
646                 @Override
647                 public void onAnimationCancel(Animator animation) { }
648             };
649 
getFinishDelay()650     private long getFinishDelay() {
651         return mCanAssumeUdfps ? 400L : 250L;
652     }
653 
654     // Give the user a chance to see progress completed before jumping to the next stage.
655     private final Runnable mDelayedFinishRunnable = new Runnable() {
656         @Override
657         public void run() {
658             launchFinish(mToken);
659         }
660     };
661 
662     private final Animatable2.AnimationCallback mIconAnimationCallback =
663             new Animatable2.AnimationCallback() {
664         @Override
665         public void onAnimationEnd(Drawable d) {
666             if (mAnimationCancelled) {
667                 return;
668             }
669 
670             // Start animation after it has ended.
671             mProgressBar.post(new Runnable() {
672                 @Override
673                 public void run() {
674                     startIconAnimation();
675                 }
676             });
677         }
678     };
679 
680     private final Runnable mShowDialogRunnable = new Runnable() {
681         @Override
682         public void run() {
683             showIconTouchDialog();
684         }
685     };
686 
687     private final Runnable mTouchAgainRunnable = new Runnable() {
688         @Override
689         public void run() {
690             showError(getString(R.string.security_settings_fingerprint_enroll_lift_touch_again));
691         }
692     };
693 
694     @Override
getMetricsCategory()695     public int getMetricsCategory() {
696         return SettingsEnums.FINGERPRINT_ENROLLING;
697     }
698 
updateOrientation(int orientation)699     private void updateOrientation(int orientation) {
700         switch(orientation) {
701             case Configuration.ORIENTATION_LANDSCAPE: {
702                 mIllustrationLottie = null;
703                 break;
704             }
705             case Configuration.ORIENTATION_PORTRAIT: {
706                 if (mShouldShowLottie) {
707                     mIllustrationLottie = findViewById(R.id.illustration_lottie);
708                 }
709                 break;
710             }
711             default:
712                 Log.e(TAG, "Error unhandled configuration change");
713                 break;
714         }
715     }
716 
717     @Override
onConfigurationChanged(@onNull Configuration newConfig)718     public void onConfigurationChanged(@NonNull Configuration newConfig) {
719         if (!mShowingNewUdfpsEnroll) {
720             return;
721         }
722 
723         switch(newConfig.orientation) {
724             case Configuration.ORIENTATION_LANDSCAPE: {
725                 updateOrientation(Configuration.ORIENTATION_LANDSCAPE);
726                 break;
727             }
728             case Configuration.ORIENTATION_PORTRAIT: {
729                 updateOrientation(Configuration.ORIENTATION_PORTRAIT);
730                 break;
731             }
732             default:
733                 Log.e(TAG, "Error unhandled configuration change");
734                 break;
735         }
736     }
737 
738     public static class IconTouchDialog extends InstrumentedDialogFragment {
739 
740         @Override
onCreateDialog(Bundle savedInstanceState)741         public Dialog onCreateDialog(Bundle savedInstanceState) {
742             AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
743             builder.setTitle(R.string.security_settings_fingerprint_enroll_touch_dialog_title)
744                     .setMessage(R.string.security_settings_fingerprint_enroll_touch_dialog_message)
745                     .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok,
746                             new DialogInterface.OnClickListener() {
747                                 @Override
748                                 public void onClick(DialogInterface dialog, int which) {
749                                     dialog.dismiss();
750                                 }
751                             });
752             return builder.create();
753         }
754 
755         @Override
getMetricsCategory()756         public int getMetricsCategory() {
757             return SettingsEnums.DIALOG_FINGERPRINT_ICON_TOUCH;
758         }
759     }
760 }
761