| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| |
| package com.android.settings.fingerprint; |
| |
| import android.animation.Animator; |
| import android.animation.AnimatorListenerAdapter; |
| import android.animation.ObjectAnimator; |
| import android.animation.ValueAnimator; |
| import android.app.Activity; |
| import android.app.AlertDialog; |
| import android.app.Dialog; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.graphics.drawable.Animatable2; |
| import android.graphics.drawable.AnimatedVectorDrawable; |
| import android.graphics.drawable.Drawable; |
| import android.graphics.drawable.LayerDrawable; |
| import android.hardware.fingerprint.FingerprintManager; |
| import android.os.Bundle; |
| import android.os.UserHandle; |
| import android.text.TextUtils; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.animation.AnimationUtils; |
| import android.view.animation.Interpolator; |
| import android.widget.Button; |
| import android.widget.ProgressBar; |
| import android.widget.TextView; |
| |
| import com.android.internal.logging.nano.MetricsProto.MetricsEvent; |
| import com.android.settings.R; |
| import com.android.settings.core.instrumentation.InstrumentedDialogFragment; |
| import com.android.settings.password.ChooseLockSettingsHelper; |
| |
| /** |
| * Activity which handles the actual enrolling for fingerprint. |
| */ |
| public class FingerprintEnrollEnrolling extends FingerprintEnrollBase |
| implements FingerprintEnrollSidecar.Listener { |
| |
| static final String TAG_SIDECAR = "sidecar"; |
| |
| private static final int PROGRESS_BAR_MAX = 10000; |
| private static final int FINISH_DELAY = 250; |
| |
| /** |
| * If we don't see progress during this time, we show an error message to remind the user that |
| * he needs to lift the finger and touch again. |
| */ |
| private static final int HINT_TIMEOUT_DURATION = 2500; |
| |
| /** |
| * How long the user needs to touch the icon until we show the dialog. |
| */ |
| private static final long ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN = 500; |
| |
| /** |
| * How many times the user needs to touch the icon until we show the dialog that this is not the |
| * fingerprint sensor. |
| */ |
| private static final int ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN = 3; |
| |
| private ProgressBar mProgressBar; |
| private ObjectAnimator mProgressAnim; |
| private TextView mStartMessage; |
| private TextView mRepeatMessage; |
| private TextView mErrorText; |
| private Interpolator mFastOutSlowInInterpolator; |
| private Interpolator mLinearOutSlowInInterpolator; |
| private Interpolator mFastOutLinearInInterpolator; |
| private int mIconTouchCount; |
| private FingerprintEnrollSidecar mSidecar; |
| private boolean mAnimationCancelled; |
| private AnimatedVectorDrawable mIconAnimationDrawable; |
| private Drawable mIconBackgroundDrawable; |
| private int mIndicatorBackgroundRestingColor; |
| private int mIndicatorBackgroundActivatedColor; |
| private boolean mRestoring; |
| |
| @Override |
| protected void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| setContentView(R.layout.fingerprint_enroll_enrolling); |
| setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title); |
| mStartMessage = (TextView) findViewById(R.id.start_message); |
| mRepeatMessage = (TextView) findViewById(R.id.repeat_message); |
| mErrorText = (TextView) findViewById(R.id.error_text); |
| mProgressBar = (ProgressBar) findViewById(R.id.fingerprint_progress_bar); |
| |
| Button skipButton = findViewById(R.id.skip_button); |
| skipButton.setOnClickListener(this); |
| |
| final LayerDrawable fingerprintDrawable = (LayerDrawable) mProgressBar.getBackground(); |
| mIconAnimationDrawable = (AnimatedVectorDrawable) |
| fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_animation); |
| mIconBackgroundDrawable = |
| fingerprintDrawable.findDrawableByLayerId(R.id.fingerprint_background); |
| mIconAnimationDrawable.registerAnimationCallback(mIconAnimationCallback); |
| mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator( |
| this, android.R.interpolator.fast_out_slow_in); |
| mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator( |
| this, android.R.interpolator.linear_out_slow_in); |
| mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator( |
| this, android.R.interpolator.fast_out_linear_in); |
| mProgressBar.setOnTouchListener(new View.OnTouchListener() { |
| @Override |
| public boolean onTouch(View v, MotionEvent event) { |
| if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { |
| mIconTouchCount++; |
| if (mIconTouchCount == ICON_TOUCH_COUNT_SHOW_UNTIL_DIALOG_SHOWN) { |
| showIconTouchDialog(); |
| } else { |
| mProgressBar.postDelayed(mShowDialogRunnable, |
| ICON_TOUCH_DURATION_UNTIL_DIALOG_SHOWN); |
| } |
| } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL |
| || event.getActionMasked() == MotionEvent.ACTION_UP) { |
| mProgressBar.removeCallbacks(mShowDialogRunnable); |
| } |
| return true; |
| } |
| }); |
| mIndicatorBackgroundRestingColor |
| = getColor(R.color.fingerprint_indicator_background_resting); |
| mIndicatorBackgroundActivatedColor |
| = getColor(R.color.fingerprint_indicator_background_activated); |
| mIconBackgroundDrawable.setTint(mIndicatorBackgroundRestingColor); |
| mRestoring = savedInstanceState != null; |
| } |
| |
| @Override |
| protected void onStart() { |
| super.onStart(); |
| mSidecar = (FingerprintEnrollSidecar) getFragmentManager().findFragmentByTag(TAG_SIDECAR); |
| if (mSidecar == null) { |
| mSidecar = new FingerprintEnrollSidecar(); |
| getFragmentManager().beginTransaction().add(mSidecar, TAG_SIDECAR).commit(); |
| } |
| mSidecar.setListener(this); |
| updateProgress(false /* animate */); |
| updateDescription(); |
| if (mRestoring) { |
| startIconAnimation(); |
| } |
| } |
| |
| @Override |
| public void onEnterAnimationComplete() { |
| super.onEnterAnimationComplete(); |
| mAnimationCancelled = false; |
| startIconAnimation(); |
| } |
| |
| @Override |
| protected void onResume() { |
| super.onResume(); |
| if (mSidecar != null) { |
| mSidecar.setListener(this); |
| } |
| } |
| |
| @Override |
| protected void onPause() { |
| super.onPause(); |
| if (mSidecar != null) { |
| mSidecar.setListener(null); |
| } |
| } |
| |
| private void startIconAnimation() { |
| mIconAnimationDrawable.start(); |
| } |
| |
| private void stopIconAnimation() { |
| mAnimationCancelled = true; |
| mIconAnimationDrawable.stop(); |
| } |
| |
| @Override |
| protected void onStop() { |
| super.onStop(); |
| if (mSidecar != null) { |
| mSidecar.setListener(null); |
| } |
| stopIconAnimation(); |
| if (!isChangingConfigurations()) { |
| if (mSidecar != null) { |
| mSidecar.cancelEnrollment(); |
| getFragmentManager().beginTransaction().remove(mSidecar).commitAllowingStateLoss(); |
| } |
| finish(); |
| } |
| } |
| |
| @Override |
| public void onBackPressed() { |
| if (mSidecar != null) { |
| mSidecar.setListener(null); |
| mSidecar.cancelEnrollment(); |
| getFragmentManager().beginTransaction().remove(mSidecar).commitAllowingStateLoss(); |
| mSidecar = null; |
| } |
| super.onBackPressed(); |
| } |
| |
| @Override |
| public void onClick(View v) { |
| switch (v.getId()) { |
| case R.id.skip_button: |
| setResult(RESULT_SKIP); |
| finish(); |
| break; |
| default: |
| super.onClick(v); |
| } |
| } |
| |
| private void animateProgress(int progress) { |
| if (mProgressAnim != null) { |
| mProgressAnim.cancel(); |
| } |
| ObjectAnimator anim = ObjectAnimator.ofInt(mProgressBar, "progress", |
| mProgressBar.getProgress(), progress); |
| anim.addListener(mProgressAnimationListener); |
| anim.setInterpolator(mFastOutSlowInInterpolator); |
| anim.setDuration(250); |
| anim.start(); |
| mProgressAnim = anim; |
| } |
| |
| private void animateFlash() { |
| ValueAnimator anim = ValueAnimator.ofArgb(mIndicatorBackgroundRestingColor, |
| mIndicatorBackgroundActivatedColor); |
| final ValueAnimator.AnimatorUpdateListener listener = |
| new ValueAnimator.AnimatorUpdateListener() { |
| @Override |
| public void onAnimationUpdate(ValueAnimator animation) { |
| mIconBackgroundDrawable.setTint((Integer) animation.getAnimatedValue()); |
| } |
| }; |
| anim.addUpdateListener(listener); |
| anim.addListener(new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| ValueAnimator anim = ValueAnimator.ofArgb(mIndicatorBackgroundActivatedColor, |
| mIndicatorBackgroundRestingColor); |
| anim.addUpdateListener(listener); |
| anim.setDuration(300); |
| anim.setInterpolator(mLinearOutSlowInInterpolator); |
| anim.start(); |
| } |
| }); |
| anim.setInterpolator(mFastOutSlowInInterpolator); |
| anim.setDuration(300); |
| anim.start(); |
| } |
| |
| private void launchFinish(byte[] token) { |
| Intent intent = getFinishIntent(); |
| intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT |
| | Intent.FLAG_ACTIVITY_CLEAR_TOP |
| | Intent.FLAG_ACTIVITY_SINGLE_TOP); |
| intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token); |
| if (mUserId != UserHandle.USER_NULL) { |
| intent.putExtra(Intent.EXTRA_USER_ID, mUserId); |
| } |
| startActivity(intent); |
| overridePendingTransition(R.anim.suw_slide_next_in, R.anim.suw_slide_next_out); |
| finish(); |
| } |
| |
| protected Intent getFinishIntent() { |
| return new Intent(this, FingerprintEnrollFinish.class); |
| } |
| |
| private void updateDescription() { |
| if (mSidecar.getEnrollmentSteps() == -1) { |
| mStartMessage.setVisibility(View.VISIBLE); |
| mRepeatMessage.setVisibility(View.INVISIBLE); |
| } else { |
| mStartMessage.setVisibility(View.INVISIBLE); |
| mRepeatMessage.setVisibility(View.VISIBLE); |
| } |
| } |
| |
| |
| @Override |
| public void onEnrollmentHelp(CharSequence helpString) { |
| if (!TextUtils.isEmpty(helpString)) { |
| mErrorText.removeCallbacks(mTouchAgainRunnable); |
| showError(helpString); |
| } |
| } |
| |
| @Override |
| public void onEnrollmentError(int errMsgId, CharSequence errString) { |
| int msgId; |
| switch (errMsgId) { |
| case FingerprintManager.FINGERPRINT_ERROR_TIMEOUT: |
| // This message happens when the underlying crypto layer decides to revoke the |
| // enrollment auth token. |
| msgId = R.string.security_settings_fingerprint_enroll_error_timeout_dialog_message; |
| break; |
| default: |
| // There's nothing specific to tell the user about. Ask them to try again. |
| msgId = R.string.security_settings_fingerprint_enroll_error_generic_dialog_message; |
| break; |
| } |
| showErrorDialog(getText(msgId), errMsgId); |
| stopIconAnimation(); |
| mErrorText.removeCallbacks(mTouchAgainRunnable); |
| } |
| |
| @Override |
| public void onEnrollmentProgressChange(int steps, int remaining) { |
| updateProgress(true /* animate */); |
| updateDescription(); |
| clearError(); |
| animateFlash(); |
| mErrorText.removeCallbacks(mTouchAgainRunnable); |
| mErrorText.postDelayed(mTouchAgainRunnable, HINT_TIMEOUT_DURATION); |
| } |
| |
| private void updateProgress(boolean animate) { |
| int progress = getProgress( |
| mSidecar.getEnrollmentSteps(), mSidecar.getEnrollmentRemaining()); |
| if (animate) { |
| animateProgress(progress); |
| } else { |
| mProgressBar.setProgress(progress); |
| if (progress >= PROGRESS_BAR_MAX) { |
| mDelayedFinishRunnable.run(); |
| } |
| } |
| } |
| |
| private int getProgress(int steps, int remaining) { |
| if (steps == -1) { |
| return 0; |
| } |
| int progress = Math.max(0, steps + 1 - remaining); |
| return PROGRESS_BAR_MAX * progress / (steps + 1); |
| } |
| |
| private void showErrorDialog(CharSequence msg, int msgId) { |
| ErrorDialog dlg = ErrorDialog.newInstance(msg, msgId); |
| dlg.show(getFragmentManager(), ErrorDialog.class.getName()); |
| } |
| |
| private void showIconTouchDialog() { |
| mIconTouchCount = 0; |
| new IconTouchDialog().show(getFragmentManager(), null /* tag */); |
| } |
| |
| private void showError(CharSequence error) { |
| mErrorText.setText(error); |
| if (mErrorText.getVisibility() == View.INVISIBLE) { |
| mErrorText.setVisibility(View.VISIBLE); |
| mErrorText.setTranslationY(getResources().getDimensionPixelSize( |
| R.dimen.fingerprint_error_text_appear_distance)); |
| mErrorText.setAlpha(0f); |
| mErrorText.animate() |
| .alpha(1f) |
| .translationY(0f) |
| .setDuration(200) |
| .setInterpolator(mLinearOutSlowInInterpolator) |
| .start(); |
| } else { |
| mErrorText.animate().cancel(); |
| mErrorText.setAlpha(1f); |
| mErrorText.setTranslationY(0f); |
| } |
| } |
| |
| private void clearError() { |
| if (mErrorText.getVisibility() == View.VISIBLE) { |
| mErrorText.animate() |
| .alpha(0f) |
| .translationY(getResources().getDimensionPixelSize( |
| R.dimen.fingerprint_error_text_disappear_distance)) |
| .setDuration(100) |
| .setInterpolator(mFastOutLinearInInterpolator) |
| .withEndAction(new Runnable() { |
| @Override |
| public void run() { |
| mErrorText.setVisibility(View.INVISIBLE); |
| } |
| }) |
| .start(); |
| } |
| } |
| |
| private final Animator.AnimatorListener mProgressAnimationListener |
| = new Animator.AnimatorListener() { |
| |
| @Override |
| public void onAnimationStart(Animator animation) { } |
| |
| @Override |
| public void onAnimationRepeat(Animator animation) { } |
| |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| if (mProgressBar.getProgress() >= PROGRESS_BAR_MAX) { |
| mProgressBar.postDelayed(mDelayedFinishRunnable, FINISH_DELAY); |
| } |
| } |
| |
| @Override |
| public void onAnimationCancel(Animator animation) { } |
| }; |
| |
| // Give the user a chance to see progress completed before jumping to the next stage. |
| private final Runnable mDelayedFinishRunnable = new Runnable() { |
| @Override |
| public void run() { |
| launchFinish(mToken); |
| } |
| }; |
| |
| private final Animatable2.AnimationCallback mIconAnimationCallback = |
| new Animatable2.AnimationCallback() { |
| @Override |
| public void onAnimationEnd(Drawable d) { |
| if (mAnimationCancelled) { |
| return; |
| } |
| |
| // Start animation after it has ended. |
| mProgressBar.post(new Runnable() { |
| @Override |
| public void run() { |
| startIconAnimation(); |
| } |
| }); |
| } |
| }; |
| |
| private final Runnable mShowDialogRunnable = new Runnable() { |
| @Override |
| public void run() { |
| showIconTouchDialog(); |
| } |
| }; |
| |
| private final Runnable mTouchAgainRunnable = new Runnable() { |
| @Override |
| public void run() { |
| showError(getString(R.string.security_settings_fingerprint_enroll_lift_touch_again)); |
| } |
| }; |
| |
| @Override |
| public int getMetricsCategory() { |
| return MetricsEvent.FINGERPRINT_ENROLLING; |
| } |
| |
| public static class IconTouchDialog extends InstrumentedDialogFragment { |
| |
| @Override |
| public Dialog onCreateDialog(Bundle savedInstanceState) { |
| AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); |
| builder.setTitle(R.string.security_settings_fingerprint_enroll_touch_dialog_title) |
| .setMessage(R.string.security_settings_fingerprint_enroll_touch_dialog_message) |
| .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok, |
| new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| dialog.dismiss(); |
| } |
| }); |
| return builder.create(); |
| } |
| |
| @Override |
| public int getMetricsCategory() { |
| return MetricsEvent.DIALOG_FINGERPRINT_ICON_TOUCH; |
| } |
| } |
| |
| public static class ErrorDialog extends InstrumentedDialogFragment { |
| |
| /** |
| * Create a new instance of ErrorDialog. |
| * |
| * @param msg the string to show for message text |
| * @param msgId the FingerprintManager error id so we know the cause |
| * @return a new ErrorDialog |
| */ |
| static ErrorDialog newInstance(CharSequence msg, int msgId) { |
| ErrorDialog dlg = new ErrorDialog(); |
| Bundle args = new Bundle(); |
| args.putCharSequence("error_msg", msg); |
| args.putInt("error_id", msgId); |
| dlg.setArguments(args); |
| return dlg; |
| } |
| |
| @Override |
| public Dialog onCreateDialog(Bundle savedInstanceState) { |
| AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); |
| CharSequence errorString = getArguments().getCharSequence("error_msg"); |
| final int errMsgId = getArguments().getInt("error_id"); |
| builder.setTitle(R.string.security_settings_fingerprint_enroll_error_dialog_title) |
| .setMessage(errorString) |
| .setCancelable(false) |
| .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok, |
| new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| dialog.dismiss(); |
| boolean wasTimeout = |
| errMsgId == FingerprintManager.FINGERPRINT_ERROR_TIMEOUT; |
| Activity activity = getActivity(); |
| activity.setResult(wasTimeout ? |
| RESULT_TIMEOUT : RESULT_FINISHED); |
| activity.finish(); |
| } |
| }); |
| AlertDialog dialog = builder.create(); |
| dialog.setCanceledOnTouchOutside(false); |
| return dialog; |
| } |
| |
| @Override |
| public int getMetricsCategory() { |
| return MetricsEvent.DIALOG_FINGERPINT_ERROR; |
| } |
| } |
| } |