FingerprintEnrollEnrolling.java revision 0c0858acad490a615871e7d9c1d3b73450398041
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.AlertDialog; 24import android.app.Dialog; 25import android.app.DialogFragment; 26import android.content.DialogInterface; 27import android.content.Intent; 28import android.content.res.ColorStateList; 29import android.graphics.drawable.Animatable2; 30import android.graphics.drawable.AnimatedVectorDrawable; 31import android.graphics.drawable.Drawable; 32import android.os.Bundle; 33import android.view.MotionEvent; 34import android.view.View; 35import android.view.animation.AnimationUtils; 36import android.view.animation.Interpolator; 37import android.widget.ImageView; 38import android.widget.ProgressBar; 39import android.widget.TextView; 40 41import com.android.settings.ChooseLockSettingsHelper; 42import com.android.settings.R; 43 44/** 45 * Activity which handles the actual enrolling for fingerprint. 46 */ 47public class FingerprintEnrollEnrolling extends FingerprintEnrollBase 48 implements FingerprintEnrollSidecar.Listener { 49 50 private static final String TAG_SIDECAR = "sidecar"; 51 52 private static final int PROGRESS_BAR_MAX = 10000; 53 private static final int FINISH_DELAY = 250; 54 55 /** 56 * If we don't see progress during this time, we show an error message to remind the user that 57 * he needs to lift the finger and touch again. 58 */ 59 private static final int HINT_TIMEOUT_DURATION = 2500; 60 61 /** 62 * How long the user needs to touch the icon until we show the dialog. 63 */ 64 private static final long ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN = 500; 65 66 /** 67 * How many times the user needs to touch the icon until we show the dialog that this is not the 68 * fingerprint sensor. 69 */ 70 private static final int ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN = 3; 71 72 private ProgressBar mProgressBar; 73 private ImageView mFingerprintAnimator; 74 private ObjectAnimator mProgressAnim; 75 private TextView mStartMessage; 76 private TextView mRepeatMessage; 77 private TextView mErrorText; 78 private Interpolator mFastOutSlowInInterpolator; 79 private Interpolator mLinearOutSlowInInterpolator; 80 private Interpolator mFastOutLinearInInterpolator; 81 private int mIconTouchCount; 82 private FingerprintEnrollSidecar mSidecar; 83 private boolean mAnimationCancelled; 84 private AnimatedVectorDrawable mIconAnimationDrawable; 85 private int mIndicatorBackgroundRestingColor; 86 private int mIndicatorBackgroundActivatedColor; 87 private boolean mRestoring; 88 89 @Override 90 protected void onCreate(Bundle savedInstanceState) { 91 super.onCreate(savedInstanceState); 92 setContentView(R.layout.fingerprint_enroll_enrolling); 93 setHeaderText(R.string.security_settings_fingerprint_enroll_start_title); 94 mStartMessage = (TextView) findViewById(R.id.start_message); 95 mRepeatMessage = (TextView) findViewById(R.id.repeat_message); 96 mErrorText = (TextView) findViewById(R.id.error_text); 97 mProgressBar = (ProgressBar) findViewById(R.id.fingerprint_progress_bar); 98 mFingerprintAnimator = (ImageView) findViewById(R.id.fingerprint_animator); 99 mIconAnimationDrawable = (AnimatedVectorDrawable) mFingerprintAnimator.getDrawable(); 100 mIconAnimationDrawable.registerAnimationCallback(mIconAnimationCallback); 101 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator( 102 this, android.R.interpolator.fast_out_slow_in); 103 mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator( 104 this, android.R.interpolator.linear_out_slow_in); 105 mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator( 106 this, android.R.interpolator.fast_out_linear_in); 107 mFingerprintAnimator.setOnTouchListener(new View.OnTouchListener() { 108 @Override 109 public boolean onTouch(View v, MotionEvent event) { 110 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 111 mIconTouchCount++; 112 if (mIconTouchCount == ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN) { 113 showIconTouchDialog(); 114 } else { 115 mFingerprintAnimator.postDelayed(mShowDialogRunnable, 116 ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN); 117 } 118 } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL 119 || event.getActionMasked() == MotionEvent.ACTION_UP) { 120 mFingerprintAnimator.removeCallbacks(mShowDialogRunnable); 121 } 122 return true; 123 } 124 }); 125 mIndicatorBackgroundRestingColor 126 = getColor(R.color.fingerprint_indicator_background_resting); 127 mIndicatorBackgroundActivatedColor 128 = getColor(R.color.fingerprint_indicator_background_activated); 129 mRestoring = savedInstanceState != null; 130 } 131 132 @Override 133 protected void onStart() { 134 super.onStart(); 135 mSidecar = (FingerprintEnrollSidecar) getFragmentManager().findFragmentByTag(TAG_SIDECAR); 136 if (mSidecar == null) { 137 mSidecar = new FingerprintEnrollSidecar(); 138 getFragmentManager().beginTransaction().add(mSidecar, TAG_SIDECAR).commit(); 139 } 140 mSidecar.setListener(this); 141 updateProgress(false /* animate */); 142 updateDescription(); 143 if (mRestoring) { 144 startIconAnimation(); 145 } 146 } 147 148 @Override 149 public void onEnterAnimationComplete() { 150 super.onEnterAnimationComplete(); 151 mAnimationCancelled = false; 152 startIconAnimation(); 153 } 154 155 private void startIconAnimation() { 156 mIconAnimationDrawable.start(); 157 } 158 159 private void stopIconAnimation() { 160 mAnimationCancelled = true; 161 mIconAnimationDrawable.stop(); 162 } 163 164 @Override 165 protected void onStop() { 166 super.onStop(); 167 mSidecar.setListener(null); 168 stopIconAnimation(); 169 if (!isChangingConfigurations()) { 170 finish(); 171 } 172 } 173 174 private void animateProgress(int progress) { 175 if (mProgressAnim != null) { 176 mProgressAnim.cancel(); 177 } 178 ObjectAnimator anim = ObjectAnimator.ofInt(mProgressBar, "progress", 179 mProgressBar.getProgress(), progress); 180 anim.addListener(mProgressAnimationListener); 181 anim.setInterpolator(mFastOutSlowInInterpolator); 182 anim.setDuration(250); 183 anim.start(); 184 mProgressAnim = anim; 185 } 186 187 private void animateFlash() { 188 ValueAnimator anim = ValueAnimator.ofArgb(mIndicatorBackgroundRestingColor, 189 mIndicatorBackgroundActivatedColor); 190 final ValueAnimator.AnimatorUpdateListener listener = 191 new ValueAnimator.AnimatorUpdateListener() { 192 @Override 193 public void onAnimationUpdate(ValueAnimator animation) { 194 mFingerprintAnimator.setBackgroundTintList(ColorStateList.valueOf( 195 (Integer) animation.getAnimatedValue())); 196 } 197 }; 198 anim.addUpdateListener(listener); 199 anim.addListener(new AnimatorListenerAdapter() { 200 @Override 201 public void onAnimationEnd(Animator animation) { 202 ValueAnimator anim = ValueAnimator.ofArgb(mIndicatorBackgroundActivatedColor, 203 mIndicatorBackgroundRestingColor); 204 anim.addUpdateListener(listener); 205 anim.setDuration(300); 206 anim.setInterpolator(mLinearOutSlowInInterpolator); 207 anim.start(); 208 } 209 }); 210 anim.setInterpolator(mFastOutSlowInInterpolator); 211 anim.setDuration(300); 212 anim.start(); 213 } 214 215 private void launchFinish(byte[] token) { 216 Intent intent = new Intent(); 217 intent.setClassName("com.android.settings", FingerprintEnrollFinish.class.getName()); 218 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token); 219 startActivity(intent); 220 setResult(RESULT_FINISHED); 221 finish(); 222 } 223 224 private void updateDescription() { 225 if (mSidecar.getEnrollmentSteps() == -1) { 226 setHeaderText(R.string.security_settings_fingerprint_enroll_start_title); 227 mStartMessage.setVisibility(View.VISIBLE); 228 mRepeatMessage.setVisibility(View.INVISIBLE); 229 } else { 230 setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title, 231 true /* force */); 232 mStartMessage.setVisibility(View.INVISIBLE); 233 mRepeatMessage.setVisibility(View.VISIBLE); 234 } 235 } 236 237 238 @Override 239 public void onEnrollmentHelp(CharSequence helpString) { 240 mErrorText.setText(helpString); 241 } 242 243 @Override 244 public void onEnrollmentError(CharSequence errString) { 245 showError(errString); 246 stopIconAnimation(); 247 } 248 249 @Override 250 public void onEnrollmentProgressChange(int steps, int remaining) { 251 updateProgress(true /* animate */); 252 updateDescription(); 253 clearError(); 254 animateFlash(); 255 mErrorText.removeCallbacks(mTouchAgainRunnable); 256 mErrorText.postDelayed(mTouchAgainRunnable, HINT_TIMEOUT_DURATION); 257 } 258 259 private void updateProgress(boolean animate) { 260 int progress = getProgress( 261 mSidecar.getEnrollmentSteps(), mSidecar.getEnrollmentRemaining()); 262 if (animate) { 263 animateProgress(progress); 264 } else { 265 mProgressBar.setProgress(progress); 266 } 267 } 268 269 private int getProgress(int steps, int remaining) { 270 if (steps == -1) { 271 return 0; 272 } 273 int progress = Math.max(0, steps + 1 - remaining); 274 return PROGRESS_BAR_MAX * progress / (steps + 1); 275 } 276 277 private void showIconTouchDialog() { 278 mIconTouchCount = 0; 279 new IconTouchDialog().show(getFragmentManager(), null /* tag */); 280 } 281 282 private void showError(CharSequence error) { 283 mErrorText.setText(error); 284 if (mErrorText.getVisibility() == View.INVISIBLE) { 285 mErrorText.setVisibility(View.VISIBLE); 286 mErrorText.setTranslationY(getResources().getDimensionPixelSize( 287 R.dimen.fingerprint_error_text_appear_distance)); 288 mErrorText.setAlpha(0f); 289 mErrorText.animate() 290 .alpha(1f) 291 .translationY(0f) 292 .setDuration(200) 293 .setInterpolator(mLinearOutSlowInInterpolator) 294 .start(); 295 } else { 296 mErrorText.animate().cancel(); 297 mErrorText.setAlpha(1f); 298 mErrorText.setTranslationY(0f); 299 } 300 } 301 302 private void clearError() { 303 if (mErrorText.getVisibility() == View.VISIBLE) { 304 mErrorText.animate() 305 .alpha(0f) 306 .translationY(getResources().getDimensionPixelSize( 307 R.dimen.fingerprint_error_text_disappear_distance)) 308 .setDuration(100) 309 .setInterpolator(mFastOutLinearInInterpolator) 310 .withEndAction(new Runnable() { 311 @Override 312 public void run() { 313 mErrorText.setVisibility(View.INVISIBLE); 314 } 315 }) 316 .start(); 317 } 318 } 319 320 private final Animator.AnimatorListener mProgressAnimationListener 321 = new Animator.AnimatorListener() { 322 323 @Override 324 public void onAnimationStart(Animator animation) { } 325 326 @Override 327 public void onAnimationRepeat(Animator animation) { } 328 329 @Override 330 public void onAnimationEnd(Animator animation) { 331 if (mProgressBar.getProgress() >= PROGRESS_BAR_MAX) { 332 mProgressBar.postDelayed(mDelayedFinishRunnable, FINISH_DELAY); 333 } 334 } 335 336 @Override 337 public void onAnimationCancel(Animator animation) { } 338 }; 339 340 // Give the user a chance to see progress completed before jumping to the next stage. 341 private final Runnable mDelayedFinishRunnable = new Runnable() { 342 @Override 343 public void run() { 344 launchFinish(mToken); 345 } 346 }; 347 348 private final Animatable2.AnimationCallback mIconAnimationCallback = 349 new Animatable2.AnimationCallback() { 350 @Override 351 public void onAnimationEnd(Drawable d) { 352 if (mAnimationCancelled) { 353 return; 354 } 355 356 // Start animation after it has ended. 357 mFingerprintAnimator.post(new Runnable() { 358 @Override 359 public void run() { 360 startIconAnimation(); 361 } 362 }); 363 } 364 }; 365 366 private final Runnable mShowDialogRunnable = new Runnable() { 367 @Override 368 public void run() { 369 showIconTouchDialog(); 370 } 371 }; 372 373 private final Runnable mTouchAgainRunnable = new Runnable() { 374 @Override 375 public void run() { 376 showError(getString(R.string.security_settings_fingerprint_enroll_lift_touch_again)); 377 } 378 }; 379 380 public static class IconTouchDialog extends DialogFragment { 381 382 @Override 383 public Dialog onCreateDialog(Bundle savedInstanceState) { 384 AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 385 builder.setTitle(R.string.security_settings_fingerprint_enroll_touch_dialog_title) 386 .setMessage(R.string.security_settings_fingerprint_enroll_touch_dialog_message) 387 .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok, 388 new DialogInterface.OnClickListener() { 389 @Override 390 public void onClick(DialogInterface dialog, int which) { 391 dialog.dismiss(); 392 } 393 }); 394 return builder.create(); 395 } 396 } 397} 398