1 /*
2  * Copyright (C) 2014 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.statusbar.phone;
18 
19 import com.android.systemui.R;
20 
21 import android.animation.Animator;
22 import android.animation.AnimatorListenerAdapter;
23 import android.animation.AnimatorSet;
24 import android.animation.ValueAnimator;
25 import android.content.Context;
26 import android.content.res.Resources;
27 import android.graphics.Canvas;
28 import android.graphics.Color;
29 import android.graphics.ColorFilter;
30 import android.graphics.Paint;
31 import android.graphics.PixelFormat;
32 import android.graphics.Rect;
33 import android.graphics.drawable.Drawable;
34 import android.view.animation.AccelerateDecelerateInterpolator;
35 import android.view.animation.AnimationUtils;
36 import android.view.animation.Interpolator;
37 
38 public class TrustDrawable extends Drawable {
39 
40     private static final long ENTERING_FROM_UNSET_START_DELAY = 200;
41     private static final long VISIBLE_DURATION = 1000;
42     private static final long EXIT_DURATION = 500;
43     private static final long ENTER_DURATION = 500;
44 
45     private static final int ALPHA_VISIBLE_MIN = 0x26;
46     private static final int ALPHA_VISIBLE_MAX = 0x4c;
47 
48     private static final int STATE_UNSET = -1;
49     private static final int STATE_GONE = 0;
50     private static final int STATE_ENTERING = 1;
51     private static final int STATE_VISIBLE = 2;
52     private static final int STATE_EXITING = 3;
53 
54     private int mAlpha;
55     private boolean mAnimating;
56 
57     private int mCurAlpha;
58     private float mCurInnerRadius;
59     private Animator mCurAnimator;
60     private int mState = STATE_UNSET;
61     private Paint mPaint;
62     private boolean mTrustManaged;
63 
64     private final float mInnerRadiusVisibleMin;
65     private final float mInnerRadiusVisibleMax;
66     private final float mInnerRadiusExit;
67     private final float mInnerRadiusEnter;
68     private final float mThickness;
69 
70     private final Animator mVisibleAnimator;
71 
72     private final Interpolator mLinearOutSlowInInterpolator;
73     private final Interpolator mFastOutSlowInInterpolator;
74     private final Interpolator mAccelerateDecelerateInterpolator;
75 
TrustDrawable(Context context)76     public TrustDrawable(Context context) {
77         Resources r = context.getResources();
78         mInnerRadiusVisibleMin = r.getDimension(R.dimen.trust_circle_inner_radius_visible_min);
79         mInnerRadiusVisibleMax = r.getDimension(R.dimen.trust_circle_inner_radius_visible_max);
80         mInnerRadiusExit = r.getDimension(R.dimen.trust_circle_inner_radius_exit);
81         mInnerRadiusEnter = r.getDimension(R.dimen.trust_circle_inner_radius_enter);
82         mThickness = r.getDimension(R.dimen.trust_circle_thickness);
83 
84         mCurInnerRadius = mInnerRadiusEnter;
85 
86         mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
87                 context, android.R.interpolator.linear_out_slow_in);
88         mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(
89                 context, android.R.interpolator.fast_out_slow_in);
90         mAccelerateDecelerateInterpolator = new AccelerateDecelerateInterpolator();
91 
92         mVisibleAnimator = makeVisibleAnimator();
93 
94         mPaint = new Paint();
95         mPaint.setStyle(Paint.Style.STROKE);
96         mPaint.setColor(Color.WHITE);
97         mPaint.setAntiAlias(true);
98         mPaint.setStrokeWidth(mThickness);
99     }
100 
101     @Override
draw(Canvas canvas)102     public void draw(Canvas canvas) {
103         int newAlpha = (mCurAlpha * mAlpha) / 256;
104         if (newAlpha == 0) {
105             return;
106         }
107         final Rect r = getBounds();
108         mPaint.setAlpha(newAlpha);
109         canvas.drawCircle(r.exactCenterX(), r.exactCenterY(), mCurInnerRadius, mPaint);
110     }
111 
112     @Override
setAlpha(int alpha)113     public void setAlpha(int alpha) {
114         mAlpha = alpha;
115     }
116 
117     @Override
getAlpha()118     public int getAlpha() {
119         return mAlpha;
120     }
121 
122     @Override
setColorFilter(ColorFilter colorFilter)123     public void setColorFilter(ColorFilter colorFilter) {
124         throw new UnsupportedOperationException("not implemented");
125     }
126 
127     @Override
getOpacity()128     public int getOpacity() {
129         return PixelFormat.TRANSLUCENT;
130     }
131 
start()132     public void start() {
133         if (!mAnimating) {
134             mAnimating = true;
135             updateState(true);
136             invalidateSelf();
137         }
138     }
139 
stop()140     public void stop() {
141         if (mAnimating) {
142             mAnimating = false;
143             if (mCurAnimator != null) {
144                 mCurAnimator.cancel();
145                 mCurAnimator = null;
146             }
147             mState = STATE_UNSET;
148             mCurAlpha = 0;
149             mCurInnerRadius = mInnerRadiusEnter;
150             invalidateSelf();
151         }
152     }
153 
setTrustManaged(boolean trustManaged)154     public void setTrustManaged(boolean trustManaged) {
155         if (trustManaged == mTrustManaged && mState != STATE_UNSET) return;
156         mTrustManaged = trustManaged;
157         updateState(true);
158     }
159 
updateState(boolean allowTransientState)160     private void updateState(boolean allowTransientState) {
161         if (!mAnimating) {
162             return;
163         }
164 
165         int nextState = mState;
166         if (mState == STATE_UNSET) {
167             nextState = mTrustManaged ? STATE_ENTERING : STATE_GONE;
168         } else if (mState == STATE_GONE) {
169             if (mTrustManaged) nextState = STATE_ENTERING;
170         } else if (mState == STATE_ENTERING) {
171             if (!mTrustManaged) nextState = STATE_EXITING;
172         } else if (mState == STATE_VISIBLE) {
173             if (!mTrustManaged) nextState = STATE_EXITING;
174         } else if (mState == STATE_EXITING) {
175             if (mTrustManaged) nextState = STATE_ENTERING;
176         }
177         if (!allowTransientState) {
178             if (nextState == STATE_ENTERING) nextState = STATE_VISIBLE;
179             if (nextState == STATE_EXITING) nextState = STATE_GONE;
180         }
181 
182         if (nextState != mState) {
183             if (mCurAnimator != null) {
184                 mCurAnimator.cancel();
185                 mCurAnimator = null;
186             }
187 
188             if (nextState == STATE_GONE) {
189                 mCurAlpha = 0;
190                 mCurInnerRadius = mInnerRadiusEnter;
191             } else if (nextState == STATE_ENTERING) {
192                 mCurAnimator = makeEnterAnimator(mCurInnerRadius, mCurAlpha);
193                 if (mState == STATE_UNSET) {
194                     mCurAnimator.setStartDelay(ENTERING_FROM_UNSET_START_DELAY);
195                 }
196             } else if (nextState == STATE_VISIBLE) {
197                 mCurAlpha = ALPHA_VISIBLE_MAX;
198                 mCurInnerRadius = mInnerRadiusVisibleMax;
199                 mCurAnimator = mVisibleAnimator;
200             } else if (nextState == STATE_EXITING) {
201                 mCurAnimator = makeExitAnimator(mCurInnerRadius, mCurAlpha);
202             }
203 
204             mState = nextState;
205             if (mCurAnimator != null) {
206                 mCurAnimator.start();
207             }
208             invalidateSelf();
209         }
210     }
211 
makeVisibleAnimator()212     private Animator makeVisibleAnimator() {
213         return makeAnimators(mInnerRadiusVisibleMax, mInnerRadiusVisibleMin,
214                 ALPHA_VISIBLE_MAX, ALPHA_VISIBLE_MIN, VISIBLE_DURATION,
215                 mAccelerateDecelerateInterpolator,
216                 true /* repeating */, false /* stateUpdateListener */);
217     }
218 
makeEnterAnimator(float radius, int alpha)219     private Animator makeEnterAnimator(float radius, int alpha) {
220         return makeAnimators(radius, mInnerRadiusVisibleMax,
221                 alpha, ALPHA_VISIBLE_MAX, ENTER_DURATION, mLinearOutSlowInInterpolator,
222                 false /* repeating */, true /* stateUpdateListener */);
223     }
224 
makeExitAnimator(float radius, int alpha)225     private Animator makeExitAnimator(float radius, int alpha) {
226         return makeAnimators(radius, mInnerRadiusExit,
227                 alpha, 0, EXIT_DURATION, mFastOutSlowInInterpolator,
228                 false /* repeating */, true /* stateUpdateListener */);
229     }
230 
makeAnimators(float startRadius, float endRadius, int startAlpha, int endAlpha, long duration, Interpolator interpolator, boolean repeating, boolean stateUpdateListener)231     private Animator makeAnimators(float startRadius, float endRadius,
232             int startAlpha, int endAlpha, long duration, Interpolator interpolator,
233             boolean repeating, boolean stateUpdateListener) {
234         ValueAnimator alphaAnimator = configureAnimator(
235                 ValueAnimator.ofInt(startAlpha, endAlpha),
236                 duration, mAlphaUpdateListener, interpolator, repeating);
237         ValueAnimator sizeAnimator = configureAnimator(
238                 ValueAnimator.ofFloat(startRadius, endRadius),
239                 duration, mRadiusUpdateListener, interpolator, repeating);
240 
241         AnimatorSet set = new AnimatorSet();
242         set.playTogether(alphaAnimator, sizeAnimator);
243         if (stateUpdateListener) {
244             set.addListener(new StateUpdateAnimatorListener());
245         }
246         return set;
247     }
248 
configureAnimator(ValueAnimator animator, long duration, ValueAnimator.AnimatorUpdateListener updateListener, Interpolator interpolator, boolean repeating)249     private ValueAnimator configureAnimator(ValueAnimator animator, long duration,
250             ValueAnimator.AnimatorUpdateListener updateListener, Interpolator interpolator,
251             boolean repeating) {
252         animator.setDuration(duration);
253         animator.addUpdateListener(updateListener);
254         animator.setInterpolator(interpolator);
255         if (repeating) {
256             animator.setRepeatCount(ValueAnimator.INFINITE);
257             animator.setRepeatMode(ValueAnimator.REVERSE);
258         }
259         return animator;
260     }
261 
262     private final ValueAnimator.AnimatorUpdateListener mAlphaUpdateListener =
263             new ValueAnimator.AnimatorUpdateListener() {
264         @Override
265         public void onAnimationUpdate(ValueAnimator animation) {
266             mCurAlpha = (int) animation.getAnimatedValue();
267             invalidateSelf();
268         }
269     };
270 
271     private final ValueAnimator.AnimatorUpdateListener mRadiusUpdateListener =
272             new ValueAnimator.AnimatorUpdateListener() {
273         @Override
274         public void onAnimationUpdate(ValueAnimator animation) {
275             mCurInnerRadius = (float) animation.getAnimatedValue();
276             invalidateSelf();
277         }
278     };
279 
280     private class StateUpdateAnimatorListener extends AnimatorListenerAdapter {
281         boolean mCancelled;
282 
283         @Override
onAnimationStart(Animator animation)284         public void onAnimationStart(Animator animation) {
285             mCancelled = false;
286         }
287 
288         @Override
onAnimationCancel(Animator animation)289         public void onAnimationCancel(Animator animation) {
290             mCancelled = true;
291         }
292 
293         @Override
onAnimationEnd(Animator animation)294         public void onAnimationEnd(Animator animation) {
295             if (!mCancelled) {
296                 updateState(false);
297             }
298         }
299     }
300 }
301