• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
33 /**
34  * Manages the different states and animations of the unlock icon.
35  */
36 public class LockIcon extends KeyguardAffordanceView {
37 
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;
43 
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;
54     private int mDensity;
55 
LockIcon(Context context, AttributeSet attrs)56     public LockIcon(Context context, AttributeSet attrs) {
57         super(context, attrs);
58         mTrustDrawable = new TrustDrawable(context);
59         setBackground(mTrustDrawable);
60         mUnlockMethodCache = UnlockMethodCache.getInstance(context);
61     }
62 
63     @Override
onVisibilityChanged(View changedView, int visibility)64     protected void onVisibilityChanged(View changedView, int visibility) {
65         super.onVisibilityChanged(changedView, visibility);
66         if (isShown()) {
67             mTrustDrawable.start();
68         } else {
69             mTrustDrawable.stop();
70         }
71     }
72 
73     @Override
onDetachedFromWindow()74     protected void onDetachedFromWindow() {
75         super.onDetachedFromWindow();
76         mTrustDrawable.stop();
77     }
78 
setTransientFpError(boolean transientFpError)79     public void setTransientFpError(boolean transientFpError) {
80         mTransientFpError = transientFpError;
81         update();
82     }
83 
setDeviceInteractive(boolean deviceInteractive)84     public void setDeviceInteractive(boolean deviceInteractive) {
85         mDeviceInteractive = deviceInteractive;
86         update();
87     }
88 
setScreenOn(boolean screenOn)89     public void setScreenOn(boolean screenOn) {
90         mScreenOn = screenOn;
91         update();
92     }
93 
94     @Override
onConfigurationChanged(Configuration newConfig)95     protected void onConfigurationChanged(Configuration newConfig) {
96         super.onConfigurationChanged(newConfig);
97         final int density = newConfig.densityDpi;
98         if (density != mDensity) {
99             mDensity = density;
100             mTrustDrawable.stop();
101             mTrustDrawable = new TrustDrawable(getContext());
102             setBackground(mTrustDrawable);
103             update();
104         }
105     }
106 
update()107     public void update() {
108         update(false /* force */);
109     }
110 
update(boolean force)111     public void update(boolean force) {
112         boolean visible = isShown()
113                 && KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
114         if (visible) {
115             mTrustDrawable.start();
116         } else {
117             mTrustDrawable.stop();
118         }
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;
132                 trustHidden = true;
133             } else if (iconRes == R.drawable.trusted_state_to_error_animation) {
134                 anyFingerprintIcon = true;
135                 useAdditionalPadding = false;
136                 trustHidden = true;
137             } else if (iconRes == R.drawable.error_to_trustedstate_animation) {
138                 anyFingerprintIcon = true;
139                 useAdditionalPadding = false;
140                 trustHidden = false;
141             }
142             if (iconRes == -1) {
143                 iconRes = getIconForState(state, mScreenOn, mDeviceInteractive);
144                 isAnim = false;
145             }
146             Drawable icon = mContext.getDrawable(iconRes);
147             final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable
148                     ? (AnimatedVectorDrawable) icon
149                     : null;
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);
157             }
158             setPaddingRelative(0, 0, 0, useAdditionalPadding
159                     ? getResources().getDimensionPixelSize(
160                     R.dimen.fingerprint_icon_additional_padding)
161                     : 0);
162             setRestingAlpha(
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) {
171                 animation.forceAnimationOnUI();
172                 animation.start();
173             }
174             mLastState = state;
175             mLastDeviceInteractive = mDeviceInteractive;
176             mLastScreenOn = mScreenOn;
177         }
178 
179         // Hide trust circle when fingerprint is running.
180         boolean trustManaged = mUnlockMethodCache.isTrustManaged() && !trustHidden;
181         mTrustDrawable.setTrustManaged(trustManaged);
182         updateClickability();
183     }
184 
updateClickability()185     private void updateClickability() {
186         if (mAccessibilityController == null) {
187             return;
188         }
189         boolean clickToUnlock = mAccessibilityController.isTouchExplorationEnabled();
190         boolean clickToForceLock = mUnlockMethodCache.isTrustManaged()
191                 && !mAccessibilityController.isAccessibilityEnabled();
192         boolean longClickToForceLock = mUnlockMethodCache.isTrustManaged()
193                 && !clickToForceLock;
194         setClickable(clickToForceLock || clickToUnlock);
195         setLongClickable(longClickToForceLock);
196         setFocusable(mAccessibilityController.isAccessibilityEnabled());
197     }
198 
199     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)200     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
201         super.onInitializeAccessibilityNodeInfo(info);
202         if (mHasFingerPrintIcon) {
203             // Avoid that the button description is also spoken
204             info.setClassName(LockIcon.class.getName());
205             AccessibilityNodeInfo.AccessibilityAction unlock
206                     = new AccessibilityNodeInfo.AccessibilityAction(
207                     AccessibilityNodeInfo.ACTION_CLICK,
208                     getContext().getString(R.string.accessibility_unlock_without_fingerprint));
209             info.addAction(unlock);
210         }
211     }
212 
setAccessibilityController(AccessibilityController accessibilityController)213     public void setAccessibilityController(AccessibilityController accessibilityController) {
214         mAccessibilityController = accessibilityController;
215     }
216 
getIconForState(int state, boolean screenOn, boolean deviceInteractive)217     private int getIconForState(int state, boolean screenOn, boolean deviceInteractive) {
218         switch (state) {
219             case STATE_LOCKED:
220                 return R.drawable.ic_lock_24dp;
221             case STATE_LOCK_OPEN:
222                 return R.drawable.ic_lock_open_24dp;
223             case STATE_FACE_UNLOCK:
224                 return com.android.internal.R.drawable.ic_account_circle;
225             case STATE_FINGERPRINT:
226                 // If screen is off and device asleep, use the draw on animation so the first frame
227                 // gets drawn.
228                 return screenOn && deviceInteractive
229                         ? R.drawable.ic_fingerprint
230                         : R.drawable.lockscreen_fingerprint_draw_on_animation;
231             case STATE_FINGERPRINT_ERROR:
232                 return R.drawable.ic_fingerprint_error;
233             default:
234                 throw new IllegalArgumentException();
235         }
236     }
237 
getAnimationResForTransition(int oldState, int newState, boolean oldDeviceInteractive, boolean deviceInteractive, boolean oldScreenOn, boolean screenOn)238     private int getAnimationResForTransition(int oldState, int newState,
239             boolean oldDeviceInteractive, boolean deviceInteractive,
240             boolean oldScreenOn, boolean screenOn) {
241         if (oldState == STATE_FINGERPRINT && newState == STATE_FINGERPRINT_ERROR) {
242             return R.drawable.lockscreen_fingerprint_fp_to_error_state_animation;
243         } else if (oldState == STATE_LOCK_OPEN && newState == STATE_FINGERPRINT_ERROR) {
244             return R.drawable.trusted_state_to_error_animation;
245         } else if (oldState == STATE_FINGERPRINT_ERROR && newState == STATE_LOCK_OPEN) {
246             return R.drawable.error_to_trustedstate_animation;
247         } else if (oldState == STATE_FINGERPRINT_ERROR && newState == STATE_FINGERPRINT) {
248             return R.drawable.lockscreen_fingerprint_error_state_to_fp_animation;
249         } else if (oldState == STATE_FINGERPRINT && newState == STATE_LOCK_OPEN
250                 && !mUnlockMethodCache.isTrusted()) {
251             return R.drawable.lockscreen_fingerprint_draw_off_animation;
252         } else if (newState == STATE_FINGERPRINT && (!oldScreenOn && screenOn && deviceInteractive
253                 || screenOn && !oldDeviceInteractive && deviceInteractive)) {
254             return R.drawable.lockscreen_fingerprint_draw_on_animation;
255         } else {
256             return -1;
257         }
258     }
259 
getState()260     private int getState() {
261         KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
262         boolean fingerprintRunning = updateMonitor.isFingerprintDetectionRunning();
263         boolean unlockingAllowed = updateMonitor.isUnlockingWithFingerprintAllowed();
264         if (mTransientFpError) {
265             return STATE_FINGERPRINT_ERROR;
266         } else if (mUnlockMethodCache.canSkipBouncer()) {
267             return STATE_LOCK_OPEN;
268         } else if (mUnlockMethodCache.isFaceUnlockRunning()) {
269             return STATE_FACE_UNLOCK;
270         } else if (fingerprintRunning && unlockingAllowed) {
271             return STATE_FINGERPRINT;
272         } else {
273             return STATE_LOCKED;
274         }
275     }
276 
277     /**
278      * A wrapper around another Drawable that overrides the intrinsic size.
279      */
280     private static class IntrinsicSizeDrawable extends InsetDrawable {
281 
282         private final int mIntrinsicWidth;
283         private final int mIntrinsicHeight;
284 
IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight)285         public IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight) {
286             super(drawable, 0);
287             mIntrinsicWidth = intrinsicWidth;
288             mIntrinsicHeight = intrinsicHeight;
289         }
290 
291         @Override
getIntrinsicWidth()292         public int getIntrinsicWidth() {
293             return mIntrinsicWidth;
294         }
295 
296         @Override
getIntrinsicHeight()297         public int getIntrinsicHeight() {
298             return mIntrinsicHeight;
299         }
300     }
301 }
302