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

Commit e1d53178 authored by Curtis Belmonte's avatar Curtis Belmonte Committed by Android (Google) Code Review
Browse files

Merge "Combine UDFPS enroll segments into a single circle" into sc-v2-dev

parents d0e86ae5 67d8eec6
Loading
Loading
Loading
Loading
+35 −0
Original line number Diff line number Diff line
<?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>
+206 −73
Original line number Diff line number Diff line
@@ -16,17 +16,22 @@

package com.android.systemui.biometrics;

import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.util.TypedValue;
import android.view.animation.Interpolator;
import android.view.animation.OvershootInterpolator;

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

import java.util.ArrayList;
import java.util.List;
import com.android.systemui.R;

/**
 * UDFPS enrollment progress bar.
@@ -34,108 +39,193 @@ import java.util.List;
public class UdfpsEnrollProgressBarDrawable extends Drawable {
    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;
    @NonNull private List<UdfpsEnrollProgressBarSegment> mSegments = new ArrayList<>();
    private boolean mAfterFirstTouch;

    private int mRemainingSteps = 0;
    private int mTotalSteps = 0;
    private int mProgressSteps = 0;
    private boolean mIsShowingHelp = false;
    private float mProgress = 0f;
    @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) {
        mContext = context;
    }

    void setEnrollHelper(@Nullable UdfpsEnrollHelper enrollHelper) {
        mEnrollHelper = enrollHelper;
        if (enrollHelper != null) {
            final int stageCount = enrollHelper.getStageCount();
            mSegments = new ArrayList<>(stageCount);
            float startAngle = SEGMENT_GAP_ANGLE / 2f;
            final float sweepAngle = (360f / stageCount) - SEGMENT_GAP_ANGLE;
            final Runnable invalidateRunnable = this::invalidateSelf;
            for (int index = 0; index < stageCount; index++) {
                mSegments.add(new UdfpsEnrollProgressBarSegment(mContext, getBounds(), startAngle,
                        sweepAngle, SEGMENT_GAP_ANGLE, invalidateRunnable));
                startAngle += sweepAngle + SEGMENT_GAP_ANGLE;
        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();

        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);

        // Set background paint color and alpha.
        final int[] attrs = new int[] {android.R.attr.colorControlNormal};
        final TypedArray typedArray = context.obtainStyledAttributes(attrs);
        try {
            @ColorInt final int tintColor = typedArray.getColor(0, mBackgroundPaint.getColor());
            mBackgroundPaint.setColor(tintColor);
        } 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) {
        mTotalSteps = totalSteps;

        // Show some progress for the initial touch.
        updateState(Math.max(1, totalSteps - remaining), false /* isShowingHelp */);
        mAfterFirstTouch = true;
        updateState(remaining, totalSteps, false /* showingHelp */);
    }

    void onEnrollmentHelp(int remaining, int totalSteps) {
        updateState(Math.max(0, totalSteps - remaining), true /* isShowingHelp */);
        updateState(remaining, totalSteps, true /* showingHelp */);
    }

    void onLastStepAcquired() {
        updateState(mTotalSteps, false /* isShowingHelp */);
        updateState(0, mTotalSteps, false /* showingHelp */);
    }

    private void updateState(int progressSteps, boolean isShowingHelp) {
        updateProgress(progressSteps);
        updateFillColor(isShowingHelp);
    private void updateState(int remainingSteps, int totalSteps, boolean showingHelp) {
        updateProgress(remainingSteps, totalSteps);
        updateFillColor(showingHelp);
    }

    private void updateProgress(int progressSteps) {
        if (mProgressSteps == progressSteps) {
    private void updateProgress(int remainingSteps, int totalSteps) {
        if (mRemainingSteps == remainingSteps && mTotalSteps == totalSteps) {
            return;
        }
        mProgressSteps = progressSteps;
        mRemainingSteps = remainingSteps;
        mTotalSteps = totalSteps;

        if (mEnrollHelper == null) {
            Log.e(TAG, "updateState: UDFPS enroll helper was null");
            return;
        final int progressSteps = Math.max(0, totalSteps - remainingSteps);

        // 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;
        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;
        mProgressAnimator = ValueAnimator.ofFloat(mProgress, targetProgress);
        mProgressAnimator.setDuration(PROGRESS_ANIMATION_DURATION_MS);
        mProgressAnimator.addUpdateListener(mProgressUpdateListener);
        mProgressAnimator.start();

        if (remainingSteps == 0) {
            startCompletionAnimation();
        } else if (remainingSteps > 0) {
            rollBackCompletionAnimation();
        }
    }

            index++;
            prevThreshold = thresholdSteps;
    private void updateFillColor(boolean showingHelp) {
        if (mShowingHelp == showingHelp) {
            return;
        }
        mShowingHelp = showingHelp;

        if (progressSteps >= mTotalSteps) {
            for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
                segment.startCompletionAnimation();
        if (mFillColorAnimator != null && mFillColorAnimator.isRunning()) {
            mFillColorAnimator.cancel();
        }
        } else {
            for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
                segment.cancelCompletionAnimation();

        @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 updateFillColor(boolean isShowingHelp) {
        if (mIsShowingHelp == isShowingHelp) {
    private void rollBackCompletionAnimation() {
        if (!mComplete) {
            return;
        }
        mIsShowingHelp = isShowingHelp;
        mComplete = false;

        for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
            segment.updateFillColor(isShowingHelp);
        // 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
@@ -145,12 +235,55 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable {
        // Progress starts from the top, instead of the right
        canvas.rotate(-90f, getBounds().centerX(), getBounds().centerY());

        // Draw each of the enroll segments.
        for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
            segment.draw(canvas);
        final float halfPaddingPx = mStrokeWidthPx / 2f;

        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();

        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
+0 −280

File deleted.

Preview size limit exceeded, changes collapsed.

+0 −1
Original line number Diff line number Diff line
@@ -73,7 +73,6 @@ public class UdfpsEnrollView extends UdfpsAnimationView {
    }

    void setEnrollHelper(UdfpsEnrollHelper enrollHelper) {
        mFingerprintProgressDrawable.setEnrollHelper(enrollHelper);
        mFingerprintDrawable.setEnrollHelper(enrollHelper);
    }