1 /* 2 * Copyright (C) 2014 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.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.AnimatorSet; 22 import android.animation.ObjectAnimator; 23 import android.content.Context; 24 import android.graphics.drawable.AnimatedVectorDrawable; 25 import android.graphics.drawable.Drawable; 26 import android.text.TextUtils; 27 import android.util.AttributeSet; 28 import android.view.View; 29 import android.widget.TextView; 30 31 import androidx.annotation.StyleRes; 32 33 import com.android.app.animation.Interpolators; 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.systemui.keyguard.KeyguardIndication; 36 import com.android.systemui.res.R; 37 38 /** 39 * A view to show hints on Keyguard ("Swipe up to unlock", "Tap again to open"). 40 */ 41 public class KeyguardIndicationTextView extends TextView { 42 public static final long Y_IN_DURATION = 600L; 43 44 @StyleRes 45 private static int sStyleId = R.style.TextAppearance_Keyguard_BottomArea; 46 @StyleRes 47 private static int sButtonStyleId = R.style.TextAppearance_Keyguard_BottomArea_Button; 48 49 private boolean mAnimationsEnabled = true; 50 private CharSequence mMessage; 51 private KeyguardIndication mKeyguardIndicationInfo; 52 53 private Animator mLastAnimator; 54 private boolean mAlwaysAnnounceText; 55 KeyguardIndicationTextView(Context context)56 public KeyguardIndicationTextView(Context context) { 57 super(context); 58 } 59 KeyguardIndicationTextView(Context context, AttributeSet attrs)60 public KeyguardIndicationTextView(Context context, AttributeSet attrs) { 61 super(context, attrs); 62 } 63 KeyguardIndicationTextView(Context context, AttributeSet attrs, int defStyleAttr)64 public KeyguardIndicationTextView(Context context, AttributeSet attrs, int defStyleAttr) { 65 super(context, attrs, defStyleAttr); 66 } 67 KeyguardIndicationTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)68 public KeyguardIndicationTextView(Context context, AttributeSet attrs, int defStyleAttr, 69 int defStyleRes) { 70 super(context, attrs, defStyleAttr, defStyleRes); 71 } 72 73 /** 74 * Clears message queue and currently shown message. 75 */ clearMessages()76 public void clearMessages() { 77 if (mLastAnimator != null) { 78 mLastAnimator.cancel(); 79 } 80 mMessage = ""; 81 setText(""); 82 } 83 84 /** 85 * Changes the text with an animation. 86 */ switchIndication(int textResId)87 public void switchIndication(int textResId) { 88 switchIndication(getResources().getText(textResId), null); 89 } 90 91 /** 92 * Changes the text with an animation. 93 * 94 * @param indication The text to show. 95 */ switchIndication(KeyguardIndication indication)96 public void switchIndication(KeyguardIndication indication) { 97 switchIndication(indication == null ? null : indication.getMessage(), indication); 98 } 99 100 /** 101 * Changes the text with an animation. 102 */ switchIndication(CharSequence text, KeyguardIndication indication)103 public void switchIndication(CharSequence text, KeyguardIndication indication) { 104 switchIndication(text, indication, true, null); 105 } 106 107 /** 108 * Controls whether the text displayed in the indication area will be announced always. 109 */ setAlwaysAnnounceEnabled(boolean enabled)110 public void setAlwaysAnnounceEnabled(boolean enabled) { 111 this.mAlwaysAnnounceText = enabled; 112 if (mAlwaysAnnounceText) { 113 // We will announce the text programmatically anyway. 114 setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_NONE); 115 } else { 116 setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE); 117 } 118 } 119 120 /** 121 * Updates the text with an optional animation. 122 * 123 * @param text The text to show. 124 * @param indication optional display information for the text 125 * @param animate whether to animate this indication in - we may not want this on AOD 126 * @param onAnimationEndCallback runnable called after this indication is animated in 127 */ switchIndication(CharSequence text, KeyguardIndication indication, boolean animate, Runnable onAnimationEndCallback)128 public void switchIndication(CharSequence text, KeyguardIndication indication, 129 boolean animate, Runnable onAnimationEndCallback) { 130 mMessage = text; 131 mKeyguardIndicationInfo = indication; 132 133 if (animate) { 134 final boolean hasIcon = indication != null && indication.getIcon() != null; 135 AnimatorSet animator = new AnimatorSet(); 136 // Make sure each animation is visible for a minimum amount of time, while not worrying 137 // about fading in blank text 138 if (!TextUtils.isEmpty(mMessage) || hasIcon) { 139 Animator inAnimator = getInAnimator(); 140 inAnimator.addListener(new AnimatorListenerAdapter() { 141 @Override 142 public void onAnimationEnd(Animator animation) { 143 super.onAnimationEnd(animation); 144 if (onAnimationEndCallback != null) { 145 onAnimationEndCallback.run(); 146 } 147 } 148 }); 149 animator.playSequentially(getOutAnimator(), inAnimator); 150 } else { 151 Animator outAnimator = getOutAnimator(); 152 outAnimator.addListener(new AnimatorListenerAdapter() { 153 @Override 154 public void onAnimationEnd(Animator animation) { 155 super.onAnimationEnd(animation); 156 if (onAnimationEndCallback != null) { 157 onAnimationEndCallback.run(); 158 } 159 } 160 }); 161 animator.play(outAnimator); 162 } 163 164 if (mLastAnimator != null) { 165 mLastAnimator.cancel(); 166 } 167 mLastAnimator = animator; 168 animator.start(); 169 } else { 170 setAlpha(1f); 171 setTranslationY(0f); 172 setNextIndication(); 173 if (onAnimationEndCallback != null) { 174 onAnimationEndCallback.run(); 175 } 176 if (mLastAnimator != null) { 177 mLastAnimator.cancel(); 178 mLastAnimator = null; 179 } 180 } 181 } 182 183 /** 184 * Get the message that should be shown after the previous text animates out. 185 */ getMessage()186 public CharSequence getMessage() { 187 return mMessage; 188 } 189 getOutAnimator()190 private AnimatorSet getOutAnimator() { 191 AnimatorSet animatorSet = new AnimatorSet(); 192 Animator fadeOut = ObjectAnimator.ofFloat(this, View.ALPHA, 0f); 193 fadeOut.setDuration(getFadeOutDuration()); 194 fadeOut.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); 195 fadeOut.addListener(new AnimatorListenerAdapter() { 196 private boolean mCancelled = false; 197 @Override 198 public void onAnimationEnd(Animator animator) { 199 super.onAnimationEnd(animator); 200 if (!mCancelled) { 201 setNextIndication(); 202 } 203 } 204 205 @Override 206 public void onAnimationCancel(Animator animator) { 207 super.onAnimationCancel(animator); 208 mCancelled = true; 209 setAlpha(0); 210 } 211 }); 212 213 Animator yTranslate = 214 ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, 0, -getYTranslationPixels()); 215 yTranslate.setDuration(getFadeOutDuration()); 216 animatorSet.playTogether(fadeOut, yTranslate); 217 218 return animatorSet; 219 } 220 setNextIndication()221 private void setNextIndication() { 222 boolean forceAssertiveAccessibilityLiveRegion = false; 223 if (mKeyguardIndicationInfo != null) { 224 // First, update the style. 225 // If a background is set on the text, we don't want shadow on the text 226 if (mKeyguardIndicationInfo.getBackground() != null) { 227 setTextAppearance(sButtonStyleId); 228 } else { 229 setTextAppearance(sStyleId); 230 } 231 setBackground(mKeyguardIndicationInfo.getBackground()); 232 setTextColor(mKeyguardIndicationInfo.getTextColor()); 233 setOnClickListener(mKeyguardIndicationInfo.getClickListener()); 234 setClickable(mKeyguardIndicationInfo.getClickListener() != null); 235 final Drawable icon = mKeyguardIndicationInfo.getIcon(); 236 if (icon != null) { 237 icon.setTint(getCurrentTextColor()); 238 if (icon instanceof AnimatedVectorDrawable) { 239 ((AnimatedVectorDrawable) icon).start(); 240 } 241 } 242 setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null); 243 forceAssertiveAccessibilityLiveRegion = 244 mKeyguardIndicationInfo.getForceAssertiveAccessibilityLiveRegion(); 245 } 246 if (!forceAssertiveAccessibilityLiveRegion) { 247 setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_NONE); 248 } 249 setText(mMessage); 250 if (forceAssertiveAccessibilityLiveRegion) { 251 setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_ASSERTIVE); 252 } 253 if (mAlwaysAnnounceText) { 254 announceForAccessibility(mMessage); 255 } 256 } 257 getInAnimator()258 private AnimatorSet getInAnimator() { 259 AnimatorSet animatorSet = new AnimatorSet(); 260 ObjectAnimator fadeIn = ObjectAnimator.ofFloat(this, View.ALPHA, 1f); 261 fadeIn.setStartDelay(getFadeInDelay()); 262 fadeIn.setDuration(getFadeInDuration()); 263 fadeIn.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); 264 265 Animator yTranslate = 266 ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, getYTranslationPixels(), 0); 267 yTranslate.setDuration(getYInDuration()); 268 yTranslate.addListener(new AnimatorListenerAdapter() { 269 @Override 270 public void onAnimationCancel(Animator animation) { 271 super.onAnimationCancel(animation); 272 setTranslationY(0); 273 setAlpha(1f); 274 } 275 }); 276 animatorSet.playTogether(yTranslate, fadeIn); 277 278 return animatorSet; 279 } 280 281 @VisibleForTesting setAnimationsEnabled(boolean enabled)282 public void setAnimationsEnabled(boolean enabled) { 283 mAnimationsEnabled = enabled; 284 } 285 getFadeInDelay()286 private long getFadeInDelay() { 287 if (!mAnimationsEnabled) return 0L; 288 return 150L; 289 } 290 getFadeInDuration()291 private long getFadeInDuration() { 292 if (!mAnimationsEnabled) return 0L; 293 return 317L; 294 } 295 getYInDuration()296 private long getYInDuration() { 297 if (!mAnimationsEnabled) return 0L; 298 return Y_IN_DURATION; 299 } 300 getFadeOutDuration()301 private long getFadeOutDuration() { 302 if (!mAnimationsEnabled) return 0L; 303 return 167L; 304 } 305 getYTranslationPixels()306 private int getYTranslationPixels() { 307 return mContext.getResources().getDimensionPixelSize( 308 com.android.systemui.res.R.dimen.keyguard_indication_y_translation); 309 } 310 } 311