Home | History | Annotate | Download | only in phone
      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 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
     33 
     34 /**
     35  * Manages the different states and animations of the unlock icon.
     36  */
     37 public 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, false);
    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.isAccessibilityEnabled();
    211         boolean clickToForceLock = mUnlockMethodCache.isTrustManaged()
    212                 && !clickToUnlock;
    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