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