blob: b89aa8fa936b495902f79116907637b7a47d0560 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.systemui.statusbar.phone;
import com.android.systemui.R;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
public class TrustDrawable extends Drawable {
private static final long ENTERING_FROM_UNSET_START_DELAY = 200;
private static final long VISIBLE_DURATION = 1000;
private static final long EXIT_DURATION = 500;
private static final long ENTER_DURATION = 500;
private static final int ALPHA_VISIBLE_MIN = 0x26;
private static final int ALPHA_VISIBLE_MAX = 0x4c;
private static final int STATE_UNSET = -1;
private static final int STATE_GONE = 0;
private static final int STATE_ENTERING = 1;
private static final int STATE_VISIBLE = 2;
private static final int STATE_EXITING = 3;
private int mAlpha;
private boolean mAnimating;
private int mCurAlpha;
private float mCurInnerRadius;
private Animator mCurAnimator;
private int mState = STATE_UNSET;
private Paint mPaint;
private boolean mTrustManaged;
private final float mInnerRadiusVisibleMin;
private final float mInnerRadiusVisibleMax;
private final float mInnerRadiusExit;
private final float mInnerRadiusEnter;
private final float mThickness;
private final Animator mVisibleAnimator;
private final Interpolator mLinearOutSlowInInterpolator;
private final Interpolator mFastOutSlowInInterpolator;
private final Interpolator mAccelerateDecelerateInterpolator;
public TrustDrawable(Context context) {
Resources r = context.getResources();
mInnerRadiusVisibleMin = r.getDimension(R.dimen.trust_circle_inner_radius_visible_min);
mInnerRadiusVisibleMax = r.getDimension(R.dimen.trust_circle_inner_radius_visible_max);
mInnerRadiusExit = r.getDimension(R.dimen.trust_circle_inner_radius_exit);
mInnerRadiusEnter = r.getDimension(R.dimen.trust_circle_inner_radius_enter);
mThickness = r.getDimension(R.dimen.trust_circle_thickness);
mCurInnerRadius = mInnerRadiusEnter;
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
context, android.R.interpolator.linear_out_slow_in);
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(
context, android.R.interpolator.fast_out_slow_in);
mAccelerateDecelerateInterpolator = new AccelerateDecelerateInterpolator();
mVisibleAnimator = makeVisibleAnimator();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(mThickness);
}
@Override
public void draw(Canvas canvas) {
int newAlpha = (mCurAlpha * mAlpha) / 256;
if (newAlpha == 0) {
return;
}
final Rect r = getBounds();
mPaint.setAlpha(newAlpha);
canvas.drawCircle(r.exactCenterX(), r.exactCenterY(), mCurInnerRadius, mPaint);
}
@Override
public void setAlpha(int alpha) {
mAlpha = alpha;
}
@Override
public int getAlpha() {
return mAlpha;
}
@Override
public void setColorFilter(ColorFilter cf) {
throw new UnsupportedOperationException("not implemented");
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
public void start() {
if (!mAnimating) {
mAnimating = true;
updateState(true);
invalidateSelf();
}
}
public void stop() {
if (mAnimating) {
mAnimating = false;
if (mCurAnimator != null) {
mCurAnimator.cancel();
mCurAnimator = null;
}
mState = STATE_UNSET;
mCurAlpha = 0;
mCurInnerRadius = mInnerRadiusEnter;
invalidateSelf();
}
}
public void setTrustManaged(boolean trustManaged) {
if (trustManaged == mTrustManaged && mState != STATE_UNSET) return;
mTrustManaged = trustManaged;
updateState(true);
}
private void updateState(boolean allowTransientState) {
if (!mAnimating) {
return;
}
int nextState = mState;
if (mState == STATE_UNSET) {
nextState = mTrustManaged ? STATE_ENTERING : STATE_GONE;
} else if (mState == STATE_GONE) {
if (mTrustManaged) nextState = STATE_ENTERING;
} else if (mState == STATE_ENTERING) {
if (!mTrustManaged) nextState = STATE_EXITING;
} else if (mState == STATE_VISIBLE) {
if (!mTrustManaged) nextState = STATE_EXITING;
} else if (mState == STATE_EXITING) {
if (mTrustManaged) nextState = STATE_ENTERING;
}
if (!allowTransientState) {
if (nextState == STATE_ENTERING) nextState = STATE_VISIBLE;
if (nextState == STATE_EXITING) nextState = STATE_GONE;
}
if (nextState != mState) {
if (mCurAnimator != null) {
mCurAnimator.cancel();
mCurAnimator = null;
}
if (nextState == STATE_GONE) {
mCurAlpha = 0;
mCurInnerRadius = mInnerRadiusEnter;
} else if (nextState == STATE_ENTERING) {
mCurAnimator = makeEnterAnimator(mCurInnerRadius, mCurAlpha);
if (mState == STATE_UNSET) {
mCurAnimator.setStartDelay(ENTERING_FROM_UNSET_START_DELAY);
}
} else if (nextState == STATE_VISIBLE) {
mCurAlpha = ALPHA_VISIBLE_MAX;
mCurInnerRadius = mInnerRadiusVisibleMax;
mCurAnimator = mVisibleAnimator;
} else if (nextState == STATE_EXITING) {
mCurAnimator = makeExitAnimator(mCurInnerRadius, mCurAlpha);
}
mState = nextState;
if (mCurAnimator != null) {
mCurAnimator.start();
}
invalidateSelf();
}
}
private Animator makeVisibleAnimator() {
return makeAnimators(mInnerRadiusVisibleMax, mInnerRadiusVisibleMin,
ALPHA_VISIBLE_MAX, ALPHA_VISIBLE_MIN, VISIBLE_DURATION,
mAccelerateDecelerateInterpolator,
true /* repeating */, false /* stateUpdateListener */);
}
private Animator makeEnterAnimator(float radius, int alpha) {
return makeAnimators(radius, mInnerRadiusVisibleMax,
alpha, ALPHA_VISIBLE_MAX, ENTER_DURATION, mLinearOutSlowInInterpolator,
false /* repeating */, true /* stateUpdateListener */);
}
private Animator makeExitAnimator(float radius, int alpha) {
return makeAnimators(radius, mInnerRadiusExit,
alpha, 0, EXIT_DURATION, mFastOutSlowInInterpolator,
false /* repeating */, true /* stateUpdateListener */);
}
private Animator makeAnimators(float startRadius, float endRadius,
int startAlpha, int endAlpha, long duration, Interpolator interpolator,
boolean repeating, boolean stateUpdateListener) {
ValueAnimator alphaAnimator = configureAnimator(
ValueAnimator.ofInt(startAlpha, endAlpha),
duration, mAlphaUpdateListener, interpolator, repeating);
ValueAnimator sizeAnimator = configureAnimator(
ValueAnimator.ofFloat(startRadius, endRadius),
duration, mRadiusUpdateListener, interpolator, repeating);
AnimatorSet set = new AnimatorSet();
set.playTogether(alphaAnimator, sizeAnimator);
if (stateUpdateListener) {
set.addListener(new StateUpdateAnimatorListener());
}
return set;
}
private ValueAnimator configureAnimator(ValueAnimator animator, long duration,
ValueAnimator.AnimatorUpdateListener updateListener, Interpolator interpolator,
boolean repeating) {
animator.setDuration(duration);
animator.addUpdateListener(updateListener);
animator.setInterpolator(interpolator);
if (repeating) {
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.REVERSE);
}
return animator;
}
private final ValueAnimator.AnimatorUpdateListener mAlphaUpdateListener =
new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurAlpha = (int) animation.getAnimatedValue();
invalidateSelf();
}
};
private final ValueAnimator.AnimatorUpdateListener mRadiusUpdateListener =
new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurInnerRadius = (float) animation.getAnimatedValue();
invalidateSelf();
}
};
private class StateUpdateAnimatorListener extends AnimatorListenerAdapter {
boolean mCancelled;
@Override
public void onAnimationStart(Animator animation) {
mCancelled = false;
}
@Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;
}
@Override
public void onAnimationEnd(Animator animation) {
if (!mCancelled) {
updateState(false);
}
}
}
}