blob: 8de84bf4e2af41d017b0fb58121cb4332f045a19 [file] [log] [blame]
Kunhung Li29007e62018-07-30 19:30:25 +08001package com.android.keyguard;
2
Lucas Dupine570af62019-02-10 14:52:30 -08003import android.animation.Animator;
4import android.animation.AnimatorSet;
5import android.animation.ValueAnimator;
Robert Snoeberger0397c842019-02-07 14:25:46 -05006import android.app.WallpaperManager;
Kunhung Li29007e62018-07-30 19:30:25 +08007import android.content.Context;
8import android.graphics.Paint;
9import android.graphics.Paint.Style;
Lucas Dupine570af62019-02-10 14:52:30 -080010import android.transition.ChangeBounds;
11import android.transition.Transition;
12import android.transition.TransitionManager;
13import android.transition.TransitionValues;
Kunhung Li29007e62018-07-30 19:30:25 +080014import android.util.AttributeSet;
Lucas Dupine570af62019-02-10 14:52:30 -080015import android.util.MathUtils;
16import android.util.TypedValue;
Kunhung Li29007e62018-07-30 19:30:25 +080017import android.view.View;
18import android.view.ViewGroup;
19import android.widget.FrameLayout;
Robert Snoebergere3b3e782018-12-17 13:32:15 -050020import android.widget.RelativeLayout;
Kunhung Li29007e62018-07-30 19:30:25 +080021import android.widget.TextClock;
22
23import androidx.annotation.VisibleForTesting;
24
Robert Snoeberger0397c842019-02-07 14:25:46 -050025import com.android.internal.colorextraction.ColorExtractor;
Robert Snoeberger15b4af12019-01-18 15:37:27 -050026import com.android.keyguard.clock.ClockManager;
Kunhung Li29007e62018-07-30 19:30:25 +080027import com.android.systemui.Dependency;
Lucas Dupine570af62019-02-10 14:52:30 -080028import com.android.systemui.Interpolators;
Robert Snoeberger0397c842019-02-07 14:25:46 -050029import com.android.systemui.colorextraction.SysuiColorExtractor;
Kunhung Li29007e62018-07-30 19:30:25 +080030import com.android.systemui.plugins.ClockPlugin;
Beverly8fdb5332019-02-04 14:29:49 -050031import com.android.systemui.plugins.statusbar.StatusBarStateController;
Robert Snoeberger60854082019-01-04 13:13:17 -050032import com.android.systemui.statusbar.StatusBarState;
Kunhung Li29007e62018-07-30 19:30:25 +080033
Robert Snoeberger9c1074f2018-12-07 17:35:21 -050034import java.util.TimeZone;
Robert Snoeberger33ce6d92018-10-26 10:52:38 -040035
Kunhung Li29007e62018-07-30 19:30:25 +080036/**
37 * Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
38 */
Robert Snoebergere3b3e782018-12-17 13:32:15 -050039public class KeyguardClockSwitch extends RelativeLayout {
Robert Snoeberger046ee9c2019-01-10 18:29:38 -050040
Lucas Dupine570af62019-02-10 14:52:30 -080041 private final Transition mTransition;
Kunhung Li29007e62018-07-30 19:30:25 +080042 /**
43 * Optional/alternative clock injected via plugin.
44 */
45 private ClockPlugin mClockPlugin;
46 /**
47 * Default clock.
48 */
49 private TextClock mClockView;
Robert Snoebergere3b3e782018-12-17 13:32:15 -050050 /**
51 * Frame for default and custom clock.
52 */
Robert Snoeberger6a0140a2018-12-20 12:46:17 -050053 private FrameLayout mSmallClockFrame;
54 /**
55 * Container for big custom clock.
56 */
57 private ViewGroup mBigClockContainer;
Robert Snoebergere3b3e782018-12-17 13:32:15 -050058 /**
59 * Status area (date and other stuff) shown below the clock. Plugin can decide whether
60 * or not to show it below the alternate clock.
61 */
62 private View mKeyguardStatusArea;
Robert Snoeberger8bf1a3c2019-01-10 10:30:09 -050063 /**
Robert Snoeberger58f23152019-01-10 15:51:32 -050064 * Maintain state so that a newly connected plugin can be initialized.
65 */
66 private float mDarkAmount;
Lucas Dupine570af62019-02-10 14:52:30 -080067 /**
68 * If the Keyguard Slice has a header (big center-aligned text.)
69 */
70 private boolean mShowingHeader;
Robert Snoeberger0397c842019-02-07 14:25:46 -050071 private boolean mSupportsDarkText;
72 private int[] mColorPalette;
Kunhung Li29007e62018-07-30 19:30:25 +080073
Robert Snoeberger60854082019-01-04 13:13:17 -050074 private final StatusBarStateController.StateListener mStateListener =
75 new StatusBarStateController.StateListener() {
76 @Override
77 public void onStateChanged(int newState) {
78 if (mBigClockContainer == null) {
79 return;
80 }
81 if (newState == StatusBarState.SHADE) {
82 if (mBigClockContainer.getVisibility() == View.VISIBLE) {
83 mBigClockContainer.setVisibility(View.INVISIBLE);
84 }
85 } else {
86 if (mBigClockContainer.getVisibility() == View.INVISIBLE) {
87 mBigClockContainer.setVisibility(View.VISIBLE);
88 }
89 }
90 }
91 };
Kunhung Li29007e62018-07-30 19:30:25 +080092
Robert Snoeberger15b4af12019-01-18 15:37:27 -050093 private ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
Robert Snoeberger046ee9c2019-01-10 18:29:38 -050094
Robert Snoeberger0397c842019-02-07 14:25:46 -050095 /**
96 * Listener for changes to the color palette.
97 *
98 * The color palette changes when the wallpaper is changed.
99 */
100 private SysuiColorExtractor.OnColorsChangedListener mColorsListener = (extractor, which) -> {
101 if ((which & WallpaperManager.FLAG_LOCK) != 0) {
102 if (extractor instanceof SysuiColorExtractor) {
103 updateColors((SysuiColorExtractor) extractor);
104 } else {
105 updateColors(Dependency.get(SysuiColorExtractor.class));
106 }
107 }
108 };
109
Kunhung Li29007e62018-07-30 19:30:25 +0800110 public KeyguardClockSwitch(Context context) {
111 this(context, null);
112 }
113
114 public KeyguardClockSwitch(Context context, AttributeSet attrs) {
115 super(context, attrs);
Lucas Dupine570af62019-02-10 14:52:30 -0800116 mTransition = new ClockBoundsTransition();
Kunhung Li29007e62018-07-30 19:30:25 +0800117 }
118
Lucas Dupin7fc9dc12019-01-03 09:19:43 -0800119 /**
120 * Returns if this view is presenting a custom clock, or the default implementation.
121 */
122 public boolean hasCustomClock() {
123 return mClockPlugin != null;
124 }
125
Kunhung Li29007e62018-07-30 19:30:25 +0800126 @Override
127 protected void onFinishInflate() {
128 super.onFinishInflate();
129 mClockView = findViewById(R.id.default_clock_view);
Robert Snoeberger6a0140a2018-12-20 12:46:17 -0500130 mSmallClockFrame = findViewById(R.id.clock_view);
Robert Snoebergere3b3e782018-12-17 13:32:15 -0500131 mKeyguardStatusArea = findViewById(R.id.keyguard_status_area);
Kunhung Li29007e62018-07-30 19:30:25 +0800132 }
133
134 @Override
135 protected void onAttachedToWindow() {
136 super.onAttachedToWindow();
Robert Snoeberger15b4af12019-01-18 15:37:27 -0500137 Dependency.get(ClockManager.class).addOnClockChangedListener(mClockChangedListener);
Robert Snoeberger60854082019-01-04 13:13:17 -0500138 Dependency.get(StatusBarStateController.class).addCallback(mStateListener);
Robert Snoeberger0397c842019-02-07 14:25:46 -0500139 SysuiColorExtractor colorExtractor = Dependency.get(SysuiColorExtractor.class);
140 colorExtractor.addOnColorsChangedListener(mColorsListener);
141 updateColors(colorExtractor);
Kunhung Li29007e62018-07-30 19:30:25 +0800142 }
143
144 @Override
145 protected void onDetachedFromWindow() {
146 super.onDetachedFromWindow();
Robert Snoeberger15b4af12019-01-18 15:37:27 -0500147 Dependency.get(ClockManager.class).removeOnClockChangedListener(mClockChangedListener);
Robert Snoeberger60854082019-01-04 13:13:17 -0500148 Dependency.get(StatusBarStateController.class).removeCallback(mStateListener);
Robert Snoeberger0397c842019-02-07 14:25:46 -0500149 Dependency.get(SysuiColorExtractor.class)
150 .removeOnColorsChangedListener(mColorsListener);
Kunhung Li29007e62018-07-30 19:30:25 +0800151 }
152
Robert Snoeberger8bf1a3c2019-01-10 10:30:09 -0500153 private void setClockPlugin(ClockPlugin plugin) {
154 // Disconnect from existing plugin.
155 if (mClockPlugin != null) {
156 View smallClockView = mClockPlugin.getView();
157 if (smallClockView != null && smallClockView.getParent() == mSmallClockFrame) {
158 mSmallClockFrame.removeView(smallClockView);
159 }
160 if (mBigClockContainer != null) {
161 mBigClockContainer.removeAllViews();
162 mBigClockContainer.setVisibility(View.GONE);
163 }
164 mClockPlugin = null;
165 }
166 if (plugin == null) {
167 mClockView.setVisibility(View.VISIBLE);
168 mKeyguardStatusArea.setVisibility(View.VISIBLE);
169 return;
170 }
171 // Attach small and big clock views to hierarchy.
172 View smallClockView = plugin.getView();
173 if (smallClockView != null) {
174 mSmallClockFrame.addView(smallClockView, -1,
175 new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
176 ViewGroup.LayoutParams.WRAP_CONTENT));
177 mClockView.setVisibility(View.GONE);
178 }
179 View bigClockView = plugin.getBigClockView();
180 if (bigClockView != null && mBigClockContainer != null) {
181 mBigClockContainer.addView(bigClockView);
182 mBigClockContainer.setVisibility(View.VISIBLE);
183 }
184 // Hide default clock.
185 if (!plugin.shouldShowStatusArea()) {
186 mKeyguardStatusArea.setVisibility(View.GONE);
187 }
188 // Initialize plugin parameters.
189 mClockPlugin = plugin;
190 mClockPlugin.setStyle(getPaint().getStyle());
191 mClockPlugin.setTextColor(getCurrentTextColor());
Robert Snoeberger58f23152019-01-10 15:51:32 -0500192 mClockPlugin.setDarkAmount(mDarkAmount);
Robert Snoeberger0397c842019-02-07 14:25:46 -0500193 if (mColorPalette != null) {
194 mClockPlugin.setColorPalette(mSupportsDarkText, mColorPalette);
195 }
Robert Snoeberger8bf1a3c2019-01-10 10:30:09 -0500196 }
197
Kunhung Li29007e62018-07-30 19:30:25 +0800198 /**
Robert Snoeberger6a0140a2018-12-20 12:46:17 -0500199 * Set container for big clock face appearing behind NSSL and KeyguardStatusView.
200 */
201 public void setBigClockContainer(ViewGroup container) {
202 if (mClockPlugin != null && container != null) {
203 View bigClockView = mClockPlugin.getBigClockView();
204 if (bigClockView != null) {
205 container.addView(bigClockView);
Robert Snoebergera1df7fb2019-02-11 14:00:14 -0500206 if (container.getVisibility() == View.GONE) {
207 container.setVisibility(View.VISIBLE);
208 }
Robert Snoeberger6a0140a2018-12-20 12:46:17 -0500209 }
210 }
211 mBigClockContainer = container;
212 }
213
214 /**
Kunhung Li29007e62018-07-30 19:30:25 +0800215 * It will also update plugin setStyle if plugin is connected.
216 */
217 public void setStyle(Style style) {
218 mClockView.getPaint().setStyle(style);
219 if (mClockPlugin != null) {
220 mClockPlugin.setStyle(style);
221 }
222 }
223
224 /**
225 * It will also update plugin setTextColor if plugin is connected.
226 */
227 public void setTextColor(int color) {
228 mClockView.setTextColor(color);
229 if (mClockPlugin != null) {
230 mClockPlugin.setTextColor(color);
231 }
232 }
233
234 public void setShowCurrentUserTime(boolean showCurrentUserTime) {
235 mClockView.setShowCurrentUserTime(showCurrentUserTime);
236 }
237
Kunhung Li29007e62018-07-30 19:30:25 +0800238 public void setTextSize(int unit, float size) {
239 mClockView.setTextSize(unit, size);
240 }
241
242 public void setFormat12Hour(CharSequence format) {
243 mClockView.setFormat12Hour(format);
244 }
245
246 public void setFormat24Hour(CharSequence format) {
247 mClockView.setFormat24Hour(format);
248 }
249
Robert Snoebergere32efd72018-11-12 08:38:34 -0500250 /**
251 * Set the amount (ratio) that the device has transitioned to doze.
252 * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
253 */
254 public void setDarkAmount(float darkAmount) {
Robert Snoeberger58f23152019-01-10 15:51:32 -0500255 mDarkAmount = darkAmount;
Robert Snoebergere32efd72018-11-12 08:38:34 -0500256 if (mClockPlugin != null) {
257 mClockPlugin.setDarkAmount(darkAmount);
258 }
259 }
260
Kunhung Li29007e62018-07-30 19:30:25 +0800261 public Paint getPaint() {
262 return mClockView.getPaint();
263 }
264
265 public int getCurrentTextColor() {
266 return mClockView.getCurrentTextColor();
267 }
268
269 public float getTextSize() {
270 return mClockView.getTextSize();
271 }
272
273 public void refresh() {
274 mClockView.refresh();
275 }
276
277 /**
Robert Snoebergerffbe3152018-11-08 15:53:27 -0500278 * Notifies that time tick alarm from doze service fired.
279 */
280 public void dozeTimeTick() {
281 if (mClockPlugin != null) {
282 mClockPlugin.dozeTimeTick();
283 }
284 }
285
286 /**
Robert Snoeberger9c1074f2018-12-07 17:35:21 -0500287 * Notifies that the time zone has changed.
288 */
289 public void onTimeZoneChanged(TimeZone timeZone) {
290 if (mClockPlugin != null) {
291 mClockPlugin.onTimeZoneChanged(timeZone);
292 }
293 }
294
Robert Snoeberger0397c842019-02-07 14:25:46 -0500295 private void updateColors(SysuiColorExtractor colorExtractor) {
296 ColorExtractor.GradientColors colors = colorExtractor.getColors(WallpaperManager.FLAG_LOCK,
297 true);
298 mSupportsDarkText = colors.supportsDarkText();
299 mColorPalette = colors.getColorPalette();
300 if (mClockPlugin != null) {
301 mClockPlugin.setColorPalette(mSupportsDarkText, mColorPalette);
302 }
303 }
304
Lucas Dupine570af62019-02-10 14:52:30 -0800305 /**
306 * Sets if the keyguard slice is showing a center-aligned header. We need a smaller clock
307 * in these cases.
308 */
309 public void setKeyguardShowingHeader(boolean hasHeader) {
310 if (mShowingHeader == hasHeader || hasCustomClock()) {
311 return;
312 }
313 mShowingHeader = hasHeader;
314
315 TransitionManager.beginDelayedTransition((ViewGroup) mClockView.getParent(), mTransition);
316 int fontSize = mContext.getResources().getDimensionPixelSize(mShowingHeader
317 ? R.dimen.widget_small_font_size : R.dimen.widget_big_font_size);
318 int paddingBottom = mContext.getResources().getDimensionPixelSize(mShowingHeader
319 ? R.dimen.widget_vertical_padding_clock : R.dimen.header_subtitle_padding);
320 mClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize);
321 mClockView.setPadding(mClockView.getPaddingLeft(), mClockView.getPaddingTop(),
322 mClockView.getPaddingRight(), paddingBottom);
323 }
324
Kunhung Li29007e62018-07-30 19:30:25 +0800325 @VisibleForTesting (otherwise = VisibleForTesting.NONE)
Robert Snoeberger15b4af12019-01-18 15:37:27 -0500326 ClockManager.ClockChangedListener getClockChangedListener() {
327 return mClockChangedListener;
Kunhung Li29007e62018-07-30 19:30:25 +0800328 }
Robert Snoeberger60854082019-01-04 13:13:17 -0500329
330 @VisibleForTesting (otherwise = VisibleForTesting.NONE)
331 StatusBarStateController.StateListener getStateListener() {
332 return mStateListener;
333 }
Lucas Dupine570af62019-02-10 14:52:30 -0800334
335 /**
336 * Special layout transition that scales the clock view as its bounds change, to make it look
337 * like the text is shrinking.
338 */
339 private class ClockBoundsTransition extends ChangeBounds {
340
341 ClockBoundsTransition() {
342 setDuration(KeyguardSliceView.DEFAULT_ANIM_DURATION / 2);
343 setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
344 }
345
346 @Override
347 public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
348 TransitionValues endValues) {
349 Animator animator = super.createAnimator(sceneRoot, startValues, endValues);
350 if (animator == null || startValues.view != mClockView) {
351 return animator;
352 }
353
354 ValueAnimator boundsAnimator = null;
355 if (animator instanceof AnimatorSet) {
356 Animator first = ((AnimatorSet) animator).getChildAnimations().get(0);
357 if (first instanceof ValueAnimator) {
358 boundsAnimator = (ValueAnimator) first;
359 }
360 } else if (animator instanceof ValueAnimator) {
361 boundsAnimator = (ValueAnimator) animator;
362 }
363
364 if (boundsAnimator != null) {
365 float bigFontSize = mContext.getResources()
366 .getDimensionPixelSize(R.dimen.widget_big_font_size);
367 float smallFontSize = mContext.getResources()
368 .getDimensionPixelSize(R.dimen.widget_small_font_size);
369 float startScale = mShowingHeader
370 ? bigFontSize / smallFontSize : smallFontSize / bigFontSize;
371 boundsAnimator.addUpdateListener(animation -> {
372 float scale = MathUtils.lerp(startScale, 1f /* stop */,
373 animation.getAnimatedFraction());
374 mClockView.setPivotX(mClockView.getWidth() / 2);
375 mClockView.setPivotY(0);
376 mClockView.setScaleX(scale);
377 mClockView.setScaleY(scale);
378 });
379 }
380
381 return animator;
382 }
383 }
Kunhung Li29007e62018-07-30 19:30:25 +0800384}