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