Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2e3ccbb3 authored by Adrian Roos's avatar Adrian Roos
Browse files

Implement trust managed visuals

Updates the trust managed indicator to match the
speced visuals and behavior.

Bug: 15519687
Change-Id: I2be04e8b37f3b5d9865ae4d25b46222cafadd113
parent 03861d07
Loading
Loading
Loading
Loading
+0 −22
Original line number 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
+12 −0
Original line number Diff line number Diff line
@@ -430,4 +430,16 @@

    <!-- The text size for battery level -->
    <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>
+38 −6
Original line number Diff line number Diff line
@@ -82,21 +82,24 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
    private KeyguardIndicationController mIndicationController;
    private boolean mFaceUnlockRunning;

    private final TrustDrawable mTrustDrawable;

    public KeyguardBottomAreaView(Context context) {
        super(context);
        this(context, null);
    }

    public KeyguardBottomAreaView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this(context, attrs, 0);
    }

    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,
            int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        mTrustDrawable = new TrustDrawable(mContext);
    }

    @Override
@@ -120,6 +123,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
        mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext));
        inflatePreviews();
        mLockIcon.setOnClickListener(this);
        mLockIcon.setBackground(mTrustDrawable);
    }

    @Override
@@ -267,14 +271,31 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
    @Override
    protected void onVisibilityChanged(View changedView, int visibility) {
        super.onVisibilityChanged(changedView, visibility);
        if (isShown()) {
            mTrustDrawable.start();
        } else {
            mTrustDrawable.stop();
        }
        if (changedView == this && visibility == VISIBLE) {
            updateLockIcon();
            updateCameraVisibility();
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mTrustDrawable.stop();
    }

    private void updateLockIcon() {
        if (getVisibility() != VISIBLE) {
        boolean visible = isShown() && KeyguardUpdateMonitor.getInstance(mContext).isScreenOn();
        if (visible) {
            mTrustDrawable.start();
        } else {
            mTrustDrawable.stop();
        }
        if (!visible) {
            return;
        }
        // TODO: Real icon for facelock.
@@ -283,11 +304,12 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
                : R.drawable.ic_lock_24dp;
        mLockIcon.setImageResource(iconRes);
        boolean trustManaged = mUnlockMethodCache.isTrustManaged();
        mLockIcon.setBackgroundResource(trustManaged && !mFaceUnlockRunning
                ? R.drawable.trust_circle : 0);
        mTrustDrawable.setTrustManaged(trustManaged);
        mLockIcon.setClickable(trustManaged);
    }



    public KeyguardAffordanceView getPhoneView() {
        return mPhoneImageView;
    }
@@ -359,6 +381,16 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
            mFaceUnlockRunning = running;
            updateLockIcon();
        }

        @Override
        public void onScreenTurnedOn() {
            updateLockIcon();
        }

        @Override
        public void onScreenTurnedOff(int why) {
            updateLockIcon();
        }
    };

    public void setKeyguardIndicationController(
+297 −0
Original line number 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);
            }
        }
    }
}