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