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 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 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 113 public void setAlpha(int alpha) { 114 mAlpha = alpha; 115 } 116 117 @Override 118 public int getAlpha() { 119 return mAlpha; 120 } 121 122 @Override 123 public void setColorFilter(ColorFilter cf) { 124 throw new UnsupportedOperationException("not implemented"); 125 } 126 127 @Override 128 public int getOpacity() { 129 return PixelFormat.TRANSLUCENT; 130 } 131 132 public void start() { 133 if (!mAnimating) { 134 mAnimating = true; 135 updateState(true); 136 } 137 } 138 139 public void stop() { 140 if (mAnimating) { 141 mAnimating = false; 142 if (mCurAnimator != null) { 143 mCurAnimator.cancel(); 144 mCurAnimator = null; 145 } 146 mState = STATE_UNSET; 147 mCurAlpha = 0; 148 mCurInnerRadius = mInnerRadiusEnter; 149 } 150 } 151 152 public void setTrustManaged(boolean trustManaged) { 153 if (trustManaged == mTrustManaged && mState != STATE_UNSET) return; 154 mTrustManaged = trustManaged; 155 if (mAnimating) { 156 updateState(true); 157 } 158 } 159 160 private void updateState(boolean animate) { 161 int nextState = mState; 162 if (mState == STATE_UNSET) { 163 nextState = mTrustManaged ? STATE_ENTERING : STATE_GONE; 164 } else if (mState == STATE_GONE) { 165 if (mTrustManaged) nextState = STATE_ENTERING; 166 } else if (mState == STATE_ENTERING) { 167 if (!mTrustManaged) nextState = STATE_EXITING; 168 } else if (mState == STATE_VISIBLE) { 169 if (!mTrustManaged) nextState = STATE_EXITING; 170 } else if (mState == STATE_EXITING) { 171 if (mTrustManaged) nextState = STATE_ENTERING; 172 } 173 if (!animate) { 174 if (nextState == STATE_ENTERING) nextState = STATE_VISIBLE; 175 if (nextState == STATE_EXITING) nextState = STATE_GONE; 176 } 177 178 if (nextState != mState) { 179 if (mCurAnimator != null) { 180 mCurAnimator.cancel(); 181 mCurAnimator = null; 182 } 183 184 if (nextState == STATE_GONE) { 185 mCurAlpha = 0; 186 mCurInnerRadius = mInnerRadiusEnter; 187 } else if (nextState == STATE_ENTERING) { 188 mCurAnimator = makeEnterAnimator(mCurInnerRadius, mCurAlpha); 189 if (mState == STATE_UNSET) { 190 mCurAnimator.setStartDelay(ENTERING_FROM_UNSET_START_DELAY); 191 } 192 } else if (nextState == STATE_VISIBLE) { 193 mCurAlpha = ALPHA_VISIBLE_MAX; 194 mCurInnerRadius = mInnerRadiusVisibleMax; 195 mCurAnimator = mVisibleAnimator; 196 } else if (nextState == STATE_EXITING) { 197 mCurAnimator = makeExitAnimator(mCurInnerRadius, mCurAlpha); 198 } 199 200 mState = nextState; 201 if (mCurAnimator != null) { 202 mCurAnimator.start(); 203 } else { 204 invalidateSelf(); 205 } 206 } 207 } 208 209 private Animator makeVisibleAnimator() { 210 return makeAnimators(mInnerRadiusVisibleMax, mInnerRadiusVisibleMin, 211 ALPHA_VISIBLE_MAX, ALPHA_VISIBLE_MIN, VISIBLE_DURATION, 212 mAccelerateDecelerateInterpolator, 213 true /* repeating */, false /* stateUpdateListener */); 214 } 215 216 private Animator makeEnterAnimator(float radius, int alpha) { 217 return makeAnimators(radius, mInnerRadiusVisibleMax, 218 alpha, ALPHA_VISIBLE_MAX, ENTER_DURATION, mLinearOutSlowInInterpolator, 219 false /* repeating */, true /* stateUpdateListener */); 220 } 221 222 private Animator makeExitAnimator(float radius, int alpha) { 223 return makeAnimators(radius, mInnerRadiusExit, 224 alpha, 0, EXIT_DURATION, mFastOutSlowInInterpolator, 225 false /* repeating */, true /* stateUpdateListener */); 226 } 227 228 private Animator makeAnimators(float startRadius, float endRadius, 229 int startAlpha, int endAlpha, long duration, Interpolator interpolator, 230 boolean repeating, boolean stateUpdateListener) { 231 ValueAnimator alphaAnimator = configureAnimator( 232 ValueAnimator.ofInt(startAlpha, endAlpha), 233 duration, mAlphaUpdateListener, interpolator, repeating); 234 ValueAnimator sizeAnimator = configureAnimator( 235 ValueAnimator.ofFloat(startRadius, endRadius), 236 duration, mRadiusUpdateListener, interpolator, repeating); 237 238 AnimatorSet set = new AnimatorSet(); 239 set.playTogether(alphaAnimator, sizeAnimator); 240 if (stateUpdateListener) { 241 set.addListener(new StateUpdateAnimatorListener()); 242 } 243 return set; 244 } 245 246 private ValueAnimator configureAnimator(ValueAnimator animator, long duration, 247 ValueAnimator.AnimatorUpdateListener updateListener, Interpolator interpolator, 248 boolean repeating) { 249 animator.setDuration(duration); 250 animator.addUpdateListener(updateListener); 251 animator.setInterpolator(interpolator); 252 if (repeating) { 253 animator.setRepeatCount(ValueAnimator.INFINITE); 254 animator.setRepeatMode(ValueAnimator.REVERSE); 255 } 256 return animator; 257 } 258 259 private final ValueAnimator.AnimatorUpdateListener mAlphaUpdateListener = 260 new ValueAnimator.AnimatorUpdateListener() { 261 @Override 262 public void onAnimationUpdate(ValueAnimator animation) { 263 mCurAlpha = (int) animation.getAnimatedValue(); 264 invalidateSelf(); 265 } 266 }; 267 268 private final ValueAnimator.AnimatorUpdateListener mRadiusUpdateListener = 269 new ValueAnimator.AnimatorUpdateListener() { 270 @Override 271 public void onAnimationUpdate(ValueAnimator animation) { 272 mCurInnerRadius = (float) animation.getAnimatedValue(); 273 invalidateSelf(); 274 } 275 }; 276 277 private class StateUpdateAnimatorListener extends AnimatorListenerAdapter { 278 boolean mCancelled; 279 280 @Override 281 public void onAnimationStart(Animator animation) { 282 mCancelled = false; 283 } 284 285 @Override 286 public void onAnimationCancel(Animator animation) { 287 mCancelled = true; 288 } 289 290 @Override 291 public void onAnimationEnd(Animator animation) { 292 if (!mCancelled) { 293 updateState(false); 294 } 295 } 296 } 297 } 298