/* * Copyright (C) 2015 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 android.content.Context; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.util.AttributeSet; import android.view.View; import android.view.accessibility.AccessibilityNodeInfo; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.R; import com.android.systemui.statusbar.KeyguardAffordanceView; import com.android.systemui.statusbar.policy.AccessibilityController; /** * Manages the different states and animations of the unlock icon. */ public class LockIcon extends KeyguardAffordanceView { /** * Delay animations a bit when the screen just turned on as a heuristic to start them after * the screen has actually turned on. */ private static final long ANIM_DELAY_AFTER_SCREEN_ON = 250; private static final int STATE_LOCKED = 0; private static final int STATE_LOCK_OPEN = 1; private static final int STATE_FACE_UNLOCK = 2; private static final int STATE_FINGERPRINT = 3; private static final int STATE_FINGERPRINT_ERROR = 4; private int mLastState = 0; private boolean mLastDeviceInteractive; private boolean mTransientFpError; private boolean mDeviceInteractive; private final TrustDrawable mTrustDrawable; private final UnlockMethodCache mUnlockMethodCache; private AccessibilityController mAccessibilityController; private boolean mHasFingerPrintIcon; public LockIcon(Context context, AttributeSet attrs) { super(context, attrs); mTrustDrawable = new TrustDrawable(context); setBackground(mTrustDrawable); mUnlockMethodCache = UnlockMethodCache.getInstance(context); } @Override protected void onVisibilityChanged(View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); if (isShown()) { mTrustDrawable.start(); } else { mTrustDrawable.stop(); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mTrustDrawable.stop(); } public void setTransientFpError(boolean transientFpError) { mTransientFpError = transientFpError; update(); } public void setDeviceInteractive(boolean deviceInteractive) { mDeviceInteractive = deviceInteractive; update(); } public void update() { boolean visible = isShown() && KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive(); if (visible) { mTrustDrawable.start(); } else { mTrustDrawable.stop(); } if (!visible) { return; } // TODO: Real icon for facelock. int state = getState(); boolean anyFingerprintIcon = state == STATE_FINGERPRINT || state == STATE_FINGERPRINT_ERROR; if (state != mLastState || mDeviceInteractive != mLastDeviceInteractive) { int iconRes = getAnimationResForTransition(mLastState, state, mLastDeviceInteractive, mDeviceInteractive); if (iconRes == R.drawable.lockscreen_fingerprint_draw_off_animation) { anyFingerprintIcon = true; } if (iconRes == -1) { iconRes = getIconForState(state); } Drawable icon = mContext.getDrawable(iconRes); final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable ? (AnimatedVectorDrawable) icon : null; int iconHeight = getResources().getDimensionPixelSize( R.dimen.keyguard_affordance_icon_height); int iconWidth = getResources().getDimensionPixelSize( R.dimen.keyguard_affordance_icon_width); if (!anyFingerprintIcon && (icon.getIntrinsicHeight() != iconHeight || icon.getIntrinsicWidth() != iconWidth)) { icon = new IntrinsicSizeDrawable(icon, iconWidth, iconHeight); } setPaddingRelative(0, 0, 0, anyFingerprintIcon ? getResources().getDimensionPixelSize( R.dimen.fingerprint_icon_additional_padding) : 0); setRestingAlpha( anyFingerprintIcon ? 1f : KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT); setImageDrawable(icon); String contentDescription = getResources().getString(anyFingerprintIcon ? R.string.accessibility_unlock_button_fingerprint : R.string.accessibility_unlock_button); setContentDescription(contentDescription); mHasFingerPrintIcon = anyFingerprintIcon; if (animation != null) { // If we play the draw on animation, delay it by one frame when the screen is // actually turned on. if (iconRes == R.drawable.lockscreen_fingerprint_draw_on_animation) { postOnAnimationDelayed(new Runnable() { @Override public void run() { animation.start(); } }, ANIM_DELAY_AFTER_SCREEN_ON); } else { animation.start(); } } mLastState = state; mLastDeviceInteractive = mDeviceInteractive; } // Hide trust circle when fingerprint is running. boolean trustManaged = mUnlockMethodCache.isTrustManaged() && !anyFingerprintIcon; mTrustDrawable.setTrustManaged(trustManaged); updateClickability(); } private void updateClickability() { if (mAccessibilityController == null) { return; } boolean clickToUnlock = mAccessibilityController.isTouchExplorationEnabled(); boolean clickToForceLock = mUnlockMethodCache.isTrustManaged() && !mAccessibilityController.isAccessibilityEnabled(); boolean longClickToForceLock = mUnlockMethodCache.isTrustManaged() && !clickToForceLock; setClickable(clickToForceLock || clickToUnlock); setLongClickable(longClickToForceLock); setFocusable(mAccessibilityController.isAccessibilityEnabled()); } @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); if (mHasFingerPrintIcon) { // Avoid that the button description is also spoken info.setClassName(LockIcon.class.getName()); AccessibilityNodeInfo.AccessibilityAction unlock = new AccessibilityNodeInfo.AccessibilityAction( AccessibilityNodeInfo.ACTION_CLICK, getContext().getString(R.string.accessibility_unlock_without_fingerprint)); info.addAction(unlock); } } public void setAccessibilityController(AccessibilityController accessibilityController) { mAccessibilityController = accessibilityController; } private int getIconForState(int state) { switch (state) { case STATE_LOCKED: return R.drawable.ic_lock_24dp; case STATE_LOCK_OPEN: return R.drawable.ic_lock_open_24dp; case STATE_FACE_UNLOCK: return com.android.internal.R.drawable.ic_account_circle; case STATE_FINGERPRINT: return R.drawable.ic_fingerprint; case STATE_FINGERPRINT_ERROR: return R.drawable.ic_fingerprint_error; default: throw new IllegalArgumentException(); } } private int getAnimationResForTransition(int oldState, int newState, boolean oldScreenOn, boolean screenOn) { if (oldState == STATE_FINGERPRINT && newState == STATE_FINGERPRINT_ERROR) { return R.drawable.lockscreen_fingerprint_fp_to_error_state_animation; } else if (oldState == STATE_FINGERPRINT_ERROR && newState == STATE_FINGERPRINT) { return R.drawable.lockscreen_fingerprint_error_state_to_fp_animation; } else if (oldState == STATE_FINGERPRINT && newState == STATE_LOCK_OPEN && !mUnlockMethodCache.isTrusted()) { return R.drawable.lockscreen_fingerprint_draw_off_animation; } else if (newState == STATE_FINGERPRINT && !oldScreenOn && screenOn) { return R.drawable.lockscreen_fingerprint_draw_on_animation; } else { return -1; } } private int getState() { KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext); boolean fingerprintRunning = updateMonitor.isFingerprintDetectionRunning(); boolean unlockingAllowed = updateMonitor.isUnlockingWithFingerprintAllowed(); if (mUnlockMethodCache.canSkipBouncer()) { return STATE_LOCK_OPEN; } else if (mTransientFpError) { return STATE_FINGERPRINT_ERROR; } else if (fingerprintRunning && unlockingAllowed) { return STATE_FINGERPRINT; } else if (mUnlockMethodCache.isFaceUnlockRunning()) { return STATE_FACE_UNLOCK; } else { return STATE_LOCKED; } } /** * A wrapper around another Drawable that overrides the intrinsic size. */ private static class IntrinsicSizeDrawable extends InsetDrawable { private final int mIntrinsicWidth; private final int mIntrinsicHeight; public IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight) { super(drawable, 0); mIntrinsicWidth = intrinsicWidth; mIntrinsicHeight = intrinsicHeight; } @Override public int getIntrinsicWidth() { return mIntrinsicWidth; } @Override public int getIntrinsicHeight() { return mIntrinsicHeight; } } }