Merge "Combine UDFPS enroll segments into a single circle" into sc-v2-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
e1d531787c
35
packages/SystemUI/res/drawable/udfps_enroll_checkmark.xml
Normal file
35
packages/SystemUI/res/drawable/udfps_enroll_checkmark.xml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="54dp"
|
||||||
|
android:height="54dp"
|
||||||
|
android:viewportWidth="54"
|
||||||
|
android:viewportHeight="54">
|
||||||
|
<path
|
||||||
|
android:pathData="M26.9999,3.9619C39.7029,3.9619 50.0369,14.2969 50.0369,26.9999C50.0369,39.7029 39.7029,50.0379 26.9999,50.0379C14.2969,50.0379 3.9629,39.7029 3.9629,26.9999C3.9629,14.2969 14.2969,3.9619 26.9999,3.9619Z"
|
||||||
|
android:fillColor="?android:colorBackground"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M27,0C12.088,0 0,12.088 0,27C0,41.912 12.088,54 27,54C41.912,54 54,41.912 54,27C54,12.088 41.912,0 27,0ZM27,3.962C39.703,3.962 50.037,14.297 50.037,27C50.037,39.703 39.703,50.038 27,50.038C14.297,50.038 3.963,39.703 3.963,27C3.963,14.297 14.297,3.962 27,3.962Z"
|
||||||
|
android:fillColor="@color/udfps_enroll_progress"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M23.0899,38.8534L10.4199,26.1824L13.2479,23.3544L23.0899,33.1974L41.2389,15.0474L44.0679,17.8754L23.0899,38.8534Z"
|
||||||
|
android:fillColor="@color/udfps_enroll_progress"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</vector>
|
||||||
@@ -16,17 +16,22 @@
|
|||||||
|
|
||||||
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.TypedValue;
|
||||||
|
import android.view.animation.Interpolator;
|
||||||
|
import android.view.animation.OvershootInterpolator;
|
||||||
|
|
||||||
|
import androidx.annotation.ColorInt;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import com.android.systemui.R;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UDFPS enrollment progress bar.
|
* UDFPS enrollment progress bar.
|
||||||
@@ -34,108 +39,193 @@ import java.util.List;
|
|||||||
public class UdfpsEnrollProgressBarDrawable extends Drawable {
|
public class UdfpsEnrollProgressBarDrawable extends Drawable {
|
||||||
private static final String TAG = "UdfpsProgressBar";
|
private static final String TAG = "UdfpsProgressBar";
|
||||||
|
|
||||||
private static final float SEGMENT_GAP_ANGLE = 12f;
|
private static final long CHECKMARK_ANIMATION_DELAY_MS = 200L;
|
||||||
|
private static final long CHECKMARK_ANIMATION_DURATION_MS = 300L;
|
||||||
|
private static final long FILL_COLOR_ANIMATION_DURATION_MS = 200L;
|
||||||
|
private static final long PROGRESS_ANIMATION_DURATION_MS = 400L;
|
||||||
|
private static final float STROKE_WIDTH_DP = 12f;
|
||||||
|
|
||||||
@NonNull private final Context mContext;
|
private final float mStrokeWidthPx;
|
||||||
|
@ColorInt private final int mProgressColor;
|
||||||
|
@ColorInt private final int mHelpColor;
|
||||||
|
@NonNull private final Drawable mCheckmarkDrawable;
|
||||||
|
@NonNull private final Interpolator mCheckmarkInterpolator;
|
||||||
|
@NonNull private final Paint mBackgroundPaint;
|
||||||
|
@NonNull private final Paint mFillPaint;
|
||||||
|
|
||||||
@Nullable private UdfpsEnrollHelper mEnrollHelper;
|
private boolean mAfterFirstTouch;
|
||||||
@NonNull private List<UdfpsEnrollProgressBarSegment> mSegments = new ArrayList<>();
|
|
||||||
|
private int mRemainingSteps = 0;
|
||||||
private int mTotalSteps = 0;
|
private int mTotalSteps = 0;
|
||||||
private int mProgressSteps = 0;
|
private float mProgress = 0f;
|
||||||
private boolean mIsShowingHelp = false;
|
@Nullable private ValueAnimator mProgressAnimator;
|
||||||
|
@NonNull private final ValueAnimator.AnimatorUpdateListener mProgressUpdateListener;
|
||||||
|
|
||||||
|
private boolean mShowingHelp = false;
|
||||||
|
@Nullable private ValueAnimator mFillColorAnimator;
|
||||||
|
@NonNull private final ValueAnimator.AnimatorUpdateListener mFillColorUpdateListener;
|
||||||
|
|
||||||
|
private boolean mComplete = false;
|
||||||
|
private float mCheckmarkScale = 0f;
|
||||||
|
@Nullable private ValueAnimator mCheckmarkAnimator;
|
||||||
|
@NonNull private final ValueAnimator.AnimatorUpdateListener mCheckmarkUpdateListener;
|
||||||
|
|
||||||
public UdfpsEnrollProgressBarDrawable(@NonNull Context context) {
|
public UdfpsEnrollProgressBarDrawable(@NonNull Context context) {
|
||||||
mContext = context;
|
mStrokeWidthPx = Utils.dpToPixels(context, STROKE_WIDTH_DP);
|
||||||
}
|
mProgressColor = context.getColor(R.color.udfps_enroll_progress);
|
||||||
|
mHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
|
||||||
|
mCheckmarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark);
|
||||||
|
mCheckmarkDrawable.mutate();
|
||||||
|
mCheckmarkInterpolator = new OvershootInterpolator();
|
||||||
|
|
||||||
void setEnrollHelper(@Nullable UdfpsEnrollHelper enrollHelper) {
|
mBackgroundPaint = new Paint();
|
||||||
mEnrollHelper = enrollHelper;
|
mBackgroundPaint.setStrokeWidth(mStrokeWidthPx);
|
||||||
if (enrollHelper != null) {
|
mBackgroundPaint.setColor(context.getColor(R.color.white_disabled));
|
||||||
final int stageCount = enrollHelper.getStageCount();
|
mBackgroundPaint.setAntiAlias(true);
|
||||||
mSegments = new ArrayList<>(stageCount);
|
mBackgroundPaint.setStyle(Paint.Style.STROKE);
|
||||||
float startAngle = SEGMENT_GAP_ANGLE / 2f;
|
mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||||
final float sweepAngle = (360f / stageCount) - SEGMENT_GAP_ANGLE;
|
|
||||||
final Runnable invalidateRunnable = this::invalidateSelf;
|
// Set background paint color and alpha.
|
||||||
for (int index = 0; index < stageCount; index++) {
|
final int[] attrs = new int[] {android.R.attr.colorControlNormal};
|
||||||
mSegments.add(new UdfpsEnrollProgressBarSegment(mContext, getBounds(), startAngle,
|
final TypedArray typedArray = context.obtainStyledAttributes(attrs);
|
||||||
sweepAngle, SEGMENT_GAP_ANGLE, invalidateRunnable));
|
try {
|
||||||
startAngle += sweepAngle + SEGMENT_GAP_ANGLE;
|
@ColorInt final int tintColor = typedArray.getColor(0, mBackgroundPaint.getColor());
|
||||||
}
|
mBackgroundPaint.setColor(tintColor);
|
||||||
invalidateSelf();
|
} finally {
|
||||||
|
typedArray.recycle();
|
||||||
}
|
}
|
||||||
|
TypedValue alpha = new TypedValue();
|
||||||
|
context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true);
|
||||||
|
mBackgroundPaint.setAlpha((int) (alpha.getFloat() * 255f));
|
||||||
|
|
||||||
|
// Progress fill should *not* use the extracted system color.
|
||||||
|
mFillPaint = new Paint();
|
||||||
|
mFillPaint.setStrokeWidth(mStrokeWidthPx);
|
||||||
|
mFillPaint.setColor(mProgressColor);
|
||||||
|
mFillPaint.setAntiAlias(true);
|
||||||
|
mFillPaint.setStyle(Paint.Style.STROKE);
|
||||||
|
mFillPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||||
|
|
||||||
|
mProgressUpdateListener = animation -> {
|
||||||
|
mProgress = (float) animation.getAnimatedValue();
|
||||||
|
invalidateSelf();
|
||||||
|
};
|
||||||
|
|
||||||
|
mFillColorUpdateListener = animation -> {
|
||||||
|
mFillPaint.setColor((int) animation.getAnimatedValue());
|
||||||
|
invalidateSelf();
|
||||||
|
};
|
||||||
|
|
||||||
|
mCheckmarkUpdateListener = animation -> {
|
||||||
|
mCheckmarkScale = (float) animation.getAnimatedValue();
|
||||||
|
invalidateSelf();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void onEnrollmentProgress(int remaining, int totalSteps) {
|
void onEnrollmentProgress(int remaining, int totalSteps) {
|
||||||
mTotalSteps = totalSteps;
|
mAfterFirstTouch = true;
|
||||||
|
updateState(remaining, totalSteps, false /* showingHelp */);
|
||||||
// Show some progress for the initial touch.
|
|
||||||
updateState(Math.max(1, totalSteps - remaining), false /* isShowingHelp */);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onEnrollmentHelp(int remaining, int totalSteps) {
|
void onEnrollmentHelp(int remaining, int totalSteps) {
|
||||||
updateState(Math.max(0, totalSteps - remaining), true /* isShowingHelp */);
|
updateState(remaining, totalSteps, true /* showingHelp */);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onLastStepAcquired() {
|
void onLastStepAcquired() {
|
||||||
updateState(mTotalSteps, false /* isShowingHelp */);
|
updateState(0, mTotalSteps, false /* showingHelp */);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateState(int progressSteps, boolean isShowingHelp) {
|
private void updateState(int remainingSteps, int totalSteps, boolean showingHelp) {
|
||||||
updateProgress(progressSteps);
|
updateProgress(remainingSteps, totalSteps);
|
||||||
updateFillColor(isShowingHelp);
|
updateFillColor(showingHelp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateProgress(int progressSteps) {
|
private void updateProgress(int remainingSteps, int totalSteps) {
|
||||||
if (mProgressSteps == progressSteps) {
|
if (mRemainingSteps == remainingSteps && mTotalSteps == totalSteps) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mProgressSteps = progressSteps;
|
mRemainingSteps = remainingSteps;
|
||||||
|
mTotalSteps = totalSteps;
|
||||||
|
|
||||||
if (mEnrollHelper == null) {
|
final int progressSteps = Math.max(0, totalSteps - remainingSteps);
|
||||||
Log.e(TAG, "updateState: UDFPS enroll helper was null");
|
|
||||||
return;
|
// If needed, add 1 to progress and total steps to account for initial touch.
|
||||||
|
final int adjustedSteps = mAfterFirstTouch ? progressSteps + 1 : progressSteps;
|
||||||
|
final int adjustedTotal = mAfterFirstTouch ? mTotalSteps + 1 : mTotalSteps;
|
||||||
|
|
||||||
|
final float targetProgress = Math.min(1f, (float) adjustedSteps / (float) adjustedTotal);
|
||||||
|
|
||||||
|
if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
|
||||||
|
mProgressAnimator.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
int index = 0;
|
mProgressAnimator = ValueAnimator.ofFloat(mProgress, targetProgress);
|
||||||
int prevThreshold = 0;
|
mProgressAnimator.setDuration(PROGRESS_ANIMATION_DURATION_MS);
|
||||||
while (index < mSegments.size()) {
|
mProgressAnimator.addUpdateListener(mProgressUpdateListener);
|
||||||
final UdfpsEnrollProgressBarSegment segment = mSegments.get(index);
|
mProgressAnimator.start();
|
||||||
final int thresholdSteps = mEnrollHelper.getStageThresholdSteps(mTotalSteps, index);
|
|
||||||
if (progressSteps >= thresholdSteps && segment.getProgress() < 1f) {
|
|
||||||
segment.updateProgress(1f);
|
|
||||||
break;
|
|
||||||
} else if (progressSteps >= prevThreshold && progressSteps < thresholdSteps) {
|
|
||||||
final int relativeSteps = progressSteps - prevThreshold;
|
|
||||||
final int relativeThreshold = thresholdSteps - prevThreshold;
|
|
||||||
final float segmentProgress = (float) relativeSteps / (float) relativeThreshold;
|
|
||||||
segment.updateProgress(segmentProgress);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
if (remainingSteps == 0) {
|
||||||
prevThreshold = thresholdSteps;
|
startCompletionAnimation();
|
||||||
}
|
} else if (remainingSteps > 0) {
|
||||||
|
rollBackCompletionAnimation();
|
||||||
if (progressSteps >= mTotalSteps) {
|
|
||||||
for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
|
|
||||||
segment.startCompletionAnimation();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
|
|
||||||
segment.cancelCompletionAnimation();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateFillColor(boolean isShowingHelp) {
|
private void updateFillColor(boolean showingHelp) {
|
||||||
if (mIsShowingHelp == isShowingHelp) {
|
if (mShowingHelp == showingHelp) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mIsShowingHelp = isShowingHelp;
|
mShowingHelp = showingHelp;
|
||||||
|
|
||||||
for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
|
if (mFillColorAnimator != null && mFillColorAnimator.isRunning()) {
|
||||||
segment.updateFillColor(isShowingHelp);
|
mFillColorAnimator.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ColorInt final int targetColor = showingHelp ? mHelpColor : mProgressColor;
|
||||||
|
mFillColorAnimator = ValueAnimator.ofArgb(mFillPaint.getColor(), targetColor);
|
||||||
|
mFillColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
|
||||||
|
mFillColorAnimator.addUpdateListener(mFillColorUpdateListener);
|
||||||
|
mFillColorAnimator.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startCompletionAnimation() {
|
||||||
|
if (mComplete) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mComplete = true;
|
||||||
|
|
||||||
|
if (mCheckmarkAnimator != null && mCheckmarkAnimator.isRunning()) {
|
||||||
|
mCheckmarkAnimator.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
mCheckmarkAnimator = ValueAnimator.ofFloat(mCheckmarkScale, 1f);
|
||||||
|
mCheckmarkAnimator.setStartDelay(CHECKMARK_ANIMATION_DELAY_MS);
|
||||||
|
mCheckmarkAnimator.setDuration(CHECKMARK_ANIMATION_DURATION_MS);
|
||||||
|
mCheckmarkAnimator.setInterpolator(mCheckmarkInterpolator);
|
||||||
|
mCheckmarkAnimator.addUpdateListener(mCheckmarkUpdateListener);
|
||||||
|
mCheckmarkAnimator.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rollBackCompletionAnimation() {
|
||||||
|
if (!mComplete) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mComplete = false;
|
||||||
|
|
||||||
|
// Adjust duration based on how much of the completion animation has played.
|
||||||
|
final float animatedFraction = mCheckmarkAnimator != null
|
||||||
|
? mCheckmarkAnimator.getAnimatedFraction()
|
||||||
|
: 0f;
|
||||||
|
final long durationMs = Math.round(CHECKMARK_ANIMATION_DELAY_MS * animatedFraction);
|
||||||
|
|
||||||
|
if (mCheckmarkAnimator != null && mCheckmarkAnimator.isRunning()) {
|
||||||
|
mCheckmarkAnimator.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
mCheckmarkAnimator = ValueAnimator.ofFloat(mCheckmarkScale, 0f);
|
||||||
|
mCheckmarkAnimator.setDuration(durationMs);
|
||||||
|
mCheckmarkAnimator.addUpdateListener(mCheckmarkUpdateListener);
|
||||||
|
mCheckmarkAnimator.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -145,12 +235,55 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable {
|
|||||||
// Progress starts from the top, instead of the right
|
// Progress starts from the top, instead of the right
|
||||||
canvas.rotate(-90f, getBounds().centerX(), getBounds().centerY());
|
canvas.rotate(-90f, getBounds().centerX(), getBounds().centerY());
|
||||||
|
|
||||||
// Draw each of the enroll segments.
|
final float halfPaddingPx = mStrokeWidthPx / 2f;
|
||||||
for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
|
|
||||||
segment.draw(canvas);
|
if (mProgress < 1f) {
|
||||||
|
// Draw the background color of the progress circle.
|
||||||
|
canvas.drawArc(
|
||||||
|
halfPaddingPx,
|
||||||
|
halfPaddingPx,
|
||||||
|
getBounds().right - halfPaddingPx,
|
||||||
|
getBounds().bottom - halfPaddingPx,
|
||||||
|
0f /* startAngle */,
|
||||||
|
360f /* sweepAngle */,
|
||||||
|
false /* useCenter */,
|
||||||
|
mBackgroundPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mProgress > 0f) {
|
||||||
|
// Draw the filled portion of the progress circle.
|
||||||
|
canvas.drawArc(
|
||||||
|
halfPaddingPx,
|
||||||
|
halfPaddingPx,
|
||||||
|
getBounds().right - halfPaddingPx,
|
||||||
|
getBounds().bottom - halfPaddingPx,
|
||||||
|
0f /* startAngle */,
|
||||||
|
360f * mProgress /* sweepAngle */,
|
||||||
|
false /* useCenter */,
|
||||||
|
mFillPaint);
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
|
|
||||||
|
if (mCheckmarkScale > 0f) {
|
||||||
|
final float offsetScale = (float) Math.sqrt(2) / 2f;
|
||||||
|
final float centerXOffset = (getBounds().width() - mStrokeWidthPx) / 2f * offsetScale;
|
||||||
|
final float centerYOffset = (getBounds().height() - mStrokeWidthPx) / 2f * offsetScale;
|
||||||
|
final float centerX = getBounds().centerX() + centerXOffset;
|
||||||
|
final float centerY = getBounds().centerY() + centerYOffset;
|
||||||
|
|
||||||
|
final float boundsXOffset =
|
||||||
|
mCheckmarkDrawable.getIntrinsicWidth() / 2f * mCheckmarkScale;
|
||||||
|
final float boundsYOffset =
|
||||||
|
mCheckmarkDrawable.getIntrinsicHeight() / 2f * mCheckmarkScale;
|
||||||
|
|
||||||
|
final int left = Math.round(centerX - boundsXOffset);
|
||||||
|
final int top = Math.round(centerY - boundsYOffset);
|
||||||
|
final int right = Math.round(centerX + boundsXOffset);
|
||||||
|
final int bottom = Math.round(centerY + boundsYOffset);
|
||||||
|
mCheckmarkDrawable.setBounds(left, top, right, bottom);
|
||||||
|
mCheckmarkDrawable.draw(canvas);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,280 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.systemui.biometrics;
|
|
||||||
|
|
||||||
import android.animation.ValueAnimator;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.util.TypedValue;
|
|
||||||
|
|
||||||
import androidx.annotation.ColorInt;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.android.systemui.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A single segment of the UDFPS enrollment progress bar.
|
|
||||||
*/
|
|
||||||
public class UdfpsEnrollProgressBarSegment {
|
|
||||||
private static final String TAG = "UdfpsProgressBarSegment";
|
|
||||||
|
|
||||||
private static final long FILL_COLOR_ANIMATION_DURATION_MS = 200L;
|
|
||||||
private static final long PROGRESS_ANIMATION_DURATION_MS = 400L;
|
|
||||||
private static final long OVER_SWEEP_ANIMATION_DELAY_MS = 200L;
|
|
||||||
private static final long OVER_SWEEP_ANIMATION_DURATION_MS = 200L;
|
|
||||||
|
|
||||||
private static final float STROKE_WIDTH_DP = 12f;
|
|
||||||
|
|
||||||
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
|
||||||
|
|
||||||
@NonNull private final Rect mBounds;
|
|
||||||
@NonNull private final Runnable mInvalidateRunnable;
|
|
||||||
private final float mStartAngle;
|
|
||||||
private final float mSweepAngle;
|
|
||||||
private final float mMaxOverSweepAngle;
|
|
||||||
private final float mStrokeWidthPx;
|
|
||||||
@ColorInt private final int mProgressColor;
|
|
||||||
@ColorInt private final int mHelpColor;
|
|
||||||
|
|
||||||
@NonNull private final Paint mBackgroundPaint;
|
|
||||||
@NonNull private final Paint mProgressPaint;
|
|
||||||
|
|
||||||
private float mProgress = 0f;
|
|
||||||
private float mAnimatedProgress = 0f;
|
|
||||||
@Nullable private ValueAnimator mProgressAnimator;
|
|
||||||
@NonNull private final ValueAnimator.AnimatorUpdateListener mProgressUpdateListener;
|
|
||||||
|
|
||||||
private boolean mIsShowingHelp = false;
|
|
||||||
@Nullable private ValueAnimator mFillColorAnimator;
|
|
||||||
@NonNull private final ValueAnimator.AnimatorUpdateListener mFillColorUpdateListener;
|
|
||||||
|
|
||||||
private float mOverSweepAngle = 0f;
|
|
||||||
@Nullable private ValueAnimator mOverSweepAnimator;
|
|
||||||
@Nullable private ValueAnimator mOverSweepReverseAnimator;
|
|
||||||
@NonNull private final ValueAnimator.AnimatorUpdateListener mOverSweepUpdateListener;
|
|
||||||
@NonNull private final Runnable mOverSweepAnimationRunnable;
|
|
||||||
|
|
||||||
public UdfpsEnrollProgressBarSegment(@NonNull Context context, @NonNull Rect bounds,
|
|
||||||
float startAngle, float sweepAngle, float maxOverSweepAngle,
|
|
||||||
@NonNull Runnable invalidateRunnable) {
|
|
||||||
|
|
||||||
mBounds = bounds;
|
|
||||||
mInvalidateRunnable = invalidateRunnable;
|
|
||||||
mStartAngle = startAngle;
|
|
||||||
mSweepAngle = sweepAngle;
|
|
||||||
mMaxOverSweepAngle = maxOverSweepAngle;
|
|
||||||
mStrokeWidthPx = Utils.dpToPixels(context, STROKE_WIDTH_DP);
|
|
||||||
mProgressColor = context.getColor(R.color.udfps_enroll_progress);
|
|
||||||
mHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
|
|
||||||
|
|
||||||
mBackgroundPaint = new Paint();
|
|
||||||
mBackgroundPaint.setStrokeWidth(mStrokeWidthPx);
|
|
||||||
mBackgroundPaint.setColor(context.getColor(R.color.white_disabled));
|
|
||||||
mBackgroundPaint.setAntiAlias(true);
|
|
||||||
mBackgroundPaint.setStyle(Paint.Style.STROKE);
|
|
||||||
mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND);
|
|
||||||
|
|
||||||
// Background paint color + alpha
|
|
||||||
final int[] attrs = new int[] {android.R.attr.colorControlNormal};
|
|
||||||
final TypedArray ta = context.obtainStyledAttributes(attrs);
|
|
||||||
@ColorInt final int tintColor = ta.getColor(0, mBackgroundPaint.getColor());
|
|
||||||
mBackgroundPaint.setColor(tintColor);
|
|
||||||
ta.recycle();
|
|
||||||
TypedValue alpha = new TypedValue();
|
|
||||||
context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true);
|
|
||||||
mBackgroundPaint.setAlpha((int) (alpha.getFloat() * 255f));
|
|
||||||
|
|
||||||
// Progress should not be color extracted
|
|
||||||
mProgressPaint = new Paint();
|
|
||||||
mProgressPaint.setStrokeWidth(mStrokeWidthPx);
|
|
||||||
mProgressPaint.setColor(mProgressColor);
|
|
||||||
mProgressPaint.setAntiAlias(true);
|
|
||||||
mProgressPaint.setStyle(Paint.Style.STROKE);
|
|
||||||
mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
|
|
||||||
|
|
||||||
mProgressUpdateListener = animation -> {
|
|
||||||
mAnimatedProgress = (float) animation.getAnimatedValue();
|
|
||||||
mInvalidateRunnable.run();
|
|
||||||
};
|
|
||||||
|
|
||||||
mFillColorUpdateListener = animation -> {
|
|
||||||
mProgressPaint.setColor((int) animation.getAnimatedValue());
|
|
||||||
mInvalidateRunnable.run();
|
|
||||||
};
|
|
||||||
|
|
||||||
mOverSweepUpdateListener = animation -> {
|
|
||||||
mOverSweepAngle = (float) animation.getAnimatedValue();
|
|
||||||
mInvalidateRunnable.run();
|
|
||||||
};
|
|
||||||
mOverSweepAnimationRunnable = () -> {
|
|
||||||
if (mOverSweepAnimator != null && mOverSweepAnimator.isRunning()) {
|
|
||||||
mOverSweepAnimator.cancel();
|
|
||||||
}
|
|
||||||
mOverSweepAnimator = ValueAnimator.ofFloat(mOverSweepAngle, mMaxOverSweepAngle);
|
|
||||||
mOverSweepAnimator.setDuration(OVER_SWEEP_ANIMATION_DURATION_MS);
|
|
||||||
mOverSweepAnimator.addUpdateListener(mOverSweepUpdateListener);
|
|
||||||
mOverSweepAnimator.start();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws this segment to the given canvas.
|
|
||||||
*/
|
|
||||||
public void draw(@NonNull Canvas canvas) {
|
|
||||||
final float halfPaddingPx = mStrokeWidthPx / 2f;
|
|
||||||
|
|
||||||
if (mAnimatedProgress < 1f) {
|
|
||||||
// Draw the unfilled background color of the segment.
|
|
||||||
canvas.drawArc(
|
|
||||||
halfPaddingPx,
|
|
||||||
halfPaddingPx,
|
|
||||||
mBounds.right - halfPaddingPx,
|
|
||||||
mBounds.bottom - halfPaddingPx,
|
|
||||||
mStartAngle,
|
|
||||||
mSweepAngle,
|
|
||||||
false /* useCenter */,
|
|
||||||
mBackgroundPaint);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mAnimatedProgress > 0f) {
|
|
||||||
// Draw the filled progress portion of the segment.
|
|
||||||
canvas.drawArc(
|
|
||||||
halfPaddingPx,
|
|
||||||
halfPaddingPx,
|
|
||||||
mBounds.right - halfPaddingPx,
|
|
||||||
mBounds.bottom - halfPaddingPx,
|
|
||||||
mStartAngle,
|
|
||||||
mSweepAngle * mAnimatedProgress + mOverSweepAngle,
|
|
||||||
false /* useCenter */,
|
|
||||||
mProgressPaint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The fill progress of this segment, in the range [0, 1]. If fill progress is being
|
|
||||||
* animated, returns the value it is animating to.
|
|
||||||
*/
|
|
||||||
public float getProgress() {
|
|
||||||
return mProgress;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the fill progress of this segment, animating if necessary.
|
|
||||||
*
|
|
||||||
* @param progress The new fill progress, in the range [0, 1].
|
|
||||||
*/
|
|
||||||
public void updateProgress(float progress) {
|
|
||||||
updateProgress(progress, PROGRESS_ANIMATION_DURATION_MS);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateProgress(float progress, long animationDurationMs) {
|
|
||||||
if (mProgress == progress) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mProgress = progress;
|
|
||||||
|
|
||||||
if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
|
|
||||||
mProgressAnimator.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
mProgressAnimator = ValueAnimator.ofFloat(mAnimatedProgress, progress);
|
|
||||||
mProgressAnimator.setDuration(animationDurationMs);
|
|
||||||
mProgressAnimator.addUpdateListener(mProgressUpdateListener);
|
|
||||||
mProgressAnimator.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the fill color of this segment, animating if necessary.
|
|
||||||
*
|
|
||||||
* @param isShowingHelp Whether fill color should indicate that a help message is being shown.
|
|
||||||
*/
|
|
||||||
public void updateFillColor(boolean isShowingHelp) {
|
|
||||||
if (mIsShowingHelp == isShowingHelp) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mIsShowingHelp = isShowingHelp;
|
|
||||||
|
|
||||||
if (mFillColorAnimator != null && mFillColorAnimator.isRunning()) {
|
|
||||||
mFillColorAnimator.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@ColorInt final int targetColor = isShowingHelp ? mHelpColor : mProgressColor;
|
|
||||||
mFillColorAnimator = ValueAnimator.ofArgb(mProgressPaint.getColor(), targetColor);
|
|
||||||
mFillColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
|
|
||||||
mFillColorAnimator.addUpdateListener(mFillColorUpdateListener);
|
|
||||||
mFillColorAnimator.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Queues and runs the completion animation for this segment.
|
|
||||||
*/
|
|
||||||
public void startCompletionAnimation() {
|
|
||||||
final boolean hasCallback = mHandler.hasCallbacks(mOverSweepAnimationRunnable);
|
|
||||||
if (hasCallback || mOverSweepAngle >= mMaxOverSweepAngle) {
|
|
||||||
Log.d(TAG, "startCompletionAnimation skipped: hasCallback = " + hasCallback
|
|
||||||
+ ", mOverSweepAngle = " + mOverSweepAngle);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset sweep angle back to zero if the animation is being rolled back.
|
|
||||||
if (mOverSweepReverseAnimator != null && mOverSweepReverseAnimator.isRunning()) {
|
|
||||||
mOverSweepReverseAnimator.cancel();
|
|
||||||
mOverSweepAngle = 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear help color and start filling the segment if it isn't already.
|
|
||||||
if (mAnimatedProgress < 1f) {
|
|
||||||
updateProgress(1f, OVER_SWEEP_ANIMATION_DELAY_MS);
|
|
||||||
updateFillColor(false /* isShowingHelp */);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Queue the animation to run after fill completes.
|
|
||||||
mHandler.postDelayed(mOverSweepAnimationRunnable, OVER_SWEEP_ANIMATION_DELAY_MS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancels (and reverses, if necessary) a queued or running completion animation.
|
|
||||||
*/
|
|
||||||
public void cancelCompletionAnimation() {
|
|
||||||
// Cancel the animation if it's queued or running.
|
|
||||||
mHandler.removeCallbacks(mOverSweepAnimationRunnable);
|
|
||||||
if (mOverSweepAnimator != null && mOverSweepAnimator.isRunning()) {
|
|
||||||
mOverSweepAnimator.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Roll back the animation if it has at least partially run.
|
|
||||||
if (mOverSweepAngle > 0f) {
|
|
||||||
if (mOverSweepReverseAnimator != null && mOverSweepReverseAnimator.isRunning()) {
|
|
||||||
mOverSweepReverseAnimator.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
final float completion = mOverSweepAngle / mMaxOverSweepAngle;
|
|
||||||
final long proratedDuration = (long) (OVER_SWEEP_ANIMATION_DURATION_MS * completion);
|
|
||||||
mOverSweepReverseAnimator = ValueAnimator.ofFloat(mOverSweepAngle, 0f);
|
|
||||||
mOverSweepReverseAnimator.setDuration(proratedDuration);
|
|
||||||
mOverSweepReverseAnimator.addUpdateListener(mOverSweepUpdateListener);
|
|
||||||
mOverSweepReverseAnimator.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -73,7 +73,6 @@ public class UdfpsEnrollView extends UdfpsAnimationView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setEnrollHelper(UdfpsEnrollHelper enrollHelper) {
|
void setEnrollHelper(UdfpsEnrollHelper enrollHelper) {
|
||||||
mFingerprintProgressDrawable.setEnrollHelper(enrollHelper);
|
|
||||||
mFingerprintDrawable.setEnrollHelper(enrollHelper);
|
mFingerprintDrawable.setEnrollHelper(enrollHelper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user