1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.systemui.statusbar.phone; 18 19 import android.content.Context; 20 import android.graphics.drawable.AnimatedVectorDrawable; 21 import android.graphics.drawable.Drawable; 22 import android.graphics.drawable.InsetDrawable; 23 import android.util.AttributeSet; 24 import android.view.View; 25 import android.view.accessibility.AccessibilityNodeInfo; 26 27 import com.android.keyguard.KeyguardUpdateMonitor; 28 import com.android.systemui.R; 29 import com.android.systemui.statusbar.KeyguardAffordanceView; 30 import com.android.systemui.statusbar.policy.AccessibilityController; 31 32 /** 33 * Manages the different states and animations of the unlock icon. 34 */ 35 public class LockIcon extends KeyguardAffordanceView { 36 37 private static final int STATE_LOCKED = 0; 38 private static final int STATE_LOCK_OPEN = 1; 39 private static final int STATE_FACE_UNLOCK = 2; 40 private static final int STATE_FINGERPRINT = 3; 41 private static final int STATE_FINGERPRINT_ERROR = 4; 42 43 private int mLastState = 0; 44 private boolean mLastDeviceInteractive; 45 private boolean mTransientFpError; 46 private boolean mDeviceInteractive; 47 private boolean mScreenOn; 48 private boolean mLastScreenOn; 49 private final TrustDrawable mTrustDrawable; 50 private final UnlockMethodCache mUnlockMethodCache; 51 private AccessibilityController mAccessibilityController; 52 private boolean mHasFingerPrintIcon; 53 LockIcon(Context context, AttributeSet attrs)54 public LockIcon(Context context, AttributeSet attrs) { 55 super(context, attrs); 56 mTrustDrawable = new TrustDrawable(context); 57 setBackground(mTrustDrawable); 58 mUnlockMethodCache = UnlockMethodCache.getInstance(context); 59 } 60 61 @Override onVisibilityChanged(View changedView, int visibility)62 protected void onVisibilityChanged(View changedView, int visibility) { 63 super.onVisibilityChanged(changedView, visibility); 64 if (isShown()) { 65 mTrustDrawable.start(); 66 } else { 67 mTrustDrawable.stop(); 68 } 69 } 70 71 @Override onDetachedFromWindow()72 protected void onDetachedFromWindow() { 73 super.onDetachedFromWindow(); 74 mTrustDrawable.stop(); 75 } 76 setTransientFpError(boolean transientFpError)77 public void setTransientFpError(boolean transientFpError) { 78 mTransientFpError = transientFpError; 79 update(); 80 } 81 setDeviceInteractive(boolean deviceInteractive)82 public void setDeviceInteractive(boolean deviceInteractive) { 83 mDeviceInteractive = deviceInteractive; 84 update(); 85 } 86 setScreenOn(boolean screenOn)87 public void setScreenOn(boolean screenOn) { 88 mScreenOn = screenOn; 89 update(); 90 } 91 update()92 public void update() { 93 boolean visible = isShown() 94 && KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive(); 95 if (visible) { 96 mTrustDrawable.start(); 97 } else { 98 mTrustDrawable.stop(); 99 } 100 // TODO: Real icon for facelock. 101 int state = getState(); 102 boolean anyFingerprintIcon = state == STATE_FINGERPRINT || state == STATE_FINGERPRINT_ERROR; 103 boolean useAdditionalPadding = anyFingerprintIcon; 104 boolean trustHidden = anyFingerprintIcon; 105 if (state != mLastState || mDeviceInteractive != mLastDeviceInteractive 106 || mScreenOn != mLastScreenOn) { 107 boolean isAnim = true; 108 int iconRes = getAnimationResForTransition(mLastState, state, mLastDeviceInteractive, 109 mDeviceInteractive, mLastScreenOn, mScreenOn); 110 if (iconRes == R.drawable.lockscreen_fingerprint_draw_off_animation) { 111 anyFingerprintIcon = true; 112 useAdditionalPadding = true; 113 trustHidden = true; 114 } else if (iconRes == R.drawable.trusted_state_to_error_animation) { 115 anyFingerprintIcon = true; 116 useAdditionalPadding = false; 117 trustHidden = true; 118 } else if (iconRes == R.drawable.error_to_trustedstate_animation) { 119 anyFingerprintIcon = true; 120 useAdditionalPadding = false; 121 trustHidden = false; 122 } 123 if (iconRes == -1) { 124 iconRes = getIconForState(state, mScreenOn, mDeviceInteractive); 125 isAnim = false; 126 } 127 Drawable icon = mContext.getDrawable(iconRes); 128 final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable 129 ? (AnimatedVectorDrawable) icon 130 : null; 131 int iconHeight = getResources().getDimensionPixelSize( 132 R.dimen.keyguard_affordance_icon_height); 133 int iconWidth = getResources().getDimensionPixelSize( 134 R.dimen.keyguard_affordance_icon_width); 135 if (!anyFingerprintIcon && (icon.getIntrinsicHeight() != iconHeight 136 || icon.getIntrinsicWidth() != iconWidth)) { 137 icon = new IntrinsicSizeDrawable(icon, iconWidth, iconHeight); 138 } 139 setPaddingRelative(0, 0, 0, useAdditionalPadding 140 ? getResources().getDimensionPixelSize( 141 R.dimen.fingerprint_icon_additional_padding) 142 : 0); 143 setRestingAlpha( 144 anyFingerprintIcon ? 1f : KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT); 145 setImageDrawable(icon); 146 String contentDescription = getResources().getString(anyFingerprintIcon 147 ? R.string.accessibility_unlock_button_fingerprint 148 : R.string.accessibility_unlock_button); 149 setContentDescription(contentDescription); 150 mHasFingerPrintIcon = anyFingerprintIcon; 151 if (animation != null && isAnim) { 152 animation.start(); 153 } 154 mLastState = state; 155 mLastDeviceInteractive = mDeviceInteractive; 156 mLastScreenOn = mScreenOn; 157 } 158 159 // Hide trust circle when fingerprint is running. 160 boolean trustManaged = mUnlockMethodCache.isTrustManaged() && !trustHidden; 161 mTrustDrawable.setTrustManaged(trustManaged); 162 updateClickability(); 163 } 164 updateClickability()165 private void updateClickability() { 166 if (mAccessibilityController == null) { 167 return; 168 } 169 boolean clickToUnlock = mAccessibilityController.isTouchExplorationEnabled(); 170 boolean clickToForceLock = mUnlockMethodCache.isTrustManaged() 171 && !mAccessibilityController.isAccessibilityEnabled(); 172 boolean longClickToForceLock = mUnlockMethodCache.isTrustManaged() 173 && !clickToForceLock; 174 setClickable(clickToForceLock || clickToUnlock); 175 setLongClickable(longClickToForceLock); 176 setFocusable(mAccessibilityController.isAccessibilityEnabled()); 177 } 178 179 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)180 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 181 super.onInitializeAccessibilityNodeInfo(info); 182 if (mHasFingerPrintIcon) { 183 // Avoid that the button description is also spoken 184 info.setClassName(LockIcon.class.getName()); 185 AccessibilityNodeInfo.AccessibilityAction unlock 186 = new AccessibilityNodeInfo.AccessibilityAction( 187 AccessibilityNodeInfo.ACTION_CLICK, 188 getContext().getString(R.string.accessibility_unlock_without_fingerprint)); 189 info.addAction(unlock); 190 } 191 } 192 setAccessibilityController(AccessibilityController accessibilityController)193 public void setAccessibilityController(AccessibilityController accessibilityController) { 194 mAccessibilityController = accessibilityController; 195 } 196 getIconForState(int state, boolean screenOn, boolean deviceInteractive)197 private int getIconForState(int state, boolean screenOn, boolean deviceInteractive) { 198 switch (state) { 199 case STATE_LOCKED: 200 return R.drawable.ic_lock_24dp; 201 case STATE_LOCK_OPEN: 202 return R.drawable.ic_lock_open_24dp; 203 case STATE_FACE_UNLOCK: 204 return com.android.internal.R.drawable.ic_account_circle; 205 case STATE_FINGERPRINT: 206 // If screen is off and device asleep, use the draw on animation so the first frame 207 // gets drawn. 208 return screenOn && deviceInteractive 209 ? R.drawable.ic_fingerprint 210 : R.drawable.lockscreen_fingerprint_draw_on_animation; 211 case STATE_FINGERPRINT_ERROR: 212 return R.drawable.ic_fingerprint_error; 213 default: 214 throw new IllegalArgumentException(); 215 } 216 } 217 getAnimationResForTransition(int oldState, int newState, boolean oldDeviceInteractive, boolean deviceInteractive, boolean oldScreenOn, boolean screenOn)218 private int getAnimationResForTransition(int oldState, int newState, 219 boolean oldDeviceInteractive, boolean deviceInteractive, 220 boolean oldScreenOn, boolean screenOn) { 221 if (oldState == STATE_FINGERPRINT && newState == STATE_FINGERPRINT_ERROR) { 222 return R.drawable.lockscreen_fingerprint_fp_to_error_state_animation; 223 } else if (oldState == STATE_LOCK_OPEN && newState == STATE_FINGERPRINT_ERROR) { 224 return R.drawable.trusted_state_to_error_animation; 225 } else if (oldState == STATE_FINGERPRINT_ERROR && newState == STATE_LOCK_OPEN) { 226 return R.drawable.error_to_trustedstate_animation; 227 } else if (oldState == STATE_FINGERPRINT_ERROR && newState == STATE_FINGERPRINT) { 228 return R.drawable.lockscreen_fingerprint_error_state_to_fp_animation; 229 } else if (oldState == STATE_FINGERPRINT && newState == STATE_LOCK_OPEN 230 && !mUnlockMethodCache.isTrusted()) { 231 return R.drawable.lockscreen_fingerprint_draw_off_animation; 232 } else if (newState == STATE_FINGERPRINT && (!oldScreenOn && screenOn && deviceInteractive 233 || screenOn && !oldDeviceInteractive && deviceInteractive)) { 234 return R.drawable.lockscreen_fingerprint_draw_on_animation; 235 } else { 236 return -1; 237 } 238 } 239 getState()240 private int getState() { 241 KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext); 242 boolean fingerprintRunning = updateMonitor.isFingerprintDetectionRunning(); 243 boolean unlockingAllowed = updateMonitor.isUnlockingWithFingerprintAllowed(); 244 if (mTransientFpError) { 245 return STATE_FINGERPRINT_ERROR; 246 } else if (mUnlockMethodCache.canSkipBouncer()) { 247 return STATE_LOCK_OPEN; 248 } else if (mUnlockMethodCache.isFaceUnlockRunning()) { 249 return STATE_FACE_UNLOCK; 250 } else if (fingerprintRunning && unlockingAllowed) { 251 return STATE_FINGERPRINT; 252 } else { 253 return STATE_LOCKED; 254 } 255 } 256 257 /** 258 * A wrapper around another Drawable that overrides the intrinsic size. 259 */ 260 private static class IntrinsicSizeDrawable extends InsetDrawable { 261 262 private final int mIntrinsicWidth; 263 private final int mIntrinsicHeight; 264 IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight)265 public IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight) { 266 super(drawable, 0); 267 mIntrinsicWidth = intrinsicWidth; 268 mIntrinsicHeight = intrinsicHeight; 269 } 270 271 @Override getIntrinsicWidth()272 public int getIntrinsicWidth() { 273 return mIntrinsicWidth; 274 } 275 276 @Override getIntrinsicHeight()277 public int getIntrinsicHeight() { 278 return mIntrinsicHeight; 279 } 280 } 281 } 282