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.Animator; 20 import android.animation.AnimatorSet; 21 import android.animation.ValueAnimator; 22 import android.content.Context; 23 import android.content.res.TypedArray; 24 import android.graphics.Canvas; 25 import android.graphics.Paint; 26 import android.graphics.PointF; 27 import android.graphics.Rect; 28 import android.graphics.RectF; 29 import android.graphics.drawable.Drawable; 30 import android.os.Handler; 31 import android.os.Looper; 32 import android.util.AttributeSet; 33 import android.view.animation.AccelerateDecelerateInterpolator; 34 35 import androidx.annotation.NonNull; 36 import androidx.annotation.Nullable; 37 38 import com.android.systemui.R; 39 40 /** 41 * UDFPS fingerprint drawable that is shown when enrolling 42 */ 43 public class UdfpsEnrollDrawable extends UdfpsDrawable { 44 private static final String TAG = "UdfpsAnimationEnroll"; 45 46 private static final long TARGET_ANIM_DURATION_LONG = 800L; 47 private static final long TARGET_ANIM_DURATION_SHORT = 600L; 48 // 1 + SCALE_MAX is the maximum that the moving target will animate to 49 private static final float SCALE_MAX = 0.25f; 50 51 private final Handler mHandler = new Handler(Looper.getMainLooper()); 52 53 @NonNull private final Drawable mMovingTargetFpIcon; 54 @NonNull private final Paint mSensorOutlinePaint; 55 @NonNull private final Paint mBlueFill; 56 57 @Nullable private RectF mSensorRect; 58 @Nullable private UdfpsEnrollHelper mEnrollHelper; 59 60 // Moving target animator set 61 @Nullable AnimatorSet mTargetAnimatorSet; 62 // Moving target location 63 float mCurrentX; 64 float mCurrentY; 65 // Moving target size 66 float mCurrentScale = 1.f; 67 68 @NonNull private final Animator.AnimatorListener mTargetAnimListener; 69 70 private boolean mShouldShowTipHint = false; 71 private boolean mShouldShowEdgeHint = false; 72 73 private int mEnrollIcon; 74 private int mMovingTargetFill; 75 UdfpsEnrollDrawable(@onNull Context context, @Nullable AttributeSet attrs)76 UdfpsEnrollDrawable(@NonNull Context context, @Nullable AttributeSet attrs) { 77 super(context); 78 79 loadResources(context, attrs); 80 mSensorOutlinePaint = new Paint(0 /* flags */); 81 mSensorOutlinePaint.setAntiAlias(true); 82 mSensorOutlinePaint.setColor(mMovingTargetFill); 83 mSensorOutlinePaint.setStyle(Paint.Style.FILL); 84 85 mBlueFill = new Paint(0 /* flags */); 86 mBlueFill.setAntiAlias(true); 87 mBlueFill.setColor(mMovingTargetFill); 88 mBlueFill.setStyle(Paint.Style.FILL); 89 90 mMovingTargetFpIcon = context.getResources() 91 .getDrawable(R.drawable.ic_kg_fingerprint, null); 92 mMovingTargetFpIcon.setTint(mEnrollIcon); 93 mMovingTargetFpIcon.mutate(); 94 95 getFingerprintDrawable().setTint(mEnrollIcon); 96 97 mTargetAnimListener = new Animator.AnimatorListener() { 98 @Override 99 public void onAnimationStart(Animator animation) {} 100 101 @Override 102 public void onAnimationEnd(Animator animation) { 103 updateTipHintVisibility(); 104 } 105 106 @Override 107 public void onAnimationCancel(Animator animation) {} 108 109 @Override 110 public void onAnimationRepeat(Animator animation) {} 111 }; 112 } 113 loadResources(Context context, @Nullable AttributeSet attrs)114 void loadResources(Context context, @Nullable AttributeSet attrs) { 115 final TypedArray ta = context.obtainStyledAttributes(attrs, 116 R.styleable.BiometricsEnrollView, R.attr.biometricsEnrollStyle, 117 R.style.BiometricsEnrollStyle); 118 mEnrollIcon = ta.getColor(R.styleable.BiometricsEnrollView_biometricsEnrollIcon, 0); 119 mMovingTargetFill = ta.getColor( 120 R.styleable.BiometricsEnrollView_biometricsMovingTargetFill, 0); 121 ta.recycle(); 122 } 123 setEnrollHelper(@onNull UdfpsEnrollHelper helper)124 void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) { 125 mEnrollHelper = helper; 126 } 127 128 @Override onSensorRectUpdated(@onNull RectF sensorRect)129 public void onSensorRectUpdated(@NonNull RectF sensorRect) { 130 super.onSensorRectUpdated(sensorRect); 131 mSensorRect = sensorRect; 132 } 133 134 @Override updateFingerprintIconBounds(@onNull Rect bounds)135 protected void updateFingerprintIconBounds(@NonNull Rect bounds) { 136 super.updateFingerprintIconBounds(bounds); 137 mMovingTargetFpIcon.setBounds(bounds); 138 invalidateSelf(); 139 } 140 onEnrollmentProgress(int remaining, int totalSteps)141 void onEnrollmentProgress(int remaining, int totalSteps) { 142 if (mEnrollHelper == null) { 143 return; 144 } 145 146 if (!mEnrollHelper.isCenterEnrollmentStage()) { 147 if (mTargetAnimatorSet != null && mTargetAnimatorSet.isRunning()) { 148 mTargetAnimatorSet.end(); 149 } 150 151 final PointF point = mEnrollHelper.getNextGuidedEnrollmentPoint(); 152 if (mCurrentX != point.x || mCurrentY != point.y) { 153 final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x); 154 x.addUpdateListener(animation -> { 155 mCurrentX = (float) animation.getAnimatedValue(); 156 invalidateSelf(); 157 }); 158 159 final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y); 160 y.addUpdateListener(animation -> { 161 mCurrentY = (float) animation.getAnimatedValue(); 162 invalidateSelf(); 163 }); 164 165 final boolean isMovingToCenter = point.x == 0f && point.y == 0f; 166 final long duration = isMovingToCenter 167 ? TARGET_ANIM_DURATION_SHORT 168 : TARGET_ANIM_DURATION_LONG; 169 170 final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI); 171 scale.setDuration(duration); 172 scale.addUpdateListener(animation -> { 173 // Grow then shrink 174 mCurrentScale = 1 175 + SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue()); 176 invalidateSelf(); 177 }); 178 179 mTargetAnimatorSet = new AnimatorSet(); 180 181 mTargetAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); 182 mTargetAnimatorSet.setDuration(duration); 183 mTargetAnimatorSet.addListener(mTargetAnimListener); 184 mTargetAnimatorSet.playTogether(x, y, scale); 185 mTargetAnimatorSet.start(); 186 } else { 187 updateTipHintVisibility(); 188 } 189 } else { 190 updateTipHintVisibility(); 191 } 192 193 updateEdgeHintVisibility(); 194 } 195 updateTipHintVisibility()196 private void updateTipHintVisibility() { 197 final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isTipEnrollmentStage(); 198 // With the new update, we will git rid of most of this code, and instead 199 // we will change the fingerprint icon. 200 if (mShouldShowTipHint == shouldShow) { 201 return; 202 } 203 mShouldShowTipHint = shouldShow; 204 } 205 updateEdgeHintVisibility()206 private void updateEdgeHintVisibility() { 207 final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isEdgeEnrollmentStage(); 208 if (mShouldShowEdgeHint == shouldShow) { 209 return; 210 } 211 mShouldShowEdgeHint = shouldShow; 212 } 213 214 @Override draw(@onNull Canvas canvas)215 public void draw(@NonNull Canvas canvas) { 216 if (isDisplayConfigured()) { 217 return; 218 } 219 220 // Draw moving target 221 if (mEnrollHelper != null && !mEnrollHelper.isCenterEnrollmentStage()) { 222 canvas.save(); 223 canvas.translate(mCurrentX, mCurrentY); 224 225 if (mSensorRect != null) { 226 canvas.scale(mCurrentScale, mCurrentScale, 227 mSensorRect.centerX(), mSensorRect.centerY()); 228 canvas.drawOval(mSensorRect, mBlueFill); 229 } 230 231 mMovingTargetFpIcon.draw(canvas); 232 canvas.restore(); 233 } else { 234 if (mSensorRect != null) { 235 canvas.drawOval(mSensorRect, mSensorOutlinePaint); 236 } 237 getFingerprintDrawable().draw(canvas); 238 getFingerprintDrawable().setAlpha(getAlpha()); 239 mSensorOutlinePaint.setAlpha(getAlpha()); 240 } 241 242 } 243 244 @Override setAlpha(int alpha)245 public void setAlpha(int alpha) { 246 super.setAlpha(alpha); 247 mSensorOutlinePaint.setAlpha(alpha); 248 mBlueFill.setAlpha(alpha); 249 mMovingTargetFpIcon.setAlpha(alpha); 250 invalidateSelf(); 251 } 252 } 253