Loading packages/SystemUI/res/drawable/trust_circle.xmldeleted 100644 → 0 +0 −22 Original line number Original line Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ 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 --> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="ring" android:innerRadius="22dp" android:thickness="2dp"> <solid android:color="#4cffffff" /> </shape> No newline at end of file packages/SystemUI/res/values/dimens.xml +12 −0 Original line number Original line Diff line number Diff line Loading @@ -430,4 +430,16 @@ <!-- The text size for battery level --> <!-- The text size for battery level --> <dimen name="battery_level_text_size">12sp</dimen> <dimen name="battery_level_text_size">12sp</dimen> <!-- TrustDrawable: Minimum inner radius of the breathing animation --> <dimen name="trust_circle_inner_radius_visible_min">22dp</dimen> <!-- TrustDrawable: Maximum inner radius of the breathing animation --> <dimen name="trust_circle_inner_radius_visible_max">24dp</dimen> <!-- TrustDrawable: Inner radius at the end of the exit animation --> <dimen name="trust_circle_inner_radius_exit">38dp</dimen> <!-- TrustDrawable: Inner radius at the beginning of the enter animation --> <dimen name="trust_circle_inner_radius_enter">18dp</dimen> <!-- TrustDrawable: Thickness of the circle --> <dimen name="trust_circle_thickness">2dp</dimen> </resources> </resources> packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +38 −6 Original line number Original line Diff line number Diff line Loading @@ -82,21 +82,24 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private KeyguardIndicationController mIndicationController; private KeyguardIndicationController mIndicationController; private boolean mFaceUnlockRunning; private boolean mFaceUnlockRunning; private final TrustDrawable mTrustDrawable; public KeyguardBottomAreaView(Context context) { public KeyguardBottomAreaView(Context context) { super(context); this(context, null); } } public KeyguardBottomAreaView(Context context, AttributeSet attrs) { public KeyguardBottomAreaView(Context context, AttributeSet attrs) { super(context, attrs); this(context, attrs, 0); } } public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr) { public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this(context, attrs, defStyleAttr, 0); } } public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr, public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); super(context, attrs, defStyleAttr, defStyleRes); mTrustDrawable = new TrustDrawable(mContext); } } @Override @Override Loading @@ -120,6 +123,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext)); mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext)); inflatePreviews(); inflatePreviews(); mLockIcon.setOnClickListener(this); mLockIcon.setOnClickListener(this); mLockIcon.setBackground(mTrustDrawable); } } @Override @Override Loading Loading @@ -267,14 +271,31 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL @Override @Override protected void onVisibilityChanged(View changedView, int visibility) { protected void onVisibilityChanged(View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); super.onVisibilityChanged(changedView, visibility); if (isShown()) { mTrustDrawable.start(); } else { mTrustDrawable.stop(); } if (changedView == this && visibility == VISIBLE) { if (changedView == this && visibility == VISIBLE) { updateLockIcon(); updateLockIcon(); updateCameraVisibility(); updateCameraVisibility(); } } } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mTrustDrawable.stop(); } private void updateLockIcon() { private void updateLockIcon() { if (getVisibility() != VISIBLE) { boolean visible = isShown() && KeyguardUpdateMonitor.getInstance(mContext).isScreenOn(); if (visible) { mTrustDrawable.start(); } else { mTrustDrawable.stop(); } if (!visible) { return; return; } } // TODO: Real icon for facelock. // TODO: Real icon for facelock. Loading @@ -283,11 +304,12 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL : R.drawable.ic_lock_24dp; : R.drawable.ic_lock_24dp; mLockIcon.setImageResource(iconRes); mLockIcon.setImageResource(iconRes); boolean trustManaged = mUnlockMethodCache.isTrustManaged(); boolean trustManaged = mUnlockMethodCache.isTrustManaged(); mLockIcon.setBackgroundResource(trustManaged && !mFaceUnlockRunning mTrustDrawable.setTrustManaged(trustManaged); ? R.drawable.trust_circle : 0); mLockIcon.setClickable(trustManaged); mLockIcon.setClickable(trustManaged); } } public KeyguardAffordanceView getPhoneView() { public KeyguardAffordanceView getPhoneView() { return mPhoneImageView; return mPhoneImageView; } } Loading Loading @@ -359,6 +381,16 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mFaceUnlockRunning = running; mFaceUnlockRunning = running; updateLockIcon(); updateLockIcon(); } } @Override public void onScreenTurnedOn() { updateLockIcon(); } @Override public void onScreenTurnedOff(int why) { updateLockIcon(); } }; }; public void setKeyguardIndicationController( public void setKeyguardIndicationController( Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/TrustDrawable.java 0 → 100644 +297 −0 Original line number Original line Diff line number Diff line /* * 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); } } public void stop() { if (mAnimating) { mAnimating = false; if (mCurAnimator != null) { mCurAnimator.cancel(); mCurAnimator = null; } mState = STATE_UNSET; mCurAlpha = 0; mCurInnerRadius = mInnerRadiusEnter; } } public void setTrustManaged(boolean trustManaged) { if (trustManaged == mTrustManaged && mState != STATE_UNSET) return; mTrustManaged = trustManaged; if (mAnimating) { updateState(true); } } private void updateState(boolean animate) { 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 (!animate) { 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(); } else { 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); } } } } Loading
packages/SystemUI/res/drawable/trust_circle.xmldeleted 100644 → 0 +0 −22 Original line number Original line Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ 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 --> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="ring" android:innerRadius="22dp" android:thickness="2dp"> <solid android:color="#4cffffff" /> </shape> No newline at end of file
packages/SystemUI/res/values/dimens.xml +12 −0 Original line number Original line Diff line number Diff line Loading @@ -430,4 +430,16 @@ <!-- The text size for battery level --> <!-- The text size for battery level --> <dimen name="battery_level_text_size">12sp</dimen> <dimen name="battery_level_text_size">12sp</dimen> <!-- TrustDrawable: Minimum inner radius of the breathing animation --> <dimen name="trust_circle_inner_radius_visible_min">22dp</dimen> <!-- TrustDrawable: Maximum inner radius of the breathing animation --> <dimen name="trust_circle_inner_radius_visible_max">24dp</dimen> <!-- TrustDrawable: Inner radius at the end of the exit animation --> <dimen name="trust_circle_inner_radius_exit">38dp</dimen> <!-- TrustDrawable: Inner radius at the beginning of the enter animation --> <dimen name="trust_circle_inner_radius_enter">18dp</dimen> <!-- TrustDrawable: Thickness of the circle --> <dimen name="trust_circle_thickness">2dp</dimen> </resources> </resources>
packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +38 −6 Original line number Original line Diff line number Diff line Loading @@ -82,21 +82,24 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private KeyguardIndicationController mIndicationController; private KeyguardIndicationController mIndicationController; private boolean mFaceUnlockRunning; private boolean mFaceUnlockRunning; private final TrustDrawable mTrustDrawable; public KeyguardBottomAreaView(Context context) { public KeyguardBottomAreaView(Context context) { super(context); this(context, null); } } public KeyguardBottomAreaView(Context context, AttributeSet attrs) { public KeyguardBottomAreaView(Context context, AttributeSet attrs) { super(context, attrs); this(context, attrs, 0); } } public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr) { public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this(context, attrs, defStyleAttr, 0); } } public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr, public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); super(context, attrs, defStyleAttr, defStyleRes); mTrustDrawable = new TrustDrawable(mContext); } } @Override @Override Loading @@ -120,6 +123,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext)); mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext)); inflatePreviews(); inflatePreviews(); mLockIcon.setOnClickListener(this); mLockIcon.setOnClickListener(this); mLockIcon.setBackground(mTrustDrawable); } } @Override @Override Loading Loading @@ -267,14 +271,31 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL @Override @Override protected void onVisibilityChanged(View changedView, int visibility) { protected void onVisibilityChanged(View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); super.onVisibilityChanged(changedView, visibility); if (isShown()) { mTrustDrawable.start(); } else { mTrustDrawable.stop(); } if (changedView == this && visibility == VISIBLE) { if (changedView == this && visibility == VISIBLE) { updateLockIcon(); updateLockIcon(); updateCameraVisibility(); updateCameraVisibility(); } } } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mTrustDrawable.stop(); } private void updateLockIcon() { private void updateLockIcon() { if (getVisibility() != VISIBLE) { boolean visible = isShown() && KeyguardUpdateMonitor.getInstance(mContext).isScreenOn(); if (visible) { mTrustDrawable.start(); } else { mTrustDrawable.stop(); } if (!visible) { return; return; } } // TODO: Real icon for facelock. // TODO: Real icon for facelock. Loading @@ -283,11 +304,12 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL : R.drawable.ic_lock_24dp; : R.drawable.ic_lock_24dp; mLockIcon.setImageResource(iconRes); mLockIcon.setImageResource(iconRes); boolean trustManaged = mUnlockMethodCache.isTrustManaged(); boolean trustManaged = mUnlockMethodCache.isTrustManaged(); mLockIcon.setBackgroundResource(trustManaged && !mFaceUnlockRunning mTrustDrawable.setTrustManaged(trustManaged); ? R.drawable.trust_circle : 0); mLockIcon.setClickable(trustManaged); mLockIcon.setClickable(trustManaged); } } public KeyguardAffordanceView getPhoneView() { public KeyguardAffordanceView getPhoneView() { return mPhoneImageView; return mPhoneImageView; } } Loading Loading @@ -359,6 +381,16 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mFaceUnlockRunning = running; mFaceUnlockRunning = running; updateLockIcon(); updateLockIcon(); } } @Override public void onScreenTurnedOn() { updateLockIcon(); } @Override public void onScreenTurnedOff(int why) { updateLockIcon(); } }; }; public void setKeyguardIndicationController( public void setKeyguardIndicationController( Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/TrustDrawable.java 0 → 100644 +297 −0 Original line number Original line Diff line number Diff line /* * 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); } } public void stop() { if (mAnimating) { mAnimating = false; if (mCurAnimator != null) { mCurAnimator.cancel(); mCurAnimator = null; } mState = STATE_UNSET; mCurAlpha = 0; mCurInnerRadius = mInnerRadiusEnter; } } public void setTrustManaged(boolean trustManaged) { if (trustManaged == mTrustManaged && mState != STATE_UNSET) return; mTrustManaged = trustManaged; if (mAnimating) { updateState(true); } } private void updateState(boolean animate) { 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 (!animate) { 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(); } else { 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); } } } }