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.content.res.Configuration; 21 import android.graphics.drawable.AnimatedVectorDrawable; 22 import android.graphics.drawable.Drawable; 23 import android.graphics.drawable.InsetDrawable; 24 import android.util.AttributeSet; 25 import android.view.View; 26 import android.view.accessibility.AccessibilityNodeInfo; 27 28 import com.android.keyguard.KeyguardUpdateMonitor; 29 import com.android.systemui.R; 30 import com.android.systemui.statusbar.KeyguardAffordanceView; 31 import com.android.systemui.statusbar.policy.AccessibilityController; 32 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener; 33 34 /** 35 * Manages the different states and animations of the unlock icon. 36 */ 37 public class LockIcon extends KeyguardAffordanceView implements OnUserInfoChangedListener { 38 39 private static final int FP_DRAW_OFF_TIMEOUT = 800; 40 41 private static final int STATE_LOCKED = 0; 42 private static final int STATE_LOCK_OPEN = 1; 43 private static final int STATE_FACE_UNLOCK = 2; 44 private static final int STATE_FINGERPRINT = 3; 45 private static final int STATE_FINGERPRINT_ERROR = 4; 46 47 private int mLastState = 0; 48 private boolean mLastDeviceInteractive; 49 private boolean mTransientFpError; 50 private boolean mDeviceInteractive; 51 private boolean mScreenOn; 52 private boolean mLastScreenOn; 53 private Drawable mUserAvatarIcon; 54 private TrustDrawable mTrustDrawable; 55 private final UnlockMethodCache mUnlockMethodCache; 56 private AccessibilityController mAccessibilityController; 57 private boolean mHasFingerPrintIcon; 58 private int mDensity; 59 60 private final Runnable mDrawOffTimeout = () -> update(true /* forceUpdate */); 61 LockIcon(Context context, AttributeSet attrs)62 public LockIcon(Context context, AttributeSet attrs) { 63 super(context, attrs); 64 mTrustDrawable = new TrustDrawable(context); 65 setBackground(mTrustDrawable); 66 mUnlockMethodCache = UnlockMethodCache.getInstance(context); 67 } 68 69 @Override onVisibilityChanged(View changedView, int visibility)70 protected void onVisibilityChanged(View changedView, int visibility) { 71 super.onVisibilityChanged(changedView, visibility); 72 if (isShown()) { 73 mTrustDrawable.start(); 74 } else { 75 mTrustDrawable.stop(); 76 } 77 } 78 79 @Override onDetachedFromWindow()80 protected void onDetachedFromWindow() { 81 super.onDetachedFromWindow(); 82 mTrustDrawable.stop(); 83 } 84 85 @Override onUserInfoChanged(String name, Drawable picture, String userAccount)86 public void onUserInfoChanged(String name, Drawable picture, String userAccount) { 87 mUserAvatarIcon = picture; 88 update(); 89 } 90 setTransientFpError(boolean transientFpError)91 public void setTransientFpError(boolean transientFpError) { 92 mTransientFpError = transientFpError; 93 update(); 94 } 95 setDeviceInteractive(boolean deviceInteractive)96 public void setDeviceInteractive(boolean deviceInteractive) { 97 mDeviceInteractive = deviceInteractive; 98 update(); 99 } 100 setScreenOn(boolean screenOn)101 public void setScreenOn(boolean screenOn) { 102 mScreenOn = screenOn; 103 update(); 104 } 105 106 @Override onConfigurationChanged(Configuration newConfig)107 protected void onConfigurationChanged(Configuration newConfig) { 108 super.onConfigurationChanged(newConfig); 109 final int density = newConfig.densityDpi; 110 if (density != mDensity) { 111 mDensity = density; 112 mTrustDrawable.stop(); 113 mTrustDrawable = new TrustDrawable(getContext()); 114 setBackground(mTrustDrawable); 115 update(); 116 } 117 } 118 update()119 public void update() { 120 update(false /* force */); 121 } 122 update(boolean force)123 public void update(boolean force) { 124 boolean visible = isShown() 125 && KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive(); 126 if (visible) { 127 mTrustDrawable.start(); 128 } else { 129 mTrustDrawable.stop(); 130 } 131 int state = getState(); 132 boolean anyFingerprintIcon = state == STATE_FINGERPRINT || state == STATE_FINGERPRINT_ERROR; 133 boolean useAdditionalPadding = anyFingerprintIcon; 134 boolean trustHidden = anyFingerprintIcon; 135 if (state != mLastState || mDeviceInteractive != mLastDeviceInteractive 136 || mScreenOn != mLastScreenOn || force) { 137 int iconAnimRes = 138 getAnimationResForTransition(mLastState, state, mLastDeviceInteractive, 139 mDeviceInteractive, mLastScreenOn, mScreenOn); 140 boolean isAnim = iconAnimRes != -1; 141 if (iconAnimRes == R.drawable.lockscreen_fingerprint_draw_off_animation) { 142 anyFingerprintIcon = true; 143 useAdditionalPadding = true; 144 trustHidden = true; 145 } else if (iconAnimRes == R.drawable.trusted_state_to_error_animation) { 146 anyFingerprintIcon = true; 147 useAdditionalPadding = false; 148 trustHidden = true; 149 } else if (iconAnimRes == R.drawable.error_to_trustedstate_animation) { 150 anyFingerprintIcon = true; 151 useAdditionalPadding = false; 152 trustHidden = false; 153 } 154 155 Drawable icon; 156 if (isAnim) { 157 // Load the animation resource. 158 icon = mContext.getDrawable(iconAnimRes); 159 } else { 160 // Load the static icon resource based on the current state. 161 icon = getIconForState(state, mScreenOn, mDeviceInteractive); 162 } 163 164 final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable 165 ? (AnimatedVectorDrawable) icon 166 : null; 167 int iconHeight = getResources().getDimensionPixelSize( 168 R.dimen.keyguard_affordance_icon_height); 169 int iconWidth = getResources().getDimensionPixelSize( 170 R.dimen.keyguard_affordance_icon_width); 171 if (!anyFingerprintIcon && (icon.getIntrinsicHeight() != iconHeight 172 || icon.getIntrinsicWidth() != iconWidth)) { 173 icon = new IntrinsicSizeDrawable(icon, iconWidth, iconHeight); 174 } 175 setPaddingRelative(0, 0, 0, useAdditionalPadding 176 ? getResources().getDimensionPixelSize( 177 R.dimen.fingerprint_icon_additional_padding) 178 : 0); 179 setRestingAlpha( 180 anyFingerprintIcon ? 1f : KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT); 181 setImageDrawable(icon); 182 mHasFingerPrintIcon = anyFingerprintIcon; 183 if (animation != null && isAnim) { 184 animation.forceAnimationOnUI(); 185 animation.start(); 186 } 187 188 if (iconAnimRes == R.drawable.lockscreen_fingerprint_draw_off_animation) { 189 removeCallbacks(mDrawOffTimeout); 190 postDelayed(mDrawOffTimeout, FP_DRAW_OFF_TIMEOUT); 191 } else { 192 removeCallbacks(mDrawOffTimeout); 193 } 194 195 mLastState = state; 196 mLastDeviceInteractive = mDeviceInteractive; 197 mLastScreenOn = mScreenOn; 198 } 199 200 // Hide trust circle when fingerprint is running. 201 boolean trustManaged = mUnlockMethodCache.isTrustManaged() && !trustHidden; 202 mTrustDrawable.setTrustManaged(trustManaged); 203 updateClickability(); 204 } 205 updateClickability()206 private void updateClickability() { 207 if (mAccessibilityController == null) { 208 return; 209 } 210 boolean clickToUnlock = mAccessibilityController.isTouchExplorationEnabled(); 211 boolean clickToForceLock = mUnlockMethodCache.isTrustManaged() 212 && !mAccessibilityController.isAccessibilityEnabled(); 213 boolean longClickToForceLock = mUnlockMethodCache.isTrustManaged() 214 && !clickToForceLock; 215 setClickable(clickToForceLock || clickToUnlock); 216 setLongClickable(longClickToForceLock); 217 setFocusable(mAccessibilityController.isAccessibilityEnabled()); 218 } 219 220 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)221 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 222 super.onInitializeAccessibilityNodeInfo(info); 223 if (mHasFingerPrintIcon) { 224 AccessibilityNodeInfo.AccessibilityAction unlock 225 = new AccessibilityNodeInfo.AccessibilityAction( 226 AccessibilityNodeInfo.ACTION_CLICK, 227 getContext().getString(R.string.accessibility_unlock_without_fingerprint)); 228 info.addAction(unlock); 229 info.setHintText(getContext().getString( 230 R.string.accessibility_waiting_for_fingerprint)); 231 } 232 } 233 setAccessibilityController(AccessibilityController accessibilityController)234 public void setAccessibilityController(AccessibilityController accessibilityController) { 235 mAccessibilityController = accessibilityController; 236 } 237 getIconForState(int state, boolean screenOn, boolean deviceInteractive)238 private Drawable getIconForState(int state, boolean screenOn, boolean deviceInteractive) { 239 int iconRes; 240 switch (state) { 241 case STATE_LOCKED: 242 iconRes = R.drawable.ic_lock_24dp; 243 break; 244 case STATE_LOCK_OPEN: 245 if (mUnlockMethodCache.isTrustManaged() && mUnlockMethodCache.isTrusted() 246 && mUserAvatarIcon != null) { 247 return mUserAvatarIcon; 248 } else { 249 iconRes = R.drawable.ic_lock_open_24dp; 250 } 251 break; 252 case STATE_FACE_UNLOCK: 253 iconRes = com.android.internal.R.drawable.ic_account_circle; 254 break; 255 case STATE_FINGERPRINT: 256 // If screen is off and device asleep, use the draw on animation so the first frame 257 // gets drawn. 258 iconRes = screenOn && deviceInteractive 259 ? R.drawable.ic_fingerprint 260 : R.drawable.lockscreen_fingerprint_draw_on_animation; 261 break; 262 case STATE_FINGERPRINT_ERROR: 263 iconRes = R.drawable.ic_fingerprint_error; 264 break; 265 default: 266 throw new IllegalArgumentException(); 267 } 268 269 return mContext.getDrawable(iconRes); 270 } 271 getAnimationResForTransition(int oldState, int newState, boolean oldDeviceInteractive, boolean deviceInteractive, boolean oldScreenOn, boolean screenOn)272 private int getAnimationResForTransition(int oldState, int newState, 273 boolean oldDeviceInteractive, boolean deviceInteractive, 274 boolean oldScreenOn, boolean screenOn) { 275 if (oldState == STATE_FINGERPRINT && newState == STATE_FINGERPRINT_ERROR) { 276 return R.drawable.lockscreen_fingerprint_fp_to_error_state_animation; 277 } else if (oldState == STATE_LOCK_OPEN && newState == STATE_FINGERPRINT_ERROR) { 278 return R.drawable.trusted_state_to_error_animation; 279 } else if (oldState == STATE_FINGERPRINT_ERROR && newState == STATE_LOCK_OPEN) { 280 return R.drawable.error_to_trustedstate_animation; 281 } else if (oldState == STATE_FINGERPRINT_ERROR && newState == STATE_FINGERPRINT) { 282 return R.drawable.lockscreen_fingerprint_error_state_to_fp_animation; 283 } else if (oldState == STATE_FINGERPRINT && newState == STATE_LOCK_OPEN 284 && !mUnlockMethodCache.isTrusted()) { 285 return R.drawable.lockscreen_fingerprint_draw_off_animation; 286 } else if (newState == STATE_FINGERPRINT && (!oldScreenOn && screenOn && deviceInteractive 287 || screenOn && !oldDeviceInteractive && deviceInteractive)) { 288 return R.drawable.lockscreen_fingerprint_draw_on_animation; 289 } else { 290 return -1; 291 } 292 } 293 getState()294 private int getState() { 295 KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext); 296 boolean fingerprintRunning = updateMonitor.isFingerprintDetectionRunning(); 297 boolean unlockingAllowed = updateMonitor.isUnlockingWithFingerprintAllowed(); 298 if (mTransientFpError) { 299 return STATE_FINGERPRINT_ERROR; 300 } else if (mUnlockMethodCache.canSkipBouncer()) { 301 return STATE_LOCK_OPEN; 302 } else if (mUnlockMethodCache.isFaceUnlockRunning()) { 303 return STATE_FACE_UNLOCK; 304 } else if (fingerprintRunning && unlockingAllowed) { 305 return STATE_FINGERPRINT; 306 } else { 307 return STATE_LOCKED; 308 } 309 } 310 311 /** 312 * A wrapper around another Drawable that overrides the intrinsic size. 313 */ 314 private static class IntrinsicSizeDrawable extends InsetDrawable { 315 316 private final int mIntrinsicWidth; 317 private final int mIntrinsicHeight; 318 IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight)319 public IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight) { 320 super(drawable, 0); 321 mIntrinsicWidth = intrinsicWidth; 322 mIntrinsicHeight = intrinsicHeight; 323 } 324 325 @Override getIntrinsicWidth()326 public int getIntrinsicWidth() { 327 return mIntrinsicWidth; 328 } 329 330 @Override getIntrinsicHeight()331 public int getIntrinsicHeight() { 332 return mIntrinsicHeight; 333 } 334 } 335 } 336