diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index d48d562b6692e..a3d595c230950 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -146,6 +146,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing private CryptoObject mCryptoObject; @Nullable private RemoveTracker mRemoveTracker; private Handler mHandler; + @Nullable private float[] mEnrollStageThresholds; /** * Retrieves a list of properties for all fingerprint sensors on the device. @@ -1326,6 +1327,46 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } } + /** + * @hide + */ + public int getEnrollStageCount() { + if (mEnrollStageThresholds == null) { + mEnrollStageThresholds = createEnrollStageThresholds(mContext); + } + return mEnrollStageThresholds.length + 1; + } + + /** + * @hide + */ + public float getEnrollStageThreshold(int index) { + if (mEnrollStageThresholds == null) { + mEnrollStageThresholds = createEnrollStageThresholds(mContext); + } + + if (index < 0 || index > mEnrollStageThresholds.length) { + Slog.w(TAG, "Unsupported enroll stage index: " + index); + return index < 0 ? 0f : 1f; + } + + // The implicit threshold for the final stage is always 1. + return index == mEnrollStageThresholds.length ? 1f : mEnrollStageThresholds[index]; + } + + @NonNull + private static float[] createEnrollStageThresholds(@NonNull Context context) { + // TODO(b/200604947): Fetch this value from FingerprintService, rather than internal config + final String[] enrollStageThresholdStrings = context.getResources().getStringArray( + com.android.internal.R.array.config_udfps_enroll_stage_thresholds); + + final float[] enrollStageThresholds = new float[enrollStageThresholdStrings.length]; + for (int i = 0; i < enrollStageThresholds.length; i++) { + enrollStageThresholds[i] = Float.parseFloat(enrollStageThresholdStrings[i]); + } + return enrollStageThresholds; + } + /** * @hide */ diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 985d810995384..e3e5f841725fb 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4601,6 +4601,13 @@ false + + + 0.25 + 0.5 + 0.75 + + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 97d5e28036c20..b6c76596d1318 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2613,6 +2613,7 @@ + @@ -4418,7 +4419,7 @@ - + diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 535f09159bc35..5018e57caf983 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -244,7 +244,7 @@ public class UdfpsController implements DozeReceiver { final UdfpsEnrollHelper enrollHelper; if (reason == BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR || reason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING) { - enrollHelper = new UdfpsEnrollHelper(mContext, reason); + enrollHelper = new UdfpsEnrollHelper(mContext, mFingerprintManager, reason); } else { enrollHelper = null; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java index d4077563cfeac..2034ff35be709 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java @@ -16,9 +16,11 @@ package com.android.systemui.biometrics; +import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.content.Context; +import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; @@ -26,11 +28,17 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Looper; +import android.util.TypedValue; import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.LinearInterpolator; +import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.graphics.ColorUtils; import com.android.systemui.R; /** @@ -39,10 +47,20 @@ import com.android.systemui.R; public class UdfpsEnrollDrawable extends UdfpsDrawable { private static final String TAG = "UdfpsAnimationEnroll"; - private static final long ANIM_DURATION = 800; + private static final long HINT_COLOR_ANIM_DELAY_MS = 233L; + private static final long HINT_COLOR_ANIM_DURATION_MS = 517L; + private static final long HINT_WIDTH_ANIM_DURATION_MS = 233L; + private static final long TARGET_ANIM_DURATION_LONG = 800L; + private static final long TARGET_ANIM_DURATION_SHORT = 600L; // 1 + SCALE_MAX is the maximum that the moving target will animate to private static final float SCALE_MAX = 0.25f; + private static final float HINT_PADDING_DP = 10f; + private static final float HINT_MAX_WIDTH_DP = 6f; + private static final float HINT_ANGLE = 40f; + + private final Handler mHandler = new Handler(Looper.getMainLooper()); + @NonNull private final Drawable mMovingTargetFpIcon; @NonNull private final Paint mSensorOutlinePaint; @NonNull private final Paint mBlueFill; @@ -51,17 +69,41 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { @Nullable private UdfpsEnrollHelper mEnrollHelper; // Moving target animator set - @Nullable AnimatorSet mAnimatorSet; + @Nullable AnimatorSet mTargetAnimatorSet; // Moving target location float mCurrentX; float mCurrentY; // Moving target size float mCurrentScale = 1.f; + @ColorInt private final int mHintColorFaded; + @ColorInt private final int mHintColorHighlight; + private final float mHintMaxWidthPx; + private final float mHintPaddingPx; + + @NonNull private final Animator.AnimatorListener mTargetAnimListener; + + private boolean mShouldShowTipHint = false; + @NonNull private final Paint mTipHintPaint; + @Nullable private AnimatorSet mTipHintAnimatorSet; + @Nullable private ValueAnimator mTipHintColorAnimator; + @Nullable private ValueAnimator mTipHintWidthAnimator; + @NonNull private final ValueAnimator.AnimatorUpdateListener mTipHintColorUpdateListener; + @NonNull private final ValueAnimator.AnimatorUpdateListener mTipHintWidthUpdateListener; + @NonNull private final Animator.AnimatorListener mTipHintPulseListener; + + private boolean mShouldShowEdgeHint = false; + @NonNull private final Paint mEdgeHintPaint; + @Nullable private AnimatorSet mEdgeHintAnimatorSet; + @Nullable private ValueAnimator mEdgeHintColorAnimator; + @Nullable private ValueAnimator mEdgeHintWidthAnimator; + @NonNull private final ValueAnimator.AnimatorUpdateListener mEdgeHintColorUpdateListener; + @NonNull private final ValueAnimator.AnimatorUpdateListener mEdgeHintWidthUpdateListener; + @NonNull private final Animator.AnimatorListener mEdgeHintPulseListener; + UdfpsEnrollDrawable(@NonNull Context context) { super(context); - mSensorOutlinePaint = new Paint(0 /* flags */); mSensorOutlinePaint.setAntiAlias(true); mSensorOutlinePaint.setColor(mContext.getColor(R.color.udfps_enroll_icon)); @@ -78,6 +120,117 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { mMovingTargetFpIcon.mutate(); mFingerprintDrawable.setTint(mContext.getColor(R.color.udfps_enroll_icon)); + + mHintColorFaded = getHintColorFaded(context); + mHintColorHighlight = context.getColor(R.color.udfps_enroll_progress); + mHintMaxWidthPx = Utils.dpToPixels(context, HINT_MAX_WIDTH_DP); + mHintPaddingPx = Utils.dpToPixels(context, HINT_PADDING_DP); + + mTargetAnimListener = new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) {} + + @Override + public void onAnimationEnd(Animator animation) { + updateTipHintVisibility(); + } + + @Override + public void onAnimationCancel(Animator animation) {} + + @Override + public void onAnimationRepeat(Animator animation) {} + }; + + mTipHintPaint = new Paint(0 /* flags */); + mTipHintPaint.setAntiAlias(true); + mTipHintPaint.setColor(mHintColorFaded); + mTipHintPaint.setStyle(Paint.Style.STROKE); + mTipHintPaint.setStrokeCap(Paint.Cap.ROUND); + mTipHintPaint.setStrokeWidth(0f); + mTipHintColorUpdateListener = animation -> { + mTipHintPaint.setColor((int) animation.getAnimatedValue()); + invalidateSelf(); + }; + mTipHintWidthUpdateListener = animation -> { + mTipHintPaint.setStrokeWidth((float) animation.getAnimatedValue()); + invalidateSelf(); + }; + mTipHintPulseListener = new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) {} + + @Override + public void onAnimationEnd(Animator animation) { + mHandler.postDelayed(() -> { + mTipHintColorAnimator = + ValueAnimator.ofArgb(mTipHintPaint.getColor(), mHintColorFaded); + mTipHintColorAnimator.setInterpolator(new LinearInterpolator()); + mTipHintColorAnimator.setDuration(HINT_COLOR_ANIM_DURATION_MS); + mTipHintColorAnimator.addUpdateListener(mTipHintColorUpdateListener); + mTipHintColorAnimator.start(); + }, HINT_COLOR_ANIM_DELAY_MS); + } + + @Override + public void onAnimationCancel(Animator animation) {} + + @Override + public void onAnimationRepeat(Animator animation) {} + }; + + mEdgeHintPaint = new Paint(0 /* flags */); + mEdgeHintPaint.setAntiAlias(true); + mEdgeHintPaint.setColor(mHintColorFaded); + mEdgeHintPaint.setStyle(Paint.Style.STROKE); + mEdgeHintPaint.setStrokeCap(Paint.Cap.ROUND); + mEdgeHintPaint.setStrokeWidth(0f); + mEdgeHintColorUpdateListener = animation -> { + mEdgeHintPaint.setColor((int) animation.getAnimatedValue()); + invalidateSelf(); + }; + mEdgeHintWidthUpdateListener = animation -> { + mEdgeHintPaint.setStrokeWidth((float) animation.getAnimatedValue()); + invalidateSelf(); + }; + mEdgeHintPulseListener = new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) {} + + @Override + public void onAnimationEnd(Animator animation) { + mHandler.postDelayed(() -> { + mEdgeHintColorAnimator = + ValueAnimator.ofArgb(mEdgeHintPaint.getColor(), mHintColorFaded); + mEdgeHintColorAnimator.setInterpolator(new LinearInterpolator()); + mEdgeHintColorAnimator.setDuration(HINT_COLOR_ANIM_DURATION_MS); + mEdgeHintColorAnimator.addUpdateListener(mEdgeHintColorUpdateListener); + mEdgeHintColorAnimator.start(); + }, HINT_COLOR_ANIM_DELAY_MS); + } + + @Override + public void onAnimationCancel(Animator animation) {} + + @Override + public void onAnimationRepeat(Animator animation) {} + }; + } + + @ColorInt + private static int getHintColorFaded(@NonNull Context context) { + final TypedValue tv = new TypedValue(); + context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, tv, true); + final int alpha = (int) (tv.getFloat() * 255f); + + final int[] attrs = new int[] {android.R.attr.colorControlNormal}; + final TypedArray ta = context.obtainStyledAttributes(attrs); + try { + @ColorInt final int color = ta.getColor(0, context.getColor(R.color.white_disabled)); + return ColorUtils.setAlphaComponent(color, alpha); + } finally { + ta.recycle(); + } } void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) { @@ -98,41 +251,154 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { } void onEnrollmentProgress(int remaining, int totalSteps) { - if (mEnrollHelper.isCenterEnrollmentComplete()) { - if (mAnimatorSet != null && mAnimatorSet.isRunning()) { - mAnimatorSet.end(); + if (mEnrollHelper == null) { + return; + } + + if (!mEnrollHelper.isCenterEnrollmentStage()) { + if (mTargetAnimatorSet != null && mTargetAnimatorSet.isRunning()) { + mTargetAnimatorSet.end(); } final PointF point = mEnrollHelper.getNextGuidedEnrollmentPoint(); + if (mCurrentX != point.x || mCurrentY != point.y) { + final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x); + x.addUpdateListener(animation -> { + mCurrentX = (float) animation.getAnimatedValue(); + invalidateSelf(); + }); - final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x); - x.addUpdateListener(animation -> { - mCurrentX = (float) animation.getAnimatedValue(); - invalidateSelf(); - }); + final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y); + y.addUpdateListener(animation -> { + mCurrentY = (float) animation.getAnimatedValue(); + invalidateSelf(); + }); - final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y); - y.addUpdateListener(animation -> { - mCurrentY = (float) animation.getAnimatedValue(); - invalidateSelf(); - }); + final boolean isMovingToCenter = point.x == 0f && point.y == 0f; + final long duration = isMovingToCenter + ? TARGET_ANIM_DURATION_SHORT + : TARGET_ANIM_DURATION_LONG; - final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI); - scale.setDuration(ANIM_DURATION); - scale.addUpdateListener(animation -> { - // Grow then shrink - mCurrentScale = 1 + - SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue()); - invalidateSelf(); - }); + final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI); + scale.setDuration(duration); + scale.addUpdateListener(animation -> { + // Grow then shrink + mCurrentScale = 1 + + SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue()); + invalidateSelf(); + }); - mAnimatorSet = new AnimatorSet(); + mTargetAnimatorSet = new AnimatorSet(); - mAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); - mAnimatorSet.setDuration(ANIM_DURATION); - mAnimatorSet.playTogether(x, y, scale); - mAnimatorSet.start(); + mTargetAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); + mTargetAnimatorSet.setDuration(duration); + mTargetAnimatorSet.addListener(mTargetAnimListener); + mTargetAnimatorSet.playTogether(x, y, scale); + mTargetAnimatorSet.start(); + } else { + updateTipHintVisibility(); + } + } else { + updateTipHintVisibility(); } + + updateEdgeHintVisibility(); + } + + private void updateTipHintVisibility() { + final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isTipEnrollmentStage(); + if (mShouldShowTipHint == shouldShow) { + return; + } + mShouldShowTipHint = shouldShow; + + if (mTipHintWidthAnimator != null && mTipHintWidthAnimator.isRunning()) { + mTipHintWidthAnimator.cancel(); + } + + final float targetWidth = shouldShow ? mHintMaxWidthPx : 0f; + mTipHintWidthAnimator = ValueAnimator.ofFloat(mTipHintPaint.getStrokeWidth(), targetWidth); + mTipHintWidthAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS); + mTipHintWidthAnimator.addUpdateListener(mTipHintWidthUpdateListener); + + if (shouldShow) { + startTipHintPulseAnimation(); + } else { + mTipHintWidthAnimator.start(); + } + } + + private void updateEdgeHintVisibility() { + final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isEdgeEnrollmentStage(); + if (mShouldShowEdgeHint == shouldShow) { + return; + } + mShouldShowEdgeHint = shouldShow; + + if (mEdgeHintWidthAnimator != null && mEdgeHintWidthAnimator.isRunning()) { + mEdgeHintWidthAnimator.cancel(); + } + + final float targetWidth = shouldShow ? mHintMaxWidthPx : 0f; + mEdgeHintWidthAnimator = + ValueAnimator.ofFloat(mEdgeHintPaint.getStrokeWidth(), targetWidth); + mEdgeHintWidthAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS); + mEdgeHintWidthAnimator.addUpdateListener(mEdgeHintWidthUpdateListener); + + if (shouldShow) { + startEdgeHintPulseAnimation(); + } else { + mEdgeHintWidthAnimator.start(); + } + } + + private void startTipHintPulseAnimation() { + mHandler.removeCallbacksAndMessages(null); + if (mTipHintAnimatorSet != null && mTipHintAnimatorSet.isRunning()) { + mTipHintAnimatorSet.cancel(); + } + if (mTipHintColorAnimator != null && mTipHintColorAnimator.isRunning()) { + mTipHintColorAnimator.cancel(); + } + + mTipHintColorAnimator = ValueAnimator.ofArgb(mTipHintPaint.getColor(), mHintColorHighlight); + mTipHintColorAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS); + mTipHintColorAnimator.addUpdateListener(mTipHintColorUpdateListener); + mTipHintColorAnimator.addListener(mTipHintPulseListener); + + mTipHintAnimatorSet = new AnimatorSet(); + mTipHintAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); + mTipHintAnimatorSet.playTogether(mTipHintColorAnimator, mTipHintWidthAnimator); + mTipHintAnimatorSet.start(); + } + + private void startEdgeHintPulseAnimation() { + mHandler.removeCallbacksAndMessages(null); + if (mEdgeHintAnimatorSet != null && mEdgeHintAnimatorSet.isRunning()) { + mEdgeHintAnimatorSet.cancel(); + } + if (mEdgeHintColorAnimator != null && mEdgeHintColorAnimator.isRunning()) { + mEdgeHintColorAnimator.cancel(); + } + + mEdgeHintColorAnimator = + ValueAnimator.ofArgb(mEdgeHintPaint.getColor(), mHintColorHighlight); + mEdgeHintColorAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS); + mEdgeHintColorAnimator.addUpdateListener(mEdgeHintColorUpdateListener); + mEdgeHintColorAnimator.addListener(mEdgeHintPulseListener); + + mEdgeHintAnimatorSet = new AnimatorSet(); + mEdgeHintAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); + mEdgeHintAnimatorSet.playTogether(mEdgeHintColorAnimator, mEdgeHintWidthAnimator); + mEdgeHintAnimatorSet.start(); + } + + private boolean isTipHintVisible() { + return mTipHintPaint.getStrokeWidth() > 0f; + } + + private boolean isEdgeHintVisible() { + return mEdgeHintPaint.getStrokeWidth() > 0f; } @Override @@ -142,7 +408,7 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { } // Draw moving target - if (mEnrollHelper.isCenterEnrollmentComplete()) { + if (mEnrollHelper != null && !mEnrollHelper.isCenterEnrollmentStage()) { canvas.save(); canvas.translate(mCurrentX, mCurrentY); @@ -162,6 +428,59 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { mFingerprintDrawable.setAlpha(mAlpha); mSensorOutlinePaint.setAlpha(mAlpha); } + + // Draw the finger tip or edges hint. + if (isTipHintVisible() || isEdgeHintVisible()) { + canvas.save(); + + // Make arcs start from the top, rather than the right. + canvas.rotate(-90f, mSensorRect.centerX(), mSensorRect.centerY()); + + final float halfSensorHeight = Math.abs(mSensorRect.bottom - mSensorRect.top) / 2f; + final float halfSensorWidth = Math.abs(mSensorRect.right - mSensorRect.left) / 2f; + final float hintXOffset = halfSensorWidth + mHintPaddingPx; + final float hintYOffset = halfSensorHeight + mHintPaddingPx; + + if (isTipHintVisible()) { + canvas.drawArc( + mSensorRect.centerX() - hintXOffset, + mSensorRect.centerY() - hintYOffset, + mSensorRect.centerX() + hintXOffset, + mSensorRect.centerY() + hintYOffset, + -HINT_ANGLE / 2f, + HINT_ANGLE, + false /* useCenter */, + mTipHintPaint); + } + + if (isEdgeHintVisible()) { + // Draw right edge hint. + canvas.rotate(-90f, mSensorRect.centerX(), mSensorRect.centerY()); + canvas.drawArc( + mSensorRect.centerX() - hintXOffset, + mSensorRect.centerY() - hintYOffset, + mSensorRect.centerX() + hintXOffset, + mSensorRect.centerY() + hintYOffset, + -HINT_ANGLE / 2f, + HINT_ANGLE, + false /* useCenter */, + mEdgeHintPaint); + + // Draw left edge hint. + canvas.rotate(180f, mSensorRect.centerX(), mSensorRect.centerY()); + canvas.drawArc( + mSensorRect.centerX() - hintXOffset, + mSensorRect.centerY() - hintYOffset, + mSensorRect.centerX() + hintXOffset, + mSensorRect.centerY() + hintYOffset, + -HINT_ANGLE / 2f, + HINT_ANGLE, + false /* useCenter */, + mEdgeHintPaint); + } + + canvas.restore(); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java index c6d2192b5b055..d5c763d3b6e23 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.Context; import android.graphics.PointF; import android.hardware.biometrics.BiometricOverlayConstants; +import android.hardware.fingerprint.FingerprintManager; import android.os.Build; import android.os.UserHandle; import android.provider.Settings; @@ -44,16 +45,14 @@ public class UdfpsEnrollHelper { private static final String NEW_COORDS_OVERRIDE = "com.android.systemui.biometrics.UdfpsNewCoords"; - // Enroll with two center touches before going to guided enrollment - private static final int NUM_CENTER_TOUCHES = 2; - interface Listener { void onEnrollmentProgress(int remaining, int totalSteps); + void onEnrollmentHelp(int remaining, int totalSteps); void onLastStepAcquired(); - void onEnrollmentHelp(); } @NonNull private final Context mContext; + @NonNull private final FingerprintManager mFingerprintManager; // IUdfpsOverlayController reason private final int mEnrollReason; private final boolean mAccessibilityEnabled; @@ -66,10 +65,15 @@ public class UdfpsEnrollHelper { // interface makes no promises about monotonically increasing by one each time. private int mLocationsEnrolled = 0; + private int mCenterTouchCount = 0; + @Nullable Listener mListener; - public UdfpsEnrollHelper(@NonNull Context context, int reason) { + public UdfpsEnrollHelper(@NonNull Context context, + @NonNull FingerprintManager fingerprintManager, int reason) { + mContext = context; + mFingerprintManager = fingerprintManager; mEnrollReason = reason; final AccessibilityManager am = context.getSystemService(AccessibilityManager.class); @@ -118,6 +122,14 @@ public class UdfpsEnrollHelper { } } + int getStageCount() { + return mFingerprintManager.getEnrollStageCount(); + } + + int getStageThresholdSteps(int totalSteps, int stageIndex) { + return Math.round(totalSteps * mFingerprintManager.getEnrollStageThreshold(stageIndex)); + } + boolean shouldShowProgressBar() { return mEnrollReason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING; } @@ -129,6 +141,9 @@ public class UdfpsEnrollHelper { if (remaining != mRemainingSteps) { mLocationsEnrolled++; + if (isCenterEnrollmentStage()) { + mCenterTouchCount++; + } } mRemainingSteps = remaining; @@ -140,7 +155,7 @@ public class UdfpsEnrollHelper { void onEnrollmentHelp() { if (mListener != null) { - mListener.onEnrollmentHelp(); + mListener.onEnrollmentHelp(mRemainingSteps, mTotalSteps); } } @@ -155,19 +170,41 @@ public class UdfpsEnrollHelper { } } - boolean isCenterEnrollmentComplete() { + boolean isCenterEnrollmentStage() { if (mTotalSteps == -1 || mRemainingSteps == -1) { - return false; - } else if (mAccessibilityEnabled) { + return true; + } + return mTotalSteps - mRemainingSteps < getStageThresholdSteps(mTotalSteps, 0); + } + + boolean isGuidedEnrollmentStage() { + if (mAccessibilityEnabled || mTotalSteps == -1 || mRemainingSteps == -1) { return false; } - final int stepsEnrolled = mTotalSteps - mRemainingSteps; - return stepsEnrolled >= NUM_CENTER_TOUCHES; + final int progressSteps = mTotalSteps - mRemainingSteps; + return progressSteps >= getStageThresholdSteps(mTotalSteps, 0) + && progressSteps < getStageThresholdSteps(mTotalSteps, 1); + } + + boolean isTipEnrollmentStage() { + if (mTotalSteps == -1 || mRemainingSteps == -1) { + return false; + } + final int progressSteps = mTotalSteps - mRemainingSteps; + return progressSteps >= getStageThresholdSteps(mTotalSteps, 1) + && progressSteps < getStageThresholdSteps(mTotalSteps, 2); + } + + boolean isEdgeEnrollmentStage() { + if (mTotalSteps == -1 || mRemainingSteps == -1) { + return false; + } + return mTotalSteps - mRemainingSteps >= getStageThresholdSteps(mTotalSteps, 2); } @NonNull PointF getNextGuidedEnrollmentPoint() { - if (mAccessibilityEnabled) { + if (mAccessibilityEnabled || !isGuidedEnrollmentStage()) { return new PointF(0f, 0f); } @@ -177,7 +214,7 @@ public class UdfpsEnrollHelper { SCALE_OVERRIDE, SCALE, UserHandle.USER_CURRENT); } - final int index = mLocationsEnrolled - NUM_CENTER_TOUCHES; + final int index = mLocationsEnrolled - mCenterTouchCount; final PointF originalPoint = mGuidedEnrollmentPoints .get(index % mGuidedEnrollmentPoints.size()); return new PointF(originalPoint.x * scale, originalPoint.y * scale); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java index 373d17c8ef665..b2a54097539dd 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java @@ -16,163 +16,129 @@ package com.android.systemui.biometrics; -import android.animation.ArgbEvaluator; -import android.animation.ValueAnimator; -import android.annotation.ColorInt; import android.content.Context; -import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.ColorFilter; -import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.util.Log; -import android.util.TypedValue; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.systemui.R; +import java.util.ArrayList; +import java.util.List; /** * UDFPS enrollment progress bar. */ public class UdfpsEnrollProgressBarDrawable extends Drawable { + private static final String TAG = "UdfpsProgressBar"; - private static final String TAG = "UdfpsEnrollProgressBarDrawable"; - - private static final float PROGRESS_BAR_THICKNESS_DP = 12; + private static final float SEGMENT_GAP_ANGLE = 12f; @NonNull private final Context mContext; - @NonNull private final Paint mBackgroundCirclePaint; - @NonNull private final Paint mProgressPaint; - @Nullable private ValueAnimator mProgressAnimator; - @Nullable private ValueAnimator mProgressShowingHelpAnimator; - @Nullable private ValueAnimator mProgressHidingHelpAnimator; - @ColorInt private final int mProgressColor; - @ColorInt private final int mProgressHelpColor; - private final int mShortAnimationDuration; - private float mProgress; - private int mRotation; // After last step, rotate the progress bar once - private boolean mLastStepAcquired; + @Nullable private UdfpsEnrollHelper mEnrollHelper; + @NonNull private List mSegments = new ArrayList<>(); + private int mTotalSteps = 1; + private int mProgressSteps = 0; + private boolean mIsShowingHelp = false; public UdfpsEnrollProgressBarDrawable(@NonNull Context context) { mContext = context; - - mShortAnimationDuration = context.getResources() - .getInteger(com.android.internal.R.integer.config_shortAnimTime); - mProgressColor = context.getColor(R.color.udfps_enroll_progress); - mProgressHelpColor = context.getColor(R.color.udfps_enroll_progress_help); - - mBackgroundCirclePaint = new Paint(); - mBackgroundCirclePaint.setStrokeWidth(Utils.dpToPixels(context, PROGRESS_BAR_THICKNESS_DP)); - mBackgroundCirclePaint.setColor(context.getColor(R.color.white_disabled)); - mBackgroundCirclePaint.setAntiAlias(true); - mBackgroundCirclePaint.setStyle(Paint.Style.STROKE); - - // Background circle color + alpha - TypedArray tc = context.obtainStyledAttributes( - new int[] {android.R.attr.colorControlNormal}); - int tintColor = tc.getColor(0, mBackgroundCirclePaint.getColor()); - mBackgroundCirclePaint.setColor(tintColor); - tc.recycle(); - TypedValue alpha = new TypedValue(); - context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true); - mBackgroundCirclePaint.setAlpha((int) (alpha.getFloat() * 255)); - - // Progress should not be color extracted - mProgressPaint = new Paint(); - mProgressPaint.setStrokeWidth(Utils.dpToPixels(context, PROGRESS_BAR_THICKNESS_DP)); - mProgressPaint.setColor(mProgressColor); - mProgressPaint.setAntiAlias(true); - mProgressPaint.setStyle(Paint.Style.STROKE); - mProgressPaint.setStrokeCap(Paint.Cap.ROUND); } - void setEnrollmentProgress(int remaining, int totalSteps) { - // Add one so that the first steps actually changes progress, but also so that the last - // step ends at 1.0 - final float progress = (totalSteps - remaining + 1) / (float) (totalSteps + 1); - setEnrollmentProgress(progress); - } - - private void setEnrollmentProgress(float progress) { - if (mLastStepAcquired) { - return; - } - - long animationDuration = mShortAnimationDuration; - - hideEnrollmentHelp(); - - if (progress == 1.f) { - animationDuration = 400; - final ValueAnimator rotationAnimator = ValueAnimator.ofInt(0, 400); - rotationAnimator.setDuration(animationDuration); - rotationAnimator.addUpdateListener(animation -> { - Log.d(TAG, "Rotation: " + mRotation); - mRotation = (int) animation.getAnimatedValue(); - invalidateSelf(); - }); - rotationAnimator.start(); - } - - if (mProgressAnimator != null && mProgressAnimator.isRunning()) { - mProgressAnimator.cancel(); - } - - mProgressAnimator = ValueAnimator.ofFloat(mProgress, progress); - mProgressAnimator.setDuration(animationDuration); - mProgressAnimator.addUpdateListener(animation -> { - mProgress = (float) animation.getAnimatedValue(); + void setEnrollHelper(@Nullable UdfpsEnrollHelper enrollHelper) { + mEnrollHelper = enrollHelper; + if (enrollHelper != null) { + final int stageCount = enrollHelper.getStageCount(); + mSegments = new ArrayList<>(stageCount); + float startAngle = SEGMENT_GAP_ANGLE / 2f; + final float sweepAngle = (360f / stageCount) - SEGMENT_GAP_ANGLE; + final Runnable invalidateRunnable = this::invalidateSelf; + for (int index = 0; index < stageCount; index++) { + mSegments.add(new UdfpsEnrollProgressBarSegment(mContext, getBounds(), startAngle, + sweepAngle, SEGMENT_GAP_ANGLE, invalidateRunnable)); + startAngle += sweepAngle + SEGMENT_GAP_ANGLE; + } invalidateSelf(); - }); - mProgressAnimator.start(); + } + } + + void onEnrollmentProgress(int remaining, int totalSteps) { + mTotalSteps = totalSteps; + updateState(getProgressSteps(remaining, totalSteps), false /* isShowingHelp */); + } + + void onEnrollmentHelp(int remaining, int totalSteps) { + updateState(getProgressSteps(remaining, totalSteps), true /* isShowingHelp */); } void onLastStepAcquired() { - setEnrollmentProgress(1.f); - mLastStepAcquired = true; + updateState(mTotalSteps, false /* isShowingHelp */); } - void onEnrollmentHelp() { - if (mProgressShowingHelpAnimator != null || mProgressAnimator == null) { - return; // already showing or at 0% (no progress bar visible) - } - - if (mProgressHidingHelpAnimator != null && mProgressHidingHelpAnimator.isRunning()) { - mProgressHidingHelpAnimator.cancel(); - } - mProgressHidingHelpAnimator = null; - - mProgressShowingHelpAnimator = getProgressColorAnimator( - mProgressPaint.getColor(), mProgressHelpColor); - mProgressShowingHelpAnimator.start(); + private static int getProgressSteps(int remaining, int totalSteps) { + // Show some progress for the initial touch. + return Math.max(1, totalSteps - remaining); } - private void hideEnrollmentHelp() { - if (mProgressHidingHelpAnimator != null || mProgressShowingHelpAnimator == null) { - return; // already hidden or help never shown - } - - if (mProgressShowingHelpAnimator != null && mProgressShowingHelpAnimator.isRunning()) { - mProgressShowingHelpAnimator.cancel(); - } - mProgressShowingHelpAnimator = null; - - mProgressHidingHelpAnimator = getProgressColorAnimator( - mProgressPaint.getColor(), mProgressColor); - mProgressHidingHelpAnimator.start(); + private void updateState(int progressSteps, boolean isShowingHelp) { + updateProgress(progressSteps); + updateFillColor(isShowingHelp); } - private ValueAnimator getProgressColorAnimator(@ColorInt int from, @ColorInt int to) { - final ValueAnimator animator = ValueAnimator.ofObject( - ArgbEvaluator.getInstance(), from, to); - animator.setDuration(mShortAnimationDuration); - animator.addUpdateListener(animation -> { - mProgressPaint.setColor((int) animation.getAnimatedValue()); - }); - return animator; + private void updateProgress(int progressSteps) { + if (mProgressSteps == progressSteps) { + return; + } + mProgressSteps = progressSteps; + + if (mEnrollHelper == null) { + Log.e(TAG, "updateState: UDFPS enroll helper was null"); + return; + } + + int index = 0; + int prevThreshold = 0; + while (index < mSegments.size()) { + final UdfpsEnrollProgressBarSegment segment = mSegments.get(index); + final int thresholdSteps = mEnrollHelper.getStageThresholdSteps(mTotalSteps, index); + if (progressSteps >= thresholdSteps && segment.getProgress() < 1f) { + segment.updateProgress(1f); + break; + } else if (progressSteps >= prevThreshold && progressSteps < thresholdSteps) { + final int relativeSteps = progressSteps - prevThreshold; + final int relativeThreshold = thresholdSteps - prevThreshold; + final float segmentProgress = (float) relativeSteps / (float) relativeThreshold; + segment.updateProgress(segmentProgress); + break; + } + + index++; + prevThreshold = thresholdSteps; + } + + if (progressSteps >= mTotalSteps) { + for (final UdfpsEnrollProgressBarSegment segment : mSegments) { + segment.startCompletionAnimation(); + } + } else { + for (final UdfpsEnrollProgressBarSegment segment : mSegments) { + segment.cancelCompletionAnimation(); + } + } + } + + private void updateFillColor(boolean isShowingHelp) { + if (mIsShowingHelp == isShowingHelp) { + return; + } + mIsShowingHelp = isShowingHelp; + + for (final UdfpsEnrollProgressBarSegment segment : mSegments) { + segment.updateFillColor(isShowingHelp); + } } @Override @@ -180,43 +146,22 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable { canvas.save(); // Progress starts from the top, instead of the right - canvas.rotate(-90 + mRotation, getBounds().centerX(), getBounds().centerY()); + canvas.rotate(-90f, getBounds().centerX(), getBounds().centerY()); - // Progress bar "background track" - final float halfPaddingPx = Utils.dpToPixels(mContext, PROGRESS_BAR_THICKNESS_DP) / 2; - canvas.drawArc(halfPaddingPx, - halfPaddingPx, - getBounds().right - halfPaddingPx, - getBounds().bottom - halfPaddingPx, - 0, - 360, - false, - mBackgroundCirclePaint - ); - - final float progress = 360.f * mProgress; - // Progress - canvas.drawArc(halfPaddingPx, - halfPaddingPx, - getBounds().right - halfPaddingPx, - getBounds().bottom - halfPaddingPx, - 0, - progress, - false, - mProgressPaint - ); + // Draw each of the enroll segments. + for (final UdfpsEnrollProgressBarSegment segment : mSegments) { + segment.draw(canvas); + } canvas.restore(); } @Override public void setAlpha(int alpha) { - } @Override public void setColorFilter(@Nullable ColorFilter colorFilter) { - } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java new file mode 100644 index 0000000000000..bd6ab4443630b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2021 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.systemui.biometrics; + +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.util.TypedValue; + +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.systemui.R; + +/** + * A single segment of the UDFPS enrollment progress bar. + */ +public class UdfpsEnrollProgressBarSegment { + private static final String TAG = "UdfpsProgressBarSegment"; + + private static final long FILL_COLOR_ANIMATION_DURATION_MS = 200L; + private static final long PROGRESS_ANIMATION_DURATION_MS = 400L; + private static final long OVER_SWEEP_ANIMATION_DELAY_MS = 200L; + private static final long OVER_SWEEP_ANIMATION_DURATION_MS = 200L; + + private static final float STROKE_WIDTH_DP = 12f; + + private final Handler mHandler = new Handler(Looper.getMainLooper()); + + @NonNull private final Rect mBounds; + @NonNull private final Runnable mInvalidateRunnable; + private final float mStartAngle; + private final float mSweepAngle; + private final float mMaxOverSweepAngle; + private final float mStrokeWidthPx; + @ColorInt private final int mProgressColor; + @ColorInt private final int mHelpColor; + + @NonNull private final Paint mBackgroundPaint; + @NonNull private final Paint mProgressPaint; + + private float mProgress = 0f; + private float mAnimatedProgress = 0f; + @Nullable private ValueAnimator mProgressAnimator; + @NonNull private final ValueAnimator.AnimatorUpdateListener mProgressUpdateListener; + + private boolean mIsShowingHelp = false; + @Nullable private ValueAnimator mFillColorAnimator; + @NonNull private final ValueAnimator.AnimatorUpdateListener mFillColorUpdateListener; + + private float mOverSweepAngle = 0f; + @Nullable private ValueAnimator mOverSweepAnimator; + @Nullable private ValueAnimator mOverSweepReverseAnimator; + @NonNull private final ValueAnimator.AnimatorUpdateListener mOverSweepUpdateListener; + @NonNull private final Runnable mOverSweepAnimationRunnable; + + public UdfpsEnrollProgressBarSegment(@NonNull Context context, @NonNull Rect bounds, + float startAngle, float sweepAngle, float maxOverSweepAngle, + @NonNull Runnable invalidateRunnable) { + + mBounds = bounds; + mInvalidateRunnable = invalidateRunnable; + mStartAngle = startAngle; + mSweepAngle = sweepAngle; + mMaxOverSweepAngle = maxOverSweepAngle; + mStrokeWidthPx = Utils.dpToPixels(context, STROKE_WIDTH_DP); + mProgressColor = context.getColor(R.color.udfps_enroll_progress); + mHelpColor = context.getColor(R.color.udfps_enroll_progress_help); + + mBackgroundPaint = new Paint(); + mBackgroundPaint.setStrokeWidth(mStrokeWidthPx); + mBackgroundPaint.setColor(context.getColor(R.color.white_disabled)); + mBackgroundPaint.setAntiAlias(true); + mBackgroundPaint.setStyle(Paint.Style.STROKE); + mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND); + + // Background paint color + alpha + final int[] attrs = new int[] {android.R.attr.colorControlNormal}; + final TypedArray ta = context.obtainStyledAttributes(attrs); + @ColorInt final int tintColor = ta.getColor(0, mBackgroundPaint.getColor()); + mBackgroundPaint.setColor(tintColor); + ta.recycle(); + TypedValue alpha = new TypedValue(); + context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true); + mBackgroundPaint.setAlpha((int) (alpha.getFloat() * 255f)); + + // Progress should not be color extracted + mProgressPaint = new Paint(); + mProgressPaint.setStrokeWidth(mStrokeWidthPx); + mProgressPaint.setColor(mProgressColor); + mProgressPaint.setAntiAlias(true); + mProgressPaint.setStyle(Paint.Style.STROKE); + mProgressPaint.setStrokeCap(Paint.Cap.ROUND); + + mProgressUpdateListener = animation -> { + mAnimatedProgress = (float) animation.getAnimatedValue(); + mInvalidateRunnable.run(); + }; + + mFillColorUpdateListener = animation -> { + mProgressPaint.setColor((int) animation.getAnimatedValue()); + mInvalidateRunnable.run(); + }; + + mOverSweepUpdateListener = animation -> { + mOverSweepAngle = (float) animation.getAnimatedValue(); + mInvalidateRunnable.run(); + }; + mOverSweepAnimationRunnable = () -> { + if (mOverSweepAnimator != null && mOverSweepAnimator.isRunning()) { + mOverSweepAnimator.cancel(); + } + mOverSweepAnimator = ValueAnimator.ofFloat(mOverSweepAngle, mMaxOverSweepAngle); + mOverSweepAnimator.setDuration(OVER_SWEEP_ANIMATION_DURATION_MS); + mOverSweepAnimator.addUpdateListener(mOverSweepUpdateListener); + mOverSweepAnimator.start(); + }; + } + + /** + * Draws this segment to the given canvas. + */ + public void draw(@NonNull Canvas canvas) { + final float halfPaddingPx = mStrokeWidthPx / 2f; + + if (mAnimatedProgress < 1f) { + // Draw the unfilled background color of the segment. + canvas.drawArc( + halfPaddingPx, + halfPaddingPx, + mBounds.right - halfPaddingPx, + mBounds.bottom - halfPaddingPx, + mStartAngle, + mSweepAngle, + false /* useCenter */, + mBackgroundPaint); + } + + if (mAnimatedProgress > 0f) { + // Draw the filled progress portion of the segment. + canvas.drawArc( + halfPaddingPx, + halfPaddingPx, + mBounds.right - halfPaddingPx, + mBounds.bottom - halfPaddingPx, + mStartAngle, + mSweepAngle * mAnimatedProgress + mOverSweepAngle, + false /* useCenter */, + mProgressPaint); + } + } + + /** + * @return The fill progress of this segment, in the range [0, 1]. If fill progress is being + * animated, returns the value it is animating to. + */ + public float getProgress() { + return mProgress; + } + + /** + * Updates the fill progress of this segment, animating if necessary. + * + * @param progress The new fill progress, in the range [0, 1]. + */ + public void updateProgress(float progress) { + updateProgress(progress, PROGRESS_ANIMATION_DURATION_MS); + } + + private void updateProgress(float progress, long animationDurationMs) { + if (mProgress == progress) { + return; + } + mProgress = progress; + + if (mProgressAnimator != null && mProgressAnimator.isRunning()) { + mProgressAnimator.cancel(); + } + + mProgressAnimator = ValueAnimator.ofFloat(mAnimatedProgress, progress); + mProgressAnimator.setDuration(animationDurationMs); + mProgressAnimator.addUpdateListener(mProgressUpdateListener); + mProgressAnimator.start(); + } + + /** + * Updates the fill color of this segment, animating if necessary. + * + * @param isShowingHelp Whether fill color should indicate that a help message is being shown. + */ + public void updateFillColor(boolean isShowingHelp) { + if (mIsShowingHelp == isShowingHelp) { + return; + } + mIsShowingHelp = isShowingHelp; + + if (mFillColorAnimator != null && mFillColorAnimator.isRunning()) { + mFillColorAnimator.cancel(); + } + + @ColorInt final int targetColor = isShowingHelp ? mHelpColor : mProgressColor; + mFillColorAnimator = ValueAnimator.ofArgb(mProgressPaint.getColor(), targetColor); + mFillColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS); + mFillColorAnimator.addUpdateListener(mFillColorUpdateListener); + mFillColorAnimator.start(); + } + + /** + * Queues and runs the completion animation for this segment. + */ + public void startCompletionAnimation() { + final boolean hasCallback = mHandler.hasCallbacks(mOverSweepAnimationRunnable); + if (hasCallback || mOverSweepAngle >= mMaxOverSweepAngle) { + Log.d(TAG, "startCompletionAnimation skipped: hasCallback = " + hasCallback + + ", mOverSweepAngle = " + mOverSweepAngle); + return; + } + + // Reset sweep angle back to zero if the animation is being rolled back. + if (mOverSweepReverseAnimator != null && mOverSweepReverseAnimator.isRunning()) { + mOverSweepReverseAnimator.cancel(); + mOverSweepAngle = 0f; + } + + // Clear help color and start filling the segment if it isn't already. + if (mAnimatedProgress < 1f) { + updateProgress(1f, OVER_SWEEP_ANIMATION_DELAY_MS); + updateFillColor(false /* isShowingHelp */); + } + + // Queue the animation to run after fill completes. + mHandler.postDelayed(mOverSweepAnimationRunnable, OVER_SWEEP_ANIMATION_DELAY_MS); + } + + /** + * Cancels (and reverses, if necessary) a queued or running completion animation. + */ + public void cancelCompletionAnimation() { + // Cancel the animation if it's queued or running. + mHandler.removeCallbacks(mOverSweepAnimationRunnable); + if (mOverSweepAnimator != null && mOverSweepAnimator.isRunning()) { + mOverSweepAnimator.cancel(); + } + + // Roll back the animation if it has at least partially run. + if (mOverSweepAngle > 0f) { + if (mOverSweepReverseAnimator != null && mOverSweepReverseAnimator.isRunning()) { + mOverSweepReverseAnimator.cancel(); + } + + final float completion = mOverSweepAngle / mMaxOverSweepAngle; + final long proratedDuration = (long) (OVER_SWEEP_ANIMATION_DURATION_MS * completion); + mOverSweepReverseAnimator = ValueAnimator.ofFloat(mOverSweepAngle, 0f); + mOverSweepReverseAnimator.setDuration(proratedDuration); + mOverSweepReverseAnimator.addUpdateListener(mOverSweepUpdateListener); + mOverSweepReverseAnimator.start(); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java index c83006d8092a1..729838ed59aae 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java @@ -73,23 +73,22 @@ public class UdfpsEnrollView extends UdfpsAnimationView { } void setEnrollHelper(UdfpsEnrollHelper enrollHelper) { + mFingerprintProgressDrawable.setEnrollHelper(enrollHelper); mFingerprintDrawable.setEnrollHelper(enrollHelper); } void onEnrollmentProgress(int remaining, int totalSteps) { mHandler.post(() -> { - mFingerprintProgressDrawable.setEnrollmentProgress(remaining, totalSteps); + mFingerprintProgressDrawable.onEnrollmentProgress(remaining, totalSteps); mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps); }); } - void onLastStepAcquired() { - mHandler.post(() -> { - mFingerprintProgressDrawable.onLastStepAcquired(); - }); + void onEnrollmentHelp(int remaining, int totalSteps) { + mHandler.post(() -> mFingerprintProgressDrawable.onEnrollmentHelp(remaining, totalSteps)); } - void onEnrollmentHelp() { - mHandler.post(mFingerprintProgressDrawable::onEnrollmentHelp); + void onLastStepAcquired() { + mHandler.post(mFingerprintProgressDrawable::onLastStepAcquired); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java index 33fbe7b6ebece..af7c3522dc231 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java @@ -35,21 +35,21 @@ public class UdfpsEnrollViewController extends UdfpsAnimationViewController