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