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.annotation.IntDef; 20 import android.content.Context; 21 import android.content.res.ColorStateList; 22 import android.content.res.Configuration; 23 import android.graphics.Color; 24 import android.graphics.drawable.Animatable2; 25 import android.graphics.drawable.AnimatedVectorDrawable; 26 import android.graphics.drawable.Drawable; 27 import android.os.Trace; 28 import android.provider.Settings; 29 import android.text.TextUtils; 30 import android.util.AttributeSet; 31 import android.util.SparseArray; 32 import android.view.ViewTreeObserver.OnPreDrawListener; 33 34 import com.android.internal.graphics.ColorUtils; 35 import com.android.systemui.Interpolators; 36 import com.android.systemui.R; 37 import com.android.systemui.statusbar.KeyguardAffordanceView; 38 39 import java.lang.annotation.Retention; 40 import java.lang.annotation.RetentionPolicy; 41 42 /** 43 * Manages the different states and animations of the unlock icon. 44 */ 45 public class LockIcon extends KeyguardAffordanceView { 46 47 static final int STATE_LOCKED = 0; 48 static final int STATE_LOCK_OPEN = 1; 49 static final int STATE_SCANNING_FACE = 2; 50 static final int STATE_BIOMETRICS_ERROR = 3; 51 private float mDozeAmount; 52 private int mIconColor; 53 private int mOldState; 54 private int mState; 55 private boolean mPulsing; 56 private boolean mDozing; 57 private boolean mKeyguardJustShown; 58 private boolean mPredrawRegistered; 59 private final SparseArray<Drawable> mDrawableCache = new SparseArray<>(); 60 61 private final OnPreDrawListener mOnPreDrawListener = new OnPreDrawListener() { 62 @Override 63 public boolean onPreDraw() { 64 getViewTreeObserver().removeOnPreDrawListener(this); 65 mPredrawRegistered = false; 66 67 int newState = mState; 68 Drawable icon = getIcon(newState); 69 setImageDrawable(icon, false); 70 71 if (newState == STATE_SCANNING_FACE) { 72 announceForAccessibility(getResources().getString( 73 R.string.accessibility_scanning_face)); 74 } 75 76 if (icon instanceof AnimatedVectorDrawable) { 77 final AnimatedVectorDrawable animation = (AnimatedVectorDrawable) icon; 78 animation.forceAnimationOnUI(); 79 animation.clearAnimationCallbacks(); 80 animation.registerAnimationCallback( 81 new Animatable2.AnimationCallback() { 82 @Override 83 public void onAnimationEnd(Drawable drawable) { 84 if (getDrawable() == animation 85 && newState == mState 86 && newState == STATE_SCANNING_FACE) { 87 animation.start(); 88 } else { 89 Trace.endAsyncSection("LockIcon#Animation", newState); 90 } 91 } 92 }); 93 Trace.beginAsyncSection("LockIcon#Animation", newState); 94 animation.start(); 95 } 96 97 return true; 98 } 99 }; 100 LockIcon(Context context, AttributeSet attrs)101 public LockIcon(Context context, AttributeSet attrs) { 102 super(context, attrs); 103 } 104 105 @Override onConfigurationChanged(Configuration newConfig)106 protected void onConfigurationChanged(Configuration newConfig) { 107 super.onConfigurationChanged(newConfig); 108 mDrawableCache.clear(); 109 } 110 111 /** 112 * Update the icon visibility 113 * @return true if the visibility changed 114 */ updateIconVisibility(boolean visible)115 boolean updateIconVisibility(boolean visible) { 116 boolean wasVisible = getVisibility() == VISIBLE; 117 if (visible != wasVisible) { 118 setVisibility(visible ? VISIBLE : INVISIBLE); 119 animate().cancel(); 120 if (visible) { 121 setScaleX(0); 122 setScaleY(0); 123 animate() 124 .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN) 125 .scaleX(1) 126 .scaleY(1) 127 .withLayer() 128 .setDuration(233) 129 .start(); 130 } 131 return true; 132 } 133 return false; 134 } 135 update(int newState, boolean pulsing, boolean dozing, boolean keyguardJustShown)136 void update(int newState, boolean pulsing, boolean dozing, boolean keyguardJustShown) { 137 mOldState = mState; 138 mState = newState; 139 mPulsing = pulsing; 140 mDozing = dozing; 141 mKeyguardJustShown = keyguardJustShown; 142 143 if (!mPredrawRegistered) { 144 mPredrawRegistered = true; 145 getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener); 146 } 147 } 148 setDozeAmount(float dozeAmount)149 void setDozeAmount(float dozeAmount) { 150 mDozeAmount = dozeAmount; 151 updateDarkTint(); 152 } 153 onThemeChange(int iconColor)154 void onThemeChange(int iconColor) { 155 mDrawableCache.clear(); 156 mIconColor = iconColor; 157 updateDarkTint(); 158 } 159 updateDarkTint()160 private void updateDarkTint() { 161 int color = ColorUtils.blendARGB(mIconColor, Color.WHITE, mDozeAmount); 162 setImageTintList(ColorStateList.valueOf(color)); 163 } 164 getIcon(int newState)165 private Drawable getIcon(int newState) { 166 @LockAnimIndex final int lockAnimIndex = 167 getAnimationIndexForTransition(mOldState, newState, mPulsing, mDozing, 168 mKeyguardJustShown); 169 170 boolean isAnim = lockAnimIndex != -1; 171 int iconRes = isAnim ? getThemedAnimationResId(lockAnimIndex) : getIconForState(newState); 172 173 if (!mDrawableCache.contains(iconRes)) { 174 mDrawableCache.put(iconRes, getResources().getDrawable(iconRes)); 175 } 176 177 return mDrawableCache.get(iconRes); 178 } 179 getIconForState(int state)180 private static int getIconForState(int state) { 181 int iconRes; 182 switch (state) { 183 case STATE_LOCKED: 184 // Scanning animation is a pulsing padlock. This means that the resting state is 185 // just a padlock. 186 case STATE_SCANNING_FACE: 187 // Error animation also starts and ands on the padlock. 188 case STATE_BIOMETRICS_ERROR: 189 iconRes = com.android.internal.R.drawable.ic_lock; 190 break; 191 case STATE_LOCK_OPEN: 192 iconRes = com.android.internal.R.drawable.ic_lock_open; 193 break; 194 default: 195 throw new IllegalArgumentException(); 196 } 197 198 return iconRes; 199 } 200 getAnimationIndexForTransition(int oldState, int newState, boolean pulsing, boolean dozing, boolean keyguardJustShown)201 private static int getAnimationIndexForTransition(int oldState, int newState, boolean pulsing, 202 boolean dozing, boolean keyguardJustShown) { 203 204 // Never animate when screen is off 205 if (dozing && !pulsing) { 206 return -1; 207 } 208 209 if (newState == STATE_BIOMETRICS_ERROR) { 210 return ERROR; 211 } else if (oldState != STATE_LOCK_OPEN && newState == STATE_LOCK_OPEN) { 212 return UNLOCK; 213 } else if (oldState == STATE_LOCK_OPEN && newState == STATE_LOCKED && !keyguardJustShown) { 214 return LOCK; 215 } else if (newState == STATE_SCANNING_FACE) { 216 return SCANNING; 217 } 218 return -1; 219 } 220 221 @Retention(RetentionPolicy.SOURCE) 222 @IntDef({ERROR, UNLOCK, LOCK, SCANNING}) 223 @interface LockAnimIndex {} 224 static final int ERROR = 0, UNLOCK = 1, LOCK = 2, SCANNING = 3; 225 private static final int[][] LOCK_ANIM_RES_IDS = new int[][] { 226 { 227 R.anim.lock_to_error, 228 R.anim.lock_unlock, 229 R.anim.lock_lock, 230 R.anim.lock_scanning 231 }, 232 { 233 R.anim.lock_to_error_circular, 234 R.anim.lock_unlock_circular, 235 R.anim.lock_lock_circular, 236 R.anim.lock_scanning_circular 237 }, 238 { 239 R.anim.lock_to_error_filled, 240 R.anim.lock_unlock_filled, 241 R.anim.lock_lock_filled, 242 R.anim.lock_scanning_filled 243 }, 244 { 245 R.anim.lock_to_error_rounded, 246 R.anim.lock_unlock_rounded, 247 R.anim.lock_lock_rounded, 248 R.anim.lock_scanning_rounded 249 }, 250 }; 251 getThemedAnimationResId(@ockAnimIndex int lockAnimIndex)252 private int getThemedAnimationResId(@LockAnimIndex int lockAnimIndex) { 253 final String setting = TextUtils.emptyIfNull( 254 Settings.Secure.getString(getContext().getContentResolver(), 255 Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES)); 256 if (setting.contains("com.android.theme.icon_pack.circular.android")) { 257 return LOCK_ANIM_RES_IDS[1][lockAnimIndex]; 258 } else if (setting.contains("com.android.theme.icon_pack.filled.android")) { 259 return LOCK_ANIM_RES_IDS[2][lockAnimIndex]; 260 } else if (setting.contains("com.android.theme.icon_pack.rounded.android")) { 261 return LOCK_ANIM_RES_IDS[3][lockAnimIndex]; 262 } 263 return LOCK_ANIM_RES_IDS[0][lockAnimIndex]; 264 } 265 } 266