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