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