blob: a5b5f1cbf1e730e474685f8f2992859becd13493 [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.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.keyguard.KeyguardIndication;
import java.util.LinkedList;
/**
* A view to show hints on Keyguard ("Swipe up to unlock", "Tap again to open").
*/
public class KeyguardIndicationTextView extends TextView {
private static final long MSG_MIN_DURATION_MILLIS_DEFAULT = 1500;
@StyleRes
private static int sStyleId = R.style.TextAppearance_Keyguard_BottomArea;
@StyleRes
private static int sButtonStyleId = R.style.TextAppearance_Keyguard_BottomArea_Button;
private long mNextAnimationTime = 0;
private boolean mAnimationsEnabled = true;
private LinkedList<CharSequence> mMessages = new LinkedList<>();
private LinkedList<KeyguardIndication> mKeyguardIndicationInfo = new LinkedList<>();
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.
*/
public void clearMessages() {
mMessages.clear();
mKeyguardIndicationInfo.clear();
}
/**
* Changes the text with an animation and makes sure a single indication is shown long enough.
*/
public void switchIndication(int textResId) {
switchIndication(getResources().getText(textResId), null);
}
/**
* Changes the text with an animation and makes sure a single indication is shown long enough.
*
* @param indication The text to show.
*/
public void switchIndication(KeyguardIndication indication) {
switchIndication(indication == null ? null : indication.getMessage(), indication);
}
/**
* Changes the text with an animation and makes sure a single indication is shown long enough.
*
* @param text The text to show.
* @param indication optional display information for the text
*/
public void switchIndication(CharSequence text, KeyguardIndication indication) {
if (text == null) text = "";
CharSequence lastPendingMessage = mMessages.peekLast();
if (TextUtils.equals(lastPendingMessage, text)
|| (lastPendingMessage == null && TextUtils.equals(text, getText()))) {
return;
}
mMessages.add(text);
mKeyguardIndicationInfo.add(indication);
final boolean hasIcon = indication != null && indication.getIcon() != null;
final AnimatorSet animSet = new AnimatorSet();
final AnimatorSet.Builder animSetBuilder = animSet.play(getOutAnimator());
// Make sure each animation is visible for a minimum amount of time, while not worrying
// about fading in blank text
long timeInMillis = System.currentTimeMillis();
long delay = Math.max(0, mNextAnimationTime - timeInMillis);
setNextAnimationTime(timeInMillis + delay + getFadeOutDuration());
final long minDurationMillis =
(indication != null && indication.getMinVisibilityMillis() != null)
? indication.getMinVisibilityMillis()
: MSG_MIN_DURATION_MILLIS_DEFAULT;
if (!text.equals("") || hasIcon) {
setNextAnimationTime(mNextAnimationTime + minDurationMillis);
animSetBuilder.before(getInAnimator());
}
animSet.setStartDelay(delay);
animSet.start();
}
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() {
@Override
public void onAnimationEnd(Animator animator) {
KeyguardIndication info = mKeyguardIndicationInfo.poll();
if (info != null) {
// First, update the style.
// If a background is set on the text, we don't want shadow on the text
if (info.getBackground() != null) {
setTextAppearance(sButtonStyleId);
} else {
setTextAppearance(sStyleId);
}
setBackground(info.getBackground());
setTextColor(info.getTextColor());
setOnClickListener(info.getClickListener());
setClickable(info.getClickListener() != null);
final Drawable icon = info.getIcon();
if (icon != null) {
icon.setTint(getCurrentTextColor());
if (icon instanceof AnimatedVectorDrawable) {
((AnimatedVectorDrawable) icon).start();
}
}
setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null);
}
setText(mMessages.poll());
}
});
Animator yTranslate =
ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, 0, -getYTranslationPixels());
yTranslate.setDuration(getFadeOutDuration());
animatorSet.playTogether(fadeOut, yTranslate);
return animatorSet;
}
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) {
setTranslationY(0);
}
});
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 600L;
}
private long getFadeOutDuration() {
if (!mAnimationsEnabled) return 0L;
return 167L;
}
private void setNextAnimationTime(long time) {
if (mAnimationsEnabled) {
mNextAnimationTime = time;
} else {
mNextAnimationTime = 0L;
}
}
private int getYTranslationPixels() {
return mContext.getResources().getDimensionPixelSize(
com.android.systemui.R.dimen.keyguard_indication_y_translation);
}
}