blob: 8de84bf4e2af41d017b0fb58121cb4332f045a19 [file] [log] [blame]
package com.android.keyguard;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.app.WallpaperManager;
import android.content.Context;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.transition.ChangeBounds;
import android.transition.Transition;
import android.transition.TransitionManager;
import android.transition.TransitionValues;
import android.util.AttributeSet;
import android.util.MathUtils;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.TextClock;
import androidx.annotation.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.keyguard.clock.ClockManager;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
import java.util.TimeZone;
/**
* Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
*/
public class KeyguardClockSwitch extends RelativeLayout {
private final Transition mTransition;
/**
* Optional/alternative clock injected via plugin.
*/
private ClockPlugin mClockPlugin;
/**
* Default clock.
*/
private TextClock mClockView;
/**
* Frame for default and custom clock.
*/
private FrameLayout mSmallClockFrame;
/**
* Container for big custom clock.
*/
private ViewGroup mBigClockContainer;
/**
* Status area (date and other stuff) shown below the clock. Plugin can decide whether
* or not to show it below the alternate clock.
*/
private View mKeyguardStatusArea;
/**
* Maintain state so that a newly connected plugin can be initialized.
*/
private float mDarkAmount;
/**
* If the Keyguard Slice has a header (big center-aligned text.)
*/
private boolean mShowingHeader;
private boolean mSupportsDarkText;
private int[] mColorPalette;
private final StatusBarStateController.StateListener mStateListener =
new StatusBarStateController.StateListener() {
@Override
public void onStateChanged(int newState) {
if (mBigClockContainer == null) {
return;
}
if (newState == StatusBarState.SHADE) {
if (mBigClockContainer.getVisibility() == View.VISIBLE) {
mBigClockContainer.setVisibility(View.INVISIBLE);
}
} else {
if (mBigClockContainer.getVisibility() == View.INVISIBLE) {
mBigClockContainer.setVisibility(View.VISIBLE);
}
}
}
};
private ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
/**
* Listener for changes to the color palette.
*
* The color palette changes when the wallpaper is changed.
*/
private SysuiColorExtractor.OnColorsChangedListener mColorsListener = (extractor, which) -> {
if ((which & WallpaperManager.FLAG_LOCK) != 0) {
if (extractor instanceof SysuiColorExtractor) {
updateColors((SysuiColorExtractor) extractor);
} else {
updateColors(Dependency.get(SysuiColorExtractor.class));
}
}
};
public KeyguardClockSwitch(Context context) {
this(context, null);
}
public KeyguardClockSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
mTransition = new ClockBoundsTransition();
}
/**
* Returns if this view is presenting a custom clock, or the default implementation.
*/
public boolean hasCustomClock() {
return mClockPlugin != null;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mClockView = findViewById(R.id.default_clock_view);
mSmallClockFrame = findViewById(R.id.clock_view);
mKeyguardStatusArea = findViewById(R.id.keyguard_status_area);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
Dependency.get(ClockManager.class).addOnClockChangedListener(mClockChangedListener);
Dependency.get(StatusBarStateController.class).addCallback(mStateListener);
SysuiColorExtractor colorExtractor = Dependency.get(SysuiColorExtractor.class);
colorExtractor.addOnColorsChangedListener(mColorsListener);
updateColors(colorExtractor);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
Dependency.get(ClockManager.class).removeOnClockChangedListener(mClockChangedListener);
Dependency.get(StatusBarStateController.class).removeCallback(mStateListener);
Dependency.get(SysuiColorExtractor.class)
.removeOnColorsChangedListener(mColorsListener);
}
private void setClockPlugin(ClockPlugin plugin) {
// Disconnect from existing plugin.
if (mClockPlugin != null) {
View smallClockView = mClockPlugin.getView();
if (smallClockView != null && smallClockView.getParent() == mSmallClockFrame) {
mSmallClockFrame.removeView(smallClockView);
}
if (mBigClockContainer != null) {
mBigClockContainer.removeAllViews();
mBigClockContainer.setVisibility(View.GONE);
}
mClockPlugin = null;
}
if (plugin == null) {
mClockView.setVisibility(View.VISIBLE);
mKeyguardStatusArea.setVisibility(View.VISIBLE);
return;
}
// Attach small and big clock views to hierarchy.
View smallClockView = plugin.getView();
if (smallClockView != null) {
mSmallClockFrame.addView(smallClockView, -1,
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
mClockView.setVisibility(View.GONE);
}
View bigClockView = plugin.getBigClockView();
if (bigClockView != null && mBigClockContainer != null) {
mBigClockContainer.addView(bigClockView);
mBigClockContainer.setVisibility(View.VISIBLE);
}
// Hide default clock.
if (!plugin.shouldShowStatusArea()) {
mKeyguardStatusArea.setVisibility(View.GONE);
}
// Initialize plugin parameters.
mClockPlugin = plugin;
mClockPlugin.setStyle(getPaint().getStyle());
mClockPlugin.setTextColor(getCurrentTextColor());
mClockPlugin.setDarkAmount(mDarkAmount);
if (mColorPalette != null) {
mClockPlugin.setColorPalette(mSupportsDarkText, mColorPalette);
}
}
/**
* Set container for big clock face appearing behind NSSL and KeyguardStatusView.
*/
public void setBigClockContainer(ViewGroup container) {
if (mClockPlugin != null && container != null) {
View bigClockView = mClockPlugin.getBigClockView();
if (bigClockView != null) {
container.addView(bigClockView);
if (container.getVisibility() == View.GONE) {
container.setVisibility(View.VISIBLE);
}
}
}
mBigClockContainer = container;
}
/**
* It will also update plugin setStyle if plugin is connected.
*/
public void setStyle(Style style) {
mClockView.getPaint().setStyle(style);
if (mClockPlugin != null) {
mClockPlugin.setStyle(style);
}
}
/**
* It will also update plugin setTextColor if plugin is connected.
*/
public void setTextColor(int color) {
mClockView.setTextColor(color);
if (mClockPlugin != null) {
mClockPlugin.setTextColor(color);
}
}
public void setShowCurrentUserTime(boolean showCurrentUserTime) {
mClockView.setShowCurrentUserTime(showCurrentUserTime);
}
public void setTextSize(int unit, float size) {
mClockView.setTextSize(unit, size);
}
public void setFormat12Hour(CharSequence format) {
mClockView.setFormat12Hour(format);
}
public void setFormat24Hour(CharSequence format) {
mClockView.setFormat24Hour(format);
}
/**
* Set the amount (ratio) that the device has transitioned to doze.
* @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
*/
public void setDarkAmount(float darkAmount) {
mDarkAmount = darkAmount;
if (mClockPlugin != null) {
mClockPlugin.setDarkAmount(darkAmount);
}
}
public Paint getPaint() {
return mClockView.getPaint();
}
public int getCurrentTextColor() {
return mClockView.getCurrentTextColor();
}
public float getTextSize() {
return mClockView.getTextSize();
}
public void refresh() {
mClockView.refresh();
}
/**
* Notifies that time tick alarm from doze service fired.
*/
public void dozeTimeTick() {
if (mClockPlugin != null) {
mClockPlugin.dozeTimeTick();
}
}
/**
* Notifies that the time zone has changed.
*/
public void onTimeZoneChanged(TimeZone timeZone) {
if (mClockPlugin != null) {
mClockPlugin.onTimeZoneChanged(timeZone);
}
}
private void updateColors(SysuiColorExtractor colorExtractor) {
ColorExtractor.GradientColors colors = colorExtractor.getColors(WallpaperManager.FLAG_LOCK,
true);
mSupportsDarkText = colors.supportsDarkText();
mColorPalette = colors.getColorPalette();
if (mClockPlugin != null) {
mClockPlugin.setColorPalette(mSupportsDarkText, mColorPalette);
}
}
/**
* Sets if the keyguard slice is showing a center-aligned header. We need a smaller clock
* in these cases.
*/
public void setKeyguardShowingHeader(boolean hasHeader) {
if (mShowingHeader == hasHeader || hasCustomClock()) {
return;
}
mShowingHeader = hasHeader;
TransitionManager.beginDelayedTransition((ViewGroup) mClockView.getParent(), mTransition);
int fontSize = mContext.getResources().getDimensionPixelSize(mShowingHeader
? R.dimen.widget_small_font_size : R.dimen.widget_big_font_size);
int paddingBottom = mContext.getResources().getDimensionPixelSize(mShowingHeader
? R.dimen.widget_vertical_padding_clock : R.dimen.header_subtitle_padding);
mClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize);
mClockView.setPadding(mClockView.getPaddingLeft(), mClockView.getPaddingTop(),
mClockView.getPaddingRight(), paddingBottom);
}
@VisibleForTesting (otherwise = VisibleForTesting.NONE)
ClockManager.ClockChangedListener getClockChangedListener() {
return mClockChangedListener;
}
@VisibleForTesting (otherwise = VisibleForTesting.NONE)
StatusBarStateController.StateListener getStateListener() {
return mStateListener;
}
/**
* Special layout transition that scales the clock view as its bounds change, to make it look
* like the text is shrinking.
*/
private class ClockBoundsTransition extends ChangeBounds {
ClockBoundsTransition() {
setDuration(KeyguardSliceView.DEFAULT_ANIM_DURATION / 2);
setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
}
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
TransitionValues endValues) {
Animator animator = super.createAnimator(sceneRoot, startValues, endValues);
if (animator == null || startValues.view != mClockView) {
return animator;
}
ValueAnimator boundsAnimator = null;
if (animator instanceof AnimatorSet) {
Animator first = ((AnimatorSet) animator).getChildAnimations().get(0);
if (first instanceof ValueAnimator) {
boundsAnimator = (ValueAnimator) first;
}
} else if (animator instanceof ValueAnimator) {
boundsAnimator = (ValueAnimator) animator;
}
if (boundsAnimator != null) {
float bigFontSize = mContext.getResources()
.getDimensionPixelSize(R.dimen.widget_big_font_size);
float smallFontSize = mContext.getResources()
.getDimensionPixelSize(R.dimen.widget_small_font_size);
float startScale = mShowingHeader
? bigFontSize / smallFontSize : smallFontSize / bigFontSize;
boundsAnimator.addUpdateListener(animation -> {
float scale = MathUtils.lerp(startScale, 1f /* stop */,
animation.getAnimatedFraction());
mClockView.setPivotX(mClockView.getWidth() / 2);
mClockView.setPivotY(0);
mClockView.setScaleX(scale);
mClockView.setScaleY(scale);
});
}
return animator;
}
}
}