Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 63a9cc90 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Support configurable multi-stage UDFPS enrollment" into sc-v2-dev am: 8f8783ca

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15876070

Change-Id: I2a05114500ec35d26f02ddd0039ff47f0f76710f
parents 1e30da44 8f8783ca
Loading
Loading
Loading
Loading
+41 −0
Original line number Diff line number Diff line
@@ -146,6 +146,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
    private CryptoObject mCryptoObject;
    @Nullable private RemoveTracker mRemoveTracker;
    private Handler mHandler;
    @Nullable private float[] mEnrollStageThresholds;

    /**
     * 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
     */
+7 −0
Original line number Diff line number Diff line
@@ -4601,6 +4601,13 @@
    <!-- Indicates whether device has a power button fingerprint sensor. -->
    <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
         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 -->
+2 −1
Original line number Diff line number Diff line
@@ -2613,6 +2613,7 @@
  <java-symbol type="array" name="config_sfps_sensor_props" />
  <java-symbol type="integer" name="config_udfps_illumination_transition_ms" />
  <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_vendor_enroll_ignorelist" />
+1 −1
Original line number Diff line number Diff line
@@ -244,7 +244,7 @@ public class UdfpsController implements DozeReceiver {
                final UdfpsEnrollHelper enrollHelper;
                if (reason == BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
                        || reason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING) {
                    enrollHelper = new UdfpsEnrollHelper(mContext, reason);
                    enrollHelper = new UdfpsEnrollHelper(mContext, mFingerprintManager, reason);
                } else {
                    enrollHelper = null;
                }
+353 −34
Original line number Diff line number Diff line
@@ -16,9 +16,11 @@

package com.android.systemui.biometrics;

import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
@@ -26,11 +28,17 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
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.LinearInterpolator;

import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.internal.graphics.ColorUtils;
import com.android.systemui.R;

/**
@@ -39,10 +47,20 @@ import com.android.systemui.R;
public class UdfpsEnrollDrawable extends UdfpsDrawable {
    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
    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 Paint mSensorOutlinePaint;
    @NonNull private final Paint mBlueFill;
@@ -51,17 +69,41 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable {
    @Nullable private UdfpsEnrollHelper mEnrollHelper;

    // Moving target animator set
    @Nullable AnimatorSet mAnimatorSet;
    @Nullable AnimatorSet mTargetAnimatorSet;
    // Moving target location
    float mCurrentX;
    float mCurrentY;
    // Moving target size
    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) {
        super(context);


        mSensorOutlinePaint = new Paint(0 /* flags */);
        mSensorOutlinePaint.setAntiAlias(true);
        mSensorOutlinePaint.setColor(mContext.getColor(R.color.udfps_enroll_icon));
@@ -78,6 +120,117 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable {
        mMovingTargetFpIcon.mutate();

        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) {
@@ -98,13 +251,17 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable {
    }

    void onEnrollmentProgress(int remaining, int totalSteps) {
        if (mEnrollHelper.isCenterEnrollmentComplete()) {
            if (mAnimatorSet != null && mAnimatorSet.isRunning()) {
                mAnimatorSet.end();
        if (mEnrollHelper == null) {
            return;
        }

            final PointF point = mEnrollHelper.getNextGuidedEnrollmentPoint();
        if (!mEnrollHelper.isCenterEnrollmentStage()) {
            if (mTargetAnimatorSet != null && mTargetAnimatorSet.isRunning()) {
                mTargetAnimatorSet.end();
            }

            final PointF point = mEnrollHelper.getNextGuidedEnrollmentPoint();
            if (mCurrentX != point.x || mCurrentY != point.y) {
                final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x);
                x.addUpdateListener(animation -> {
                    mCurrentX = (float) animation.getAnimatedValue();
@@ -117,22 +274,131 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable {
                    invalidateSelf();
                });

                final boolean isMovingToCenter = point.x == 0f && point.y == 0f;
                final long duration = isMovingToCenter
                        ? TARGET_ANIM_DURATION_SHORT
                        : TARGET_ANIM_DURATION_LONG;

                final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI);
            scale.setDuration(ANIM_DURATION);
                scale.setDuration(duration);
                scale.addUpdateListener(animation -> {
                    // Grow then shrink
                mCurrentScale = 1 +
                        SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue());
                    mCurrentScale = 1
                            + SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue());
                    invalidateSelf();
                });

            mAnimatorSet = new AnimatorSet();
                mTargetAnimatorSet = new AnimatorSet();

                mTargetAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
                mTargetAnimatorSet.setDuration(duration);
                mTargetAnimatorSet.addListener(mTargetAnimListener);
                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);

            mAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
            mAnimatorSet.setDuration(ANIM_DURATION);
            mAnimatorSet.playTogether(x, y, scale);
            mAnimatorSet.start();
        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
@@ -142,7 +408,7 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable {
        }

        // Draw moving target
        if (mEnrollHelper.isCenterEnrollmentComplete()) {
        if (mEnrollHelper != null && !mEnrollHelper.isCenterEnrollmentStage()) {
            canvas.save();
            canvas.translate(mCurrentX, mCurrentY);

@@ -162,6 +428,59 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable {
            mFingerprintDrawable.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
Loading