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
17package com.android.systemui.statusbar.phone;
18
19import android.content.Context;
20import android.content.res.Configuration;
21import android.graphics.drawable.AnimatedVectorDrawable;
22import android.graphics.drawable.Drawable;
23import android.graphics.drawable.InsetDrawable;
24import android.util.AttributeSet;
25import android.view.View;
26import android.view.accessibility.AccessibilityNodeInfo;
27
28import com.android.keyguard.KeyguardUpdateMonitor;
29import com.android.systemui.R;
30import com.android.systemui.statusbar.KeyguardAffordanceView;
31import com.android.systemui.statusbar.policy.AccessibilityController;
32import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
33
34/**
35 * Manages the different states and animations of the unlock icon.
36 */
37public 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
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
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
80    protected void onDetachedFromWindow() {
81        super.onDetachedFromWindow();
82        mTrustDrawable.stop();
83    }
84
85    @Override
86    public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
87        mUserAvatarIcon = picture;
88        update();
89    }
90
91    public void setTransientFpError(boolean transientFpError) {
92        mTransientFpError = transientFpError;
93        update();
94    }
95
96    public void setDeviceInteractive(boolean deviceInteractive) {
97        mDeviceInteractive = deviceInteractive;
98        update();
99    }
100
101    public void setScreenOn(boolean screenOn) {
102        mScreenOn = screenOn;
103        update();
104    }
105
106    @Override
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
119    public void update() {
120        update(false /* force */);
121    }
122
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
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
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
234    public void setAccessibilityController(AccessibilityController accessibilityController) {
235        mAccessibilityController = accessibilityController;
236    }
237
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
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
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
319        public IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight) {
320            super(drawable, 0);
321            mIntrinsicWidth = intrinsicWidth;
322            mIntrinsicHeight = intrinsicHeight;
323        }
324
325        @Override
326        public int getIntrinsicWidth() {
327            return mIntrinsicWidth;
328        }
329
330        @Override
331        public int getIntrinsicHeight() {
332            return mIntrinsicHeight;
333        }
334    }
335}
336