diff options
author | Curtis Belmonte <curtislb@google.com> | 2021-09-07 18:15:07 -0700 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2021-09-14 17:00:12 +0000 |
commit | bd1aa4aa1e5aad648eacccf3aeded32e39a21b2e (patch) | |
tree | 2726e1a92f42be45ae4e5c5c5087a4b4446bbff8 | |
parent | 71373f029e961a17ecc88c716a5c9727c7c88a1d (diff) | |
download | base-bd1aa4aa1e5aad648eacccf3aeded32e39a21b2e.tar.gz |
RESTRICT AUTOMERGE Show tip/edge hints for UDFPS enrollment
Shows visual hints during the tip and edge stages of UDFPS enrollment.
Test: Manual
Bug: 198858684
Change-Id: Ib2e08fcb7c524916a874932d1b9d558b19028ff0
Merged-In: Ifda20133f1ee90c9e1612e9066c19efbb6ffc9cd
Merged-In: Ic12b85bd6f8f1d4068952eb83bcc8a1df21f2e8e
(cherry picked from commit 1d4c7dc05bd43e6573274ca8290293ef85d8da2d)
-rw-r--r-- | packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java | 342 | ||||
-rw-r--r-- | packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java | 15 |
2 files changed, 345 insertions, 12 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java index 194113c77527..2034ff35be70 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,9 +251,13 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { } void onEnrollmentProgress(int remaining, int totalSteps) { - if (mEnrollHelper != null && !mEnrollHelper.isCenterEnrollmentStage()) { - 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(); @@ -117,8 +274,13 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { 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.setDuration(duration); scale.addUpdateListener(animation -> { // Grow then shrink mCurrentScale = 1 @@ -126,16 +288,119 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { 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 public void draw(@NonNull Canvas canvas) { if (isIlluminationShowing()) { @@ -163,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 bce013d1df53..14b18636f292 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java @@ -202,6 +202,21 @@ public class UdfpsEnrollHelper { return progressSteps >= STAGE_THRESHOLDS[0] && progressSteps < STAGE_THRESHOLDS[1]; } + boolean isTipEnrollmentStage() { + if (mTotalSteps == -1 || mRemainingSteps == -1) { + return false; + } + final int progressSteps = mTotalSteps - mRemainingSteps; + return progressSteps >= STAGE_THRESHOLDS[1] && progressSteps < STAGE_THRESHOLDS[2]; + } + + boolean isEdgeEnrollmentStage() { + if (mTotalSteps == -1 || mRemainingSteps == -1) { + return false; + } + return mTotalSteps - mRemainingSteps >= STAGE_THRESHOLDS[2]; + } + @NonNull PointF getNextGuidedEnrollmentPoint() { if (mAccessibilityEnabled || !isGuidedEnrollmentStage()) { |