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.graphics.drawable.AnimatedVectorDrawable;
21 import android.graphics.drawable.Drawable;
22 import android.graphics.drawable.InsetDrawable;
23 import android.util.AttributeSet;
24 import android.view.View;
25 import android.view.accessibility.AccessibilityNodeInfo;
26 
27 import com.android.keyguard.KeyguardUpdateMonitor;
28 import com.android.systemui.R;
29 import com.android.systemui.statusbar.KeyguardAffordanceView;
30 import com.android.systemui.statusbar.policy.AccessibilityController;
31 
32 /**
33  * Manages the different states and animations of the unlock icon.
34  */
35 public class LockIcon extends KeyguardAffordanceView {
36 
37     /**
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;
42 
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;
50     private boolean mLastDeviceInteractive;
51     private boolean mTransientFpError;
52     private boolean mDeviceInteractive;
53     private final TrustDrawable mTrustDrawable;
54     private final UnlockMethodCache mUnlockMethodCache;
55     private AccessibilityController mAccessibilityController;
56     private boolean mHasFingerPrintIcon;
57 
LockIcon(Context context, AttributeSet attrs)58     public LockIcon(Context context, AttributeSet attrs) {
59         super(context, attrs);
60         mTrustDrawable = new TrustDrawable(context);
61         setBackground(mTrustDrawable);
62         mUnlockMethodCache = UnlockMethodCache.getInstance(context);
63     }
64 
65     @Override
onVisibilityChanged(View changedView, int visibility)66     protected void onVisibilityChanged(View changedView, int visibility) {
67         super.onVisibilityChanged(changedView, visibility);
68         if (isShown()) {
69             mTrustDrawable.start();
70         } else {
71             mTrustDrawable.stop();
72         }
73     }
74 
75     @Override
onDetachedFromWindow()76     protected void onDetachedFromWindow() {
77         super.onDetachedFromWindow();
78         mTrustDrawable.stop();
79     }
80 
setTransientFpError(boolean transientFpError)81     public void setTransientFpError(boolean transientFpError) {
82         mTransientFpError = transientFpError;
83         update();
84     }
85 
setDeviceInteractive(boolean deviceInteractive)86     public void setDeviceInteractive(boolean deviceInteractive) {
87         mDeviceInteractive = deviceInteractive;
88         update();
89     }
90 
update()91     public void update() {
92         boolean visible = isShown()
93                 && KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
94         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;
105         if (state != mLastState || mDeviceInteractive != mLastDeviceInteractive) {
106             int iconRes = getAnimationResForTransition(mLastState, state, mLastDeviceInteractive,
107                     mDeviceInteractive);
108             if (iconRes == R.drawable.lockscreen_fingerprint_draw_off_animation) {
109                 anyFingerprintIcon = true;
110             }
111             if (iconRes == -1) {
112                 iconRes = getIconForState(state);
113             }
114             Drawable icon = mContext.getDrawable(iconRes);
115             final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable
116                     ? (AnimatedVectorDrawable) icon
117                     : null;
118             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);
133             String contentDescription = getResources().getString(anyFingerprintIcon
134                     ? R.string.accessibility_unlock_button_fingerprint
135                     : R.string.accessibility_unlock_button);
136             setContentDescription(contentDescription);
137             mHasFingerPrintIcon = anyFingerprintIcon;
138             if (animation != null) {
139 
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                 }
152             }
153             mLastState = state;
154             mLastDeviceInteractive = mDeviceInteractive;
155         }
156 
157         // Hide trust circle when fingerprint is running.
158         boolean trustManaged = mUnlockMethodCache.isTrustManaged() && !anyFingerprintIcon;
159         mTrustDrawable.setTrustManaged(trustManaged);
160         updateClickability();
161     }
162 
updateClickability()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 
177     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)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 
setAccessibilityController(AccessibilityController accessibilityController)191     public void setAccessibilityController(AccessibilityController accessibilityController) {
192         mAccessibilityController = accessibilityController;
193     }
194 
getIconForState(int state)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 
getAnimationResForTransition(int oldState, int newState, boolean oldScreenOn, boolean screenOn)212     private int getAnimationResForTransition(int oldState, int newState, boolean oldScreenOn,
213             boolean screenOn) {
214         if (oldState == STATE_FINGERPRINT && newState == STATE_FINGERPRINT_ERROR) {
215             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;
218         } else if (oldState == STATE_FINGERPRINT && newState == STATE_LOCK_OPEN
219                 && !mUnlockMethodCache.isTrusted()) {
220             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;
223         } else {
224             return -1;
225         }
226     }
227 
getState()228     private int getState() {
229         KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
230         boolean fingerprintRunning = updateMonitor.isFingerprintDetectionRunning();
231         boolean unlockingAllowed = updateMonitor.isUnlockingWithFingerprintAllowed();
232         if (mUnlockMethodCache.canSkipBouncer()) {
233             return STATE_LOCK_OPEN;
234         } else if (mTransientFpError) {
235             return STATE_FINGERPRINT_ERROR;
236         } else if (fingerprintRunning && unlockingAllowed) {
237             return STATE_FINGERPRINT;
238         } else if (mUnlockMethodCache.isFaceUnlockRunning()) {
239             return STATE_FACE_UNLOCK;
240         } 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 
IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight)253         public IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight) {
254             super(drawable, 0);
255             mIntrinsicWidth = intrinsicWidth;
256             mIntrinsicHeight = intrinsicHeight;
257         }
258 
259         @Override
getIntrinsicWidth()260         public int getIntrinsicWidth() {
261             return mIntrinsicWidth;
262         }
263 
264         @Override
getIntrinsicHeight()265         public int getIntrinsicHeight() {
266             return mIntrinsicHeight;
267         }
268     }
269 }
270