blob: 61c1cc82482aa0d9907d45e18aee9650cafea62d [file] [log] [blame]
/*
* 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;
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);
}
/**
* 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);
}
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);
}
}