blob: d40fa661192c8ca56afe29cc34e35c37869dcaac [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;
Lucas Dupin43499522019-03-04 16:45:58 -08004import android.animation.AnimatorListenerAdapter;
Lucas Dupine570af62019-02-10 14:52:30 -08005import android.animation.AnimatorSet;
6import android.animation.ValueAnimator;
Robert Snoeberger0397c842019-02-07 14:25:46 -05007import android.app.WallpaperManager;
Kunhung Li29007e62018-07-30 19:30:25 +08008import android.content.Context;
9import android.graphics.Paint;
10import android.graphics.Paint.Style;
Lucas Dupine570af62019-02-10 14:52:30 -080011import android.transition.ChangeBounds;
12import android.transition.Transition;
13import android.transition.TransitionManager;
14import android.transition.TransitionValues;
Kunhung Li29007e62018-07-30 19:30:25 +080015import android.util.AttributeSet;
Lucas Dupine570af62019-02-10 14:52:30 -080016import android.util.MathUtils;
17import android.util.TypedValue;
Kunhung Li29007e62018-07-30 19:30:25 +080018import android.view.View;
19import android.view.ViewGroup;
20import android.widget.FrameLayout;
Robert Snoebergere3b3e782018-12-17 13:32:15 -050021import android.widget.RelativeLayout;
Kunhung Li29007e62018-07-30 19:30:25 +080022import android.widget.TextClock;
23
24import androidx.annotation.VisibleForTesting;
25
Robert Snoeberger0397c842019-02-07 14:25:46 -050026import com.android.internal.colorextraction.ColorExtractor;
Robert Snoeberger15b4af12019-01-18 15:37:27 -050027import com.android.keyguard.clock.ClockManager;
Kunhung Li29007e62018-07-30 19:30:25 +080028import com.android.systemui.Dependency;
Lucas Dupine570af62019-02-10 14:52:30 -080029import com.android.systemui.Interpolators;
Robert Snoeberger0397c842019-02-07 14:25:46 -050030import com.android.systemui.colorextraction.SysuiColorExtractor;
Kunhung Li29007e62018-07-30 19:30:25 +080031import com.android.systemui.plugins.ClockPlugin;
Beverly8fdb5332019-02-04 14:29:49 -050032import com.android.systemui.plugins.statusbar.StatusBarStateController;
Robert Snoeberger60854082019-01-04 13:13:17 -050033import com.android.systemui.statusbar.StatusBarState;
Kunhung Li29007e62018-07-30 19:30:25 +080034
Lucas Dupin43499522019-03-04 16:45:58 -080035import java.io.FileDescriptor;
36import java.io.PrintWriter;
37import java.util.Arrays;
Robert Snoeberger9c1074f2018-12-07 17:35:21 -050038import java.util.TimeZone;
Robert Snoeberger33ce6d92018-10-26 10:52:38 -040039
Kunhung Li29007e62018-07-30 19:30:25 +080040/**
41 * Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
42 */
Robert Snoebergere3b3e782018-12-17 13:32:15 -050043public class KeyguardClockSwitch extends RelativeLayout {
Robert Snoeberger046ee9c2019-01-10 18:29:38 -050044
Lucas Dupine570af62019-02-10 14:52:30 -080045 private final Transition mTransition;
Kunhung Li29007e62018-07-30 19:30:25 +080046 /**
47 * Optional/alternative clock injected via plugin.
48 */
49 private ClockPlugin mClockPlugin;
50 /**
51 * Default clock.
52 */
53 private TextClock mClockView;
Robert Snoebergere3b3e782018-12-17 13:32:15 -050054 /**
55 * Frame for default and custom clock.
56 */
Robert Snoeberger6a0140a2018-12-20 12:46:17 -050057 private FrameLayout mSmallClockFrame;
58 /**
59 * Container for big custom clock.
60 */
61 private ViewGroup mBigClockContainer;
Robert Snoebergere3b3e782018-12-17 13:32:15 -050062 /**
63 * Status area (date and other stuff) shown below the clock. Plugin can decide whether
64 * or not to show it below the alternate clock.
65 */
66 private View mKeyguardStatusArea;
Robert Snoeberger8bf1a3c2019-01-10 10:30:09 -050067 /**
Robert Snoeberger58f23152019-01-10 15:51:32 -050068 * Maintain state so that a newly connected plugin can be initialized.
69 */
70 private float mDarkAmount;
Lucas Dupine570af62019-02-10 14:52:30 -080071 /**
72 * If the Keyguard Slice has a header (big center-aligned text.)
73 */
74 private boolean mShowingHeader;
Robert Snoeberger0397c842019-02-07 14:25:46 -050075 private boolean mSupportsDarkText;
76 private int[] mColorPalette;
Kunhung Li29007e62018-07-30 19:30:25 +080077
Robert Snoeberger60854082019-01-04 13:13:17 -050078 private final StatusBarStateController.StateListener mStateListener =
79 new StatusBarStateController.StateListener() {
80 @Override
81 public void onStateChanged(int newState) {
82 if (mBigClockContainer == null) {
83 return;
84 }
85 if (newState == StatusBarState.SHADE) {
86 if (mBigClockContainer.getVisibility() == View.VISIBLE) {
87 mBigClockContainer.setVisibility(View.INVISIBLE);
88 }
89 } else {
90 if (mBigClockContainer.getVisibility() == View.INVISIBLE) {
91 mBigClockContainer.setVisibility(View.VISIBLE);
92 }
93 }
94 }
95 };
Kunhung Li29007e62018-07-30 19:30:25 +080096
Robert Snoeberger15b4af12019-01-18 15:37:27 -050097 private ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
Robert Snoeberger046ee9c2019-01-10 18:29:38 -050098
Robert Snoeberger0397c842019-02-07 14:25:46 -050099 /**
100 * Listener for changes to the color palette.
101 *
102 * The color palette changes when the wallpaper is changed.
103 */
104 private SysuiColorExtractor.OnColorsChangedListener mColorsListener = (extractor, which) -> {
105 if ((which & WallpaperManager.FLAG_LOCK) != 0) {
106 if (extractor instanceof SysuiColorExtractor) {
107 updateColors((SysuiColorExtractor) extractor);
108 } else {
109 updateColors(Dependency.get(SysuiColorExtractor.class));
110 }
111 }
112 };
113
Kunhung Li29007e62018-07-30 19:30:25 +0800114 public KeyguardClockSwitch(Context context) {
115 this(context, null);
116 }
117
118 public KeyguardClockSwitch(Context context, AttributeSet attrs) {
119 super(context, attrs);
Lucas Dupine570af62019-02-10 14:52:30 -0800120 mTransition = new ClockBoundsTransition();
Kunhung Li29007e62018-07-30 19:30:25 +0800121 }
122
Lucas Dupin7fc9dc12019-01-03 09:19:43 -0800123 /**
124 * Returns if this view is presenting a custom clock, or the default implementation.
125 */
126 public boolean hasCustomClock() {
127 return mClockPlugin != null;
128 }
129
Kunhung Li29007e62018-07-30 19:30:25 +0800130 @Override
131 protected void onFinishInflate() {
132 super.onFinishInflate();
133 mClockView = findViewById(R.id.default_clock_view);
Robert Snoeberger6a0140a2018-12-20 12:46:17 -0500134 mSmallClockFrame = findViewById(R.id.clock_view);
Robert Snoebergere3b3e782018-12-17 13:32:15 -0500135 mKeyguardStatusArea = findViewById(R.id.keyguard_status_area);
Kunhung Li29007e62018-07-30 19:30:25 +0800136 }
137
138 @Override
139 protected void onAttachedToWindow() {
140 super.onAttachedToWindow();
Robert Snoeberger15b4af12019-01-18 15:37:27 -0500141 Dependency.get(ClockManager.class).addOnClockChangedListener(mClockChangedListener);
Robert Snoeberger60854082019-01-04 13:13:17 -0500142 Dependency.get(StatusBarStateController.class).addCallback(mStateListener);
Robert Snoeberger0397c842019-02-07 14:25:46 -0500143 SysuiColorExtractor colorExtractor = Dependency.get(SysuiColorExtractor.class);
144 colorExtractor.addOnColorsChangedListener(mColorsListener);
145 updateColors(colorExtractor);
Kunhung Li29007e62018-07-30 19:30:25 +0800146 }
147
148 @Override
149 protected void onDetachedFromWindow() {
150 super.onDetachedFromWindow();
Robert Snoeberger15b4af12019-01-18 15:37:27 -0500151 Dependency.get(ClockManager.class).removeOnClockChangedListener(mClockChangedListener);
Robert Snoeberger60854082019-01-04 13:13:17 -0500152 Dependency.get(StatusBarStateController.class).removeCallback(mStateListener);
Robert Snoeberger0397c842019-02-07 14:25:46 -0500153 Dependency.get(SysuiColorExtractor.class)
154 .removeOnColorsChangedListener(mColorsListener);
Robert Snoebergerd7470252019-03-04 16:05:36 -0500155 setClockPlugin(null);
Kunhung Li29007e62018-07-30 19:30:25 +0800156 }
157
Robert Snoeberger8bf1a3c2019-01-10 10:30:09 -0500158 private void setClockPlugin(ClockPlugin plugin) {
159 // Disconnect from existing plugin.
160 if (mClockPlugin != null) {
161 View smallClockView = mClockPlugin.getView();
162 if (smallClockView != null && smallClockView.getParent() == mSmallClockFrame) {
163 mSmallClockFrame.removeView(smallClockView);
164 }
165 if (mBigClockContainer != null) {
166 mBigClockContainer.removeAllViews();
167 mBigClockContainer.setVisibility(View.GONE);
168 }
169 mClockPlugin = null;
170 }
171 if (plugin == null) {
172 mClockView.setVisibility(View.VISIBLE);
173 mKeyguardStatusArea.setVisibility(View.VISIBLE);
174 return;
175 }
176 // Attach small and big clock views to hierarchy.
177 View smallClockView = plugin.getView();
178 if (smallClockView != null) {
179 mSmallClockFrame.addView(smallClockView, -1,
180 new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
181 ViewGroup.LayoutParams.WRAP_CONTENT));
182 mClockView.setVisibility(View.GONE);
183 }
184 View bigClockView = plugin.getBigClockView();
185 if (bigClockView != null && mBigClockContainer != null) {
186 mBigClockContainer.addView(bigClockView);
187 mBigClockContainer.setVisibility(View.VISIBLE);
188 }
189 // Hide default clock.
190 if (!plugin.shouldShowStatusArea()) {
191 mKeyguardStatusArea.setVisibility(View.GONE);
192 }
193 // Initialize plugin parameters.
194 mClockPlugin = plugin;
195 mClockPlugin.setStyle(getPaint().getStyle());
196 mClockPlugin.setTextColor(getCurrentTextColor());
Robert Snoeberger58f23152019-01-10 15:51:32 -0500197 mClockPlugin.setDarkAmount(mDarkAmount);
Robert Snoeberger0397c842019-02-07 14:25:46 -0500198 if (mColorPalette != null) {
199 mClockPlugin.setColorPalette(mSupportsDarkText, mColorPalette);
200 }
Robert Snoeberger8bf1a3c2019-01-10 10:30:09 -0500201 }
202
Kunhung Li29007e62018-07-30 19:30:25 +0800203 /**
Robert Snoeberger6a0140a2018-12-20 12:46:17 -0500204 * Set container for big clock face appearing behind NSSL and KeyguardStatusView.
205 */
206 public void setBigClockContainer(ViewGroup container) {
207 if (mClockPlugin != null && container != null) {
208 View bigClockView = mClockPlugin.getBigClockView();
209 if (bigClockView != null) {
210 container.addView(bigClockView);
Robert Snoebergera1df7fb2019-02-11 14:00:14 -0500211 if (container.getVisibility() == View.GONE) {
212 container.setVisibility(View.VISIBLE);
213 }
Robert Snoeberger6a0140a2018-12-20 12:46:17 -0500214 }
215 }
216 mBigClockContainer = container;
217 }
218
219 /**
Kunhung Li29007e62018-07-30 19:30:25 +0800220 * It will also update plugin setStyle if plugin is connected.
221 */
222 public void setStyle(Style style) {
223 mClockView.getPaint().setStyle(style);
224 if (mClockPlugin != null) {
225 mClockPlugin.setStyle(style);
226 }
227 }
228
229 /**
230 * It will also update plugin setTextColor if plugin is connected.
231 */
232 public void setTextColor(int color) {
233 mClockView.setTextColor(color);
234 if (mClockPlugin != null) {
235 mClockPlugin.setTextColor(color);
236 }
237 }
238
239 public void setShowCurrentUserTime(boolean showCurrentUserTime) {
240 mClockView.setShowCurrentUserTime(showCurrentUserTime);
241 }
242
Kunhung Li29007e62018-07-30 19:30:25 +0800243 public void setTextSize(int unit, float size) {
244 mClockView.setTextSize(unit, size);
245 }
246
247 public void setFormat12Hour(CharSequence format) {
248 mClockView.setFormat12Hour(format);
249 }
250
251 public void setFormat24Hour(CharSequence format) {
252 mClockView.setFormat24Hour(format);
253 }
254
Robert Snoebergere32efd72018-11-12 08:38:34 -0500255 /**
256 * Set the amount (ratio) that the device has transitioned to doze.
257 * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
258 */
259 public void setDarkAmount(float darkAmount) {
Robert Snoeberger58f23152019-01-10 15:51:32 -0500260 mDarkAmount = darkAmount;
Robert Snoebergere32efd72018-11-12 08:38:34 -0500261 if (mClockPlugin != null) {
262 mClockPlugin.setDarkAmount(darkAmount);
263 }
264 }
265
Kunhung Li29007e62018-07-30 19:30:25 +0800266 public Paint getPaint() {
267 return mClockView.getPaint();
268 }
269
270 public int getCurrentTextColor() {
271 return mClockView.getCurrentTextColor();
272 }
273
274 public float getTextSize() {
275 return mClockView.getTextSize();
276 }
277
278 public void refresh() {
279 mClockView.refresh();
280 }
281
282 /**
Robert Snoebergerffbe3152018-11-08 15:53:27 -0500283 * Notifies that time tick alarm from doze service fired.
284 */
285 public void dozeTimeTick() {
286 if (mClockPlugin != null) {
Robert Snoeberger9ad03f42019-02-28 14:47:49 -0500287 mClockPlugin.onTimeTick();
Robert Snoebergerffbe3152018-11-08 15:53:27 -0500288 }
289 }
290
291 /**
Robert Snoeberger9c1074f2018-12-07 17:35:21 -0500292 * Notifies that the time zone has changed.
293 */
294 public void onTimeZoneChanged(TimeZone timeZone) {
295 if (mClockPlugin != null) {
296 mClockPlugin.onTimeZoneChanged(timeZone);
297 }
298 }
299
Robert Snoeberger0397c842019-02-07 14:25:46 -0500300 private void updateColors(SysuiColorExtractor colorExtractor) {
301 ColorExtractor.GradientColors colors = colorExtractor.getColors(WallpaperManager.FLAG_LOCK,
302 true);
303 mSupportsDarkText = colors.supportsDarkText();
304 mColorPalette = colors.getColorPalette();
305 if (mClockPlugin != null) {
306 mClockPlugin.setColorPalette(mSupportsDarkText, mColorPalette);
307 }
308 }
309
Lucas Dupine570af62019-02-10 14:52:30 -0800310 /**
311 * Sets if the keyguard slice is showing a center-aligned header. We need a smaller clock
312 * in these cases.
313 */
314 public void setKeyguardShowingHeader(boolean hasHeader) {
315 if (mShowingHeader == hasHeader || hasCustomClock()) {
316 return;
317 }
318 mShowingHeader = hasHeader;
319
320 TransitionManager.beginDelayedTransition((ViewGroup) mClockView.getParent(), mTransition);
321 int fontSize = mContext.getResources().getDimensionPixelSize(mShowingHeader
322 ? R.dimen.widget_small_font_size : R.dimen.widget_big_font_size);
323 int paddingBottom = mContext.getResources().getDimensionPixelSize(mShowingHeader
324 ? R.dimen.widget_vertical_padding_clock : R.dimen.header_subtitle_padding);
325 mClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize);
326 mClockView.setPadding(mClockView.getPaddingLeft(), mClockView.getPaddingTop(),
327 mClockView.getPaddingRight(), paddingBottom);
328 }
329
Kunhung Li29007e62018-07-30 19:30:25 +0800330 @VisibleForTesting (otherwise = VisibleForTesting.NONE)
Robert Snoeberger15b4af12019-01-18 15:37:27 -0500331 ClockManager.ClockChangedListener getClockChangedListener() {
332 return mClockChangedListener;
Kunhung Li29007e62018-07-30 19:30:25 +0800333 }
Robert Snoeberger60854082019-01-04 13:13:17 -0500334
335 @VisibleForTesting (otherwise = VisibleForTesting.NONE)
336 StatusBarStateController.StateListener getStateListener() {
337 return mStateListener;
338 }
Lucas Dupine570af62019-02-10 14:52:30 -0800339
Lucas Dupin43499522019-03-04 16:45:58 -0800340 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
341 pw.println("KeyguardClockSwitch:");
342 pw.println(" mClockPlugin: " + mClockPlugin);
343 pw.println(" mClockView: " + mClockView);
344 pw.println(" mSmallClockFrame: " + mSmallClockFrame);
345 pw.println(" mBigClockContainer: " + mBigClockContainer);
346 pw.println(" mKeyguardStatusArea: " + mKeyguardStatusArea);
347 pw.println(" mDarkAmount: " + mDarkAmount);
348 pw.println(" mShowingHeader: " + mShowingHeader);
349 pw.println(" mSupportsDarkText: " + mSupportsDarkText);
350 pw.println(" mColorPalette: " + Arrays.toString(mColorPalette));
351 }
352
Lucas Dupine570af62019-02-10 14:52:30 -0800353 /**
354 * Special layout transition that scales the clock view as its bounds change, to make it look
355 * like the text is shrinking.
356 */
357 private class ClockBoundsTransition extends ChangeBounds {
358
359 ClockBoundsTransition() {
360 setDuration(KeyguardSliceView.DEFAULT_ANIM_DURATION / 2);
361 setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
362 }
363
364 @Override
365 public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
366 TransitionValues endValues) {
367 Animator animator = super.createAnimator(sceneRoot, startValues, endValues);
368 if (animator == null || startValues.view != mClockView) {
369 return animator;
370 }
371
372 ValueAnimator boundsAnimator = null;
373 if (animator instanceof AnimatorSet) {
374 Animator first = ((AnimatorSet) animator).getChildAnimations().get(0);
375 if (first instanceof ValueAnimator) {
376 boundsAnimator = (ValueAnimator) first;
377 }
378 } else if (animator instanceof ValueAnimator) {
379 boundsAnimator = (ValueAnimator) animator;
380 }
381
382 if (boundsAnimator != null) {
383 float bigFontSize = mContext.getResources()
384 .getDimensionPixelSize(R.dimen.widget_big_font_size);
385 float smallFontSize = mContext.getResources()
386 .getDimensionPixelSize(R.dimen.widget_small_font_size);
387 float startScale = mShowingHeader
388 ? bigFontSize / smallFontSize : smallFontSize / bigFontSize;
389 boundsAnimator.addUpdateListener(animation -> {
390 float scale = MathUtils.lerp(startScale, 1f /* stop */,
391 animation.getAnimatedFraction());
Lucas Dupin43499522019-03-04 16:45:58 -0800392 mClockView.setPivotX(mClockView.getWidth() / 2f);
Lucas Dupine570af62019-02-10 14:52:30 -0800393 mClockView.setPivotY(0);
394 mClockView.setScaleX(scale);
395 mClockView.setScaleY(scale);
396 });
Lucas Dupin43499522019-03-04 16:45:58 -0800397 boundsAnimator.addListener(new AnimatorListenerAdapter() {
398 @Override
399 public void onAnimationEnd(Animator animator) {
400 mClockView.setScaleX(1f);
401 mClockView.setScaleY(1f);
402 }
403
404 @Override
405 public void onAnimationCancel(Animator animator) {
406 onAnimationEnd(animator);
407 }
408 });
Lucas Dupine570af62019-02-10 14:52:30 -0800409 }
410
411 return animator;
412 }
413 }
Kunhung Li29007e62018-07-30 19:30:25 +0800414}