RESTRICT AUTOMERGE Support UDFPS multi-stage enrollment am: aee9eaa9de
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15763851 Change-Id: Id2314c8786bb8177a0d97b99778504531e721767
This commit is contained in:
@@ -210,6 +210,7 @@ public class UdfpsController implements DozeReceiver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onAcquiredGood() {
|
void onAcquiredGood() {
|
||||||
|
Log.d(TAG, "onAcquiredGood");
|
||||||
if (mEnrollHelper != null) {
|
if (mEnrollHelper != null) {
|
||||||
mEnrollHelper.animateIfLastStep();
|
mEnrollHelper.animateIfLastStep();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,13 +98,13 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onEnrollmentProgress(int remaining, int totalSteps) {
|
void onEnrollmentProgress(int remaining, int totalSteps) {
|
||||||
if (mEnrollHelper.isCenterEnrollmentComplete()) {
|
if (mEnrollHelper != null && !mEnrollHelper.isCenterEnrollmentStage()) {
|
||||||
if (mAnimatorSet != null && mAnimatorSet.isRunning()) {
|
if (mAnimatorSet != null && mAnimatorSet.isRunning()) {
|
||||||
mAnimatorSet.end();
|
mAnimatorSet.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
final PointF point = mEnrollHelper.getNextGuidedEnrollmentPoint();
|
final PointF point = mEnrollHelper.getNextGuidedEnrollmentPoint();
|
||||||
|
if (mCurrentX != point.x || mCurrentY != point.y) {
|
||||||
final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x);
|
final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x);
|
||||||
x.addUpdateListener(animation -> {
|
x.addUpdateListener(animation -> {
|
||||||
mCurrentX = (float) animation.getAnimatedValue();
|
mCurrentX = (float) animation.getAnimatedValue();
|
||||||
@@ -121,8 +121,8 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable {
|
|||||||
scale.setDuration(ANIM_DURATION);
|
scale.setDuration(ANIM_DURATION);
|
||||||
scale.addUpdateListener(animation -> {
|
scale.addUpdateListener(animation -> {
|
||||||
// Grow then shrink
|
// Grow then shrink
|
||||||
mCurrentScale = 1 +
|
mCurrentScale = 1
|
||||||
SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue());
|
+ SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue());
|
||||||
invalidateSelf();
|
invalidateSelf();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -134,6 +134,7 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable {
|
|||||||
mAnimatorSet.start();
|
mAnimatorSet.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(@NonNull Canvas canvas) {
|
public void draw(@NonNull Canvas canvas) {
|
||||||
@@ -142,7 +143,7 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw moving target
|
// Draw moving target
|
||||||
if (mEnrollHelper.isCenterEnrollmentComplete()) {
|
if (mEnrollHelper != null && !mEnrollHelper.isCenterEnrollmentStage()) {
|
||||||
canvas.save();
|
canvas.save();
|
||||||
canvas.translate(mCurrentX, mCurrentY);
|
canvas.translate(mCurrentX, mCurrentY);
|
||||||
|
|
||||||
|
|||||||
@@ -44,8 +44,15 @@ public class UdfpsEnrollHelper {
|
|||||||
private static final String NEW_COORDS_OVERRIDE =
|
private static final String NEW_COORDS_OVERRIDE =
|
||||||
"com.android.systemui.biometrics.UdfpsNewCoords";
|
"com.android.systemui.biometrics.UdfpsNewCoords";
|
||||||
|
|
||||||
// Enroll with two center touches before going to guided enrollment
|
static final int ENROLL_STAGE_COUNT = 4;
|
||||||
private static final int NUM_CENTER_TOUCHES = 2;
|
|
||||||
|
// TODO(b/198928407): Consolidate with FingerprintEnrollEnrolling
|
||||||
|
private static final int[] STAGE_THRESHOLDS = new int[] {
|
||||||
|
2, // center
|
||||||
|
18, // guided
|
||||||
|
22, // fingertip
|
||||||
|
38, // edges
|
||||||
|
};
|
||||||
|
|
||||||
interface Listener {
|
interface Listener {
|
||||||
void onEnrollmentProgress(int remaining, int totalSteps);
|
void onEnrollmentProgress(int remaining, int totalSteps);
|
||||||
@@ -65,6 +72,8 @@ public class UdfpsEnrollHelper {
|
|||||||
// interface makes no promises about monotonically increasing by one each time.
|
// interface makes no promises about monotonically increasing by one each time.
|
||||||
private int mLocationsEnrolled = 0;
|
private int mLocationsEnrolled = 0;
|
||||||
|
|
||||||
|
private int mCenterTouchCount = 0;
|
||||||
|
|
||||||
@Nullable Listener mListener;
|
@Nullable Listener mListener;
|
||||||
|
|
||||||
public UdfpsEnrollHelper(@NonNull Context context, int reason) {
|
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() {
|
boolean shouldShowProgressBar() {
|
||||||
return mEnrollReason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING;
|
return mEnrollReason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING;
|
||||||
}
|
}
|
||||||
|
|
||||||
void onEnrollmentProgress(int remaining) {
|
void onEnrollmentProgress(int remaining) {
|
||||||
if (mTotalSteps == -1) {
|
Log.d(TAG, "onEnrollmentProgress: remaining = " + remaining
|
||||||
mTotalSteps = remaining;
|
+ ", mRemainingSteps = " + mRemainingSteps
|
||||||
}
|
+ ", mTotalSteps = " + mTotalSteps
|
||||||
|
+ ", mLocationsEnrolled = " + mLocationsEnrolled
|
||||||
|
+ ", mCenterTouchCount = " + mCenterTouchCount);
|
||||||
|
|
||||||
if (remaining != mRemainingSteps) {
|
if (remaining != mRemainingSteps) {
|
||||||
mLocationsEnrolled++;
|
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;
|
mRemainingSteps = remaining;
|
||||||
@@ -152,19 +187,24 @@ public class UdfpsEnrollHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isCenterEnrollmentComplete() {
|
boolean isCenterEnrollmentStage() {
|
||||||
if (mTotalSteps == -1 || mRemainingSteps == -1) {
|
if (mTotalSteps == -1 || mRemainingSteps == -1) {
|
||||||
return false;
|
return true;
|
||||||
} else if (mAccessibilityEnabled) {
|
}
|
||||||
|
return mTotalSteps - mRemainingSteps < STAGE_THRESHOLDS[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isGuidedEnrollmentStage() {
|
||||||
|
if (mAccessibilityEnabled || mTotalSteps == -1 || mRemainingSteps == -1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final int stepsEnrolled = mTotalSteps - mRemainingSteps;
|
final int progressSteps = mTotalSteps - mRemainingSteps;
|
||||||
return stepsEnrolled >= NUM_CENTER_TOUCHES;
|
return progressSteps >= STAGE_THRESHOLDS[0] && progressSteps < STAGE_THRESHOLDS[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
PointF getNextGuidedEnrollmentPoint() {
|
PointF getNextGuidedEnrollmentPoint() {
|
||||||
if (mAccessibilityEnabled) {
|
if (mAccessibilityEnabled || !isGuidedEnrollmentStage()) {
|
||||||
return new PointF(0f, 0f);
|
return new PointF(0f, 0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,13 +214,14 @@ public class UdfpsEnrollHelper {
|
|||||||
SCALE_OVERRIDE, SCALE,
|
SCALE_OVERRIDE, SCALE,
|
||||||
UserHandle.USER_CURRENT);
|
UserHandle.USER_CURRENT);
|
||||||
}
|
}
|
||||||
final int index = mLocationsEnrolled - NUM_CENTER_TOUCHES;
|
final int index = mLocationsEnrolled - mCenterTouchCount;
|
||||||
final PointF originalPoint = mGuidedEnrollmentPoints
|
final PointF originalPoint = mGuidedEnrollmentPoints
|
||||||
.get(index % mGuidedEnrollmentPoints.size());
|
.get(index % mGuidedEnrollmentPoints.size());
|
||||||
return new PointF(originalPoint.x * scale, originalPoint.y * scale);
|
return new PointF(originalPoint.x * scale, originalPoint.y * scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
void animateIfLastStep() {
|
void animateIfLastStep() {
|
||||||
|
Log.d(TAG, "animateIfLastStep: mRemainingSteps = " + mRemainingSteps);
|
||||||
if (mListener == null) {
|
if (mListener == null) {
|
||||||
Log.e(TAG, "animateIfLastStep, null listener");
|
Log.e(TAG, "animateIfLastStep, null listener");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -16,141 +16,107 @@
|
|||||||
|
|
||||||
package com.android.systemui.biometrics;
|
package com.android.systemui.biometrics;
|
||||||
|
|
||||||
import android.animation.ValueAnimator;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.ColorFilter;
|
import android.graphics.ColorFilter;
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.TypedValue;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.android.systemui.R;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UDFPS enrollment progress bar.
|
* UDFPS enrollment progress bar.
|
||||||
*/
|
*/
|
||||||
public class UdfpsEnrollProgressBarDrawable extends Drawable {
|
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 List<UdfpsEnrollProgressBarSegment> mSegments;
|
||||||
|
|
||||||
@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;
|
|
||||||
|
|
||||||
public UdfpsEnrollProgressBarDrawable(@NonNull Context context) {
|
public UdfpsEnrollProgressBarDrawable(@NonNull Context context) {
|
||||||
mContext = context;
|
mSegments = new ArrayList<>(UdfpsEnrollHelper.ENROLL_STAGE_COUNT);
|
||||||
|
float startAngle = SEGMENT_GAP_ANGLE / 2f;
|
||||||
mBackgroundCirclePaint = new Paint();
|
final float sweepAngle = (360f / UdfpsEnrollHelper.ENROLL_STAGE_COUNT) - SEGMENT_GAP_ANGLE;
|
||||||
mBackgroundCirclePaint.setStrokeWidth(Utils.dpToPixels(context, PROGRESS_BAR_THICKNESS_DP));
|
final Runnable invalidateRunnable = this::invalidateSelf;
|
||||||
mBackgroundCirclePaint.setColor(context.getColor(R.color.white_disabled));
|
for (int index = 0; index < UdfpsEnrollHelper.ENROLL_STAGE_COUNT; index++) {
|
||||||
mBackgroundCirclePaint.setAntiAlias(true);
|
mSegments.add(new UdfpsEnrollProgressBarSegment(context, getBounds(), startAngle,
|
||||||
mBackgroundCirclePaint.setStyle(Paint.Style.STROKE);
|
sweepAngle, SEGMENT_GAP_ANGLE, invalidateRunnable));
|
||||||
|
startAngle += sweepAngle + SEGMENT_GAP_ANGLE;
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setEnrollmentProgress(int remaining, int totalSteps) {
|
void setEnrollmentProgress(int remaining, int totalSteps) {
|
||||||
// Add one so that the first steps actually changes progress, but also so that the last
|
if (remaining == totalSteps) {
|
||||||
// step ends at 1.0
|
// Show some progress for the initial touch.
|
||||||
final float progress = (totalSteps - remaining + 1) / (float) (totalSteps + 1);
|
setEnrollmentProgress(1);
|
||||||
setEnrollmentProgress(progress);
|
} else {
|
||||||
|
setEnrollmentProgress(totalSteps - remaining);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setEnrollmentProgress(float progress) {
|
private void setEnrollmentProgress(int progressSteps) {
|
||||||
if (mLastStepAcquired) {
|
Log.d(TAG, "setEnrollmentProgress: progressSteps = " + progressSteps);
|
||||||
return;
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
long animationDuration = 150;
|
segmentIndex++;
|
||||||
|
prevThreshold = threshold;
|
||||||
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()) {
|
if (progressSteps >= UdfpsEnrollHelper.getLastStageThreshold()) {
|
||||||
mProgressAnimator.cancel();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mProgressAnimator = ValueAnimator.ofFloat(mProgress, progress);
|
|
||||||
mProgressAnimator.setDuration(animationDuration);
|
|
||||||
mProgressAnimator.addUpdateListener(animation -> {
|
|
||||||
mProgress = (float) animation.getAnimatedValue();
|
|
||||||
invalidateSelf();
|
|
||||||
});
|
|
||||||
mProgressAnimator.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onLastStepAcquired() {
|
void onLastStepAcquired() {
|
||||||
setEnrollmentProgress(1.f);
|
Log.d(TAG, "setEnrollmentProgress: onLastStepAcquired");
|
||||||
mLastStepAcquired = true;
|
setEnrollmentProgress(UdfpsEnrollHelper.getLastStageThreshold());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(@NonNull Canvas canvas) {
|
public void draw(@NonNull Canvas canvas) {
|
||||||
|
Log.d(TAG, "setEnrollmentProgress: draw");
|
||||||
|
|
||||||
canvas.save();
|
canvas.save();
|
||||||
|
|
||||||
// Progress starts from the top, instead of the right
|
// 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"
|
// Draw each of the enroll segments.
|
||||||
final float halfPaddingPx = Utils.dpToPixels(mContext, PROGRESS_BAR_THICKNESS_DP) / 2;
|
for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
|
||||||
canvas.drawArc(halfPaddingPx,
|
segment.draw(canvas);
|
||||||
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
|
|
||||||
);
|
|
||||||
|
|
||||||
canvas.restore();
|
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
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public PointF getTouchTranslation() {
|
public PointF getTouchTranslation() {
|
||||||
if (!mEnrollHelper.isCenterEnrollmentComplete()) {
|
if (!mEnrollHelper.isGuidedEnrollmentStage()) {
|
||||||
return new PointF(0, 0);
|
return new PointF(0, 0);
|
||||||
} else {
|
} else {
|
||||||
return mEnrollHelper.getNextGuidedEnrollmentPoint();
|
return mEnrollHelper.getNextGuidedEnrollmentPoint();
|
||||||
|
|||||||
Reference in New Issue
Block a user