blob: 60015efef92ae98b5a99e0f4426586f397bc6d3a [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;
Selim Cinek6ebba592016-05-31 15:28:28 -070020import android.content.res.Configuration;
Lucas Dupinc9e5d762019-01-28 09:34:30 -080021import android.content.res.TypedArray;
Lucas Dupinc90a6bf2018-10-10 13:53:20 -070022import android.graphics.Color;
23import android.graphics.PorterDuff;
Jorim Jaggi27267d62015-04-28 13:27:12 -070024import android.graphics.drawable.AnimatedVectorDrawable;
25import android.graphics.drawable.Drawable;
Jorim Jaggi27267d62015-04-28 13:27:12 -070026import android.util.AttributeSet;
Lucas Dupinc9e5d762019-01-28 09:34:30 -080027import android.view.ContextThemeWrapper;
Selim Cinekc99d9a952015-06-19 18:44:50 -070028import android.view.accessibility.AccessibilityNodeInfo;
Jorim Jaggi27267d62015-04-28 13:27:12 -070029
Lucas Dupinc90a6bf2018-10-10 13:53:20 -070030import com.android.internal.graphics.ColorUtils;
Jorim Jaggi27267d62015-04-28 13:27:12 -070031import com.android.keyguard.KeyguardUpdateMonitor;
32import com.android.systemui.R;
33import com.android.systemui.statusbar.KeyguardAffordanceView;
34import com.android.systemui.statusbar.policy.AccessibilityController;
Zachary Iqbalf50284c2017-01-22 18:54:46 -080035import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
Jorim Jaggi27267d62015-04-28 13:27:12 -070036
37/**
38 * Manages the different states and animations of the unlock icon.
39 */
Zachary Iqbalf50284c2017-01-22 18:54:46 -080040public class LockIcon extends KeyguardAffordanceView implements OnUserInfoChangedListener {
Jorim Jaggi27267d62015-04-28 13:27:12 -070041
Jorim Jaggi8dea48c2016-11-28 14:47:45 +010042 private static final int FP_DRAW_OFF_TIMEOUT = 800;
43
Jorim Jaggi27267d62015-04-28 13:27:12 -070044 private static final int STATE_LOCKED = 0;
45 private static final int STATE_LOCK_OPEN = 1;
46 private static final int STATE_FACE_UNLOCK = 2;
47 private static final int STATE_FINGERPRINT = 3;
Lucas Dupinc9e5d762019-01-28 09:34:30 -080048 private static final int STATE_BIOMETRICS_ERROR = 4;
Jorim Jaggi27267d62015-04-28 13:27:12 -070049
50 private int mLastState = 0;
Lucas Dupinc9e5d762019-01-28 09:34:30 -080051 private boolean mTransientBiometricsError;
Jorim Jaggif1518da2015-07-30 11:56:36 -070052 private boolean mScreenOn;
53 private boolean mLastScreenOn;
Zachary Iqbalf50284c2017-01-22 18:54:46 -080054 private Drawable mUserAvatarIcon;
Jorim Jaggi27267d62015-04-28 13:27:12 -070055 private final UnlockMethodCache mUnlockMethodCache;
56 private AccessibilityController mAccessibilityController;
Lucas Dupinc9e5d762019-01-28 09:34:30 -080057 private boolean mHasFingerPrintState;
58 private boolean mIsFaceUnlockState;
Selim Cinek6ebba592016-05-31 15:28:28 -070059 private int mDensity;
Lucas Dupinf22194f2018-12-26 11:43:57 -080060 private boolean mPulsing;
61 private boolean mDozing;
Lucas Dupinc9e5d762019-01-28 09:34:30 -080062 private boolean mLastDozing;
63 private boolean mLastPulsing;
Jorim Jaggi27267d62015-04-28 13:27:12 -070064
Jorim Jaggi8dea48c2016-11-28 14:47:45 +010065 private final Runnable mDrawOffTimeout = () -> update(true /* forceUpdate */);
Lucas Dupinc90a6bf2018-10-10 13:53:20 -070066 private float mDarkAmount;
Jorim Jaggi8dea48c2016-11-28 14:47:45 +010067
Jorim Jaggi582b57f2015-04-28 16:28:26 -070068 public LockIcon(Context context, AttributeSet attrs) {
Jorim Jaggi27267d62015-04-28 13:27:12 -070069 super(context, attrs);
Lucas Dupinc9e5d762019-01-28 09:34:30 -080070 TypedArray typedArray = context.getTheme().obtainStyledAttributes(
71 attrs, new int[]{ R.attr.backgroundProtectedStyle }, 0, 0);
72 mContext = new ContextThemeWrapper(context,
73 typedArray.getResourceId(0, R.style.BackgroundProtectedStyle));
74 typedArray.recycle();
Jorim Jaggi27267d62015-04-28 13:27:12 -070075 mUnlockMethodCache = UnlockMethodCache.getInstance(context);
76 }
77
78 @Override
Zachary Iqbalf50284c2017-01-22 18:54:46 -080079 public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
80 mUserAvatarIcon = picture;
81 update();
82 }
83
Lucas Dupinc9e5d762019-01-28 09:34:30 -080084 /**
85 * If we're currently presenting an authentication error message.
86 */
87 public void setTransientBiometricsError(boolean transientBiometricsError) {
88 mTransientBiometricsError = transientBiometricsError;
Jorim Jaggi864e64b2015-05-20 14:13:23 -070089 update();
90 }
91
Jorim Jaggif1518da2015-07-30 11:56:36 -070092 public void setScreenOn(boolean screenOn) {
93 mScreenOn = screenOn;
94 update();
95 }
96
Selim Cinek6ebba592016-05-31 15:28:28 -070097 @Override
98 protected void onConfigurationChanged(Configuration newConfig) {
99 super.onConfigurationChanged(newConfig);
100 final int density = newConfig.densityDpi;
101 if (density != mDensity) {
102 mDensity = density;
Selim Cinek6ebba592016-05-31 15:28:28 -0700103 update();
104 }
105 }
106
Jorim Jaggi27267d62015-04-28 13:27:12 -0700107 public void update() {
Selim Cinek6ebba592016-05-31 15:28:28 -0700108 update(false /* force */);
109 }
110
111 public void update(boolean force) {
Jorim Jaggi27267d62015-04-28 13:27:12 -0700112 int state = getState();
Lucas Dupinc9e5d762019-01-28 09:34:30 -0800113 boolean anyFingerprintState = state == STATE_FINGERPRINT
114 || state == STATE_BIOMETRICS_ERROR;
115 mIsFaceUnlockState = state == STATE_FACE_UNLOCK;
116 if (state != mLastState || mLastDozing == mDozing || mLastPulsing == mPulsing
117 || mLastScreenOn != mScreenOn || force) {
118 int iconAnimRes = getAnimationResForTransition(mLastState, state, mLastPulsing,
119 mPulsing, mLastDozing, mDozing);
Zachary Iqbalf50284c2017-01-22 18:54:46 -0800120 boolean isAnim = iconAnimRes != -1;
Zachary Iqbalf50284c2017-01-22 18:54:46 -0800121
122 Drawable icon;
123 if (isAnim) {
124 // Load the animation resource.
125 icon = mContext.getDrawable(iconAnimRes);
126 } else {
127 // Load the static icon resource based on the current state.
Lucas Dupinc9e5d762019-01-28 09:34:30 -0800128 icon = getIconForState(state);
Jorim Jaggi27267d62015-04-28 13:27:12 -0700129 }
Zachary Iqbalf50284c2017-01-22 18:54:46 -0800130
Jorim Jaggi864e64b2015-05-20 14:13:23 -0700131 final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable
132 ? (AnimatedVectorDrawable) icon
133 : null;
Evan Lairdb0ddf872017-07-24 15:52:59 -0400134 setImageDrawable(icon, false);
Lucas Dupinc90a6bf2018-10-10 13:53:20 -0700135 updateDarkTint();
Lucas Dupinc9e5d762019-01-28 09:34:30 -0800136 if (mIsFaceUnlockState) {
Bingyu Zhangbfe6dcf2018-04-25 10:20:36 -0700137 announceForAccessibility(getContext().getString(
138 R.string.accessibility_scanning_face));
139 }
140
Lucas Dupinc9e5d762019-01-28 09:34:30 -0800141 mHasFingerPrintState = anyFingerprintState;
Jorim Jaggif1518da2015-07-30 11:56:36 -0700142 if (animation != null && isAnim) {
Jorim Jaggi23bb84b2016-06-30 19:11:38 -0700143 animation.forceAnimationOnUI();
Jorim Jaggif1518da2015-07-30 11:56:36 -0700144 animation.start();
Jorim Jaggi27267d62015-04-28 13:27:12 -0700145 }
Jorim Jaggi8dea48c2016-11-28 14:47:45 +0100146
Lucas Dupinc9e5d762019-01-28 09:34:30 -0800147 if (isAnim && !mLastScreenOn) {
Jorim Jaggi8dea48c2016-11-28 14:47:45 +0100148 removeCallbacks(mDrawOffTimeout);
149 postDelayed(mDrawOffTimeout, FP_DRAW_OFF_TIMEOUT);
150 } else {
151 removeCallbacks(mDrawOffTimeout);
152 }
153
Jorim Jaggi864e64b2015-05-20 14:13:23 -0700154 mLastState = state;
Jorim Jaggif1518da2015-07-30 11:56:36 -0700155 mLastScreenOn = mScreenOn;
Lucas Dupinc9e5d762019-01-28 09:34:30 -0800156 mLastDozing = mDozing;
157 mLastPulsing = mPulsing;
Jorim Jaggi27267d62015-04-28 13:27:12 -0700158 }
159
Lucas Dupinf22194f2018-12-26 11:43:57 -0800160 setVisibility(mDozing && !mPulsing ? GONE : VISIBLE);
Jorim Jaggi27267d62015-04-28 13:27:12 -0700161 updateClickability();
162 }
163
164 private void updateClickability() {
165 if (mAccessibilityController == null) {
166 return;
167 }
Lucas Dupinc9e5d762019-01-28 09:34:30 -0800168 boolean canLock = mUnlockMethodCache.isMethodSecure()
169 && mUnlockMethodCache.canSkipBouncer();
Adrian Roosa94e9642017-08-17 17:29:19 +0200170 boolean clickToUnlock = mAccessibilityController.isAccessibilityEnabled();
Lucas Dupinc9e5d762019-01-28 09:34:30 -0800171 boolean clickToForceLock = canLock && !clickToUnlock;
172 boolean longClickToForceLock = canLock && !clickToForceLock;
Jorim Jaggi27267d62015-04-28 13:27:12 -0700173 setClickable(clickToForceLock || clickToUnlock);
174 setLongClickable(longClickToForceLock);
175 setFocusable(mAccessibilityController.isAccessibilityEnabled());
176 }
177
Selim Cinekc99d9a952015-06-19 18:44:50 -0700178 @Override
179 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
180 super.onInitializeAccessibilityNodeInfo(info);
Lucas Dupinc9e5d762019-01-28 09:34:30 -0800181 if (mHasFingerPrintState) {
Selim Cinekc99d9a952015-06-19 18:44:50 -0700182 AccessibilityNodeInfo.AccessibilityAction unlock
183 = new AccessibilityNodeInfo.AccessibilityAction(
184 AccessibilityNodeInfo.ACTION_CLICK,
185 getContext().getString(R.string.accessibility_unlock_without_fingerprint));
186 info.addAction(unlock);
Selim Cinek947c77c2017-03-23 14:20:32 -0700187 info.setHintText(getContext().getString(
188 R.string.accessibility_waiting_for_fingerprint));
Lucas Dupinc9e5d762019-01-28 09:34:30 -0800189 } else if (mIsFaceUnlockState) {
Bingyu Zhangbfe6dcf2018-04-25 10:20:36 -0700190 //Avoid 'button' to be spoken for scanning face
191 info.setClassName(LockIcon.class.getName());
192 info.setContentDescription(getContext().getString(
193 R.string.accessibility_scanning_face));
Selim Cinekc99d9a952015-06-19 18:44:50 -0700194 }
195 }
196
Jorim Jaggi27267d62015-04-28 13:27:12 -0700197 public void setAccessibilityController(AccessibilityController accessibilityController) {
198 mAccessibilityController = accessibilityController;
199 }
200
Lucas Dupinc9e5d762019-01-28 09:34:30 -0800201 private Drawable getIconForState(int state) {
Zachary Iqbalf50284c2017-01-22 18:54:46 -0800202 int iconRes;
Jorim Jaggi27267d62015-04-28 13:27:12 -0700203 switch (state) {
Lucas Dupin117365d2018-10-03 15:49:46 -0700204 case STATE_FINGERPRINT:
Jorim Jaggi27267d62015-04-28 13:27:12 -0700205 case STATE_LOCKED:
Lucas Dupinc9e5d762019-01-28 09:34:30 -0800206 case STATE_FACE_UNLOCK:
207 iconRes = com.android.internal.R.drawable.ic_lock_24dp;
Zachary Iqbalf50284c2017-01-22 18:54:46 -0800208 break;
Jorim Jaggi27267d62015-04-28 13:27:12 -0700209 case STATE_LOCK_OPEN:
Zachary Iqbalf50284c2017-01-22 18:54:46 -0800210 if (mUnlockMethodCache.isTrustManaged() && mUnlockMethodCache.isTrusted()
211 && mUserAvatarIcon != null) {
212 return mUserAvatarIcon;
213 } else {
Lucas Dupinc9e5d762019-01-28 09:34:30 -0800214 iconRes = com.android.internal.R.drawable.ic_lock_open_24dp;
Zachary Iqbalf50284c2017-01-22 18:54:46 -0800215 }
216 break;
Lucas Dupinc9e5d762019-01-28 09:34:30 -0800217 case STATE_BIOMETRICS_ERROR:
218 iconRes = com.android.internal.R.drawable.ic_auth_error;
Zachary Iqbalf50284c2017-01-22 18:54:46 -0800219 break;
Jorim Jaggi27267d62015-04-28 13:27:12 -0700220 default:
221 throw new IllegalArgumentException();
222 }
Zachary Iqbalf50284c2017-01-22 18:54:46 -0800223
224 return mContext.getDrawable(iconRes);
Jorim Jaggi27267d62015-04-28 13:27:12 -0700225 }
226
Jorim Jaggif1518da2015-07-30 11:56:36 -0700227 private int getAnimationResForTransition(int oldState, int newState,
Lucas Dupinc9e5d762019-01-28 09:34:30 -0800228 boolean wasPulsing, boolean pulsing,
229 boolean wasDozing, boolean dozing) {
230
231 boolean isError = newState == STATE_BIOMETRICS_ERROR;
232 boolean isUnlocked = newState == STATE_LOCK_OPEN;
233 boolean isLocked = !isUnlocked;
234 boolean wasUnlocked = oldState == STATE_LOCK_OPEN;
235
236 if (isError) {
237 return com.android.internal.R.anim.lock_to_error;
238 } else if (isUnlocked) {
239 return com.android.internal.R.anim.lock_unlock;
240 } else if (wasUnlocked && isLocked && mScreenOn) {
241 return com.android.internal.R.anim.lock_lock;
242 } else if (isLocked && (!wasPulsing && pulsing || wasDozing && !dozing)) {
243 return com.android.internal.R.anim.lock_in;
Jorim Jaggi27267d62015-04-28 13:27:12 -0700244 }
Lucas Dupinc9e5d762019-01-28 09:34:30 -0800245 return -1;
Jorim Jaggi27267d62015-04-28 13:27:12 -0700246 }
247
248 private int getState() {
Selim Cinek1fcafc42015-07-20 14:39:25 -0700249 KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
250 boolean fingerprintRunning = updateMonitor.isFingerprintDetectionRunning();
Gilad Brettercb51b8b2018-03-22 17:04:51 +0200251 boolean unlockingAllowed = updateMonitor.isUnlockingWithBiometricAllowed();
Lucas Dupinc9e5d762019-01-28 09:34:30 -0800252 if (mTransientBiometricsError) {
253 return STATE_BIOMETRICS_ERROR;
Selim Cinekc3841982015-08-25 18:34:29 -0700254 } else if (mUnlockMethodCache.canSkipBouncer()) {
255 return STATE_LOCK_OPEN;
Lucas Dupinc9e5d762019-01-28 09:34:30 -0800256 } else if (mUnlockMethodCache.isFaceUnlockRunning()
257 || updateMonitor.isFaceDetectionRunning()) {
Jorim Jaggi27267d62015-04-28 13:27:12 -0700258 return STATE_FACE_UNLOCK;
Adrian Roose07cbaf2015-09-02 13:29:42 -0700259 } else if (fingerprintRunning && unlockingAllowed) {
260 return STATE_FINGERPRINT;
Jorim Jaggi27267d62015-04-28 13:27:12 -0700261 } else {
262 return STATE_LOCKED;
263 }
264 }
Lucas Dupinc90a6bf2018-10-10 13:53:20 -0700265
266 public void setDarkAmount(float darkAmount) {
267 mDarkAmount = darkAmount;
268 updateDarkTint();
269 }
270
Lucas Dupinf22194f2018-12-26 11:43:57 -0800271 /**
272 * When keyguard is in pulsing (AOD2) state.
273 * @param pulsing {@code true} when pulsing.
274 * @param animated if transition should be animated.
275 */
276 public void setPulsing(boolean pulsing, boolean animated) {
277 mPulsing = pulsing;
278 update();
279 }
280
281 /**
282 * Sets the dozing state of the keyguard.
283 */
284 public void setDozing(boolean dozing) {
285 mDozing = dozing;
286 update();
287 }
288
Lucas Dupinc90a6bf2018-10-10 13:53:20 -0700289 private void updateDarkTint() {
290 Drawable drawable = getDrawable().mutate();
291 int color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.WHITE, mDarkAmount);
292 drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
293 }
Jorim Jaggi27267d62015-04-28 13:27:12 -0700294}