diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 39ea75d5d1dac..1cea1f655e648 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -210,6 +210,7 @@ public class UdfpsController implements DozeReceiver { } void onAcquiredGood() { + Log.d(TAG, "onAcquiredGood"); if (mEnrollHelper != null) { mEnrollHelper.animateIfLastStep(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java index d4077563cfeac..194113c77527f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java @@ -98,40 +98,41 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { } void onEnrollmentProgress(int remaining, int totalSteps) { - if (mEnrollHelper.isCenterEnrollmentComplete()) { + if (mEnrollHelper != null && !mEnrollHelper.isCenterEnrollmentStage()) { if (mAnimatorSet != null && mAnimatorSet.isRunning()) { mAnimatorSet.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 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(ANIM_DURATION); - scale.addUpdateListener(animation -> { - // Grow then shrink - mCurrentScale = 1 + - SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue()); - invalidateSelf(); - }); + mAnimatorSet = new AnimatorSet(); - mAnimatorSet = new AnimatorSet(); - - mAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); - mAnimatorSet.setDuration(ANIM_DURATION); - mAnimatorSet.playTogether(x, y, scale); - mAnimatorSet.start(); + mAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); + mAnimatorSet.setDuration(ANIM_DURATION); + mAnimatorSet.playTogether(x, y, scale); + mAnimatorSet.start(); + } } } @@ -142,7 +143,7 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { } // Draw moving target - if (mEnrollHelper.isCenterEnrollmentComplete()) { + if (mEnrollHelper != null && !mEnrollHelper.isCenterEnrollmentStage()) { canvas.save(); canvas.translate(mCurrentX, mCurrentY); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java index 6a918a6c8d39d..bce013d1df53a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java @@ -44,8 +44,15 @@ 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; + static final int ENROLL_STAGE_COUNT = 4; + + // TODO(b/198928407): Consolidate with FingerprintEnrollEnrolling + private static final int[] STAGE_THRESHOLDS = new int[] { + 2, // center + 18, // guided + 22, // fingertip + 38, // edges + }; interface Listener { void onEnrollmentProgress(int remaining, int totalSteps); @@ -65,6 +72,8 @@ 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) { @@ -117,17 +126,43 @@ public class UdfpsEnrollHelper { } } + static int getStageThreshold(int index) { + return STAGE_THRESHOLDS[index]; + } + + static int getLastStageThreshold() { + return STAGE_THRESHOLDS[ENROLL_STAGE_COUNT - 1]; + } + boolean shouldShowProgressBar() { return mEnrollReason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING; } void onEnrollmentProgress(int remaining) { - if (mTotalSteps == -1) { - mTotalSteps = remaining; - } + Log.d(TAG, "onEnrollmentProgress: remaining = " + remaining + + ", mRemainingSteps = " + mRemainingSteps + + ", mTotalSteps = " + mTotalSteps + + ", mLocationsEnrolled = " + mLocationsEnrolled + + ", mCenterTouchCount = " + mCenterTouchCount); if (remaining != mRemainingSteps) { mLocationsEnrolled++; + if (isCenterEnrollmentStage()) { + mCenterTouchCount++; + } + } + + if (mTotalSteps == -1) { + mTotalSteps = remaining; + + // Allocate (or subtract) any extra steps for the first enroll stage. + final int extraSteps = mTotalSteps - getLastStageThreshold(); + if (extraSteps != 0) { + for (int stageIndex = 0; stageIndex < ENROLL_STAGE_COUNT; stageIndex++) { + STAGE_THRESHOLDS[stageIndex] = + Math.max(0, STAGE_THRESHOLDS[stageIndex] + extraSteps); + } + } } mRemainingSteps = remaining; @@ -152,19 +187,24 @@ public class UdfpsEnrollHelper { } } - boolean isCenterEnrollmentComplete() { + boolean isCenterEnrollmentStage() { if (mTotalSteps == -1 || mRemainingSteps == -1) { - return false; - } else if (mAccessibilityEnabled) { + return true; + } + return mTotalSteps - mRemainingSteps < STAGE_THRESHOLDS[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 >= STAGE_THRESHOLDS[0] && progressSteps < STAGE_THRESHOLDS[1]; } @NonNull PointF getNextGuidedEnrollmentPoint() { - if (mAccessibilityEnabled) { + if (mAccessibilityEnabled || !isGuidedEnrollmentStage()) { return new PointF(0f, 0f); } @@ -174,13 +214,14 @@ 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); } void animateIfLastStep() { + Log.d(TAG, "animateIfLastStep: mRemainingSteps = " + mRemainingSteps); if (mListener == null) { Log.e(TAG, "animateIfLastStep, null listener"); return; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java index 4c26e0c767d11..b56543f4851b9 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java @@ -16,141 +16,107 @@ 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.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 SEGMENT_GAP_ANGLE = 12f; - private static final float PROGRESS_BAR_THICKNESS_DP = 12; - - @NonNull private final Context mContext; - @NonNull private final Paint mBackgroundCirclePaint; - @NonNull private final Paint mProgressPaint; - - @Nullable private ValueAnimator mProgressAnimator; - private float mProgress; - private int mRotation; // After last step, rotate the progress bar once - private boolean mLastStepAcquired; + @NonNull private final List mSegments; public UdfpsEnrollProgressBarDrawable(@NonNull Context context) { - mContext = context; - - 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(context.getColor(R.color.udfps_enroll_progress)); - mProgressPaint.setAntiAlias(true); - mProgressPaint.setStyle(Paint.Style.STROKE); - mProgressPaint.setStrokeCap(Paint.Cap.ROUND); + mSegments = new ArrayList<>(UdfpsEnrollHelper.ENROLL_STAGE_COUNT); + float startAngle = SEGMENT_GAP_ANGLE / 2f; + final float sweepAngle = (360f / UdfpsEnrollHelper.ENROLL_STAGE_COUNT) - SEGMENT_GAP_ANGLE; + final Runnable invalidateRunnable = this::invalidateSelf; + for (int index = 0; index < UdfpsEnrollHelper.ENROLL_STAGE_COUNT; index++) { + mSegments.add(new UdfpsEnrollProgressBarSegment(context, getBounds(), startAngle, + sweepAngle, SEGMENT_GAP_ANGLE, invalidateRunnable)); + startAngle += sweepAngle + SEGMENT_GAP_ANGLE; + } } 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); + if (remaining == totalSteps) { + // Show some progress for the initial touch. + setEnrollmentProgress(1); + } else { + setEnrollmentProgress(totalSteps - remaining); + } } - private void setEnrollmentProgress(float progress) { - if (mLastStepAcquired) { - return; + private void setEnrollmentProgress(int progressSteps) { + Log.d(TAG, "setEnrollmentProgress: progressSteps = " + progressSteps); + + int segmentIndex = 0; + int prevThreshold = 0; + while (segmentIndex < mSegments.size()) { + final UdfpsEnrollProgressBarSegment segment = mSegments.get(segmentIndex); + final int threshold = UdfpsEnrollHelper.getStageThreshold(segmentIndex); + + if (progressSteps >= threshold && !segment.isFilledOrFilling()) { + Log.d(TAG, "setEnrollmentProgress: segment[" + segmentIndex + "] complete"); + segment.updateProgress(1f); + break; + } else if (progressSteps >= prevThreshold && progressSteps < threshold) { + final int relativeSteps = progressSteps - prevThreshold; + final int relativeThreshold = threshold - prevThreshold; + final float segmentProgress = (float) relativeSteps / (float) relativeThreshold; + Log.d(TAG, "setEnrollmentProgress: segment[" + segmentIndex + "] progress = " + + segmentProgress); + segment.updateProgress(segmentProgress); + break; + } + + segmentIndex++; + prevThreshold = threshold; } - long animationDuration = 150; - - 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 (progressSteps >= UdfpsEnrollHelper.getLastStageThreshold()) { + Log.d(TAG, "setEnrollmentProgress: startCompletionAnimation"); + for (final UdfpsEnrollProgressBarSegment segment : mSegments) { + segment.startCompletionAnimation(); + } + } else { + Log.d(TAG, "setEnrollmentProgress: cancelCompletionAnimation"); + for (final UdfpsEnrollProgressBarSegment segment : mSegments) { + segment.cancelCompletionAnimation(); + } } - - if (mProgressAnimator != null && mProgressAnimator.isRunning()) { - mProgressAnimator.cancel(); - } - - mProgressAnimator = ValueAnimator.ofFloat(mProgress, progress); - mProgressAnimator.setDuration(animationDuration); - mProgressAnimator.addUpdateListener(animation -> { - mProgress = (float) animation.getAnimatedValue(); - invalidateSelf(); - }); - mProgressAnimator.start(); } void onLastStepAcquired() { - setEnrollmentProgress(1.f); - mLastStepAcquired = true; + Log.d(TAG, "setEnrollmentProgress: onLastStepAcquired"); + setEnrollmentProgress(UdfpsEnrollHelper.getLastStageThreshold()); } @Override public void draw(@NonNull Canvas canvas) { + Log.d(TAG, "setEnrollmentProgress: draw"); + 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(); } 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..5f24380b6ce31 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java @@ -0,0 +1,256 @@ +/* + * 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 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; + + @NonNull private final Paint mBackgroundPaint; + @NonNull private final Paint mProgressPaint; + + private boolean mIsFilledOrFilling = false; + + private float mProgress = 0f; + @Nullable private ValueAnimator mProgressAnimator; + @NonNull private final ValueAnimator.AnimatorUpdateListener mProgressUpdateListener; + + 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); + + 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(context.getColor(R.color.udfps_enroll_progress)); + mProgressPaint.setAntiAlias(true); + mProgressPaint.setStyle(Paint.Style.STROKE); + mProgressPaint.setStrokeCap(Paint.Cap.ROUND); + + mProgressUpdateListener = animation -> { + mProgress = (float) 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) { + Log.d(TAG, "draw: mProgress = " + mProgress); + + final float halfPaddingPx = mStrokeWidthPx / 2f; + + if (mProgress < 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 (mProgress > 0f) { + // Draw the filled progress portion of the segment. + canvas.drawArc( + halfPaddingPx, + halfPaddingPx, + mBounds.right - halfPaddingPx, + mBounds.bottom - halfPaddingPx, + mStartAngle, + mSweepAngle * mProgress + mOverSweepAngle, + false /* useCenter */, + mProgressPaint); + } + } + + /** + * @return Whether this segment is filled or in the process of being filled. + */ + public boolean isFilledOrFilling() { + return mIsFilledOrFilling; + } + + /** + * 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) { + Log.d(TAG, "updateProgress: progress = " + progress + + ", duration = " + animationDurationMs); + + if (mProgress == progress) { + Log.d(TAG, "updateProgress skipped: progress == mProgress"); + return; + } + + mIsFilledOrFilling = progress >= 1f; + + if (mProgressAnimator != null && mProgressAnimator.isRunning()) { + mProgressAnimator.cancel(); + } + + mProgressAnimator = ValueAnimator.ofFloat(mProgress, progress); + mProgressAnimator.setDuration(animationDurationMs); + mProgressAnimator.addUpdateListener(mProgressUpdateListener); + mProgressAnimator.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; + } + + Log.d(TAG, "startCompletionAnimation: mProgress = " + mProgress + + ", mOverSweepAngle = " + mOverSweepAngle); + + // Reset sweep angle back to zero if the animation is being rolled back. + if (mOverSweepReverseAnimator != null && mOverSweepReverseAnimator.isRunning()) { + mOverSweepReverseAnimator.cancel(); + mOverSweepAngle = 0f; + } + + // Start filling the segment if it isn't already. + if (mProgress < 1f) { + updateProgress(1f, OVER_SWEEP_ANIMATION_DELAY_MS); + } + + // 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() { + Log.d(TAG, "cancelCompletionAnimation: mProgress = " + mProgress + + ", mOverSweepAngle = " + mOverSweepAngle); + + // 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/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java index 3dab010d917cd..9ae24db188357 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java @@ -74,7 +74,7 @@ public class UdfpsEnrollViewController extends UdfpsAnimationViewController