LockIcon.java revision 6ebba59aadb287105d4a4bdbf7d1c7851f4fe210
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;
32
33/**
34 * Manages the different states and animations of the unlock icon.
35 */
36public 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
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
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
74    protected void onDetachedFromWindow() {
75        super.onDetachedFromWindow();
76        mTrustDrawable.stop();
77    }
78
79    public void setTransientFpError(boolean transientFpError) {
80        mTransientFpError = transientFpError;
81        update();
82    }
83
84    public void setDeviceInteractive(boolean deviceInteractive) {
85        mDeviceInteractive = deviceInteractive;
86        update();
87    }
88
89    public void setScreenOn(boolean screenOn) {
90        mScreenOn = screenOn;
91        update();
92    }
93
94    @Override
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
107    public void update() {
108        update(false /* force */);
109    }
110
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.start();
172            }
173            mLastState = state;
174            mLastDeviceInteractive = mDeviceInteractive;
175            mLastScreenOn = mScreenOn;
176        }
177
178        // Hide trust circle when fingerprint is running.
179        boolean trustManaged = mUnlockMethodCache.isTrustManaged() && !trustHidden;
180        mTrustDrawable.setTrustManaged(trustManaged);
181        updateClickability();
182    }
183
184    private void updateClickability() {
185        if (mAccessibilityController == null) {
186            return;
187        }
188        boolean clickToUnlock = mAccessibilityController.isTouchExplorationEnabled();
189        boolean clickToForceLock = mUnlockMethodCache.isTrustManaged()
190                && !mAccessibilityController.isAccessibilityEnabled();
191        boolean longClickToForceLock = mUnlockMethodCache.isTrustManaged()
192                && !clickToForceLock;
193        setClickable(clickToForceLock || clickToUnlock);
194        setLongClickable(longClickToForceLock);
195        setFocusable(mAccessibilityController.isAccessibilityEnabled());
196    }
197
198    @Override
199    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
200        super.onInitializeAccessibilityNodeInfo(info);
201        if (mHasFingerPrintIcon) {
202            // Avoid that the button description is also spoken
203            info.setClassName(LockIcon.class.getName());
204            AccessibilityNodeInfo.AccessibilityAction unlock
205                    = new AccessibilityNodeInfo.AccessibilityAction(
206                    AccessibilityNodeInfo.ACTION_CLICK,
207                    getContext().getString(R.string.accessibility_unlock_without_fingerprint));
208            info.addAction(unlock);
209        }
210    }
211
212    public void setAccessibilityController(AccessibilityController accessibilityController) {
213        mAccessibilityController = accessibilityController;
214    }
215
216    private int getIconForState(int state, boolean screenOn, boolean deviceInteractive) {
217        switch (state) {
218            case STATE_LOCKED:
219                return R.drawable.ic_lock_24dp;
220            case STATE_LOCK_OPEN:
221                return R.drawable.ic_lock_open_24dp;
222            case STATE_FACE_UNLOCK:
223                return com.android.internal.R.drawable.ic_account_circle;
224            case STATE_FINGERPRINT:
225                // If screen is off and device asleep, use the draw on animation so the first frame
226                // gets drawn.
227                return screenOn && deviceInteractive
228                        ? R.drawable.ic_fingerprint
229                        : R.drawable.lockscreen_fingerprint_draw_on_animation;
230            case STATE_FINGERPRINT_ERROR:
231                return R.drawable.ic_fingerprint_error;
232            default:
233                throw new IllegalArgumentException();
234        }
235    }
236
237    private int getAnimationResForTransition(int oldState, int newState,
238            boolean oldDeviceInteractive, boolean deviceInteractive,
239            boolean oldScreenOn, boolean screenOn) {
240        if (oldState == STATE_FINGERPRINT && newState == STATE_FINGERPRINT_ERROR) {
241            return R.drawable.lockscreen_fingerprint_fp_to_error_state_animation;
242        } else if (oldState == STATE_LOCK_OPEN && newState == STATE_FINGERPRINT_ERROR) {
243            return R.drawable.trusted_state_to_error_animation;
244        } else if (oldState == STATE_FINGERPRINT_ERROR && newState == STATE_LOCK_OPEN) {
245            return R.drawable.error_to_trustedstate_animation;
246        } else if (oldState == STATE_FINGERPRINT_ERROR && newState == STATE_FINGERPRINT) {
247            return R.drawable.lockscreen_fingerprint_error_state_to_fp_animation;
248        } else if (oldState == STATE_FINGERPRINT && newState == STATE_LOCK_OPEN
249                && !mUnlockMethodCache.isTrusted()) {
250            return R.drawable.lockscreen_fingerprint_draw_off_animation;
251        } else if (newState == STATE_FINGERPRINT && (!oldScreenOn && screenOn && deviceInteractive
252                || screenOn && !oldDeviceInteractive && deviceInteractive)) {
253            return R.drawable.lockscreen_fingerprint_draw_on_animation;
254        } else {
255            return -1;
256        }
257    }
258
259    private int getState() {
260        KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
261        boolean fingerprintRunning = updateMonitor.isFingerprintDetectionRunning();
262        boolean unlockingAllowed = updateMonitor.isUnlockingWithFingerprintAllowed();
263        if (mTransientFpError) {
264            return STATE_FINGERPRINT_ERROR;
265        } else if (mUnlockMethodCache.canSkipBouncer()) {
266            return STATE_LOCK_OPEN;
267        } else if (mUnlockMethodCache.isFaceUnlockRunning()) {
268            return STATE_FACE_UNLOCK;
269        } else if (fingerprintRunning && unlockingAllowed) {
270            return STATE_FINGERPRINT;
271        } else {
272            return STATE_LOCKED;
273        }
274    }
275
276    /**
277     * A wrapper around another Drawable that overrides the intrinsic size.
278     */
279    private static class IntrinsicSizeDrawable extends InsetDrawable {
280
281        private final int mIntrinsicWidth;
282        private final int mIntrinsicHeight;
283
284        public IntrinsicSizeDrawable(Drawable drawable, int intrinsicWidth, int intrinsicHeight) {
285            super(drawable, 0);
286            mIntrinsicWidth = intrinsicWidth;
287            mIntrinsicHeight = intrinsicHeight;
288        }
289
290        @Override
291        public int getIntrinsicWidth() {
292            return mIntrinsicWidth;
293        }
294
295        @Override
296        public int getIntrinsicHeight() {
297            return mIntrinsicHeight;
298        }
299    }
300}
301