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 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