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
17package com.android.systemui.statusbar.phone;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.AnimatorSet;
22import android.animation.ValueAnimator;
23import android.content.Context;
24import android.content.res.Resources;
25import android.graphics.Canvas;
26import android.graphics.Color;
27import android.graphics.ColorFilter;
28import android.graphics.Paint;
29import android.graphics.PixelFormat;
30import android.graphics.Rect;
31import android.graphics.drawable.Drawable;
32import android.view.animation.Interpolator;
33
34import com.android.systemui.Interpolators;
35import com.android.systemui.R;
36
37public 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
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
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
102    public void setAlpha(int alpha) {
103        mAlpha = alpha;
104    }
105
106    @Override
107    public int getAlpha() {
108        return mAlpha;
109    }
110
111    @Override
112    public void setColorFilter(ColorFilter colorFilter) {
113        throw new UnsupportedOperationException("not implemented");
114    }
115
116    @Override
117    public int getOpacity() {
118        return PixelFormat.TRANSLUCENT;
119    }
120
121    public void start() {
122        if (!mAnimating) {
123            mAnimating = true;
124            updateState(true);
125            invalidateSelf();
126        }
127    }
128
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
143    public void setTrustManaged(boolean trustManaged) {
144        if (trustManaged == mTrustManaged && mState != STATE_UNSET) return;
145        mTrustManaged = trustManaged;
146        updateState(true);
147    }
148
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
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
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
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
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
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
273        public void onAnimationStart(Animator animation) {
274            mCancelled = false;
275        }
276
277        @Override
278        public void onAnimationCancel(Animator animation) {
279            mCancelled = true;
280        }
281
282        @Override
283        public void onAnimationEnd(Animator animation) {
284            if (!mCancelled) {
285                updateState(false);
286            }
287        }
288    }
289}
290