Turning on dnd (and not allowing touch sounds) doesn't prevent the vibrations for fingerprint enrollment in settings, because now it'll have the correct sonification attributes that aren't muted by DND. Bug: 76016723 Test: manual Change-Id: I51cb14b25a2555628ff475c3a1b9e55fb3234c14
540 lines
20 KiB
Java
540 lines
20 KiB
Java
/*
|
|
* 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.media.AudioAttributes;
|
|
import android.os.Bundle;
|
|
import android.os.UserHandle;
|
|
import android.os.VibrationEffect;
|
|
import android.os.Vibrator;
|
|
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 static final VibrationEffect VIBRATE_EFFECT_ERROR =
|
|
VibrationEffect.createWaveform(new long[] {0, 5, 55, 60}, -1);
|
|
private static final AudioAttributes FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES =
|
|
new AudioAttributes.Builder()
|
|
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
|
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
|
|
.build();
|
|
|
|
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;
|
|
private Vibrator mVibrator;
|
|
|
|
@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);
|
|
mVibrator = getSystemService(Vibrator.class);
|
|
|
|
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();
|
|
}
|
|
|
|
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);
|
|
}
|
|
if (isResumed()) {
|
|
mVibrator.vibrate(VIBRATE_EFFECT_ERROR, FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES);
|
|
}
|
|
}
|
|
|
|
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(() -> 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;
|
|
}
|
|
}
|
|
}
|