1 /*
2  * Copyright (C) 2015 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.settings.biometrics.fingerprint;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ValueAnimator;
22 import android.annotation.ColorInt;
23 import android.content.Context;
24 import android.graphics.Canvas;
25 import android.graphics.Paint;
26 import android.util.AttributeSet;
27 import android.view.View;
28 import android.view.animation.AnimationUtils;
29 import android.view.animation.Interpolator;
30 
31 import androidx.annotation.Nullable;
32 
33 import com.android.settings.R;
34 import com.android.settings.Utils;
35 
36 /**
37  * View which plays an animation to indicate where the sensor is on the device.
38  */
39 public class FingerprintLocationAnimationView extends View implements
40         FingerprintFindSensorAnimation {
41 
42     private static final float MAX_PULSE_ALPHA = 0.15f;
43     private static final long DELAY_BETWEEN_PHASE = 1000;
44 
45     private final Interpolator mLinearOutSlowInInterpolator;
46     private final Interpolator mFastOutSlowInInterpolator;
47 
48     private final int mDotRadius;
49     private final int mMaxPulseRadius;
50     private final float mFractionCenterX;
51     private final float mFractionCenterY;
52     private final Paint mDotPaint = new Paint();
53     private final Paint mPulsePaint = new Paint();
54     private float mPulseRadius;
55     private ValueAnimator mRadiusAnimator;
56     private ValueAnimator mAlphaAnimator;
57 
FingerprintLocationAnimationView(Context context, @Nullable AttributeSet attrs)58     public FingerprintLocationAnimationView(Context context, @Nullable AttributeSet attrs) {
59         super(context, attrs);
60         mDotRadius = getResources().getDimensionPixelSize(R.dimen.fingerprint_dot_radius);
61         mMaxPulseRadius = getResources().getDimensionPixelSize(R.dimen.fingerprint_pulse_radius);
62         mFractionCenterX = getResources().getFraction(
63                 R.fraction.fingerprint_sensor_location_fraction_x, 1, 1);
64         mFractionCenterY = getResources().getFraction(
65                 R.fraction.fingerprint_sensor_location_fraction_y, 1, 1);
66         @ColorInt int colorAccent = Utils.getColorAccentDefaultColor(context);
67         mDotPaint.setAntiAlias(true);
68         mPulsePaint.setAntiAlias(true);
69         mDotPaint.setColor(colorAccent);
70         mPulsePaint.setColor(colorAccent);
71         mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
72                 android.R.interpolator.linear_out_slow_in);
73         mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
74                 android.R.interpolator.linear_out_slow_in);
75     }
76 
77     @Override
onDraw(Canvas canvas)78     protected void onDraw(Canvas canvas) {
79         drawPulse(canvas);
80         drawDot(canvas);
81     }
82 
drawDot(Canvas canvas)83     private void drawDot(Canvas canvas) {
84         canvas.drawCircle(getCenterX(), getCenterY(), mDotRadius, mDotPaint);
85     }
86 
drawPulse(Canvas canvas)87     private void drawPulse(Canvas canvas) {
88         canvas.drawCircle(getCenterX(), getCenterY(), mPulseRadius, mPulsePaint);
89     }
90 
getCenterX()91     private float getCenterX() {
92         return getWidth() * mFractionCenterX;
93     }
94 
getCenterY()95     private float getCenterY() {
96         return getHeight() * mFractionCenterY;
97     }
98 
99     @Override
startAnimation()100     public void startAnimation() {
101         startPhase();
102     }
103 
104     @Override
stopAnimation()105     public void stopAnimation() {
106         removeCallbacks(mStartPhaseRunnable);
107         if (mRadiusAnimator != null) {
108             mRadiusAnimator.cancel();
109         }
110         if (mAlphaAnimator != null) {
111             mAlphaAnimator.cancel();
112         }
113     }
114 
115     @Override
pauseAnimation()116     public void pauseAnimation() {
117         stopAnimation();
118     }
119 
startPhase()120     private void startPhase() {
121         startRadiusAnimation();
122         startAlphaAnimation();
123     }
124 
startRadiusAnimation()125     private void startRadiusAnimation() {
126         ValueAnimator animator = ValueAnimator.ofFloat(0, mMaxPulseRadius);
127         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
128             @Override
129             public void onAnimationUpdate(ValueAnimator animation) {
130                 mPulseRadius = (float) animation.getAnimatedValue();
131                 invalidate();
132             }
133         });
134         animator.addListener(new AnimatorListenerAdapter() {
135 
136             boolean mCancelled;
137 
138             @Override
139             public void onAnimationCancel(Animator animation) {
140                 mCancelled = true;
141             }
142 
143             @Override
144             public void onAnimationEnd(Animator animation) {
145                 mRadiusAnimator = null;
146                 if (!mCancelled) {
147                     postDelayed(mStartPhaseRunnable, DELAY_BETWEEN_PHASE);
148                 }
149             }
150         });
151         animator.setDuration(1000);
152         animator.setInterpolator(mLinearOutSlowInInterpolator);
153         animator.start();
154         mRadiusAnimator = animator;
155     }
156 
startAlphaAnimation()157     private void startAlphaAnimation() {
158         mPulsePaint.setAlpha((int) (255f * MAX_PULSE_ALPHA));
159         ValueAnimator animator = ValueAnimator.ofFloat(MAX_PULSE_ALPHA, 0f);
160         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
161             @Override
162             public void onAnimationUpdate(ValueAnimator animation) {
163                 mPulsePaint.setAlpha((int) (255f * (float) animation.getAnimatedValue()));
164                 invalidate();
165             }
166         });
167         animator.addListener(new AnimatorListenerAdapter() {
168             @Override
169             public void onAnimationEnd(Animator animation) {
170                 mAlphaAnimator = null;
171             }
172         });
173         animator.setDuration(750);
174         animator.setInterpolator(mFastOutSlowInInterpolator);
175         animator.setStartDelay(250);
176         animator.start();
177         mAlphaAnimator = animator;
178     }
179 
180     private final Runnable mStartPhaseRunnable = new Runnable() {
181         @Override
182         public void run() {
183             startPhase();
184         }
185     };
186 }
187