Merge "Support configurable multi-stage UDFPS enrollment" into sc-v2-dev am: 8f8783ca1b
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15876070 Change-Id: I2a05114500ec35d26f02ddd0039ff47f0f76710f
This commit is contained in:
@@ -146,6 +146,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
|
|||||||
private CryptoObject mCryptoObject;
|
private CryptoObject mCryptoObject;
|
||||||
@Nullable private RemoveTracker mRemoveTracker;
|
@Nullable private RemoveTracker mRemoveTracker;
|
||||||
private Handler mHandler;
|
private Handler mHandler;
|
||||||
|
@Nullable private float[] mEnrollStageThresholds;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a list of properties for all fingerprint sensors on the device.
|
* Retrieves a list of properties for all fingerprint sensors on the device.
|
||||||
@@ -1326,6 +1327,46 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public int getEnrollStageCount() {
|
||||||
|
if (mEnrollStageThresholds == null) {
|
||||||
|
mEnrollStageThresholds = createEnrollStageThresholds(mContext);
|
||||||
|
}
|
||||||
|
return mEnrollStageThresholds.length + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public float getEnrollStageThreshold(int index) {
|
||||||
|
if (mEnrollStageThresholds == null) {
|
||||||
|
mEnrollStageThresholds = createEnrollStageThresholds(mContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < 0 || index > mEnrollStageThresholds.length) {
|
||||||
|
Slog.w(TAG, "Unsupported enroll stage index: " + index);
|
||||||
|
return index < 0 ? 0f : 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The implicit threshold for the final stage is always 1.
|
||||||
|
return index == mEnrollStageThresholds.length ? 1f : mEnrollStageThresholds[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static float[] createEnrollStageThresholds(@NonNull Context context) {
|
||||||
|
// TODO(b/200604947): Fetch this value from FingerprintService, rather than internal config
|
||||||
|
final String[] enrollStageThresholdStrings = context.getResources().getStringArray(
|
||||||
|
com.android.internal.R.array.config_udfps_enroll_stage_thresholds);
|
||||||
|
|
||||||
|
final float[] enrollStageThresholds = new float[enrollStageThresholdStrings.length];
|
||||||
|
for (int i = 0; i < enrollStageThresholds.length; i++) {
|
||||||
|
enrollStageThresholds[i] = Float.parseFloat(enrollStageThresholdStrings[i]);
|
||||||
|
}
|
||||||
|
return enrollStageThresholds;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4601,6 +4601,13 @@
|
|||||||
<!-- Indicates whether device has a power button fingerprint sensor. -->
|
<!-- Indicates whether device has a power button fingerprint sensor. -->
|
||||||
<bool name="config_is_powerbutton_fps" translatable="false" >false</bool>
|
<bool name="config_is_powerbutton_fps" translatable="false" >false</bool>
|
||||||
|
|
||||||
|
<!-- When each intermediate UDFPS enroll stage ends, as a fraction of total progress. -->
|
||||||
|
<string-array name="config_udfps_enroll_stage_thresholds" translatable="false">
|
||||||
|
<item>0.25</item>
|
||||||
|
<item>0.5</item>
|
||||||
|
<item>0.75</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
<!-- Messages that should not be shown to the user during face auth enrollment. This should be
|
<!-- Messages that should not be shown to the user during face auth enrollment. This should be
|
||||||
used to hide messages that may be too chatty or messages that the user can't do much about.
|
used to hide messages that may be too chatty or messages that the user can't do much about.
|
||||||
Entries are defined in android.hardware.biometrics.face@1.0 types.hal -->
|
Entries are defined in android.hardware.biometrics.face@1.0 types.hal -->
|
||||||
|
|||||||
@@ -2613,6 +2613,7 @@
|
|||||||
<java-symbol type="array" name="config_sfps_sensor_props" />
|
<java-symbol type="array" name="config_sfps_sensor_props" />
|
||||||
<java-symbol type="integer" name="config_udfps_illumination_transition_ms" />
|
<java-symbol type="integer" name="config_udfps_illumination_transition_ms" />
|
||||||
<java-symbol type="bool" name="config_is_powerbutton_fps" />
|
<java-symbol type="bool" name="config_is_powerbutton_fps" />
|
||||||
|
<java-symbol type="array" name="config_udfps_enroll_stage_thresholds" />
|
||||||
|
|
||||||
<java-symbol type="array" name="config_face_acquire_enroll_ignorelist" />
|
<java-symbol type="array" name="config_face_acquire_enroll_ignorelist" />
|
||||||
<java-symbol type="array" name="config_face_acquire_vendor_enroll_ignorelist" />
|
<java-symbol type="array" name="config_face_acquire_vendor_enroll_ignorelist" />
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ public class UdfpsController implements DozeReceiver {
|
|||||||
final UdfpsEnrollHelper enrollHelper;
|
final UdfpsEnrollHelper enrollHelper;
|
||||||
if (reason == BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
|
if (reason == BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
|
||||||
|| reason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING) {
|
|| reason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING) {
|
||||||
enrollHelper = new UdfpsEnrollHelper(mContext, reason);
|
enrollHelper = new UdfpsEnrollHelper(mContext, mFingerprintManager, reason);
|
||||||
} else {
|
} else {
|
||||||
enrollHelper = null;
|
enrollHelper = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,11 @@
|
|||||||
|
|
||||||
package com.android.systemui.biometrics;
|
package com.android.systemui.biometrics;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
import android.animation.AnimatorSet;
|
import android.animation.AnimatorSet;
|
||||||
import android.animation.ValueAnimator;
|
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.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
@@ -26,11 +28,17 @@ import android.graphics.PointF;
|
|||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.RectF;
|
import android.graphics.RectF;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.TypedValue;
|
||||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||||
|
import android.view.animation.LinearInterpolator;
|
||||||
|
|
||||||
|
import androidx.annotation.ColorInt;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.android.internal.graphics.ColorUtils;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,10 +47,20 @@ import com.android.systemui.R;
|
|||||||
public class UdfpsEnrollDrawable extends UdfpsDrawable {
|
public class UdfpsEnrollDrawable extends UdfpsDrawable {
|
||||||
private static final String TAG = "UdfpsAnimationEnroll";
|
private static final String TAG = "UdfpsAnimationEnroll";
|
||||||
|
|
||||||
private static final long ANIM_DURATION = 800;
|
private static final long HINT_COLOR_ANIM_DELAY_MS = 233L;
|
||||||
|
private static final long HINT_COLOR_ANIM_DURATION_MS = 517L;
|
||||||
|
private static final long HINT_WIDTH_ANIM_DURATION_MS = 233L;
|
||||||
|
private static final long TARGET_ANIM_DURATION_LONG = 800L;
|
||||||
|
private static final long TARGET_ANIM_DURATION_SHORT = 600L;
|
||||||
// 1 + SCALE_MAX is the maximum that the moving target will animate to
|
// 1 + SCALE_MAX is the maximum that the moving target will animate to
|
||||||
private static final float SCALE_MAX = 0.25f;
|
private static final float SCALE_MAX = 0.25f;
|
||||||
|
|
||||||
|
private static final float HINT_PADDING_DP = 10f;
|
||||||
|
private static final float HINT_MAX_WIDTH_DP = 6f;
|
||||||
|
private static final float HINT_ANGLE = 40f;
|
||||||
|
|
||||||
|
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
@NonNull private final Drawable mMovingTargetFpIcon;
|
@NonNull private final Drawable mMovingTargetFpIcon;
|
||||||
@NonNull private final Paint mSensorOutlinePaint;
|
@NonNull private final Paint mSensorOutlinePaint;
|
||||||
@NonNull private final Paint mBlueFill;
|
@NonNull private final Paint mBlueFill;
|
||||||
@@ -51,17 +69,41 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable {
|
|||||||
@Nullable private UdfpsEnrollHelper mEnrollHelper;
|
@Nullable private UdfpsEnrollHelper mEnrollHelper;
|
||||||
|
|
||||||
// Moving target animator set
|
// Moving target animator set
|
||||||
@Nullable AnimatorSet mAnimatorSet;
|
@Nullable AnimatorSet mTargetAnimatorSet;
|
||||||
// Moving target location
|
// Moving target location
|
||||||
float mCurrentX;
|
float mCurrentX;
|
||||||
float mCurrentY;
|
float mCurrentY;
|
||||||
// Moving target size
|
// Moving target size
|
||||||
float mCurrentScale = 1.f;
|
float mCurrentScale = 1.f;
|
||||||
|
|
||||||
|
@ColorInt private final int mHintColorFaded;
|
||||||
|
@ColorInt private final int mHintColorHighlight;
|
||||||
|
private final float mHintMaxWidthPx;
|
||||||
|
private final float mHintPaddingPx;
|
||||||
|
|
||||||
|
@NonNull private final Animator.AnimatorListener mTargetAnimListener;
|
||||||
|
|
||||||
|
private boolean mShouldShowTipHint = false;
|
||||||
|
@NonNull private final Paint mTipHintPaint;
|
||||||
|
@Nullable private AnimatorSet mTipHintAnimatorSet;
|
||||||
|
@Nullable private ValueAnimator mTipHintColorAnimator;
|
||||||
|
@Nullable private ValueAnimator mTipHintWidthAnimator;
|
||||||
|
@NonNull private final ValueAnimator.AnimatorUpdateListener mTipHintColorUpdateListener;
|
||||||
|
@NonNull private final ValueAnimator.AnimatorUpdateListener mTipHintWidthUpdateListener;
|
||||||
|
@NonNull private final Animator.AnimatorListener mTipHintPulseListener;
|
||||||
|
|
||||||
|
private boolean mShouldShowEdgeHint = false;
|
||||||
|
@NonNull private final Paint mEdgeHintPaint;
|
||||||
|
@Nullable private AnimatorSet mEdgeHintAnimatorSet;
|
||||||
|
@Nullable private ValueAnimator mEdgeHintColorAnimator;
|
||||||
|
@Nullable private ValueAnimator mEdgeHintWidthAnimator;
|
||||||
|
@NonNull private final ValueAnimator.AnimatorUpdateListener mEdgeHintColorUpdateListener;
|
||||||
|
@NonNull private final ValueAnimator.AnimatorUpdateListener mEdgeHintWidthUpdateListener;
|
||||||
|
@NonNull private final Animator.AnimatorListener mEdgeHintPulseListener;
|
||||||
|
|
||||||
UdfpsEnrollDrawable(@NonNull Context context) {
|
UdfpsEnrollDrawable(@NonNull Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
|
|
||||||
|
|
||||||
mSensorOutlinePaint = new Paint(0 /* flags */);
|
mSensorOutlinePaint = new Paint(0 /* flags */);
|
||||||
mSensorOutlinePaint.setAntiAlias(true);
|
mSensorOutlinePaint.setAntiAlias(true);
|
||||||
mSensorOutlinePaint.setColor(mContext.getColor(R.color.udfps_enroll_icon));
|
mSensorOutlinePaint.setColor(mContext.getColor(R.color.udfps_enroll_icon));
|
||||||
@@ -78,6 +120,117 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable {
|
|||||||
mMovingTargetFpIcon.mutate();
|
mMovingTargetFpIcon.mutate();
|
||||||
|
|
||||||
mFingerprintDrawable.setTint(mContext.getColor(R.color.udfps_enroll_icon));
|
mFingerprintDrawable.setTint(mContext.getColor(R.color.udfps_enroll_icon));
|
||||||
|
|
||||||
|
mHintColorFaded = getHintColorFaded(context);
|
||||||
|
mHintColorHighlight = context.getColor(R.color.udfps_enroll_progress);
|
||||||
|
mHintMaxWidthPx = Utils.dpToPixels(context, HINT_MAX_WIDTH_DP);
|
||||||
|
mHintPaddingPx = Utils.dpToPixels(context, HINT_PADDING_DP);
|
||||||
|
|
||||||
|
mTargetAnimListener = new Animator.AnimatorListener() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationStart(Animator animation) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
updateTipHintVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationCancel(Animator animation) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationRepeat(Animator animation) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
mTipHintPaint = new Paint(0 /* flags */);
|
||||||
|
mTipHintPaint.setAntiAlias(true);
|
||||||
|
mTipHintPaint.setColor(mHintColorFaded);
|
||||||
|
mTipHintPaint.setStyle(Paint.Style.STROKE);
|
||||||
|
mTipHintPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||||
|
mTipHintPaint.setStrokeWidth(0f);
|
||||||
|
mTipHintColorUpdateListener = animation -> {
|
||||||
|
mTipHintPaint.setColor((int) animation.getAnimatedValue());
|
||||||
|
invalidateSelf();
|
||||||
|
};
|
||||||
|
mTipHintWidthUpdateListener = animation -> {
|
||||||
|
mTipHintPaint.setStrokeWidth((float) animation.getAnimatedValue());
|
||||||
|
invalidateSelf();
|
||||||
|
};
|
||||||
|
mTipHintPulseListener = new Animator.AnimatorListener() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationStart(Animator animation) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
mHandler.postDelayed(() -> {
|
||||||
|
mTipHintColorAnimator =
|
||||||
|
ValueAnimator.ofArgb(mTipHintPaint.getColor(), mHintColorFaded);
|
||||||
|
mTipHintColorAnimator.setInterpolator(new LinearInterpolator());
|
||||||
|
mTipHintColorAnimator.setDuration(HINT_COLOR_ANIM_DURATION_MS);
|
||||||
|
mTipHintColorAnimator.addUpdateListener(mTipHintColorUpdateListener);
|
||||||
|
mTipHintColorAnimator.start();
|
||||||
|
}, HINT_COLOR_ANIM_DELAY_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationCancel(Animator animation) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationRepeat(Animator animation) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
mEdgeHintPaint = new Paint(0 /* flags */);
|
||||||
|
mEdgeHintPaint.setAntiAlias(true);
|
||||||
|
mEdgeHintPaint.setColor(mHintColorFaded);
|
||||||
|
mEdgeHintPaint.setStyle(Paint.Style.STROKE);
|
||||||
|
mEdgeHintPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||||
|
mEdgeHintPaint.setStrokeWidth(0f);
|
||||||
|
mEdgeHintColorUpdateListener = animation -> {
|
||||||
|
mEdgeHintPaint.setColor((int) animation.getAnimatedValue());
|
||||||
|
invalidateSelf();
|
||||||
|
};
|
||||||
|
mEdgeHintWidthUpdateListener = animation -> {
|
||||||
|
mEdgeHintPaint.setStrokeWidth((float) animation.getAnimatedValue());
|
||||||
|
invalidateSelf();
|
||||||
|
};
|
||||||
|
mEdgeHintPulseListener = new Animator.AnimatorListener() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationStart(Animator animation) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
mHandler.postDelayed(() -> {
|
||||||
|
mEdgeHintColorAnimator =
|
||||||
|
ValueAnimator.ofArgb(mEdgeHintPaint.getColor(), mHintColorFaded);
|
||||||
|
mEdgeHintColorAnimator.setInterpolator(new LinearInterpolator());
|
||||||
|
mEdgeHintColorAnimator.setDuration(HINT_COLOR_ANIM_DURATION_MS);
|
||||||
|
mEdgeHintColorAnimator.addUpdateListener(mEdgeHintColorUpdateListener);
|
||||||
|
mEdgeHintColorAnimator.start();
|
||||||
|
}, HINT_COLOR_ANIM_DELAY_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationCancel(Animator animation) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationRepeat(Animator animation) {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
private static int getHintColorFaded(@NonNull Context context) {
|
||||||
|
final TypedValue tv = new TypedValue();
|
||||||
|
context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, tv, true);
|
||||||
|
final int alpha = (int) (tv.getFloat() * 255f);
|
||||||
|
|
||||||
|
final int[] attrs = new int[] {android.R.attr.colorControlNormal};
|
||||||
|
final TypedArray ta = context.obtainStyledAttributes(attrs);
|
||||||
|
try {
|
||||||
|
@ColorInt final int color = ta.getColor(0, context.getColor(R.color.white_disabled));
|
||||||
|
return ColorUtils.setAlphaComponent(color, alpha);
|
||||||
|
} finally {
|
||||||
|
ta.recycle();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) {
|
void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) {
|
||||||
@@ -98,41 +251,154 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onEnrollmentProgress(int remaining, int totalSteps) {
|
void onEnrollmentProgress(int remaining, int totalSteps) {
|
||||||
if (mEnrollHelper.isCenterEnrollmentComplete()) {
|
if (mEnrollHelper == null) {
|
||||||
if (mAnimatorSet != null && mAnimatorSet.isRunning()) {
|
return;
|
||||||
mAnimatorSet.end();
|
}
|
||||||
|
|
||||||
|
if (!mEnrollHelper.isCenterEnrollmentStage()) {
|
||||||
|
if (mTargetAnimatorSet != null && mTargetAnimatorSet.isRunning()) {
|
||||||
|
mTargetAnimatorSet.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);
|
||||||
|
x.addUpdateListener(animation -> {
|
||||||
|
mCurrentX = (float) animation.getAnimatedValue();
|
||||||
|
invalidateSelf();
|
||||||
|
});
|
||||||
|
|
||||||
final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x);
|
final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y);
|
||||||
x.addUpdateListener(animation -> {
|
y.addUpdateListener(animation -> {
|
||||||
mCurrentX = (float) animation.getAnimatedValue();
|
mCurrentY = (float) animation.getAnimatedValue();
|
||||||
invalidateSelf();
|
invalidateSelf();
|
||||||
});
|
});
|
||||||
|
|
||||||
final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y);
|
final boolean isMovingToCenter = point.x == 0f && point.y == 0f;
|
||||||
y.addUpdateListener(animation -> {
|
final long duration = isMovingToCenter
|
||||||
mCurrentY = (float) animation.getAnimatedValue();
|
? TARGET_ANIM_DURATION_SHORT
|
||||||
invalidateSelf();
|
: TARGET_ANIM_DURATION_LONG;
|
||||||
});
|
|
||||||
|
|
||||||
final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI);
|
final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI);
|
||||||
scale.setDuration(ANIM_DURATION);
|
scale.setDuration(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();
|
||||||
});
|
});
|
||||||
|
|
||||||
mAnimatorSet = new AnimatorSet();
|
mTargetAnimatorSet = new AnimatorSet();
|
||||||
|
|
||||||
mAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
|
mTargetAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
|
||||||
mAnimatorSet.setDuration(ANIM_DURATION);
|
mTargetAnimatorSet.setDuration(duration);
|
||||||
mAnimatorSet.playTogether(x, y, scale);
|
mTargetAnimatorSet.addListener(mTargetAnimListener);
|
||||||
mAnimatorSet.start();
|
mTargetAnimatorSet.playTogether(x, y, scale);
|
||||||
|
mTargetAnimatorSet.start();
|
||||||
|
} else {
|
||||||
|
updateTipHintVisibility();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateTipHintVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateEdgeHintVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTipHintVisibility() {
|
||||||
|
final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isTipEnrollmentStage();
|
||||||
|
if (mShouldShowTipHint == shouldShow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mShouldShowTipHint = shouldShow;
|
||||||
|
|
||||||
|
if (mTipHintWidthAnimator != null && mTipHintWidthAnimator.isRunning()) {
|
||||||
|
mTipHintWidthAnimator.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
final float targetWidth = shouldShow ? mHintMaxWidthPx : 0f;
|
||||||
|
mTipHintWidthAnimator = ValueAnimator.ofFloat(mTipHintPaint.getStrokeWidth(), targetWidth);
|
||||||
|
mTipHintWidthAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
|
||||||
|
mTipHintWidthAnimator.addUpdateListener(mTipHintWidthUpdateListener);
|
||||||
|
|
||||||
|
if (shouldShow) {
|
||||||
|
startTipHintPulseAnimation();
|
||||||
|
} else {
|
||||||
|
mTipHintWidthAnimator.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateEdgeHintVisibility() {
|
||||||
|
final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isEdgeEnrollmentStage();
|
||||||
|
if (mShouldShowEdgeHint == shouldShow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mShouldShowEdgeHint = shouldShow;
|
||||||
|
|
||||||
|
if (mEdgeHintWidthAnimator != null && mEdgeHintWidthAnimator.isRunning()) {
|
||||||
|
mEdgeHintWidthAnimator.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
final float targetWidth = shouldShow ? mHintMaxWidthPx : 0f;
|
||||||
|
mEdgeHintWidthAnimator =
|
||||||
|
ValueAnimator.ofFloat(mEdgeHintPaint.getStrokeWidth(), targetWidth);
|
||||||
|
mEdgeHintWidthAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
|
||||||
|
mEdgeHintWidthAnimator.addUpdateListener(mEdgeHintWidthUpdateListener);
|
||||||
|
|
||||||
|
if (shouldShow) {
|
||||||
|
startEdgeHintPulseAnimation();
|
||||||
|
} else {
|
||||||
|
mEdgeHintWidthAnimator.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startTipHintPulseAnimation() {
|
||||||
|
mHandler.removeCallbacksAndMessages(null);
|
||||||
|
if (mTipHintAnimatorSet != null && mTipHintAnimatorSet.isRunning()) {
|
||||||
|
mTipHintAnimatorSet.cancel();
|
||||||
|
}
|
||||||
|
if (mTipHintColorAnimator != null && mTipHintColorAnimator.isRunning()) {
|
||||||
|
mTipHintColorAnimator.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
mTipHintColorAnimator = ValueAnimator.ofArgb(mTipHintPaint.getColor(), mHintColorHighlight);
|
||||||
|
mTipHintColorAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
|
||||||
|
mTipHintColorAnimator.addUpdateListener(mTipHintColorUpdateListener);
|
||||||
|
mTipHintColorAnimator.addListener(mTipHintPulseListener);
|
||||||
|
|
||||||
|
mTipHintAnimatorSet = new AnimatorSet();
|
||||||
|
mTipHintAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
|
||||||
|
mTipHintAnimatorSet.playTogether(mTipHintColorAnimator, mTipHintWidthAnimator);
|
||||||
|
mTipHintAnimatorSet.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startEdgeHintPulseAnimation() {
|
||||||
|
mHandler.removeCallbacksAndMessages(null);
|
||||||
|
if (mEdgeHintAnimatorSet != null && mEdgeHintAnimatorSet.isRunning()) {
|
||||||
|
mEdgeHintAnimatorSet.cancel();
|
||||||
|
}
|
||||||
|
if (mEdgeHintColorAnimator != null && mEdgeHintColorAnimator.isRunning()) {
|
||||||
|
mEdgeHintColorAnimator.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
mEdgeHintColorAnimator =
|
||||||
|
ValueAnimator.ofArgb(mEdgeHintPaint.getColor(), mHintColorHighlight);
|
||||||
|
mEdgeHintColorAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
|
||||||
|
mEdgeHintColorAnimator.addUpdateListener(mEdgeHintColorUpdateListener);
|
||||||
|
mEdgeHintColorAnimator.addListener(mEdgeHintPulseListener);
|
||||||
|
|
||||||
|
mEdgeHintAnimatorSet = new AnimatorSet();
|
||||||
|
mEdgeHintAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
|
||||||
|
mEdgeHintAnimatorSet.playTogether(mEdgeHintColorAnimator, mEdgeHintWidthAnimator);
|
||||||
|
mEdgeHintAnimatorSet.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTipHintVisible() {
|
||||||
|
return mTipHintPaint.getStrokeWidth() > 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEdgeHintVisible() {
|
||||||
|
return mEdgeHintPaint.getStrokeWidth() > 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -142,7 +408,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);
|
||||||
|
|
||||||
@@ -162,6 +428,59 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable {
|
|||||||
mFingerprintDrawable.setAlpha(mAlpha);
|
mFingerprintDrawable.setAlpha(mAlpha);
|
||||||
mSensorOutlinePaint.setAlpha(mAlpha);
|
mSensorOutlinePaint.setAlpha(mAlpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw the finger tip or edges hint.
|
||||||
|
if (isTipHintVisible() || isEdgeHintVisible()) {
|
||||||
|
canvas.save();
|
||||||
|
|
||||||
|
// Make arcs start from the top, rather than the right.
|
||||||
|
canvas.rotate(-90f, mSensorRect.centerX(), mSensorRect.centerY());
|
||||||
|
|
||||||
|
final float halfSensorHeight = Math.abs(mSensorRect.bottom - mSensorRect.top) / 2f;
|
||||||
|
final float halfSensorWidth = Math.abs(mSensorRect.right - mSensorRect.left) / 2f;
|
||||||
|
final float hintXOffset = halfSensorWidth + mHintPaddingPx;
|
||||||
|
final float hintYOffset = halfSensorHeight + mHintPaddingPx;
|
||||||
|
|
||||||
|
if (isTipHintVisible()) {
|
||||||
|
canvas.drawArc(
|
||||||
|
mSensorRect.centerX() - hintXOffset,
|
||||||
|
mSensorRect.centerY() - hintYOffset,
|
||||||
|
mSensorRect.centerX() + hintXOffset,
|
||||||
|
mSensorRect.centerY() + hintYOffset,
|
||||||
|
-HINT_ANGLE / 2f,
|
||||||
|
HINT_ANGLE,
|
||||||
|
false /* useCenter */,
|
||||||
|
mTipHintPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEdgeHintVisible()) {
|
||||||
|
// Draw right edge hint.
|
||||||
|
canvas.rotate(-90f, mSensorRect.centerX(), mSensorRect.centerY());
|
||||||
|
canvas.drawArc(
|
||||||
|
mSensorRect.centerX() - hintXOffset,
|
||||||
|
mSensorRect.centerY() - hintYOffset,
|
||||||
|
mSensorRect.centerX() + hintXOffset,
|
||||||
|
mSensorRect.centerY() + hintYOffset,
|
||||||
|
-HINT_ANGLE / 2f,
|
||||||
|
HINT_ANGLE,
|
||||||
|
false /* useCenter */,
|
||||||
|
mEdgeHintPaint);
|
||||||
|
|
||||||
|
// Draw left edge hint.
|
||||||
|
canvas.rotate(180f, mSensorRect.centerX(), mSensorRect.centerY());
|
||||||
|
canvas.drawArc(
|
||||||
|
mSensorRect.centerX() - hintXOffset,
|
||||||
|
mSensorRect.centerY() - hintYOffset,
|
||||||
|
mSensorRect.centerX() + hintXOffset,
|
||||||
|
mSensorRect.centerY() + hintYOffset,
|
||||||
|
-HINT_ANGLE / 2f,
|
||||||
|
HINT_ANGLE,
|
||||||
|
false /* useCenter */,
|
||||||
|
mEdgeHintPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.PointF;
|
import android.graphics.PointF;
|
||||||
import android.hardware.biometrics.BiometricOverlayConstants;
|
import android.hardware.biometrics.BiometricOverlayConstants;
|
||||||
|
import android.hardware.fingerprint.FingerprintManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
@@ -44,16 +45,14 @@ 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
|
|
||||||
private static final int NUM_CENTER_TOUCHES = 2;
|
|
||||||
|
|
||||||
interface Listener {
|
interface Listener {
|
||||||
void onEnrollmentProgress(int remaining, int totalSteps);
|
void onEnrollmentProgress(int remaining, int totalSteps);
|
||||||
|
void onEnrollmentHelp(int remaining, int totalSteps);
|
||||||
void onLastStepAcquired();
|
void onLastStepAcquired();
|
||||||
void onEnrollmentHelp();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull private final Context mContext;
|
@NonNull private final Context mContext;
|
||||||
|
@NonNull private final FingerprintManager mFingerprintManager;
|
||||||
// IUdfpsOverlayController reason
|
// IUdfpsOverlayController reason
|
||||||
private final int mEnrollReason;
|
private final int mEnrollReason;
|
||||||
private final boolean mAccessibilityEnabled;
|
private final boolean mAccessibilityEnabled;
|
||||||
@@ -66,10 +65,15 @@ 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,
|
||||||
|
@NonNull FingerprintManager fingerprintManager, int reason) {
|
||||||
|
|
||||||
mContext = context;
|
mContext = context;
|
||||||
|
mFingerprintManager = fingerprintManager;
|
||||||
mEnrollReason = reason;
|
mEnrollReason = reason;
|
||||||
|
|
||||||
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
|
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
|
||||||
@@ -118,6 +122,14 @@ public class UdfpsEnrollHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int getStageCount() {
|
||||||
|
return mFingerprintManager.getEnrollStageCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
int getStageThresholdSteps(int totalSteps, int stageIndex) {
|
||||||
|
return Math.round(totalSteps * mFingerprintManager.getEnrollStageThreshold(stageIndex));
|
||||||
|
}
|
||||||
|
|
||||||
boolean shouldShowProgressBar() {
|
boolean shouldShowProgressBar() {
|
||||||
return mEnrollReason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING;
|
return mEnrollReason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING;
|
||||||
}
|
}
|
||||||
@@ -129,6 +141,9 @@ public class UdfpsEnrollHelper {
|
|||||||
|
|
||||||
if (remaining != mRemainingSteps) {
|
if (remaining != mRemainingSteps) {
|
||||||
mLocationsEnrolled++;
|
mLocationsEnrolled++;
|
||||||
|
if (isCenterEnrollmentStage()) {
|
||||||
|
mCenterTouchCount++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mRemainingSteps = remaining;
|
mRemainingSteps = remaining;
|
||||||
@@ -140,7 +155,7 @@ public class UdfpsEnrollHelper {
|
|||||||
|
|
||||||
void onEnrollmentHelp() {
|
void onEnrollmentHelp() {
|
||||||
if (mListener != null) {
|
if (mListener != null) {
|
||||||
mListener.onEnrollmentHelp();
|
mListener.onEnrollmentHelp(mRemainingSteps, mTotalSteps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,19 +170,41 @@ 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 < getStageThresholdSteps(mTotalSteps, 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 >= getStageThresholdSteps(mTotalSteps, 0)
|
||||||
|
&& progressSteps < getStageThresholdSteps(mTotalSteps, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isTipEnrollmentStage() {
|
||||||
|
if (mTotalSteps == -1 || mRemainingSteps == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final int progressSteps = mTotalSteps - mRemainingSteps;
|
||||||
|
return progressSteps >= getStageThresholdSteps(mTotalSteps, 1)
|
||||||
|
&& progressSteps < getStageThresholdSteps(mTotalSteps, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isEdgeEnrollmentStage() {
|
||||||
|
if (mTotalSteps == -1 || mRemainingSteps == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return mTotalSteps - mRemainingSteps >= getStageThresholdSteps(mTotalSteps, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
PointF getNextGuidedEnrollmentPoint() {
|
PointF getNextGuidedEnrollmentPoint() {
|
||||||
if (mAccessibilityEnabled) {
|
if (mAccessibilityEnabled || !isGuidedEnrollmentStage()) {
|
||||||
return new PointF(0f, 0f);
|
return new PointF(0f, 0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +214,7 @@ 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);
|
||||||
|
|||||||
@@ -16,163 +16,129 @@
|
|||||||
|
|
||||||
package com.android.systemui.biometrics;
|
package com.android.systemui.biometrics;
|
||||||
|
|
||||||
import android.animation.ArgbEvaluator;
|
|
||||||
import android.animation.ValueAnimator;
|
|
||||||
import android.annotation.ColorInt;
|
|
||||||
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 Context mContext;
|
@NonNull private final Context mContext;
|
||||||
@NonNull private final Paint mBackgroundCirclePaint;
|
|
||||||
@NonNull private final Paint mProgressPaint;
|
|
||||||
|
|
||||||
@Nullable private ValueAnimator mProgressAnimator;
|
@Nullable private UdfpsEnrollHelper mEnrollHelper;
|
||||||
@Nullable private ValueAnimator mProgressShowingHelpAnimator;
|
@NonNull private List<UdfpsEnrollProgressBarSegment> mSegments = new ArrayList<>();
|
||||||
@Nullable private ValueAnimator mProgressHidingHelpAnimator;
|
private int mTotalSteps = 1;
|
||||||
@ColorInt private final int mProgressColor;
|
private int mProgressSteps = 0;
|
||||||
@ColorInt private final int mProgressHelpColor;
|
private boolean mIsShowingHelp = false;
|
||||||
private final int mShortAnimationDuration;
|
|
||||||
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;
|
mContext = context;
|
||||||
|
|
||||||
mShortAnimationDuration = context.getResources()
|
|
||||||
.getInteger(com.android.internal.R.integer.config_shortAnimTime);
|
|
||||||
mProgressColor = context.getColor(R.color.udfps_enroll_progress);
|
|
||||||
mProgressHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
|
|
||||||
|
|
||||||
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(mProgressColor);
|
|
||||||
mProgressPaint.setAntiAlias(true);
|
|
||||||
mProgressPaint.setStyle(Paint.Style.STROKE);
|
|
||||||
mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setEnrollmentProgress(int remaining, int totalSteps) {
|
void setEnrollHelper(@Nullable UdfpsEnrollHelper enrollHelper) {
|
||||||
// Add one so that the first steps actually changes progress, but also so that the last
|
mEnrollHelper = enrollHelper;
|
||||||
// step ends at 1.0
|
if (enrollHelper != null) {
|
||||||
final float progress = (totalSteps - remaining + 1) / (float) (totalSteps + 1);
|
final int stageCount = enrollHelper.getStageCount();
|
||||||
setEnrollmentProgress(progress);
|
mSegments = new ArrayList<>(stageCount);
|
||||||
}
|
float startAngle = SEGMENT_GAP_ANGLE / 2f;
|
||||||
|
final float sweepAngle = (360f / stageCount) - SEGMENT_GAP_ANGLE;
|
||||||
private void setEnrollmentProgress(float progress) {
|
final Runnable invalidateRunnable = this::invalidateSelf;
|
||||||
if (mLastStepAcquired) {
|
for (int index = 0; index < stageCount; index++) {
|
||||||
return;
|
mSegments.add(new UdfpsEnrollProgressBarSegment(mContext, getBounds(), startAngle,
|
||||||
}
|
sweepAngle, SEGMENT_GAP_ANGLE, invalidateRunnable));
|
||||||
|
startAngle += sweepAngle + SEGMENT_GAP_ANGLE;
|
||||||
long animationDuration = mShortAnimationDuration;
|
}
|
||||||
|
|
||||||
hideEnrollmentHelp();
|
|
||||||
|
|
||||||
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()) {
|
|
||||||
mProgressAnimator.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
mProgressAnimator = ValueAnimator.ofFloat(mProgress, progress);
|
|
||||||
mProgressAnimator.setDuration(animationDuration);
|
|
||||||
mProgressAnimator.addUpdateListener(animation -> {
|
|
||||||
mProgress = (float) animation.getAnimatedValue();
|
|
||||||
invalidateSelf();
|
invalidateSelf();
|
||||||
});
|
}
|
||||||
mProgressAnimator.start();
|
}
|
||||||
|
|
||||||
|
void onEnrollmentProgress(int remaining, int totalSteps) {
|
||||||
|
mTotalSteps = totalSteps;
|
||||||
|
updateState(getProgressSteps(remaining, totalSteps), false /* isShowingHelp */);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onEnrollmentHelp(int remaining, int totalSteps) {
|
||||||
|
updateState(getProgressSteps(remaining, totalSteps), true /* isShowingHelp */);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onLastStepAcquired() {
|
void onLastStepAcquired() {
|
||||||
setEnrollmentProgress(1.f);
|
updateState(mTotalSteps, false /* isShowingHelp */);
|
||||||
mLastStepAcquired = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onEnrollmentHelp() {
|
private static int getProgressSteps(int remaining, int totalSteps) {
|
||||||
if (mProgressShowingHelpAnimator != null || mProgressAnimator == null) {
|
// Show some progress for the initial touch.
|
||||||
return; // already showing or at 0% (no progress bar visible)
|
return Math.max(1, totalSteps - remaining);
|
||||||
}
|
|
||||||
|
|
||||||
if (mProgressHidingHelpAnimator != null && mProgressHidingHelpAnimator.isRunning()) {
|
|
||||||
mProgressHidingHelpAnimator.cancel();
|
|
||||||
}
|
|
||||||
mProgressHidingHelpAnimator = null;
|
|
||||||
|
|
||||||
mProgressShowingHelpAnimator = getProgressColorAnimator(
|
|
||||||
mProgressPaint.getColor(), mProgressHelpColor);
|
|
||||||
mProgressShowingHelpAnimator.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void hideEnrollmentHelp() {
|
private void updateState(int progressSteps, boolean isShowingHelp) {
|
||||||
if (mProgressHidingHelpAnimator != null || mProgressShowingHelpAnimator == null) {
|
updateProgress(progressSteps);
|
||||||
return; // already hidden or help never shown
|
updateFillColor(isShowingHelp);
|
||||||
}
|
|
||||||
|
|
||||||
if (mProgressShowingHelpAnimator != null && mProgressShowingHelpAnimator.isRunning()) {
|
|
||||||
mProgressShowingHelpAnimator.cancel();
|
|
||||||
}
|
|
||||||
mProgressShowingHelpAnimator = null;
|
|
||||||
|
|
||||||
mProgressHidingHelpAnimator = getProgressColorAnimator(
|
|
||||||
mProgressPaint.getColor(), mProgressColor);
|
|
||||||
mProgressHidingHelpAnimator.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ValueAnimator getProgressColorAnimator(@ColorInt int from, @ColorInt int to) {
|
private void updateProgress(int progressSteps) {
|
||||||
final ValueAnimator animator = ValueAnimator.ofObject(
|
if (mProgressSteps == progressSteps) {
|
||||||
ArgbEvaluator.getInstance(), from, to);
|
return;
|
||||||
animator.setDuration(mShortAnimationDuration);
|
}
|
||||||
animator.addUpdateListener(animation -> {
|
mProgressSteps = progressSteps;
|
||||||
mProgressPaint.setColor((int) animation.getAnimatedValue());
|
|
||||||
});
|
if (mEnrollHelper == null) {
|
||||||
return animator;
|
Log.e(TAG, "updateState: UDFPS enroll helper was null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
int prevThreshold = 0;
|
||||||
|
while (index < mSegments.size()) {
|
||||||
|
final UdfpsEnrollProgressBarSegment segment = mSegments.get(index);
|
||||||
|
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++;
|
||||||
|
prevThreshold = thresholdSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progressSteps >= mTotalSteps) {
|
||||||
|
for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
|
||||||
|
segment.startCompletionAnimation();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
|
||||||
|
segment.cancelCompletionAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateFillColor(boolean isShowingHelp) {
|
||||||
|
if (mIsShowingHelp == isShowingHelp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mIsShowingHelp = isShowingHelp;
|
||||||
|
|
||||||
|
for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
|
||||||
|
segment.updateFillColor(isShowingHelp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -180,43 +146,22 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable {
|
|||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setAlpha(int alpha) {
|
public void setAlpha(int alpha) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setColorFilter(@Nullable ColorFilter colorFilter) {
|
public void setColorFilter(@Nullable ColorFilter colorFilter) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,280 @@
|
|||||||
|
/*
|
||||||
|
* 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,23 +73,22 @@ public class UdfpsEnrollView extends UdfpsAnimationView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setEnrollHelper(UdfpsEnrollHelper enrollHelper) {
|
void setEnrollHelper(UdfpsEnrollHelper enrollHelper) {
|
||||||
|
mFingerprintProgressDrawable.setEnrollHelper(enrollHelper);
|
||||||
mFingerprintDrawable.setEnrollHelper(enrollHelper);
|
mFingerprintDrawable.setEnrollHelper(enrollHelper);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onEnrollmentProgress(int remaining, int totalSteps) {
|
void onEnrollmentProgress(int remaining, int totalSteps) {
|
||||||
mHandler.post(() -> {
|
mHandler.post(() -> {
|
||||||
mFingerprintProgressDrawable.setEnrollmentProgress(remaining, totalSteps);
|
mFingerprintProgressDrawable.onEnrollmentProgress(remaining, totalSteps);
|
||||||
mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps);
|
mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void onLastStepAcquired() {
|
void onEnrollmentHelp(int remaining, int totalSteps) {
|
||||||
mHandler.post(() -> {
|
mHandler.post(() -> mFingerprintProgressDrawable.onEnrollmentHelp(remaining, totalSteps));
|
||||||
mFingerprintProgressDrawable.onLastStepAcquired();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onEnrollmentHelp() {
|
void onLastStepAcquired() {
|
||||||
mHandler.post(mFingerprintProgressDrawable::onEnrollmentHelp);
|
mHandler.post(mFingerprintProgressDrawable::onLastStepAcquired);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,21 +35,21 @@ public class UdfpsEnrollViewController extends UdfpsAnimationViewController<Udfp
|
|||||||
@NonNull private final UdfpsEnrollHelper mEnrollHelper;
|
@NonNull private final UdfpsEnrollHelper mEnrollHelper;
|
||||||
@NonNull private final UdfpsEnrollHelper.Listener mEnrollHelperListener =
|
@NonNull private final UdfpsEnrollHelper.Listener mEnrollHelperListener =
|
||||||
new UdfpsEnrollHelper.Listener() {
|
new UdfpsEnrollHelper.Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void onEnrollmentProgress(int remaining, int totalSteps) {
|
public void onEnrollmentProgress(int remaining, int totalSteps) {
|
||||||
mView.onEnrollmentProgress(remaining, totalSteps);
|
mView.onEnrollmentProgress(remaining, totalSteps);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLastStepAcquired() {
|
public void onEnrollmentHelp(int remaining, int totalSteps) {
|
||||||
mView.onLastStepAcquired();
|
mView.onEnrollmentHelp(remaining, totalSteps);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnrollmentHelp() {
|
public void onLastStepAcquired() {
|
||||||
mView.onEnrollmentHelp();
|
mView.onLastStepAcquired();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
protected UdfpsEnrollViewController(
|
protected UdfpsEnrollViewController(
|
||||||
@NonNull UdfpsEnrollView view,
|
@NonNull UdfpsEnrollView view,
|
||||||
@@ -81,7 +81,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