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