/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.systemui.statusbar.phone; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.widget.TextView; import androidx.annotation.StyleRes; import com.android.app.animation.Interpolators; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; import com.android.systemui.keyguard.KeyguardIndication; /** * A view to show hints on Keyguard ("Swipe up to unlock", "Tap again to open"). */ public class KeyguardIndicationTextView extends TextView { public static final long Y_IN_DURATION = 600L; @StyleRes private static int sStyleId = R.style.TextAppearance_Keyguard_BottomArea; @StyleRes private static int sButtonStyleId = R.style.TextAppearance_Keyguard_BottomArea_Button; private boolean mAnimationsEnabled = true; private CharSequence mMessage; private KeyguardIndication mKeyguardIndicationInfo; private Animator mLastAnimator; private boolean mAlwaysAnnounceText; public KeyguardIndicationTextView(Context context) { super(context); } public KeyguardIndicationTextView(Context context, AttributeSet attrs) { super(context, attrs); } public KeyguardIndicationTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public KeyguardIndicationTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } /** * Clears message queue and currently shown message. */ public void clearMessages() { if (mLastAnimator != null) { mLastAnimator.cancel(); } mMessage = ""; setText(""); } /** * Changes the text with an animation. */ public void switchIndication(int textResId) { switchIndication(getResources().getText(textResId), null); } /** * Changes the text with an animation. * * @param indication The text to show. */ public void switchIndication(KeyguardIndication indication) { switchIndication(indication == null ? null : indication.getMessage(), indication); } /** * Changes the text with an animation. */ public void switchIndication(CharSequence text, KeyguardIndication indication) { switchIndication(text, indication, true, null); } /** * Controls whether the text displayed in the indication area will be announced always. */ public void setAlwaysAnnounceEnabled(boolean enabled) { this.mAlwaysAnnounceText = enabled; if (mAlwaysAnnounceText) { // We will announce the text programmatically anyway. setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_NONE); } else { setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE); } } /** * Updates the text with an optional animation. * * @param text The text to show. * @param indication optional display information for the text * @param animate whether to animate this indication in - we may not want this on AOD * @param onAnimationEndCallback runnable called after this indication is animated in */ public void switchIndication(CharSequence text, KeyguardIndication indication, boolean animate, Runnable onAnimationEndCallback) { mMessage = text; mKeyguardIndicationInfo = indication; if (animate) { final boolean hasIcon = indication != null && indication.getIcon() != null; AnimatorSet animator = new AnimatorSet(); // Make sure each animation is visible for a minimum amount of time, while not worrying // about fading in blank text if (!TextUtils.isEmpty(mMessage) || hasIcon) { Animator inAnimator = getInAnimator(); inAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); if (onAnimationEndCallback != null) { onAnimationEndCallback.run(); } } }); animator.playSequentially(getOutAnimator(), inAnimator); } else { Animator outAnimator = getOutAnimator(); outAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); if (onAnimationEndCallback != null) { onAnimationEndCallback.run(); } } }); animator.play(outAnimator); } if (mLastAnimator != null) { mLastAnimator.cancel(); } mLastAnimator = animator; animator.start(); } else { setAlpha(1f); setTranslationY(0f); setNextIndication(); if (onAnimationEndCallback != null) { onAnimationEndCallback.run(); } if (mLastAnimator != null) { mLastAnimator.cancel(); mLastAnimator = null; } } } /** * Get the message that should be shown after the previous text animates out. */ public CharSequence getMessage() { return mMessage; } private AnimatorSet getOutAnimator() { AnimatorSet animatorSet = new AnimatorSet(); Animator fadeOut = ObjectAnimator.ofFloat(this, View.ALPHA, 0f); fadeOut.setDuration(getFadeOutDuration()); fadeOut.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); fadeOut.addListener(new AnimatorListenerAdapter() { private boolean mCancelled = false; @Override public void onAnimationEnd(Animator animator) { super.onAnimationEnd(animator); if (!mCancelled) { setNextIndication(); } } @Override public void onAnimationCancel(Animator animator) { super.onAnimationCancel(animator); mCancelled = true; setAlpha(0); } }); Animator yTranslate = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, 0, -getYTranslationPixels()); yTranslate.setDuration(getFadeOutDuration()); animatorSet.playTogether(fadeOut, yTranslate); return animatorSet; } private void setNextIndication() { if (mKeyguardIndicationInfo != null) { // First, update the style. // If a background is set on the text, we don't want shadow on the text if (mKeyguardIndicationInfo.getBackground() != null) { setTextAppearance(sButtonStyleId); } else { setTextAppearance(sStyleId); } setBackground(mKeyguardIndicationInfo.getBackground()); setTextColor(mKeyguardIndicationInfo.getTextColor()); setOnClickListener(mKeyguardIndicationInfo.getClickListener()); setClickable(mKeyguardIndicationInfo.getClickListener() != null); final Drawable icon = mKeyguardIndicationInfo.getIcon(); if (icon != null) { icon.setTint(getCurrentTextColor()); if (icon instanceof AnimatedVectorDrawable) { ((AnimatedVectorDrawable) icon).start(); } } setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null); } setText(mMessage); if (mAlwaysAnnounceText) { announceForAccessibility(mMessage); } } private AnimatorSet getInAnimator() { AnimatorSet animatorSet = new AnimatorSet(); ObjectAnimator fadeIn = ObjectAnimator.ofFloat(this, View.ALPHA, 1f); fadeIn.setStartDelay(getFadeInDelay()); fadeIn.setDuration(getFadeInDuration()); fadeIn.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); Animator yTranslate = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, getYTranslationPixels(), 0); yTranslate.setDuration(getYInDuration()); yTranslate.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationCancel(Animator animation) { super.onAnimationCancel(animation); setTranslationY(0); setAlpha(1f); } }); animatorSet.playTogether(yTranslate, fadeIn); return animatorSet; } @VisibleForTesting public void setAnimationsEnabled(boolean enabled) { mAnimationsEnabled = enabled; } private long getFadeInDelay() { if (!mAnimationsEnabled) return 0L; return 150L; } private long getFadeInDuration() { if (!mAnimationsEnabled) return 0L; return 317L; } private long getYInDuration() { if (!mAnimationsEnabled) return 0L; return Y_IN_DURATION; } private long getFadeOutDuration() { if (!mAnimationsEnabled) return 0L; return 167L; } private int getYTranslationPixels() { return mContext.getResources().getDimensionPixelSize( com.android.systemui.R.dimen.keyguard_indication_y_translation); } }