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