blob: 06d2fca442f066c545abed6e637af1bd7b525b3c [file] [log] [blame]
Jorim Jaggi27267d62015-04-28 13:27:12 -07001/*
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
17package com.android.systemui.statusbar.phone;
18
Jorim Jaggi27267d62015-04-28 13:27:12 -070019import android.content.Context;
20import android.graphics.drawable.AnimatedVectorDrawable;
21import android.graphics.drawable.Drawable;
22import android.graphics.drawable.InsetDrawable;
23import android.util.AttributeSet;
24import android.view.View;
Selim Cinekc99d9a952015-06-19 18:44:50 -070025import android.view.accessibility.AccessibilityNodeInfo;
Jorim Jaggi27267d62015-04-28 13:27:12 -070026
27import com.android.keyguard.KeyguardUpdateMonitor;
28import com.android.systemui.R;
29import com.android.systemui.statusbar.KeyguardAffordanceView;
30import com.android.systemui.statusbar.policy.AccessibilityController;
31
32/**
33 * Manages the different states and animations of the unlock icon.
34 */
35public class LockIcon extends KeyguardAffordanceView {
36
Jorim Jaggi864e64b2015-05-20 14:13:23 -070037 /**
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;
Jorim Jaggi27267d62015-04-28 13:27:12 -070042
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;
Jorim Jaggi0d210f62015-07-10 14:24:44 -070050 private boolean mLastDeviceInteractive;
Jorim Jaggi27267d62015-04-28 13:27:12 -070051 private boolean mTransientFpError;
Jorim Jaggi0d210f62015-07-10 14:24:44 -070052 private boolean mDeviceInteractive;
Jorim Jaggi27267d62015-04-28 13:27:12 -070053 private final TrustDrawable mTrustDrawable;
54 private final UnlockMethodCache mUnlockMethodCache;
55 private AccessibilityController mAccessibilityController;
Selim Cinekc99d9a952015-06-19 18:44:50 -070056 private boolean mHasFingerPrintIcon;
Jorim Jaggi27267d62015-04-28 13:27:12 -070057
Jorim Jaggi582b57f2015-04-28 16:28:26 -070058 public LockIcon(Context context, AttributeSet attrs) {
Jorim Jaggi27267d62015-04-28 13:27:12 -070059 super(context, attrs);
60 mTrustDrawable = new TrustDrawable(context);
61 setBackground(mTrustDrawable);
62 mUnlockMethodCache = UnlockMethodCache.getInstance(context);
63 }
64
65 @Override
Jorim Jaggi582b57f2015-04-28 16:28:26 -070066 protected void onVisibilityChanged(View changedView, int visibility) {
Jorim Jaggi27267d62015-04-28 13:27:12 -070067 super.onVisibilityChanged(changedView, visibility);
68 if (isShown()) {
69 mTrustDrawable.start();
70 } else {
71 mTrustDrawable.stop();
72 }
73 }
74
75 @Override
76 protected void onDetachedFromWindow() {
77 super.onDetachedFromWindow();
78 mTrustDrawable.stop();
79 }
80
81 public void setTransientFpError(boolean transientFpError) {
82 mTransientFpError = transientFpError;
83 update();
84 }
85
Jorim Jaggi0d210f62015-07-10 14:24:44 -070086 public void setDeviceInteractive(boolean deviceInteractive) {
87 mDeviceInteractive = deviceInteractive;
Jorim Jaggi864e64b2015-05-20 14:13:23 -070088 update();
89 }
90
Jorim Jaggi27267d62015-04-28 13:27:12 -070091 public void update() {
Jorim Jaggi0d210f62015-07-10 14:24:44 -070092 boolean visible = isShown()
93 && KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
Jorim Jaggi27267d62015-04-28 13:27:12 -070094 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;
Jorim Jaggi0d210f62015-07-10 14:24:44 -0700105 if (state != mLastState || mDeviceInteractive != mLastDeviceInteractive) {
106 int iconRes = getAnimationResForTransition(mLastState, state, mLastDeviceInteractive,
107 mDeviceInteractive);
Jorim Jaggi864e64b2015-05-20 14:13:23 -0700108 if (iconRes == R.drawable.lockscreen_fingerprint_draw_off_animation) {
109 anyFingerprintIcon = true;
110 }
Jorim Jaggi27267d62015-04-28 13:27:12 -0700111 if (iconRes == -1) {
112 iconRes = getIconForState(state);
113 }
114 Drawable icon = mContext.getDrawable(iconRes);
Jorim Jaggi864e64b2015-05-20 14:13:23 -0700115 final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable
116 ? (AnimatedVectorDrawable) icon
117 : null;
Jorim Jaggi27267d62015-04-28 13:27:12 -0700118 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);
Selim Cinekc99d9a952015-06-19 18:44:50 -0700133 String contentDescription = getResources().getString(anyFingerprintIcon
134 ? R.string.accessibility_unlock_button_fingerprint
135 : R.string.accessibility_unlock_button);
136 setContentDescription(contentDescription);
137 mHasFingerPrintIcon = anyFingerprintIcon;
Jorim Jaggi27267d62015-04-28 13:27:12 -0700138 if (animation != null) {
Jorim Jaggi864e64b2015-05-20 14:13:23 -0700139
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 }
Jorim Jaggi27267d62015-04-28 13:27:12 -0700152 }
Jorim Jaggi864e64b2015-05-20 14:13:23 -0700153 mLastState = state;
Jorim Jaggi0d210f62015-07-10 14:24:44 -0700154 mLastDeviceInteractive = mDeviceInteractive;
Jorim Jaggi27267d62015-04-28 13:27:12 -0700155 }
156
157 // Hide trust circle when fingerprint is running.
158 boolean trustManaged = mUnlockMethodCache.isTrustManaged() && !anyFingerprintIcon;
159 mTrustDrawable.setTrustManaged(trustManaged);
Jorim Jaggi27267d62015-04-28 13:27:12 -0700160 updateClickability();
161 }
162
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
Selim Cinekc99d9a952015-06-19 18:44:50 -0700177 @Override
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
Jorim Jaggi27267d62015-04-28 13:27:12 -0700191 public void setAccessibilityController(AccessibilityController accessibilityController) {
192 mAccessibilityController = accessibilityController;
193 }
194
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
Jorim Jaggi864e64b2015-05-20 14:13:23 -0700212 private int getAnimationResForTransition(int oldState, int newState, boolean oldScreenOn,
213 boolean screenOn) {
Jorim Jaggi27267d62015-04-28 13:27:12 -0700214 if (oldState == STATE_FINGERPRINT && newState == STATE_FINGERPRINT_ERROR) {
Jorim Jaggi864e64b2015-05-20 14:13:23 -0700215 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;
Selim Cinek2a46d682015-06-25 19:44:05 -0400218 } else if (oldState == STATE_FINGERPRINT && newState == STATE_LOCK_OPEN
Selim Cineke8bae622015-07-15 13:24:06 -0700219 && !mUnlockMethodCache.isTrusted()) {
Jorim Jaggi864e64b2015-05-20 14:13:23 -0700220 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;
Jorim Jaggi27267d62015-04-28 13:27:12 -0700223 } else {
224 return -1;
225 }
226 }
227
228 private int getState() {
Selim Cinek1fcafc42015-07-20 14:39:25 -0700229 KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
230 boolean fingerprintRunning = updateMonitor.isFingerprintDetectionRunning();
231 boolean unlockingAllowed = updateMonitor.isUnlockingWithFingerprintAllowed();
Selim Cineke8bae622015-07-15 13:24:06 -0700232 if (mUnlockMethodCache.canSkipBouncer()) {
Selim Cinek2a46d682015-06-25 19:44:05 -0400233 return STATE_LOCK_OPEN;
234 } else if (mTransientFpError) {
Jorim Jaggi27267d62015-04-28 13:27:12 -0700235 return STATE_FINGERPRINT_ERROR;
Selim Cinek1fcafc42015-07-20 14:39:25 -0700236 } else if (fingerprintRunning && unlockingAllowed) {
Jorim Jaggi27267d62015-04-28 13:27:12 -0700237 return STATE_FINGERPRINT;
238 } else if (mUnlockMethodCache.isFaceUnlockRunning()) {
239 return STATE_FACE_UNLOCK;
Jorim Jaggi27267d62015-04-28 13:27:12 -0700240 } 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
253 public IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight) {
254 super(drawable, 0);
255 mIntrinsicWidth = intrinsicWidth;
256 mIntrinsicHeight = intrinsicHeight;
257 }
258
259 @Override
260 public int getIntrinsicWidth() {
261 return mIntrinsicWidth;
262 }
263
264 @Override
265 public int getIntrinsicHeight() {
266 return mIntrinsicHeight;
267 }
268 }
269}