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