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

Commit 2728fa3c authored by Dave Mankoff's avatar Dave Mankoff Committed by Android (Google) Code Review
Browse files

Merge changes from topic "b150393918-lock-icon" into rvc-dev

* changes:
  Cache icon drawables.
  Remove injection from LockIcon.
parents 11523705 c4698283
Loading
Loading
Loading
Loading
+96 −354
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@

package com.android.systemui.statusbar.phone;

import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;

import android.annotation.IntDef;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -30,241 +28,98 @@ import android.os.Trace;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityNodeInfo;
import android.util.SparseArray;
import android.view.ViewTreeObserver.OnPreDrawListener;

import com.android.internal.graphics.ColorUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.KeyguardAffordanceView;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.phone.ScrimController.ScrimVisibility;
import com.android.systemui.statusbar.policy.AccessibilityController;
import com.android.systemui.statusbar.policy.KeyguardStateController;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import javax.inject.Inject;
import javax.inject.Named;

/**
 * Manages the different states and animations of the unlock icon.
 */
public class LockIcon extends KeyguardAffordanceView implements
        ViewTreeObserver.OnPreDrawListener {

    private static final int STATE_LOCKED = 0;
    private static final int STATE_LOCK_OPEN = 1;
    private static final int STATE_SCANNING_FACE = 2;
    private static final int STATE_BIOMETRICS_ERROR = 3;
    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
    private final AccessibilityController mAccessibilityController;
    private final KeyguardStateController mKeyguardStateController;
    private final KeyguardBypassController mBypassController;
    private final NotificationWakeUpCoordinator mWakeUpCoordinator;
    private final HeadsUpManagerPhone mHeadsUpManager;

    private int mLastState = 0;
    private boolean mForceUpdate;
    private boolean mTransientBiometricsError;
    private boolean mIsFaceUnlockState;
    private boolean mSimLocked;
    private int mDensity;
public class LockIcon extends KeyguardAffordanceView {

    static final int STATE_LOCKED = 0;
    static final int STATE_LOCK_OPEN = 1;
    static final int STATE_SCANNING_FACE = 2;
    static final int STATE_BIOMETRICS_ERROR = 3;
    private float mDozeAmount;
    private int mIconColor;
    private StateProvider mStateProvider;
    private int mOldState;
    private boolean mPulsing;
    private boolean mDozing;
    private boolean mDocked;
    private boolean mBlockUpdates;
    private int mIconColor;
    private float mDozeAmount;
    private boolean mBouncerShowingScrimmed;
    private boolean mWakeAndUnlockRunning;
    private boolean mKeyguardShowing;
    private boolean mShowingLaunchAffordance;
    private boolean mKeyguardJustShown;
    private boolean mUpdatePending;
    private boolean mBouncerPreHideAnimation;
    private int mStatusBarState = StatusBarState.SHADE;

    private final KeyguardStateController.Callback mKeyguardMonitorCallback =
            new KeyguardStateController.Callback() {
                @Override
                public void onKeyguardShowingChanged() {
                    boolean force = false;
                    boolean wasShowing = mKeyguardShowing;
                    mKeyguardShowing = mKeyguardStateController.isShowing();
                    if (!wasShowing && mKeyguardShowing && mBlockUpdates) {
                        mBlockUpdates = false;
                        force = true;
                    }
                    if (!wasShowing && mKeyguardShowing) {
                        mKeyguardJustShown = true;
                    }
                    update(force);
                }

                @Override
                public void onKeyguardFadingAwayChanged() {
                    if (!mKeyguardStateController.isKeyguardFadingAway()) {
                        mBouncerPreHideAnimation = false;
                        if (mBlockUpdates) {
                            mBlockUpdates = false;
                            update(true /* force */);
                        }
                    }
                }

                @Override
                public void onUnlockedChanged() {
                    update();
                }
            };

    @Inject
    public LockIcon(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
            AccessibilityController accessibilityController,
            KeyguardBypassController bypassController,
            NotificationWakeUpCoordinator wakeUpCoordinator,
            KeyguardStateController keyguardStateController,
            HeadsUpManagerPhone headsUpManager) {
        super(context, attrs);
        mContext = context;
        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
        mAccessibilityController = accessibilityController;
        mBypassController = bypassController;
        mWakeUpCoordinator = wakeUpCoordinator;
        mKeyguardStateController = keyguardStateController;
        mHeadsUpManager = headsUpManager;
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        mKeyguardStateController.addCallback(mKeyguardMonitorCallback);
        mSimLocked = mKeyguardUpdateMonitor.isSimPinSecure();
        update();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mKeyguardStateController.removeCallback(mKeyguardMonitorCallback);
    }

    /**
     * If we're currently presenting an authentication error message.
     */
    public void setTransientBiometricsError(boolean transientBiometricsError) {
        mTransientBiometricsError = transientBiometricsError;
        update();
    }

    @Override
    protected void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        final int density = newConfig.densityDpi;
        if (density != mDensity) {
            mDensity = density;
            update();
        }
    }

    public void update() {
        update(false /* force */);
    }

    public void update(boolean force) {
        if (force) {
            mForceUpdate = true;
        }
        if (!mUpdatePending) {
            mUpdatePending = true;
            getViewTreeObserver().addOnPreDrawListener(this);
        }
    }
    private final SparseArray<Drawable> mDrawableCache = new SparseArray<>();

    private final OnPreDrawListener mOnPreDrawListener = new OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
        mUpdatePending = false;
            getViewTreeObserver().removeOnPreDrawListener(this);

        int state = getState();
        int lastState = mLastState;
        boolean keyguardJustShown = mKeyguardJustShown;
        mIsFaceUnlockState = state == STATE_SCANNING_FACE;
        mLastState = state;
        mKeyguardJustShown = false;

        boolean shouldUpdate = lastState != state || mForceUpdate;
        if (mBlockUpdates && canBlockUpdates()) {
            shouldUpdate = false;
        }
        if (shouldUpdate) {
            mForceUpdate = false;
            @LockAnimIndex final int lockAnimIndex = getAnimationIndexForTransition(lastState,
                    state, mPulsing, mDozing, keyguardJustShown);
            boolean isAnim = lockAnimIndex != -1;
            int iconRes = isAnim ? getThemedAnimationResId(lockAnimIndex) : getIconForState(state);

            Drawable icon = mContext.getDrawable(iconRes);
            final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable
                    ? (AnimatedVectorDrawable) icon
                    : null;
            int newState = mStateProvider.getState();
            Drawable icon = getIcon(newState);
            setImageDrawable(icon, false);
            if (mIsFaceUnlockState) {
                announceForAccessibility(getContext().getString(

            if (newState == STATE_SCANNING_FACE) {
                announceForAccessibility(getResources().getString(
                        R.string.accessibility_scanning_face));
            }

            if (animation != null && isAnim) {
            if (icon instanceof AnimatedVectorDrawable) {
                final AnimatedVectorDrawable animation = (AnimatedVectorDrawable) icon;
                animation.forceAnimationOnUI();
                animation.clearAnimationCallbacks();
                animation.registerAnimationCallback(new Animatable2.AnimationCallback() {
                animation.registerAnimationCallback(
                        new Animatable2.AnimationCallback() {
                            @Override
                            public void onAnimationEnd(Drawable drawable) {
                        if (getDrawable() == animation && state == getState()
                                && doesAnimationLoop(lockAnimIndex)) {
                                if (getDrawable() == animation
                                        && newState == mStateProvider.getState()
                                        && newState == STATE_SCANNING_FACE) {
                                    animation.start();
                                } else {
                            Trace.endAsyncSection("LockIcon#Animation", state);
                                    Trace.endAsyncSection("LockIcon#Animation", newState);
                                }
                            }
                        });
                Trace.beginAsyncSection("LockIcon#Animation", state);
                Trace.beginAsyncSection("LockIcon#Animation", newState);
                animation.start();
            }

            return true;
        }
        updateDarkTint();
    };

        updateIconVisibility();
        updateClickability();
    public LockIcon(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

        return true;
    void setStateProvider(StateProvider stateProvider) {
        mStateProvider = stateProvider;
    }

    @Override
    protected void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mDrawableCache.clear();
    }

    /**
     * Update the icon visibility
     * @return true if the visibility changed
     */
    boolean updateIconVisibility() {
        boolean onAodNotPulsingOrDocked = mDozing && (!mPulsing || mDocked);
        boolean invisible = onAodNotPulsingOrDocked || mWakeAndUnlockRunning
                || mShowingLaunchAffordance;
        if (mBypassController.getBypassEnabled() && !mBouncerShowingScrimmed) {
            if ((mHeadsUpManager.isHeadsUpGoingAway() || mHeadsUpManager.hasPinnedHeadsUp()
                    || mStatusBarState == StatusBarState.KEYGUARD)
                    && !mWakeUpCoordinator.getNotificationsFullyHidden()) {
                invisible = true;
            }
        }
        boolean wasInvisible = getVisibility() == INVISIBLE;
        if (invisible != wasInvisible) {
            setVisibility(invisible ? INVISIBLE : VISIBLE);
    boolean updateIconVisibility(boolean visible) {
        boolean wasVisible = getVisibility() == VISIBLE;
        if (visible != wasVisible) {
            setVisibility(visible ? VISIBLE : INVISIBLE);
            animate().cancel();
            if (!invisible) {
            if (visible) {
                setScaleX(0);
                setScaleY(0);
                animate()
@@ -280,49 +135,47 @@ public class LockIcon extends KeyguardAffordanceView implements
        return false;
    }

    private boolean canBlockUpdates() {
        return mKeyguardShowing || mKeyguardStateController.isKeyguardFadingAway();
    void update(int oldState, boolean pulsing, boolean dozing, boolean keyguardJustShown) {
        mOldState = oldState;
        mPulsing = pulsing;
        mDozing = dozing;
        mKeyguardJustShown = keyguardJustShown;

        getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener);
    }

    private void updateClickability() {
        if (mAccessibilityController == null) {
            return;
    void setDozeAmount(float dozeAmount) {
        mDozeAmount = dozeAmount;
        updateDarkTint();
    }
        boolean canLock = mKeyguardStateController.isMethodSecure()
                && mKeyguardStateController.canDismissLockScreen();
        boolean clickToUnlock = mAccessibilityController.isAccessibilityEnabled();
        setClickable(clickToUnlock);
        setLongClickable(canLock && !clickToUnlock);
        setFocusable(mAccessibilityController.isAccessibilityEnabled());

    void onThemeChange(int iconColor) {
        mDrawableCache.clear();
        mIconColor = iconColor;
        updateDarkTint();
    }

    @Override
    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
        super.onInitializeAccessibilityNodeInfo(info);
        boolean fingerprintRunning = mKeyguardUpdateMonitor.isFingerprintDetectionRunning();
        // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
        // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
        // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
        // check of whether non-strong biometric is allowed
        boolean unlockingAllowed = mKeyguardUpdateMonitor
                        .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */);
        if (fingerprintRunning && unlockingAllowed) {
            AccessibilityNodeInfo.AccessibilityAction unlock
                    = new AccessibilityNodeInfo.AccessibilityAction(
                    AccessibilityNodeInfo.ACTION_CLICK,
                    getContext().getString(R.string.accessibility_unlock_without_fingerprint));
            info.addAction(unlock);
            info.setHintText(getContext().getString(
                    R.string.accessibility_waiting_for_fingerprint));
        } else if (mIsFaceUnlockState) {
            //Avoid 'button' to be spoken for scanning face
            info.setClassName(LockIcon.class.getName());
            info.setContentDescription(getContext().getString(
                R.string.accessibility_scanning_face));
    private void updateDarkTint() {
        int color = ColorUtils.blendARGB(mIconColor, Color.WHITE, mDozeAmount);
        setImageTintList(ColorStateList.valueOf(color));
    }

    private Drawable getIcon(int newState) {
        @LockAnimIndex final int lockAnimIndex =
                getAnimationIndexForTransition(mOldState, newState, mPulsing, mDozing,
                        mKeyguardJustShown);

        boolean isAnim = lockAnimIndex != -1;
        int iconRes = isAnim ? getThemedAnimationResId(lockAnimIndex) : getIconForState(newState);

        if (!mDrawableCache.contains(iconRes)) {
            mDrawableCache.put(iconRes, getResources().getDrawable(iconRes));
        }

    private int getIconForState(int state) {
        return mDrawableCache.get(iconRes);
    }

    static int getIconForState(int state) {
        int iconRes;
        switch (state) {
            case STATE_LOCKED:
@@ -343,11 +196,7 @@ public class LockIcon extends KeyguardAffordanceView implements
        return iconRes;
    }

    private boolean doesAnimationLoop(@LockAnimIndex int lockAnimIndex) {
        return lockAnimIndex == SCANNING;
    }

    private static int getAnimationIndexForTransition(int oldState, int newState, boolean pulsing,
    static int getAnimationIndexForTransition(int oldState, int newState, boolean pulsing,
            boolean dozing, boolean keyguardJustShown) {

        // Never animate when screen is off
@@ -367,42 +216,10 @@ public class LockIcon extends KeyguardAffordanceView implements
        return -1;
    }

    public void setBouncerShowingScrimmed(boolean bouncerShowing) {
        mBouncerShowingScrimmed = bouncerShowing;
        if (mBypassController.getBypassEnabled()) {
            update();
        }
    }

    /**
     * Animate padlock opening when bouncer challenge is solved.
     */
    public void onBouncerPreHideAnimation() {
        mBouncerPreHideAnimation = true;
        update();
    }

    void setIconColor(int iconColor) {
        mIconColor = iconColor;
        updateDarkTint();
    }

    void setSimLocked(boolean simLocked) {
        mSimLocked = simLocked;
    }

    /** Set if the device is docked. */
    public void setDocked(boolean docked) {
        if (mDocked != docked) {
            mDocked = docked;
            update();
        }
    }

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({ERROR, UNLOCK, LOCK, SCANNING})
    @interface LockAnimIndex {}
    private static final int ERROR = 0, UNLOCK = 1, LOCK = 2, SCANNING = 3;
    static final int ERROR = 0, UNLOCK = 1, LOCK = 2, SCANNING = 3;
    private static final int[][] LOCK_ANIM_RES_IDS = new int[][] {
            {
                    R.anim.lock_to_error,
@@ -444,83 +261,8 @@ public class LockIcon extends KeyguardAffordanceView implements
        return LOCK_ANIM_RES_IDS[0][lockAnimIndex];
    }

    private int getState() {
        KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
        if ((mKeyguardStateController.canDismissLockScreen() || !mKeyguardShowing
                || mKeyguardStateController.isKeyguardGoingAway()) && !mSimLocked) {
            return STATE_LOCK_OPEN;
        } else if (mTransientBiometricsError) {
            return STATE_BIOMETRICS_ERROR;
        } else if (updateMonitor.isFaceDetectionRunning() && !mPulsing) {
            return STATE_SCANNING_FACE;
        } else {
            return STATE_LOCKED;
        }
    interface StateProvider {
        int getState();
    }

    /**
     * When keyguard is in pulsing (AOD2) state.
     * @param pulsing {@code true} when pulsing.
     */
    public void setPulsing(boolean pulsing) {
        mPulsing = pulsing;
        update();
    }

    private void updateDarkTint() {
        int color = ColorUtils.blendARGB(mIconColor, Color.WHITE, mDozeAmount);
        setImageTintList(ColorStateList.valueOf(color));
    }

    /**
     * We need to hide the lock whenever there's a fingerprint unlock, otherwise you'll see the
     * icon on top of the black front scrim.
     * @param wakeAndUnlock are we wake and unlocking
     * @param isUnlock are we currently unlocking
     */
    public void onBiometricAuthModeChanged(boolean wakeAndUnlock, boolean isUnlock) {
        if (wakeAndUnlock) {
            mWakeAndUnlockRunning = true;
        }
        if (isUnlock && mBypassController.getBypassEnabled() && canBlockUpdates()) {
            // We don't want the icon to change while we are unlocking
            mBlockUpdates = true;
        }
        update();
    }

    /**
     * When we're launching an affordance, like double pressing power to open camera.
     */
    public void onShowingLaunchAffordanceChanged(boolean showing) {
        mShowingLaunchAffordance = showing;
        update();
    }

    /**
     * Called whenever the scrims become opaque, transparent or semi-transparent.
     */
    public void onScrimVisibilityChanged(@ScrimVisibility int scrimsVisible) {
        if (mWakeAndUnlockRunning
                && scrimsVisible == ScrimController.TRANSPARENT) {
            mWakeAndUnlockRunning = false;
            update();
        }
    }

    void setDozing(boolean dozing) {
        mDozing = dozing;
        update();
    }

    void setDozeAmount(float dozeAmount) {
        mDozeAmount = dozeAmount;
        updateDarkTint();
    }

    /** Set the StatusBarState. */
    public void setStatusBarState(int statusBarState) {
        mStatusBarState = statusBarState;
        updateIconVisibility();
    }
}
+271 −41

File changed.

Preview size limit exceeded, changes collapsed.

+0 −6
Original line number Diff line number Diff line
@@ -34,7 +34,6 @@ import com.android.systemui.qs.QuickStatusBarHeader;
import com.android.systemui.qs.customize.QSCustomizer;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.phone.LockIcon;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -147,11 +146,6 @@ public class InjectionInflationController {
         */
        KeyguardMessageArea createKeyguardMessageArea();

        /**
         * Creates the keyguard LockIcon.
         */
        LockIcon createLockIcon();

        /**
         * Creates the QSPanel.
         */
+10 −1
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.res.Resources;
import android.view.View;

import androidx.test.filters.SmallTest;
@@ -35,6 +36,7 @@ import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.policy.AccessibilityController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;

import org.junit.Before;
import org.junit.Test;
@@ -71,6 +73,12 @@ public class LockscreenIconControllerTest extends SysuiTestCase {
    private KeyguardBypassController mKeyguardBypassController;
    @Mock
    private DockManager mDockManager;
    @Mock
    private KeyguardStateController mKeyguardStateController;
    @Mock
    private Resources mResources;
    @Mock
    private HeadsUpManagerPhone mHeadsUpManagerPhone;


    @Before
@@ -81,7 +89,8 @@ public class LockscreenIconControllerTest extends SysuiTestCase {
                mLockscreenGestureLogger, mKeyguardUpdateMonitor, mLockPatternUtils,
                mShadeController, mAccessibilityController, mKeyguardIndicationController,
                mStatusBarStateController, mConfigurationController, mNotificationWakeUpCoordinator,
                mKeyguardBypassController, mDockManager);
                mKeyguardBypassController, mDockManager, mKeyguardStateController, mResources,
                mHeadsUpManagerPhone);

        mLockIconController.attach(mLockIcon);
    }