Merge "RESTRICT AUTOMERGE Support UDFPS multi-stage enrollment" into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
b4a1d57267
@@ -210,6 +210,7 @@ public class UdfpsController implements DozeReceiver {
|
||||
}
|
||||
|
||||
void onAcquiredGood() {
|
||||
Log.d(TAG, "onAcquiredGood");
|
||||
if (mEnrollHelper != null) {
|
||||
mEnrollHelper.animateIfLastStep();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<UdfpsEnrollProgressBarSegment> 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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,7 @@ public class UdfpsEnrollViewController extends UdfpsAnimationViewController<Udfp
|
||||
@NonNull
|
||||
@Override
|
||||
public PointF getTouchTranslation() {
|
||||
if (!mEnrollHelper.isCenterEnrollmentComplete()) {
|
||||
if (!mEnrollHelper.isGuidedEnrollmentStage()) {
|
||||
return new PointF(0, 0);
|
||||
} else {
|
||||
return mEnrollHelper.getNextGuidedEnrollmentPoint();
|
||||
|
||||
Reference in New Issue
Block a user