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