FingerprintEnrollEnrolling.java revision 2eb170cd6ff43db01dc0ff3c1fcac5ebba4489de
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 17package com.android.settings.fingerprint; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ObjectAnimator; 22import android.animation.ValueAnimator; 23import android.app.Activity; 24import android.app.AlertDialog; 25import android.app.Dialog; 26import android.content.DialogInterface; 27import android.content.Intent; 28import android.graphics.drawable.Animatable2; 29import android.graphics.drawable.AnimatedVectorDrawable; 30import android.graphics.drawable.Drawable; 31import android.graphics.drawable.LayerDrawable; 32import android.hardware.fingerprint.FingerprintManager; 33import android.os.Bundle; 34import android.os.UserHandle; 35import android.view.MotionEvent; 36import android.view.View; 37import android.view.animation.AnimationUtils; 38import android.view.animation.Interpolator; 39import android.widget.ProgressBar; 40import android.widget.TextView; 41 42import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 43import com.android.settings.R; 44import com.android.settings.core.instrumentation.InstrumentedDialogFragment; 45import com.android.settings.password.ChooseLockSettingsHelper; 46 47/** 48 * Activity which handles the actual enrolling for fingerprint. 49 */ 50public class FingerprintEnrollEnrolling extends FingerprintEnrollBase 51 implements FingerprintEnrollSidecar.Listener { 52 53 static final String TAG_SIDECAR = "sidecar"; 54 55 private static final int PROGRESS_BAR_MAX = 10000; 56 private static final int FINISH_DELAY = 250; 57 58 /** 59 * If we don't see progress during this time, we show an error message to remind the user that 60 * he needs to lift the finger and touch again. 61 */ 62 private static final int HINT_TIMEOUT_DURATION = 2500; 63 64 /** 65 * How long the user needs to touch the icon until we show the dialog. 66 */ 67 private static final long ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN = 500; 68 69 /** 70 * How many times the user needs to touch the icon until we show the dialog that this is not the 71 * fingerprint sensor. 72 */ 73 private static final int ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN = 3; 74 75 private ProgressBar mProgressBar; 76 private ObjectAnimator mProgressAnim; 77 private TextView mStartMessage; 78 private TextView mRepeatMessage; 79 private TextView mErrorText; 80 private Interpolator mFastOutSlowInInterpolator; 81 private Interpolator mLinearOutSlowInInterpolator; 82 private Interpolator mFastOutLinearInInterpolator; 83 private int mIconTouchCount; 84 private FingerprintEnrollSidecar mSidecar; 85 private boolean mAnimationCancelled; 86 private AnimatedVectorDrawable mIconAnimationDrawable; 87 private Drawable mIconBackgroundDrawable; 88 private int mIndicatorBackgroundRestingColor; 89 private int mIndicatorBackgroundActivatedColor; 90 private boolean mRestoring; 91 92 @Override 93 protected void onCreate(Bundle savedInstanceState) { 94 super.onCreate(savedInstanceState); 95 setContentView(R.layout.fingerprint_enroll_enrolling); 96 setHeaderText(R.string.security_settings_fingerprint_enroll_start_title); 97 mStartMessage = (TextView) findViewById(R.id.start_message); 98 mRepeatMessage = (TextView) findViewById(R.id.repeat_message); 99 mErrorText = (TextView) findViewById(R.id.error_text); 100 mProgressBar = (ProgressBar) findViewById(R.id.fingerprint_progress_bar); 101 final LayerDrawable fingerprintDrawable = (LayerDrawable) mProgressBar.getBackground(); 102 mIconAnimationDrawable = (AnimatedVectorDrawable) 103 fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_animation); 104 mIconBackgroundDrawable = 105 fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_background); 106 mIconAnimationDrawable.registerAnimationCallback(mIconAnimationCallback); 107 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator( 108 this, android.R.interpolator.fast_out_slow_in); 109 mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator( 110 this, android.R.interpolator.linear_out_slow_in); 111 mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator( 112 this, android.R.interpolator.fast_out_linear_in); 113 mProgressBar.setOnTouchListener(new View.OnTouchListener() { 114 @Override 115 public boolean onTouch(View v, MotionEvent event) { 116 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 117 mIconTouchCount++; 118 if (mIconTouchCount == ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN) { 119 showIconTouchDialog(); 120 } else { 121 mProgressBar.postDelayed(mShowDialogRunnable, 122 ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN); 123 } 124 } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL 125 || event.getActionMasked() == MotionEvent.ACTION_UP) { 126 mProgressBar.removeCallbacks(mShowDialogRunnable); 127 } 128 return true; 129 } 130 }); 131 mIndicatorBackgroundRestingColor 132 = getColor(R.color.fingerprint_indicator_background_resting); 133 mIndicatorBackgroundActivatedColor 134 = getColor(R.color.fingerprint_indicator_background_activated); 135 mIconBackgroundDrawable.setTint(mIndicatorBackgroundRestingColor); 136 mRestoring = savedInstanceState != null; 137 } 138 139 @Override 140 protected void onStart() { 141 super.onStart(); 142 mSidecar = (FingerprintEnrollSidecar) getFragmentManager().findFragmentByTag(TAG_SIDECAR); 143 if (mSidecar == null) { 144 mSidecar = new FingerprintEnrollSidecar(); 145 getFragmentManager().beginTransaction().add(mSidecar, TAG_SIDECAR).commit(); 146 } 147 mSidecar.setListener(this); 148 updateProgress(false /* animate */); 149 updateDescription(); 150 if (mRestoring) { 151 startIconAnimation(); 152 } 153 } 154 155 @Override 156 public void onEnterAnimationComplete() { 157 super.onEnterAnimationComplete(); 158 mAnimationCancelled = false; 159 startIconAnimation(); 160 } 161 162 private void startIconAnimation() { 163 mIconAnimationDrawable.start(); 164 } 165 166 private void stopIconAnimation() { 167 mAnimationCancelled = true; 168 mIconAnimationDrawable.stop(); 169 } 170 171 @Override 172 protected void onStop() { 173 super.onStop(); 174 if (mSidecar != null) { 175 mSidecar.setListener(null); 176 } 177 stopIconAnimation(); 178 if (!isChangingConfigurations()) { 179 if (mSidecar != null) { 180 mSidecar.cancelEnrollment(); 181 getFragmentManager().beginTransaction().remove(mSidecar).commitAllowingStateLoss(); 182 } 183 finish(); 184 } 185 } 186 187 @Override 188 public void onBackPressed() { 189 if (mSidecar != null) { 190 mSidecar.setListener(null); 191 mSidecar.cancelEnrollment(); 192 getFragmentManager().beginTransaction().remove(mSidecar).commitAllowingStateLoss(); 193 mSidecar = null; 194 } 195 super.onBackPressed(); 196 } 197 198 private void animateProgress(int progress) { 199 if (mProgressAnim != null) { 200 mProgressAnim.cancel(); 201 } 202 ObjectAnimator anim = ObjectAnimator.ofInt(mProgressBar, "progress", 203 mProgressBar.getProgress(), progress); 204 anim.addListener(mProgressAnimationListener); 205 anim.setInterpolator(mFastOutSlowInInterpolator); 206 anim.setDuration(250); 207 anim.start(); 208 mProgressAnim = anim; 209 } 210 211 private void animateFlash() { 212 ValueAnimator anim = ValueAnimator.ofArgb(mIndicatorBackgroundRestingColor, 213 mIndicatorBackgroundActivatedColor); 214 final ValueAnimator.AnimatorUpdateListener listener = 215 new ValueAnimator.AnimatorUpdateListener() { 216 @Override 217 public void onAnimationUpdate(ValueAnimator animation) { 218 mIconBackgroundDrawable.setTint((Integer) animation.getAnimatedValue()); 219 } 220 }; 221 anim.addUpdateListener(listener); 222 anim.addListener(new AnimatorListenerAdapter() { 223 @Override 224 public void onAnimationEnd(Animator animation) { 225 ValueAnimator anim = ValueAnimator.ofArgb(mIndicatorBackgroundActivatedColor, 226 mIndicatorBackgroundRestingColor); 227 anim.addUpdateListener(listener); 228 anim.setDuration(300); 229 anim.setInterpolator(mLinearOutSlowInInterpolator); 230 anim.start(); 231 } 232 }); 233 anim.setInterpolator(mFastOutSlowInInterpolator); 234 anim.setDuration(300); 235 anim.start(); 236 } 237 238 private void launchFinish(byte[] token) { 239 Intent intent = getFinishIntent(); 240 intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT 241 | Intent.FLAG_ACTIVITY_CLEAR_TOP 242 | Intent.FLAG_ACTIVITY_SINGLE_TOP); 243 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token); 244 if (mUserId != UserHandle.USER_NULL) { 245 intent.putExtra(Intent.EXTRA_USER_ID, mUserId); 246 } 247 startActivity(intent); 248 overridePendingTransition(R.anim.suw_slide_next_in, R.anim.suw_slide_next_out); 249 finish(); 250 } 251 252 protected Intent getFinishIntent() { 253 return new Intent(this, FingerprintEnrollFinish.class); 254 } 255 256 private void updateDescription() { 257 if (mSidecar.getEnrollmentSteps() == -1) { 258 setHeaderText(R.string.security_settings_fingerprint_enroll_start_title); 259 mStartMessage.setVisibility(View.VISIBLE); 260 mRepeatMessage.setVisibility(View.INVISIBLE); 261 } else { 262 setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title, 263 true /* force */); 264 mStartMessage.setVisibility(View.INVISIBLE); 265 mRepeatMessage.setVisibility(View.VISIBLE); 266 } 267 } 268 269 270 @Override 271 public void onEnrollmentHelp(CharSequence helpString) { 272 mErrorText.setText(helpString); 273 } 274 275 @Override 276 public void onEnrollmentError(int errMsgId, CharSequence errString) { 277 int msgId; 278 switch (errMsgId) { 279 case FingerprintManager.FINGERPRINT_ERROR_TIMEOUT: 280 // This message happens when the underlying crypto layer decides to revoke the 281 // enrollment auth token. 282 msgId = R.string.security_settings_fingerprint_enroll_error_timeout_dialog_message; 283 break; 284 default: 285 // There's nothing specific to tell the user about. Ask them to try again. 286 msgId = R.string.security_settings_fingerprint_enroll_error_generic_dialog_message; 287 break; 288 } 289 showErrorDialog(getText(msgId), errMsgId); 290 stopIconAnimation(); 291 mErrorText.removeCallbacks(mTouchAgainRunnable); 292 } 293 294 @Override 295 public void onEnrollmentProgressChange(int steps, int remaining) { 296 updateProgress(true /* animate */); 297 updateDescription(); 298 clearError(); 299 animateFlash(); 300 mErrorText.removeCallbacks(mTouchAgainRunnable); 301 mErrorText.postDelayed(mTouchAgainRunnable, HINT_TIMEOUT_DURATION); 302 } 303 304 private void updateProgress(boolean animate) { 305 int progress = getProgress( 306 mSidecar.getEnrollmentSteps(), mSidecar.getEnrollmentRemaining()); 307 if (animate) { 308 animateProgress(progress); 309 } else { 310 mProgressBar.setProgress(progress); 311 } 312 } 313 314 private int getProgress(int steps, int remaining) { 315 if (steps == -1) { 316 return 0; 317 } 318 int progress = Math.max(0, steps + 1 - remaining); 319 return PROGRESS_BAR_MAX * progress / (steps + 1); 320 } 321 322 private void showErrorDialog(CharSequence msg, int msgId) { 323 ErrorDialog dlg = ErrorDialog.newInstance(msg, msgId); 324 dlg.show(getFragmentManager(), ErrorDialog.class.getName()); 325 } 326 327 private void showIconTouchDialog() { 328 mIconTouchCount = 0; 329 new IconTouchDialog().show(getFragmentManager(), null /* tag */); 330 } 331 332 private void showError(CharSequence error) { 333 mErrorText.setText(error); 334 if (mErrorText.getVisibility() == View.INVISIBLE) { 335 mErrorText.setVisibility(View.VISIBLE); 336 mErrorText.setTranslationY(getResources().getDimensionPixelSize( 337 R.dimen.fingerprint_error_text_appear_distance)); 338 mErrorText.setAlpha(0f); 339 mErrorText.animate() 340 .alpha(1f) 341 .translationY(0f) 342 .setDuration(200) 343 .setInterpolator(mLinearOutSlowInInterpolator) 344 .start(); 345 } else { 346 mErrorText.animate().cancel(); 347 mErrorText.setAlpha(1f); 348 mErrorText.setTranslationY(0f); 349 } 350 } 351 352 private void clearError() { 353 if (mErrorText.getVisibility() == View.VISIBLE) { 354 mErrorText.animate() 355 .alpha(0f) 356 .translationY(getResources().getDimensionPixelSize( 357 R.dimen.fingerprint_error_text_disappear_distance)) 358 .setDuration(100) 359 .setInterpolator(mFastOutLinearInInterpolator) 360 .withEndAction(new Runnable() { 361 @Override 362 public void run() { 363 mErrorText.setVisibility(View.INVISIBLE); 364 } 365 }) 366 .start(); 367 } 368 } 369 370 private final Animator.AnimatorListener mProgressAnimationListener 371 = new Animator.AnimatorListener() { 372 373 @Override 374 public void onAnimationStart(Animator animation) { } 375 376 @Override 377 public void onAnimationRepeat(Animator animation) { } 378 379 @Override 380 public void onAnimationEnd(Animator animation) { 381 if (mProgressBar.getProgress() >= PROGRESS_BAR_MAX) { 382 mProgressBar.postDelayed(mDelayedFinishRunnable, FINISH_DELAY); 383 } 384 } 385 386 @Override 387 public void onAnimationCancel(Animator animation) { } 388 }; 389 390 // Give the user a chance to see progress completed before jumping to the next stage. 391 private final Runnable mDelayedFinishRunnable = new Runnable() { 392 @Override 393 public void run() { 394 launchFinish(mToken); 395 } 396 }; 397 398 private final Animatable2.AnimationCallback mIconAnimationCallback = 399 new Animatable2.AnimationCallback() { 400 @Override 401 public void onAnimationEnd(Drawable d) { 402 if (mAnimationCancelled) { 403 return; 404 } 405 406 // Start animation after it has ended. 407 mProgressBar.post(new Runnable() { 408 @Override 409 public void run() { 410 startIconAnimation(); 411 } 412 }); 413 } 414 }; 415 416 private final Runnable mShowDialogRunnable = new Runnable() { 417 @Override 418 public void run() { 419 showIconTouchDialog(); 420 } 421 }; 422 423 private final Runnable mTouchAgainRunnable = new Runnable() { 424 @Override 425 public void run() { 426 showError(getString(R.string.security_settings_fingerprint_enroll_lift_touch_again)); 427 } 428 }; 429 430 @Override 431 public int getMetricsCategory() { 432 return MetricsEvent.FINGERPRINT_ENROLLING; 433 } 434 435 public static class IconTouchDialog extends InstrumentedDialogFragment { 436 437 @Override 438 public Dialog onCreateDialog(Bundle savedInstanceState) { 439 AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 440 builder.setTitle(R.string.security_settings_fingerprint_enroll_touch_dialog_title) 441 .setMessage(R.string.security_settings_fingerprint_enroll_touch_dialog_message) 442 .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok, 443 new DialogInterface.OnClickListener() { 444 @Override 445 public void onClick(DialogInterface dialog, int which) { 446 dialog.dismiss(); 447 } 448 }); 449 return builder.create(); 450 } 451 452 @Override 453 public int getMetricsCategory() { 454 return MetricsEvent.DIALOG_FINGERPRINT_ICON_TOUCH; 455 } 456 } 457 458 public static class ErrorDialog extends InstrumentedDialogFragment { 459 460 /** 461 * Create a new instance of ErrorDialog. 462 * 463 * @param msg the string to show for message text 464 * @param msgId the FingerprintManager error id so we know the cause 465 * @return a new ErrorDialog 466 */ 467 static ErrorDialog newInstance(CharSequence msg, int msgId) { 468 ErrorDialog dlg = new ErrorDialog(); 469 Bundle args = new Bundle(); 470 args.putCharSequence("error_msg", msg); 471 args.putInt("error_id", msgId); 472 dlg.setArguments(args); 473 return dlg; 474 } 475 476 @Override 477 public Dialog onCreateDialog(Bundle savedInstanceState) { 478 AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 479 CharSequence errorString = getArguments().getCharSequence("error_msg"); 480 final int errMsgId = getArguments().getInt("error_id"); 481 builder.setTitle(R.string.security_settings_fingerprint_enroll_error_dialog_title) 482 .setMessage(errorString) 483 .setCancelable(false) 484 .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok, 485 new DialogInterface.OnClickListener() { 486 @Override 487 public void onClick(DialogInterface dialog, int which) { 488 dialog.dismiss(); 489 boolean wasTimeout = 490 errMsgId == FingerprintManager.FINGERPRINT_ERROR_TIMEOUT; 491 Activity activity = getActivity(); 492 activity.setResult(wasTimeout ? 493 RESULT_TIMEOUT : RESULT_FINISHED); 494 activity.finish(); 495 } 496 }); 497 AlertDialog dialog = builder.create(); 498 dialog.setCanceledOnTouchOutside(false); 499 return dialog; 500 } 501 502 @Override 503 public int getMetricsCategory() { 504 return MetricsEvent.DIALOG_FINGERPINT_ERROR; 505 } 506 } 507} 508