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