1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.biometrics; 18 19 import android.animation.ValueAnimator; 20 import android.content.Context; 21 import android.content.res.TypedArray; 22 import android.graphics.Canvas; 23 import android.graphics.ColorFilter; 24 import android.graphics.Paint; 25 import android.graphics.drawable.Drawable; 26 import android.util.Log; 27 import android.util.TypedValue; 28 29 import androidx.annotation.NonNull; 30 import androidx.annotation.Nullable; 31 32 import com.android.systemui.R; 33 34 /** 35 * UDFPS enrollment progress bar. 36 */ 37 public class UdfpsEnrollProgressBarDrawable extends Drawable { 38 39 private static final String TAG = "UdfpsEnrollProgressBarDrawable"; 40 41 private static final float PROGRESS_BAR_THICKNESS_DP = 12; 42 43 @NonNull private final Context mContext; 44 @NonNull private final UdfpsEnrollDrawable mParent; 45 @NonNull private final Paint mBackgroundCirclePaint; 46 @NonNull private final Paint mProgressPaint; 47 48 @Nullable private ValueAnimator mProgressAnimator; 49 private float mProgress; 50 private int mRotation; // After last step, rotate the progress bar once 51 private boolean mLastStepAcquired; 52 UdfpsEnrollProgressBarDrawable(@onNull Context context, @NonNull UdfpsEnrollDrawable parent)53 public UdfpsEnrollProgressBarDrawable(@NonNull Context context, 54 @NonNull UdfpsEnrollDrawable parent) { 55 mContext = context; 56 mParent = parent; 57 58 mBackgroundCirclePaint = new Paint(); 59 mBackgroundCirclePaint.setStrokeWidth(Utils.dpToPixels(context, PROGRESS_BAR_THICKNESS_DP)); 60 mBackgroundCirclePaint.setColor(context.getColor(R.color.white_disabled)); 61 mBackgroundCirclePaint.setAntiAlias(true); 62 mBackgroundCirclePaint.setStyle(Paint.Style.STROKE); 63 64 // Background circle color + alpha 65 TypedArray tc = context.obtainStyledAttributes( 66 new int[] {android.R.attr.colorControlNormal}); 67 int tintColor = tc.getColor(0, mBackgroundCirclePaint.getColor()); 68 mBackgroundCirclePaint.setColor(tintColor); 69 tc.recycle(); 70 TypedValue alpha = new TypedValue(); 71 context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true); 72 mBackgroundCirclePaint.setAlpha((int) (alpha.getFloat() * 255)); 73 74 // Progress should not be color extracted 75 mProgressPaint = new Paint(); 76 mProgressPaint.setStrokeWidth(Utils.dpToPixels(context, PROGRESS_BAR_THICKNESS_DP)); 77 mProgressPaint.setColor(context.getColor(R.color.udfps_enroll_progress)); 78 mProgressPaint.setAntiAlias(true); 79 mProgressPaint.setStyle(Paint.Style.STROKE); 80 mProgressPaint.setStrokeCap(Paint.Cap.ROUND); 81 } 82 setEnrollmentProgress(int remaining, int totalSteps)83 void setEnrollmentProgress(int remaining, int totalSteps) { 84 // Add one so that the first steps actually changes progress, but also so that the last 85 // step ends at 1.0 86 final float progress = (totalSteps - remaining + 1) / (float) (totalSteps + 1); 87 setEnrollmentProgress(progress); 88 } 89 setEnrollmentProgress(float progress)90 private void setEnrollmentProgress(float progress) { 91 if (mLastStepAcquired) { 92 return; 93 } 94 95 long animationDuration = 150; 96 97 if (progress == 1.f) { 98 animationDuration = 400; 99 final ValueAnimator rotationAnimator = ValueAnimator.ofInt(0, 400); 100 rotationAnimator.setDuration(animationDuration); 101 rotationAnimator.addUpdateListener(animation -> { 102 Log.d(TAG, "Rotation: " + mRotation); 103 mRotation = (int) animation.getAnimatedValue(); 104 mParent.invalidateSelf(); 105 }); 106 rotationAnimator.start(); 107 } 108 109 if (mProgressAnimator != null && mProgressAnimator.isRunning()) { 110 mProgressAnimator.cancel(); 111 } 112 113 mProgressAnimator = ValueAnimator.ofFloat(mProgress, progress); 114 mProgressAnimator.setDuration(animationDuration); 115 mProgressAnimator.addUpdateListener(animation -> { 116 mProgress = (float) animation.getAnimatedValue(); 117 // Use the parent to invalidate, since it's the one that's attached as the view's 118 // drawable and has its callback set automatically. Invalidating via 119 // `this.invalidateSelf` actually does not invoke draw(), since this drawable's callback 120 // is not really set. 121 mParent.invalidateSelf(); 122 }); 123 mProgressAnimator.start(); 124 } 125 onLastStepAcquired()126 void onLastStepAcquired() { 127 setEnrollmentProgress(1.f); 128 mLastStepAcquired = true; 129 } 130 131 @Override draw(@onNull Canvas canvas)132 public void draw(@NonNull Canvas canvas) { 133 canvas.save(); 134 135 // Progress starts from the top, instead of the right 136 canvas.rotate(-90 + mRotation, getBounds().centerX(), getBounds().centerY()); 137 138 // Progress bar "background track" 139 final float halfPaddingPx = Utils.dpToPixels(mContext, PROGRESS_BAR_THICKNESS_DP) / 2; 140 canvas.drawArc(halfPaddingPx, 141 halfPaddingPx, 142 getBounds().right - halfPaddingPx, 143 getBounds().bottom - halfPaddingPx, 144 0, 145 360, 146 false, 147 mBackgroundCirclePaint 148 ); 149 150 final float progress = 360.f * mProgress; 151 // Progress 152 canvas.drawArc(halfPaddingPx, 153 halfPaddingPx, 154 getBounds().right - halfPaddingPx, 155 getBounds().bottom - halfPaddingPx, 156 0, 157 progress, 158 false, 159 mProgressPaint 160 ); 161 162 canvas.restore(); 163 } 164 165 @Override setAlpha(int alpha)166 public void setAlpha(int alpha) { 167 168 } 169 170 @Override setColorFilter(@ullable ColorFilter colorFilter)171 public void setColorFilter(@Nullable ColorFilter colorFilter) { 172 173 } 174 175 @Override getOpacity()176 public int getOpacity() { 177 return 0; 178 } 179 } 180