• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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