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