2 * Copyright (C) 2015 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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
17 package com.android.systemui.statusbar.phone;
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;
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;
34 * Manages the different states and animations of the unlock icon.
36 public class LockIcon extends KeyguardAffordanceView {
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;
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;
56 public LockIcon(Context context, AttributeSet attrs) {
57 super(context, attrs);
58 mTrustDrawable = new TrustDrawable(context);
59 setBackground(mTrustDrawable);
60 mUnlockMethodCache = UnlockMethodCache.getInstance(context);
64 protected void onVisibilityChanged(View changedView, int visibility) {
65 super.onVisibilityChanged(changedView, visibility);
67 mTrustDrawable.start();
69 mTrustDrawable.stop();
74 protected void onDetachedFromWindow() {
75 super.onDetachedFromWindow();
76 mTrustDrawable.stop();
79 public void setTransientFpError(boolean transientFpError) {
80 mTransientFpError = transientFpError;
84 public void setDeviceInteractive(boolean deviceInteractive) {
85 mDeviceInteractive = deviceInteractive;
89 public void setScreenOn(boolean screenOn) {
95 protected void onConfigurationChanged(Configuration newConfig) {
96 super.onConfigurationChanged(newConfig);
97 final int density = newConfig.densityDpi;
98 if (density != mDensity) {
100 mTrustDrawable.stop();
101 mTrustDrawable = new TrustDrawable(getContext());
102 setBackground(mTrustDrawable);
107 public void update() {
108 update(false /* force */);
111 public void update(boolean force) {
112 boolean visible = isShown()
113 && KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
115 mTrustDrawable.start();
117 mTrustDrawable.stop();
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;
133 } else if (iconRes == R.drawable.trusted_state_to_error_animation) {
134 anyFingerprintIcon = true;
135 useAdditionalPadding = false;
137 } else if (iconRes == R.drawable.error_to_trustedstate_animation) {
138 anyFingerprintIcon = true;
139 useAdditionalPadding = false;
143 iconRes = getIconForState(state, mScreenOn, mDeviceInteractive);
146 Drawable icon = mContext.getDrawable(iconRes);
147 final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable
148 ? (AnimatedVectorDrawable) icon
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);
158 setPaddingRelative(0, 0, 0, useAdditionalPadding
159 ? getResources().getDimensionPixelSize(
160 R.dimen.fingerprint_icon_additional_padding)
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) {
174 mLastDeviceInteractive = mDeviceInteractive;
175 mLastScreenOn = mScreenOn;
178 // Hide trust circle when fingerprint is running.
179 boolean trustManaged = mUnlockMethodCache.isTrustManaged() && !trustHidden;
180 mTrustDrawable.setTrustManaged(trustManaged);
181 updateClickability();
184 private void updateClickability() {
185 if (mAccessibilityController == null) {
188 boolean clickToUnlock = mAccessibilityController.isTouchExplorationEnabled();
189 boolean clickToForceLock = mUnlockMethodCache.isTrustManaged()
190 && !mAccessibilityController.isAccessibilityEnabled();
191 boolean longClickToForceLock = mUnlockMethodCache.isTrustManaged()
192 && !clickToForceLock;
193 setClickable(clickToForceLock || clickToUnlock);
194 setLongClickable(longClickToForceLock);
195 setFocusable(mAccessibilityController.isAccessibilityEnabled());
199 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
200 super.onInitializeAccessibilityNodeInfo(info);
201 if (mHasFingerPrintIcon) {
202 // Avoid that the button description is also spoken
203 info.setClassName(LockIcon.class.getName());
204 AccessibilityNodeInfo.AccessibilityAction unlock
205 = new AccessibilityNodeInfo.AccessibilityAction(
206 AccessibilityNodeInfo.ACTION_CLICK,
207 getContext().getString(R.string.accessibility_unlock_without_fingerprint));
208 info.addAction(unlock);
212 public void setAccessibilityController(AccessibilityController accessibilityController) {
213 mAccessibilityController = accessibilityController;
216 private int getIconForState(int state, boolean screenOn, boolean deviceInteractive) {
219 return R.drawable.ic_lock_24dp;
220 case STATE_LOCK_OPEN:
221 return R.drawable.ic_lock_open_24dp;
222 case STATE_FACE_UNLOCK:
223 return com.android.internal.R.drawable.ic_account_circle;
224 case STATE_FINGERPRINT:
225 // If screen is off and device asleep, use the draw on animation so the first frame
227 return screenOn && deviceInteractive
228 ? R.drawable.ic_fingerprint
229 : R.drawable.lockscreen_fingerprint_draw_on_animation;
230 case STATE_FINGERPRINT_ERROR:
231 return R.drawable.ic_fingerprint_error;
233 throw new IllegalArgumentException();
237 private int getAnimationResForTransition(int oldState, int newState,
238 boolean oldDeviceInteractive, boolean deviceInteractive,
239 boolean oldScreenOn, boolean screenOn) {
240 if (oldState == STATE_FINGERPRINT && newState == STATE_FINGERPRINT_ERROR) {
241 return R.drawable.lockscreen_fingerprint_fp_to_error_state_animation;
242 } else if (oldState == STATE_LOCK_OPEN && newState == STATE_FINGERPRINT_ERROR) {
243 return R.drawable.trusted_state_to_error_animation;
244 } else if (oldState == STATE_FINGERPRINT_ERROR && newState == STATE_LOCK_OPEN) {
245 return R.drawable.error_to_trustedstate_animation;
246 } else if (oldState == STATE_FINGERPRINT_ERROR && newState == STATE_FINGERPRINT) {
247 return R.drawable.lockscreen_fingerprint_error_state_to_fp_animation;
248 } else if (oldState == STATE_FINGERPRINT && newState == STATE_LOCK_OPEN
249 && !mUnlockMethodCache.isTrusted()) {
250 return R.drawable.lockscreen_fingerprint_draw_off_animation;
251 } else if (newState == STATE_FINGERPRINT && (!oldScreenOn && screenOn && deviceInteractive
252 || screenOn && !oldDeviceInteractive && deviceInteractive)) {
253 return R.drawable.lockscreen_fingerprint_draw_on_animation;
259 private int getState() {
260 KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
261 boolean fingerprintRunning = updateMonitor.isFingerprintDetectionRunning();
262 boolean unlockingAllowed = updateMonitor.isUnlockingWithFingerprintAllowed();
263 if (mTransientFpError) {
264 return STATE_FINGERPRINT_ERROR;
265 } else if (mUnlockMethodCache.canSkipBouncer()) {
266 return STATE_LOCK_OPEN;
267 } else if (mUnlockMethodCache.isFaceUnlockRunning()) {
268 return STATE_FACE_UNLOCK;
269 } else if (fingerprintRunning && unlockingAllowed) {
270 return STATE_FINGERPRINT;
277 * A wrapper around another Drawable that overrides the intrinsic size.
279 private static class IntrinsicSizeDrawable extends InsetDrawable {
281 private final int mIntrinsicWidth;
282 private final int mIntrinsicHeight;
284 public IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight) {
286 mIntrinsicWidth = intrinsicWidth;
287 mIntrinsicHeight = intrinsicHeight;
291 public int getIntrinsicWidth() {
292 return mIntrinsicWidth;
296 public int getIntrinsicHeight() {
297 return mIntrinsicHeight;