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 com.android.systemui.R; 20 21import android.animation.Animator; 22import android.animation.AnimatorListenerAdapter; 23import android.animation.AnimatorSet; 24import android.animation.ValueAnimator; 25import android.content.Context; 26import android.content.res.Resources; 27import android.graphics.Canvas; 28import android.graphics.Color; 29import android.graphics.ColorFilter; 30import android.graphics.Paint; 31import android.graphics.PixelFormat; 32import android.graphics.Rect; 33import android.graphics.drawable.Drawable; 34import android.view.animation.AccelerateDecelerateInterpolator; 35import android.view.animation.AnimationUtils; 36import android.view.animation.Interpolator; 37 38public 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 invalidateSelf(); 137 } 138 } 139 140 public void stop() { 141 if (mAnimating) { 142 mAnimating = false; 143 if (mCurAnimator != null) { 144 mCurAnimator.cancel(); 145 mCurAnimator = null; 146 } 147 mState = STATE_UNSET; 148 mCurAlpha = 0; 149 mCurInnerRadius = mInnerRadiusEnter; 150 invalidateSelf(); 151 } 152 } 153 154 public void setTrustManaged(boolean trustManaged) { 155 if (trustManaged == mTrustManaged && mState != STATE_UNSET) return; 156 mTrustManaged = trustManaged; 157 updateState(true); 158 } 159 160 private void updateState(boolean allowTransientState) { 161 if (!mAnimating) { 162 return; 163 } 164 165 int nextState = mState; 166 if (mState == STATE_UNSET) { 167 nextState = mTrustManaged ? STATE_ENTERING : STATE_GONE; 168 } else if (mState == STATE_GONE) { 169 if (mTrustManaged) nextState = STATE_ENTERING; 170 } else if (mState == STATE_ENTERING) { 171 if (!mTrustManaged) nextState = STATE_EXITING; 172 } else if (mState == STATE_VISIBLE) { 173 if (!mTrustManaged) nextState = STATE_EXITING; 174 } else if (mState == STATE_EXITING) { 175 if (mTrustManaged) nextState = STATE_ENTERING; 176 } 177 if (!allowTransientState) { 178 if (nextState == STATE_ENTERING) nextState = STATE_VISIBLE; 179 if (nextState == STATE_EXITING) nextState = STATE_GONE; 180 } 181 182 if (nextState != mState) { 183 if (mCurAnimator != null) { 184 mCurAnimator.cancel(); 185 mCurAnimator = null; 186 } 187 188 if (nextState == STATE_GONE) { 189 mCurAlpha = 0; 190 mCurInnerRadius = mInnerRadiusEnter; 191 } else if (nextState == STATE_ENTERING) { 192 mCurAnimator = makeEnterAnimator(mCurInnerRadius, mCurAlpha); 193 if (mState == STATE_UNSET) { 194 mCurAnimator.setStartDelay(ENTERING_FROM_UNSET_START_DELAY); 195 } 196 } else if (nextState == STATE_VISIBLE) { 197 mCurAlpha = ALPHA_VISIBLE_MAX; 198 mCurInnerRadius = mInnerRadiusVisibleMax; 199 mCurAnimator = mVisibleAnimator; 200 } else if (nextState == STATE_EXITING) { 201 mCurAnimator = makeExitAnimator(mCurInnerRadius, mCurAlpha); 202 } 203 204 mState = nextState; 205 if (mCurAnimator != null) { 206 mCurAnimator.start(); 207 } 208 invalidateSelf(); 209 } 210 } 211 212 private Animator makeVisibleAnimator() { 213 return makeAnimators(mInnerRadiusVisibleMax, mInnerRadiusVisibleMin, 214 ALPHA_VISIBLE_MAX, ALPHA_VISIBLE_MIN, VISIBLE_DURATION, 215 mAccelerateDecelerateInterpolator, 216 true /* repeating */, false /* stateUpdateListener */); 217 } 218 219 private Animator makeEnterAnimator(float radius, int alpha) { 220 return makeAnimators(radius, mInnerRadiusVisibleMax, 221 alpha, ALPHA_VISIBLE_MAX, ENTER_DURATION, mLinearOutSlowInInterpolator, 222 false /* repeating */, true /* stateUpdateListener */); 223 } 224 225 private Animator makeExitAnimator(float radius, int alpha) { 226 return makeAnimators(radius, mInnerRadiusExit, 227 alpha, 0, EXIT_DURATION, mFastOutSlowInInterpolator, 228 false /* repeating */, true /* stateUpdateListener */); 229 } 230 231 private Animator makeAnimators(float startRadius, float endRadius, 232 int startAlpha, int endAlpha, long duration, Interpolator interpolator, 233 boolean repeating, boolean stateUpdateListener) { 234 ValueAnimator alphaAnimator = configureAnimator( 235 ValueAnimator.ofInt(startAlpha, endAlpha), 236 duration, mAlphaUpdateListener, interpolator, repeating); 237 ValueAnimator sizeAnimator = configureAnimator( 238 ValueAnimator.ofFloat(startRadius, endRadius), 239 duration, mRadiusUpdateListener, interpolator, repeating); 240 241 AnimatorSet set = new AnimatorSet(); 242 set.playTogether(alphaAnimator, sizeAnimator); 243 if (stateUpdateListener) { 244 set.addListener(new StateUpdateAnimatorListener()); 245 } 246 return set; 247 } 248 249 private ValueAnimator configureAnimator(ValueAnimator animator, long duration, 250 ValueAnimator.AnimatorUpdateListener updateListener, Interpolator interpolator, 251 boolean repeating) { 252 animator.setDuration(duration); 253 animator.addUpdateListener(updateListener); 254 animator.setInterpolator(interpolator); 255 if (repeating) { 256 animator.setRepeatCount(ValueAnimator.INFINITE); 257 animator.setRepeatMode(ValueAnimator.REVERSE); 258 } 259 return animator; 260 } 261 262 private final ValueAnimator.AnimatorUpdateListener mAlphaUpdateListener = 263 new ValueAnimator.AnimatorUpdateListener() { 264 @Override 265 public void onAnimationUpdate(ValueAnimator animation) { 266 mCurAlpha = (int) animation.getAnimatedValue(); 267 invalidateSelf(); 268 } 269 }; 270 271 private final ValueAnimator.AnimatorUpdateListener mRadiusUpdateListener = 272 new ValueAnimator.AnimatorUpdateListener() { 273 @Override 274 public void onAnimationUpdate(ValueAnimator animation) { 275 mCurInnerRadius = (float) animation.getAnimatedValue(); 276 invalidateSelf(); 277 } 278 }; 279 280 private class StateUpdateAnimatorListener extends AnimatorListenerAdapter { 281 boolean mCancelled; 282 283 @Override 284 public void onAnimationStart(Animator animation) { 285 mCancelled = false; 286 } 287 288 @Override 289 public void onAnimationCancel(Animator animation) { 290 mCancelled = true; 291 } 292 293 @Override 294 public void onAnimationEnd(Animator animation) { 295 if (!mCancelled) { 296 updateState(false); 297 } 298 } 299 } 300} 301