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.app.animation.Interpolators; 35 import com.android.internal.graphics.ColorUtils; 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 = Color.TRANSPARENT; 53 private int mOldState; 54 private int mState; 55 private boolean mDozing; 56 private boolean mKeyguardJustShown; 57 private boolean mPredrawRegistered; 58 private final SparseArray<Drawable> mDrawableCache = new SparseArray<>(); 59 60 private final OnPreDrawListener mOnPreDrawListener = new OnPreDrawListener() { 61 @Override 62 public boolean onPreDraw() { 63 getViewTreeObserver().removeOnPreDrawListener(this); 64 mPredrawRegistered = false; 65 66 int newState = mState; 67 Drawable icon = getIcon(newState); 68 setImageDrawable(icon, false); 69 70 if (newState == STATE_SCANNING_FACE) { 71 announceForAccessibility(getResources().getString( 72 R.string.accessibility_scanning_face)); 73 } 74 75 if (icon instanceof AnimatedVectorDrawable) { 76 final AnimatedVectorDrawable animation = (AnimatedVectorDrawable) icon; 77 animation.forceAnimationOnUI(); 78 animation.clearAnimationCallbacks(); 79 animation.registerAnimationCallback( 80 new Animatable2.AnimationCallback() { 81 @Override 82 public void onAnimationEnd(Drawable drawable) { 83 if (getDrawable() == animation 84 && newState == mState 85 && newState == STATE_SCANNING_FACE) { 86 animation.start(); 87 } else { 88 Trace.endAsyncSection("LockIcon#Animation", newState); 89 } 90 } 91 }); 92 Trace.beginAsyncSection("LockIcon#Animation", newState); 93 animation.start(); 94 } 95 96 return true; 97 } 98 }; 99 LockIcon(Context context, AttributeSet attrs)100 public LockIcon(Context context, AttributeSet attrs) { 101 super(context, attrs); 102 } 103 104 @Override onConfigurationChanged(Configuration newConfig)105 protected void onConfigurationChanged(Configuration newConfig) { 106 super.onConfigurationChanged(newConfig); 107 mDrawableCache.clear(); 108 } 109 110 /** 111 * Update the icon visibility 112 * @return true if the visibility changed 113 */ updateIconVisibility(boolean visible)114 boolean updateIconVisibility(boolean visible) { 115 boolean wasVisible = getVisibility() == VISIBLE; 116 if (visible != wasVisible) { 117 setVisibility(visible ? VISIBLE : INVISIBLE); 118 animate().cancel(); 119 if (visible) { 120 setScaleX(0); 121 setScaleY(0); 122 animate() 123 .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN) 124 .scaleX(1) 125 .scaleY(1) 126 .withLayer() 127 .setDuration(233) 128 .start(); 129 } 130 return true; 131 } 132 return false; 133 } 134 update(int newState, boolean dozing, boolean keyguardJustShown)135 void update(int newState, boolean dozing, boolean keyguardJustShown) { 136 mOldState = mState; 137 mState = newState; 138 mDozing = dozing; 139 mKeyguardJustShown = keyguardJustShown; 140 141 if (!mPredrawRegistered) { 142 mPredrawRegistered = true; 143 getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener); 144 } 145 } 146 setDozeAmount(float dozeAmount)147 void setDozeAmount(float dozeAmount) { 148 mDozeAmount = dozeAmount; 149 updateDarkTint(); 150 } 151 updateColor(int iconColor)152 void updateColor(int iconColor) { 153 if (mIconColor == iconColor) { 154 return; 155 } 156 mDrawableCache.clear(); 157 mIconColor = iconColor; 158 updateDarkTint(); 159 } 160 updateDarkTint()161 private void updateDarkTint() { 162 int color = ColorUtils.blendARGB(mIconColor, Color.WHITE, mDozeAmount); 163 setImageTintList(ColorStateList.valueOf(color)); 164 } 165 getIcon(int newState)166 private Drawable getIcon(int newState) { 167 @LockAnimIndex final int lockAnimIndex = 168 getAnimationIndexForTransition(mOldState, newState, mDozing, 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, getContext().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 dozing, boolean keyguardJustShown)201 private static int getAnimationIndexForTransition(int oldState, int newState, boolean dozing, 202 boolean keyguardJustShown) { 203 204 // Never animate when screen is off 205 if (dozing) { 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