• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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