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 /** 38 * Delay animations a bit when the screen just turned on as a heuristic to start them after 39 * the screen has actually turned on. 40 */ 41 private static final long ANIM_DELAY_AFTER_SCREEN_ON = 250; 42 43 private static final int STATE_LOCKED = 0; 44 private static final int STATE_LOCK_OPEN = 1; 45 private static final int STATE_FACE_UNLOCK = 2; 46 private static final int STATE_FINGERPRINT = 3; 47 private static final int STATE_FINGERPRINT_ERROR = 4; 48 49 private int mLastState = 0; 50 private boolean mLastDeviceInteractive; 51 private boolean mTransientFpError; 52 private boolean mDeviceInteractive; 53 private final TrustDrawable mTrustDrawable; 54 private final UnlockMethodCache mUnlockMethodCache; 55 private AccessibilityController mAccessibilityController; 56 private boolean mHasFingerPrintIcon; 57 LockIcon(Context context, AttributeSet attrs)58 public LockIcon(Context context, AttributeSet attrs) { 59 super(context, attrs); 60 mTrustDrawable = new TrustDrawable(context); 61 setBackground(mTrustDrawable); 62 mUnlockMethodCache = UnlockMethodCache.getInstance(context); 63 } 64 65 @Override onVisibilityChanged(View changedView, int visibility)66 protected void onVisibilityChanged(View changedView, int visibility) { 67 super.onVisibilityChanged(changedView, visibility); 68 if (isShown()) { 69 mTrustDrawable.start(); 70 } else { 71 mTrustDrawable.stop(); 72 } 73 } 74 75 @Override onDetachedFromWindow()76 protected void onDetachedFromWindow() { 77 super.onDetachedFromWindow(); 78 mTrustDrawable.stop(); 79 } 80 setTransientFpError(boolean transientFpError)81 public void setTransientFpError(boolean transientFpError) { 82 mTransientFpError = transientFpError; 83 update(); 84 } 85 setDeviceInteractive(boolean deviceInteractive)86 public void setDeviceInteractive(boolean deviceInteractive) { 87 mDeviceInteractive = deviceInteractive; 88 update(); 89 } 90 update()91 public void update() { 92 boolean visible = isShown() 93 && KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive(); 94 if (visible) { 95 mTrustDrawable.start(); 96 } else { 97 mTrustDrawable.stop(); 98 } 99 if (!visible) { 100 return; 101 } 102 // TODO: Real icon for facelock. 103 int state = getState(); 104 boolean anyFingerprintIcon = state == STATE_FINGERPRINT || state == STATE_FINGERPRINT_ERROR; 105 if (state != mLastState || mDeviceInteractive != mLastDeviceInteractive) { 106 int iconRes = getAnimationResForTransition(mLastState, state, mLastDeviceInteractive, 107 mDeviceInteractive); 108 if (iconRes == R.drawable.lockscreen_fingerprint_draw_off_animation) { 109 anyFingerprintIcon = true; 110 } 111 if (iconRes == -1) { 112 iconRes = getIconForState(state); 113 } 114 Drawable icon = mContext.getDrawable(iconRes); 115 final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable 116 ? (AnimatedVectorDrawable) icon 117 : null; 118 int iconHeight = getResources().getDimensionPixelSize( 119 R.dimen.keyguard_affordance_icon_height); 120 int iconWidth = getResources().getDimensionPixelSize( 121 R.dimen.keyguard_affordance_icon_width); 122 if (!anyFingerprintIcon && (icon.getIntrinsicHeight() != iconHeight 123 || icon.getIntrinsicWidth() != iconWidth)) { 124 icon = new IntrinsicSizeDrawable(icon, iconWidth, iconHeight); 125 } 126 setPaddingRelative(0, 0, 0, anyFingerprintIcon 127 ? getResources().getDimensionPixelSize( 128 R.dimen.fingerprint_icon_additional_padding) 129 : 0); 130 setRestingAlpha( 131 anyFingerprintIcon ? 1f : KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT); 132 setImageDrawable(icon); 133 String contentDescription = getResources().getString(anyFingerprintIcon 134 ? R.string.accessibility_unlock_button_fingerprint 135 : R.string.accessibility_unlock_button); 136 setContentDescription(contentDescription); 137 mHasFingerPrintIcon = anyFingerprintIcon; 138 if (animation != null) { 139 140 // If we play the draw on animation, delay it by one frame when the screen is 141 // actually turned on. 142 if (iconRes == R.drawable.lockscreen_fingerprint_draw_on_animation) { 143 postOnAnimationDelayed(new Runnable() { 144 @Override 145 public void run() { 146 animation.start(); 147 } 148 }, ANIM_DELAY_AFTER_SCREEN_ON); 149 } else { 150 animation.start(); 151 } 152 } 153 mLastState = state; 154 mLastDeviceInteractive = mDeviceInteractive; 155 } 156 157 // Hide trust circle when fingerprint is running. 158 boolean trustManaged = mUnlockMethodCache.isTrustManaged() && !anyFingerprintIcon; 159 mTrustDrawable.setTrustManaged(trustManaged); 160 updateClickability(); 161 } 162 updateClickability()163 private void updateClickability() { 164 if (mAccessibilityController == null) { 165 return; 166 } 167 boolean clickToUnlock = mAccessibilityController.isTouchExplorationEnabled(); 168 boolean clickToForceLock = mUnlockMethodCache.isTrustManaged() 169 && !mAccessibilityController.isAccessibilityEnabled(); 170 boolean longClickToForceLock = mUnlockMethodCache.isTrustManaged() 171 && !clickToForceLock; 172 setClickable(clickToForceLock || clickToUnlock); 173 setLongClickable(longClickToForceLock); 174 setFocusable(mAccessibilityController.isAccessibilityEnabled()); 175 } 176 177 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)178 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 179 super.onInitializeAccessibilityNodeInfo(info); 180 if (mHasFingerPrintIcon) { 181 // Avoid that the button description is also spoken 182 info.setClassName(LockIcon.class.getName()); 183 AccessibilityNodeInfo.AccessibilityAction unlock 184 = new AccessibilityNodeInfo.AccessibilityAction( 185 AccessibilityNodeInfo.ACTION_CLICK, 186 getContext().getString(R.string.accessibility_unlock_without_fingerprint)); 187 info.addAction(unlock); 188 } 189 } 190 setAccessibilityController(AccessibilityController accessibilityController)191 public void setAccessibilityController(AccessibilityController accessibilityController) { 192 mAccessibilityController = accessibilityController; 193 } 194 getIconForState(int state)195 private int getIconForState(int state) { 196 switch (state) { 197 case STATE_LOCKED: 198 return R.drawable.ic_lock_24dp; 199 case STATE_LOCK_OPEN: 200 return R.drawable.ic_lock_open_24dp; 201 case STATE_FACE_UNLOCK: 202 return com.android.internal.R.drawable.ic_account_circle; 203 case STATE_FINGERPRINT: 204 return R.drawable.ic_fingerprint; 205 case STATE_FINGERPRINT_ERROR: 206 return R.drawable.ic_fingerprint_error; 207 default: 208 throw new IllegalArgumentException(); 209 } 210 } 211 getAnimationResForTransition(int oldState, int newState, boolean oldScreenOn, boolean screenOn)212 private int getAnimationResForTransition(int oldState, int newState, boolean oldScreenOn, 213 boolean screenOn) { 214 if (oldState == STATE_FINGERPRINT && newState == STATE_FINGERPRINT_ERROR) { 215 return R.drawable.lockscreen_fingerprint_fp_to_error_state_animation; 216 } else if (oldState == STATE_FINGERPRINT_ERROR && newState == STATE_FINGERPRINT) { 217 return R.drawable.lockscreen_fingerprint_error_state_to_fp_animation; 218 } else if (oldState == STATE_FINGERPRINT && newState == STATE_LOCK_OPEN 219 && !mUnlockMethodCache.isTrusted()) { 220 return R.drawable.lockscreen_fingerprint_draw_off_animation; 221 } else if (newState == STATE_FINGERPRINT && !oldScreenOn && screenOn) { 222 return R.drawable.lockscreen_fingerprint_draw_on_animation; 223 } else { 224 return -1; 225 } 226 } 227 getState()228 private int getState() { 229 KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext); 230 boolean fingerprintRunning = updateMonitor.isFingerprintDetectionRunning(); 231 boolean unlockingAllowed = updateMonitor.isUnlockingWithFingerprintAllowed(); 232 if (mUnlockMethodCache.canSkipBouncer()) { 233 return STATE_LOCK_OPEN; 234 } else if (mTransientFpError) { 235 return STATE_FINGERPRINT_ERROR; 236 } else if (fingerprintRunning && unlockingAllowed) { 237 return STATE_FINGERPRINT; 238 } else if (mUnlockMethodCache.isFaceUnlockRunning()) { 239 return STATE_FACE_UNLOCK; 240 } else { 241 return STATE_LOCKED; 242 } 243 } 244 245 /** 246 * A wrapper around another Drawable that overrides the intrinsic size. 247 */ 248 private static class IntrinsicSizeDrawable extends InsetDrawable { 249 250 private final int mIntrinsicWidth; 251 private final int mIntrinsicHeight; 252 IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight)253 public IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight) { 254 super(drawable, 0); 255 mIntrinsicWidth = intrinsicWidth; 256 mIntrinsicHeight = intrinsicHeight; 257 } 258 259 @Override getIntrinsicWidth()260 public int getIntrinsicWidth() { 261 return mIntrinsicWidth; 262 } 263 264 @Override getIntrinsicHeight()265 public int getIntrinsicHeight() { 266 return mIntrinsicHeight; 267 } 268 } 269 } 270