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.R; 36 import com.android.systemui.keyguard.KeyguardIndication; 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 if (mKeyguardIndicationInfo != null) { 223 // First, update the style. 224 // If a background is set on the text, we don't want shadow on the text 225 if (mKeyguardIndicationInfo.getBackground() != null) { 226 setTextAppearance(sButtonStyleId); 227 } else { 228 setTextAppearance(sStyleId); 229 } 230 setBackground(mKeyguardIndicationInfo.getBackground()); 231 setTextColor(mKeyguardIndicationInfo.getTextColor()); 232 setOnClickListener(mKeyguardIndicationInfo.getClickListener()); 233 setClickable(mKeyguardIndicationInfo.getClickListener() != null); 234 final Drawable icon = mKeyguardIndicationInfo.getIcon(); 235 if (icon != null) { 236 icon.setTint(getCurrentTextColor()); 237 if (icon instanceof AnimatedVectorDrawable) { 238 ((AnimatedVectorDrawable) icon).start(); 239 } 240 } 241 setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null); 242 } 243 setText(mMessage); 244 if (mAlwaysAnnounceText) { 245 announceForAccessibility(mMessage); 246 } 247 } 248 getInAnimator()249 private AnimatorSet getInAnimator() { 250 AnimatorSet animatorSet = new AnimatorSet(); 251 ObjectAnimator fadeIn = ObjectAnimator.ofFloat(this, View.ALPHA, 1f); 252 fadeIn.setStartDelay(getFadeInDelay()); 253 fadeIn.setDuration(getFadeInDuration()); 254 fadeIn.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); 255 256 Animator yTranslate = 257 ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, getYTranslationPixels(), 0); 258 yTranslate.setDuration(getYInDuration()); 259 yTranslate.addListener(new AnimatorListenerAdapter() { 260 @Override 261 public void onAnimationCancel(Animator animation) { 262 super.onAnimationCancel(animation); 263 setTranslationY(0); 264 setAlpha(1f); 265 } 266 }); 267 animatorSet.playTogether(yTranslate, fadeIn); 268 269 return animatorSet; 270 } 271 272 @VisibleForTesting setAnimationsEnabled(boolean enabled)273 public void setAnimationsEnabled(boolean enabled) { 274 mAnimationsEnabled = enabled; 275 } 276 getFadeInDelay()277 private long getFadeInDelay() { 278 if (!mAnimationsEnabled) return 0L; 279 return 150L; 280 } 281 getFadeInDuration()282 private long getFadeInDuration() { 283 if (!mAnimationsEnabled) return 0L; 284 return 317L; 285 } 286 getYInDuration()287 private long getYInDuration() { 288 if (!mAnimationsEnabled) return 0L; 289 return Y_IN_DURATION; 290 } 291 getFadeOutDuration()292 private long getFadeOutDuration() { 293 if (!mAnimationsEnabled) return 0L; 294 return 167L; 295 } 296 getYTranslationPixels()297 private int getYTranslationPixels() { 298 return mContext.getResources().getDimensionPixelSize( 299 com.android.systemui.R.dimen.keyguard_indication_y_translation); 300 } 301 } 302