summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author TreeHugger Robot <treehugger-gerrit@google.com> 2020-11-17 21:03:18 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2020-11-17 21:03:18 +0000
commit2aa31cc0702afa54d84aa6180ca6ce91e8cb6eda (patch)
tree94a9e9c1966d811b801e8642a36a89aff6fc94cc
parent2edad48f94044c7e9b5984da88a4379e145860a7 (diff)
parent192b7c9eaabd746ebdedb306a5a93d8740716d22 (diff)
Merge "Add support for LS Clock to use > 1 Paint object"
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java81
-rw-r--r--packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java158
-rw-r--r--packages/SystemUI/src/com/android/keyguard/GradientTextClock.java149
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java10
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java28
-rw-r--r--packages/SystemUI/src/com/android/keyguard/TextAnimator.kt44
-rw-r--r--packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt81
-rw-r--r--packages/SystemUI/src/com/android/keyguard/TimeBasedColorsClockController.java176
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt37
11 files changed, 373 insertions, 407 deletions
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 939c5f9f7ecb..d339ae9920b4 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -69,8 +69,8 @@
android:layout_alignParentTop="true"
android:layout_marginBottom="24dp"
android:visibility="gone">
- <com.android.keyguard.GradientTextClock
- android:id="@+id/gradient_clock_view"
+ <com.android.keyguard.AnimatableClockView
+ android:id="@+id/animatable_clock_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="80dp"
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
new file mode 100644
index 000000000000..692e40124da9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 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.keyguard;
+
+import android.graphics.Color;
+
+import com.android.settingslib.Utils;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.util.ViewController;
+
+/**
+ * Controls the color of a GradientTextClock.
+ */
+public class AnimatableClockController extends ViewController<AnimatableClockView> {
+
+ private final StatusBarStateController mStatusBarStateController;
+ private final int[] mDozingColors = new int[] {Color.WHITE, Color.WHITE};
+ private int[] mLockScreenColors = new int[2];
+
+ private boolean mIsDozing;
+
+ public AnimatableClockController(
+ AnimatableClockView view,
+ StatusBarStateController statusBarStateController) {
+ super(view);
+ mStatusBarStateController = statusBarStateController;
+ mIsDozing = mStatusBarStateController.isDozing();
+ }
+
+ @Override
+ protected void onViewAttached() {
+ mStatusBarStateController.addCallback(mStatusBarStateListener);
+ mIsDozing = mStatusBarStateController.isDozing();
+ refreshTime();
+ initColors();
+ }
+
+ @Override
+ protected void onViewDetached() {
+ mStatusBarStateController.removeCallback(mStatusBarStateListener);
+ }
+
+ /**
+ * Updates the time for this view.
+ */
+ public void refreshTime() {
+ mView.refreshTime();
+ }
+
+ private void initColors() {
+ mLockScreenColors[0] = Utils.getColorAttrDefaultColor(getContext(),
+ com.android.systemui.R.attr.wallpaperTextColor);
+ mLockScreenColors[1] = Utils.getColorAttrDefaultColor(getContext(),
+ com.android.systemui.R.attr.wallpaperTextColorSecondary);
+ mView.setColors(mDozingColors, mLockScreenColors);
+ mView.animateDoze(mIsDozing, false);
+ }
+
+ private final StatusBarStateController.StateListener mStatusBarStateListener =
+ new StatusBarStateController.StateListener() {
+ @Override
+ public void onDozingChanged(boolean isDozing) {
+ mIsDozing = isDozing;
+ mView.animateDoze(mIsDozing, true);
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
new file mode 100644
index 000000000000..6d1d42e2988b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2020 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.keyguard;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.icu.text.DateTimePatternGenerator;
+import android.text.format.DateFormat;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import java.util.Calendar;
+
+import kotlin.Unit;
+
+/**
+ * Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
+ * The time's text color is a gradient that changes its colors based on its controller.
+ */
+public class AnimatableClockView extends TextView {
+ private static final CharSequence FORMAT_12_HOUR = "hh\nmm";
+ private static final CharSequence FORMAT_24_HOUR = "HH\nmm";
+ private static final long ANIM_DURATION = 300;
+
+ private CharSequence mFormat;
+ private CharSequence mDescFormat;
+ private Calendar mTime = Calendar.getInstance();
+ private int[] mDozingColors;
+ private int[] mLockScreenColors;
+
+ private TextAnimator mTextAnimator = null;
+ private Runnable mOnTextAnimatorInitialized;
+
+ public AnimatableClockView(Context context) {
+ this(context, null, 0, 0);
+ }
+
+ public AnimatableClockView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0, 0);
+ }
+
+ public AnimatableClockView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public AnimatableClockView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ updateTimeFormat();
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ updateTimeFormat();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ }
+
+ void refreshTime() {
+ mTime.setTimeInMillis(System.currentTimeMillis());
+ setText(DateFormat.format(mFormat, mTime));
+ setContentDescription(DateFormat.format(mDescFormat, mTime));
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ if (mTextAnimator == null) {
+ mTextAnimator = new TextAnimator(
+ getLayout(),
+ () -> {
+ invalidate();
+ return Unit.INSTANCE;
+ },
+ 2 /* number of lines (each can have a unique Paint) */);
+ if (mOnTextAnimatorInitialized != null) {
+ mOnTextAnimatorInitialized.run();
+ mOnTextAnimatorInitialized = null;
+ }
+ } else {
+ mTextAnimator.updateLayout(getLayout());
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ mTextAnimator.draw(canvas);
+ }
+
+ void setColors(int[] dozingColors, int[] lockScreenColors) {
+ mDozingColors = dozingColors;
+ mLockScreenColors = lockScreenColors;
+ }
+
+ void animateDoze(boolean isDozing, boolean animate) {
+ setTextStyle(isDozing ? 100 : 300 /* weight */,
+ -1,
+ isDozing ? mDozingColors : mLockScreenColors,
+ animate);
+ }
+
+ /**
+ * Set text style with an optional animation.
+ *
+ * By passing -1 to weight, the view preserves its current weight.
+ * By passing -1 to textSize, the view preserves its current text size.
+ *
+ * @param weight text weight.
+ * @param textSize font size.
+ * @param animate true to animate the text style change, otherwise false.
+ */
+ private void setTextStyle(
+ @IntRange(from = 0, to = 1000) int weight,
+ @FloatRange(from = 0) float textSize,
+ int[] colors,
+ boolean animate) {
+ if (mTextAnimator != null) {
+ mTextAnimator.setTextStyle(weight, textSize, colors, animate, ANIM_DURATION, null);
+ } else {
+ // when the text animator is set, update its start values
+ mOnTextAnimatorInitialized =
+ () -> mTextAnimator.setTextStyle(
+ weight, textSize, colors, false, ANIM_DURATION, null);
+ }
+ }
+
+ private void updateTimeFormat() {
+ final boolean use24HourFormat = DateFormat.is24HourFormat(getContext());
+ mFormat = use24HourFormat ? FORMAT_24_HOUR : FORMAT_12_HOUR;
+ mDescFormat = getBestDateTimePattern(getContext(), use24HourFormat ? "Hm" : "hm");
+ }
+
+ private static String getBestDateTimePattern(Context context, String skeleton) {
+ DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance(
+ context.getResources().getConfiguration().locale);
+ return dtpg.getBestPattern(skeleton);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/GradientTextClock.java b/packages/SystemUI/src/com/android/keyguard/GradientTextClock.java
deleted file mode 100644
index 3942c60a58e9..000000000000
--- a/packages/SystemUI/src/com/android/keyguard/GradientTextClock.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2020 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.keyguard;
-
-import android.annotation.FloatRange;
-import android.annotation.IntRange;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.LinearGradient;
-import android.graphics.Shader;
-import android.util.AttributeSet;
-import android.widget.TextClock;
-
-import kotlin.Unit;
-
-/**
- * Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
- * The time's text color is a gradient that changes its colors based on its controller.
- */
-public class GradientTextClock extends TextClock {
- private int[] mGradientColors;
- private float[] mPositions;
-
- private TextAnimator mTextAnimator = null;
-
- public GradientTextClock(Context context) {
- this(context, null, 0, 0);
- }
-
- public GradientTextClock(Context context, AttributeSet attrs) {
- this(context, attrs, 0, 0);
- }
-
- public GradientTextClock(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public GradientTextClock(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
- addOnLayoutChangeListener(mOnLayoutChangeListener);
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- removeOnLayoutChangeListener(mOnLayoutChangeListener);
- }
-
- @Override
- public void refreshTime() {
- super.refreshTime();
- }
-
- @Override
- public void setFormat12Hour(CharSequence format) {
- super.setFormat12Hour(FORMAT_12);
- }
-
- @Override
- public void setFormat24Hour(CharSequence format) {
- super.setFormat24Hour(FORMAT_24);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- if (mTextAnimator == null) {
- mTextAnimator = new TextAnimator(getLayout(), () -> {
- invalidate();
- return Unit.INSTANCE;
- });
- } else {
- mTextAnimator.updateLayout(getLayout());
- }
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- mTextAnimator.draw(canvas);
- }
-
- public void setGradientColors(int[] colors) {
- mGradientColors = colors;
- updatePaint();
- }
-
- public void setColorPositions(float[] positions) {
- mPositions = positions;
- }
-
- /**
- * Set text style with animation.
- *
- * By passing -1 to weight, the view preserve the current weight.
- * By passing -1 to textSize, the view preserve the current text size.
- *
- * @param weight text weight.
- * @param textSize font size.
- * @param animate true for changing text style with animation, otherwise false.
- */
- public void setTextStyle(
- @IntRange(from = 0, to = 1000) int weight,
- @FloatRange(from = 0) float textSize,
- boolean animate) {
- if (mTextAnimator != null) {
- mTextAnimator.setTextStyle(weight, textSize, animate, -1, null);
- }
- }
-
- private void updatePaint() {
- Shader shader = new LinearGradient(
- getX(), getY(), getX(), getMeasuredHeight() + getY(), mGradientColors, mPositions,
- Shader.TileMode.REPEAT);
- getPaint().setShader(shader);
- if (mTextAnimator != null) {
- mTextAnimator.setShader(shader);
- }
- }
-
- private final OnLayoutChangeListener mOnLayoutChangeListener =
- (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
- if (bottom != oldBottom || top != oldTop) {
- updatePaint();
- }
- };
-
- public static final CharSequence FORMAT_12 = "hh\nmm";
- public static final CharSequence FORMAT_24 = "HH\nmm";
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index c6ee15fcf4e3..4d8fdf339e1b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -73,11 +73,6 @@ public class KeyguardClockSwitch extends RelativeLayout {
private TextClock mClockViewBold;
/**
- * Gradient clock for usage when mode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL.
- */
- private TimeBasedColorsClockController mNewLockscreenClockViewController;
-
- /**
* Frame for clock when mode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL.
*/
private FrameLayout mNewLockscreenClockFrame;
@@ -157,7 +152,6 @@ public class KeyguardClockSwitch extends RelativeLayout {
setPaddingRelative(startEndPadding, 0, startEndPadding, 0);
mSmallClockFrame.setVisibility(GONE);
mNewLockscreenClockFrame.setVisibility(VISIBLE);
- mNewLockscreenClockViewController.init();
statusAreaLP.removeRule(RelativeLayout.BELOW);
statusAreaLP.addRule(RelativeLayout.LEFT_OF, R.id.new_lockscreen_clock_view);
@@ -181,8 +175,6 @@ public class KeyguardClockSwitch extends RelativeLayout {
mClockView = findViewById(R.id.default_clock_view);
mClockViewBold = findViewById(R.id.default_clock_view_bold);
mNewLockscreenClockFrame = findViewById(R.id.new_lockscreen_clock_view);
- mNewLockscreenClockViewController =
- new TimeBasedColorsClockController(findViewById(R.id.gradient_clock_view));
mSmallClockFrame = findViewById(R.id.clock_view);
mKeyguardStatusArea = findViewById(R.id.keyguard_status_area);
}
@@ -305,7 +297,6 @@ public class KeyguardClockSwitch extends RelativeLayout {
if (mClockPlugin != null) {
mClockPlugin.setDarkAmount(darkAmount);
}
- mNewLockscreenClockViewController.setDarkAmount(darkAmount);
updateBigClockAlpha();
}
@@ -356,7 +347,6 @@ public class KeyguardClockSwitch extends RelativeLayout {
* Refresh the time of the clock, due to either time tick broadcast or doze time tick alarm.
*/
public void refresh() {
- mNewLockscreenClockViewController.refreshTime(System.currentTimeMillis());
mClockView.refreshTime();
mClockViewBold.refreshTime();
if (mClockPlugin != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 5b89f7f46772..775ebf417a80 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -55,7 +55,12 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
private final ClockManager mClockManager;
private final KeyguardSliceViewController mKeyguardSliceViewController;
private final NotificationIconAreaController mNotificationIconAreaController;
- private FrameLayout mNewLockscreenClockFrame;
+
+ /**
+ * Gradient clock for usage when mode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL.
+ */
+ private AnimatableClockController mNewLockScreenClockViewController;
+ private FrameLayout mNewLockScreenClockFrame;
private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
@@ -119,7 +124,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mColorExtractor.addOnColorsChangedListener(mColorsListener);
mView.updateColors(getGradientColors());
updateAodIcons();
- mNewLockscreenClockFrame = mView.findViewById(R.id.new_lockscreen_clock_view);
+ mNewLockScreenClockFrame = mView.findViewById(R.id.new_lockscreen_clock_view);
}
@Override
@@ -182,6 +187,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
* Refresh clock. Called in response to TIME_TICK broadcasts.
*/
void refresh() {
+ if (mNewLockScreenClockViewController != null) {
+ mNewLockScreenClockViewController.refreshTime();
+ }
+
mView.refresh();
}
@@ -192,8 +201,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
*/
void updatePosition(int x, AnimationProperties props, boolean animate) {
x = Math.abs(x);
- if (mNewLockscreenClockFrame != null) {
- PropertyAnimator.setProperty(mNewLockscreenClockFrame, AnimatableProperty.TRANSLATION_X,
+ if (mNewLockScreenClockFrame != null) {
+ PropertyAnimator.setProperty(mNewLockScreenClockFrame, AnimatableProperty.TRANSLATION_X,
-x, props, animate);
}
mKeyguardSliceViewController.updatePosition(x, props, animate);
@@ -205,6 +214,17 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
*/
void updateLockScreenMode(int mode) {
mLockScreenMode = mode;
+ if (mode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
+ if (mNewLockScreenClockViewController == null) {
+ mNewLockScreenClockViewController =
+ new AnimatableClockController(
+ mView.findViewById(R.id.animatable_clock_view),
+ mStatusBarStateController);
+ mNewLockScreenClockViewController.init();
+ }
+ } else {
+ mNewLockScreenClockViewController = null;
+ }
mView.updateLockScreenMode(mLockScreenMode);
updateAodIcons();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt b/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
index e4c3dcde3bd4..f2d36d19afcf 100644
--- a/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
+++ b/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
@@ -21,11 +21,10 @@ import android.animation.AnimatorListenerAdapter
import android.animation.TimeInterpolator
import android.animation.ValueAnimator
import android.graphics.Canvas
-import android.graphics.Shader
import android.text.Layout
private const val TAG_WGHT = "wght"
-private const val DEFAULT_ANIMATION_DURATION: Long = 1000
+private const val DEFAULT_ANIMATION_DURATION: Long = 300
/**
* This class provides text animation between two styles.
@@ -53,9 +52,13 @@ private const val DEFAULT_ANIMATION_DURATION: Long = 1000
* </code>
* </pre>
*/
-class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
+class TextAnimator(
+ layout: Layout,
+ private val invalidateCallback: () -> Unit,
+ private val numLines: Int = 1
+) {
// Following two members are for mutable for testing purposes.
- internal var textInterpolator: TextInterpolator = TextInterpolator(layout)
+ internal var textInterpolator: TextInterpolator = TextInterpolator(layout, numLines)
internal var animator: ValueAnimator = ValueAnimator.ofFloat(1f).apply {
duration = DEFAULT_ANIMATION_DURATION
addUpdateListener {
@@ -72,19 +75,6 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
textInterpolator.layout = layout
}
- var shader: Shader
- get() = textInterpolator.basePaint.shader.also {
- require(it === textInterpolator.targetPaint.shader) {
- "base and target paint has different shader. Usually shader is not interpolatable."
- }
- }
- set(value) {
- textInterpolator.basePaint.shader = value
- textInterpolator.targetPaint.shader = value
- // Shader doesn't change the text layout, so no need to call onTargetPaintModified or
- // onBasePaintModified
- }
-
fun draw(c: Canvas) = textInterpolator.draw(c)
/**
@@ -97,6 +87,8 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
*
* @param weight an optional text weight.
* @param textSize an optional font size.
+ * @param colors an optional colors array that must be the same size as numLines passed to
+ * the TextInterpolator
* @param animate an optional boolean indicating true for showing style transition as animation,
* false for immediate style transition. True by default.
* @param duration an optional animation duration in milliseconds. This is ignored if animate is
@@ -107,6 +99,7 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
fun setTextStyle(
weight: Int = -1,
textSize: Float = -1f,
+ colors: IntArray? = null,
animate: Boolean = true,
duration: Long = -1L,
interpolator: TimeInterpolator? = null
@@ -117,10 +110,21 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
}
if (textSize >= 0) {
- textInterpolator.targetPaint.textSize = textSize
+ for (targetPaint in textInterpolator.targetPaint)
+ targetPaint.textSize = textSize
}
if (weight >= 0) {
- textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
+ for (targetPaint in textInterpolator.targetPaint)
+ targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
+ }
+ if (colors != null) {
+ require(colors.size == textInterpolator.targetPaint.size) {
+ "colors size (${colors.size}) must be the same size as" +
+ " targetPaints size (${textInterpolator.targetPaint.size})," +
+ " which was initialized as numLines ($numLines)"
+ }
+ for ((index, targetPaint) in textInterpolator.targetPaint.withIndex())
+ targetPaint.color = colors[index]
}
textInterpolator.onTargetPaintModified()
@@ -138,4 +142,4 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
textInterpolator.rebase()
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt b/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt
index 51148f3c93af..f5e01dedfe3e 100644
--- a/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt
+++ b/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt
@@ -22,21 +22,28 @@ import android.graphics.text.PositionedGlyphs
import android.graphics.text.TextRunShaper
import android.text.Layout
import android.util.MathUtils
+import com.android.internal.graphics.ColorUtils
import java.lang.Math.max
/**
* Provide text style linear interpolation for plain text.
*/
-class TextInterpolator(layout: Layout) {
+class TextInterpolator(
+ layout: Layout,
+ lines: Int = 1
+) {
+
/**
* Returns base paint used for interpolation.
*
* Once you modified the style parameters, you have to call reshapeText to recalculate base text
* layout.
*
- * @return a paint object.
+ * @return an array list of paint objects representing one paint per line of text. If this
+ * list has a smaller size than the number of lines, all extra lines will use the Paint an
+ * index 0.
*/
- val basePaint = Paint(layout.paint)
+ val basePaint = createDefaultPaint(layout.paint, lines)
/**
* Returns target paint used for interpolation.
@@ -44,9 +51,18 @@ class TextInterpolator(layout: Layout) {
* Once you modified the style parameters, you have to call reshapeText to recalculate target
* text layout.
*
- * @return a paint object
+ * @return an array list of paint objects representing one paint per line of text. If this
+ * list has a smaller size than the number of lines, all extra lines will use the Paint an
+ * index 0.
*/
- val targetPaint = Paint(layout.paint)
+ val targetPaint = createDefaultPaint(layout.paint, lines)
+
+ private fun createDefaultPaint(paint: Paint, lines: Int): ArrayList<Paint> {
+ val paintList = ArrayList<Paint>()
+ for (i in 0 until lines)
+ paintList.add(Paint(paint))
+ return paintList
+ }
/**
* A class represents a single font run.
@@ -78,7 +94,7 @@ class TextInterpolator(layout: Layout) {
private val fontInterpolator = FontInterpolator()
// Recycling object for glyph drawing. Will be extended for the longest font run if needed.
- private val tmpDrawPaint = Paint()
+ private val tmpDrawPaints = ArrayList<Paint>()
private var tmpPositionArray = FloatArray(20)
/**
@@ -115,8 +131,8 @@ class TextInterpolator(layout: Layout) {
/**
* Recalculate internal text layout for interpolation.
*
- * Whenever you modifies target paint, you have to call this method to recalculate internal text
- * layout used for interpolation.
+ * Whenever the target paint is modified, call this method to recalculate internal
+ * text layout used for interpolation.
*/
fun onTargetPaintModified() {
updatePositionsAndFonts(shapeText(layout, targetPaint), updateBase = false)
@@ -125,8 +141,8 @@ class TextInterpolator(layout: Layout) {
/**
* Recalculate internal text layout for interpolation.
*
- * Whenever you modifies base paint, you have to call this method to recalculate internal text
- * layout used for interpolation.
+ * Whenever the base paint is modified, call this method to recalculate internal
+ * text layout used for interpolation.
*/
fun onBasePaintModified() {
updatePositionsAndFonts(shapeText(layout, basePaint), updateBase = true)
@@ -192,10 +208,10 @@ class TextInterpolator(layout: Layout) {
if (progress == 0f) {
return
} else if (progress == 1f) {
- basePaint.set(targetPaint)
+ updatePaint(basePaint, targetPaint)
} else {
- lerp(basePaint, targetPaint, progress, tmpDrawPaint)
- basePaint.set(tmpDrawPaint)
+ lerp(basePaint, targetPaint, progress, tmpDrawPaints)
+ updatePaint(basePaint, tmpDrawPaints)
}
lines.forEach { line ->
@@ -211,13 +227,21 @@ class TextInterpolator(layout: Layout) {
progress = 0f
}
+ companion object {
+ fun updatePaint(toUpdate: ArrayList<Paint>, newValues: ArrayList<Paint>) {
+ toUpdate.clear()
+ for (paint in newValues)
+ toUpdate.add(Paint(paint))
+ }
+ }
+
/**
* Draws interpolated text at the given progress.
*
* @param canvas a canvas.
*/
fun draw(canvas: Canvas) {
- lerp(basePaint, targetPaint, progress, tmpDrawPaint)
+ lerp(basePaint, targetPaint, progress, tmpDrawPaints)
lines.forEachIndexed { lineNo, line ->
canvas.save()
try {
@@ -226,7 +250,10 @@ class TextInterpolator(layout: Layout) {
canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat())
line.fontRuns.forEach { run ->
- drawFontRun(canvas, line, run, tmpDrawPaint)
+ if (lineNo >= tmpDrawPaints.size)
+ drawFontRun(canvas, line, run, tmpDrawPaints[0])
+ else
+ drawFontRun(canvas, line, run, tmpDrawPaints[lineNo])
}
} finally {
canvas.restore()
@@ -384,15 +411,25 @@ class TextInterpolator(layout: Layout) {
}
// Linear interpolate the paint.
- private fun lerp(from: Paint, to: Paint, t: Float, out: Paint) {
- // Currently only font size is interpolated.
+ private fun lerp(
+ from: ArrayList<Paint>,
+ to: ArrayList<Paint>,
+ progress: Float,
+ out: ArrayList<Paint>
+ ) {
+ out.clear()
+ // Currently only font size & colors are interpolated.
// TODO(172943390): Add other interpolation or support custom interpolator.
- out.set(from)
- out.textSize = MathUtils.lerp(from.textSize, to.textSize, t)
+ for (index in from.indices) {
+ val paint = Paint(from[index])
+ paint.textSize = MathUtils.lerp(from[index].textSize, to[index].textSize, progress)
+ paint.color = ColorUtils.blendARGB(from[index].color, to[index].color, progress)
+ out.add(paint)
+ }
}
// Shape the text and stores the result to out argument.
- private fun shapeText(layout: Layout, paint: Paint): List<PositionedGlyphs> {
+ private fun shapeText(layout: Layout, paints: ArrayList<Paint>): List<PositionedGlyphs> {
val out = mutableListOf<PositionedGlyphs>()
for (lineNo in 0 until layout.lineCount) { // Shape all lines.
val lineStart = layout.getLineStart(lineNo)
@@ -403,7 +440,7 @@ class TextInterpolator(layout: Layout) {
lineStart, count, // shape context = shape range.
0f, 0f, // the layout offset. Not changed.
layout.getParagraphDirection(lineNo) == Layout.DIR_RIGHT_TO_LEFT,
- paint)) // Use given paint instead of layout's paint for style interpolation.
+ paints[lineNo])) // Use given paint instead of layout's for style interpolation.
}
return out
}
@@ -414,4 +451,4 @@ private fun Layout.getDrawOrigin(lineNo: Int) =
getLineLeft(lineNo)
} else {
getLineRight(lineNo)
- } \ No newline at end of file
+ }
diff --git a/packages/SystemUI/src/com/android/keyguard/TimeBasedColorsClockController.java b/packages/SystemUI/src/com/android/keyguard/TimeBasedColorsClockController.java
deleted file mode 100644
index 933d338c0736..000000000000
--- a/packages/SystemUI/src/com/android/keyguard/TimeBasedColorsClockController.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2020 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.keyguard;
-
-import android.util.MathUtils;
-
-import com.android.internal.graphics.ColorUtils;
-import com.android.settingslib.Utils;
-import com.android.systemui.R;
-import com.android.systemui.util.ViewController;
-
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-
-/**
- * Changes the color of the text clock based on the time of day.
- */
-public class TimeBasedColorsClockController extends ViewController<GradientTextClock> {
- private final int[] mGradientColors = new int[3];
- private final float[] mPositions = new float[3];
-
- /**
- * 0 = fully awake
- * between 0 and 1 = transitioning between awake and doze
- * 1 = fully in doze
- */
- private float mDarkAmount = 0f;
-
- public TimeBasedColorsClockController(GradientTextClock view) {
- super(view);
- }
-
- @Override
- protected void onViewAttached() {
- refreshTime(System.currentTimeMillis());
- }
-
- @Override
- protected void onViewDetached() {
-
- }
-
- /**
- * Updates the time for this view. Also updates any color changes.
- */
- public void refreshTime(long timeInMillis) {
- updateColors(timeInMillis);
- updatePositions(timeInMillis);
- mView.refreshTime();
- }
-
- /**
- * 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;
-
- refreshTime(System.currentTimeMillis());
-
- int weight = (int) MathUtils.lerp(200, 400, 1f - darkAmount);
- mView.setTextStyle(weight, -1 /* unchange text size */, true);
- }
-
- private int getTimeIndex(long timeInMillis) {
- Calendar now = getCalendar(timeInMillis);
- int hour = now.get(Calendar.HOUR_OF_DAY); // 0 - 23
- if (hour < mTimes[0]) {
- return mTimes.length - 1;
- }
-
- for (int i = 1; i < mTimes.length; i++) {
- if (hour < mTimes[i]) {
- return i - 1;
- }
- }
-
- return mTimes.length - 1;
- }
-
- private void updateColors(long timeInMillis) {
- final int index = getTimeIndex(timeInMillis);
- final int wallpaperTextColor =
- Utils.getColorAttrDefaultColor(mView.getContext(), R.attr.wallpaperTextColor);
- for (int i = 0; i < mGradientColors.length; i++) {
- // wallpaperTextColor on LS when mDarkAmount = 0f
- // full color on AOD when mDarkAmount = 1f
- mGradientColors[i] =
- ColorUtils.blendARGB(wallpaperTextColor, COLORS[index][i], mDarkAmount);
- }
- mView.setGradientColors(mGradientColors);
- }
-
- private void updatePositions(long timeInMillis) {
- Calendar now = getCalendar(timeInMillis);
- final int index = getTimeIndex(timeInMillis);
-
- final Calendar startTime = new GregorianCalendar();
- startTime.setTimeInMillis(now.getTimeInMillis());
- startTime.set(Calendar.HOUR_OF_DAY, mTimes[index]);
- if (startTime.getTimeInMillis() > now.getTimeInMillis()) {
- // start should be earlier than 'now'
- startTime.add(Calendar.DATE, -1);
- }
-
- final Calendar endTime = new GregorianCalendar();
- endTime.setTimeInMillis(now.getTimeInMillis());
- if (index == mTimes.length - 1) {
- endTime.set(Calendar.HOUR_OF_DAY, mTimes[0]);
- endTime.add(Calendar.DATE, 1); // end time is tomorrow
- } else {
- endTime.set(Calendar.HOUR_OF_DAY, mTimes[index + 1]);
- }
-
- long totalTimeInThisColorGradient = endTime.getTimeInMillis() - startTime.getTimeInMillis();
- long timeIntoThisColorGradient = now.getTimeInMillis() - startTime.getTimeInMillis();
- float percentageWithinGradient =
- (float) timeIntoThisColorGradient / (float) totalTimeInThisColorGradient;
-
- for (int i = 0; i < mPositions.length; i++) {
- // currently hard-coded .3 movement of gradient
- mPositions[i] = POSITIONS[index][i] - (.3f * percentageWithinGradient);
- }
- mView.setColorPositions(mPositions);
- }
-
- private Calendar getCalendar(long timeInMillis) {
- Calendar now = new GregorianCalendar();
- now.setTimeInMillis(timeInMillis);
- return now;
- }
-
- private static final int[] SUNRISE = new int[] {0xFF6F75AA, 0xFFAFF0FF, 0xFFFFDEBF};
- private static final int[] DAY = new int[] {0xFF9BD8FB, 0xFFD7F5FF, 0xFFFFF278};
- private static final int[] NIGHT = new int[] {0xFF333D5E, 0xFFC5A1D6, 0xFF907359};
-
- private static final float[] SUNRISE_START_POSITIONS = new float[] {.3f, .5f, .8f};
- private static final float[] DAY_START_POSITIONS = new float[] {.4f, .8f, 1f};
- private static final float[] NIGHT_START_POSITIONS = new float[] {.25f, .5f, .8f};
-
- // TODO (b/170228350): use TwilightManager to set sunrise/sunset times
- private final int mSunriseTime = 6; // 6am
- private final int mDaytime = 9; // 9 am
- private final int mNightTime = 19; // 7pm
-
- private int[] mTimes = new int[] {
- mSunriseTime,
- mDaytime,
- mNightTime
- };
- private static final int[][] COLORS = new int[][] {
- SUNRISE,
- DAY,
- NIGHT
- };
- private static final float[][] POSITIONS = new float[][] {
- SUNRISE_START_POSITIONS,
- DAY_START_POSITIONS,
- NIGHT_START_POSITIONS
- };
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt
index 516d015c0c1c..2e8eb0014f30 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt
@@ -35,9 +35,9 @@ import org.mockito.Mockito.verify
import kotlin.math.ceil
-private val PAINT = TextPaint().apply {
+private val PAINT = arrayListOf(TextPaint().apply {
textSize = 32f
-}
+})
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -50,10 +50,10 @@ class TextAnimatorTest : SysuiTestCase() {
@Test
fun testAnimationStarted() {
- val layout = makeLayout("Hello, World", PAINT)
+ val layout = makeLayout("Hello, World", PAINT[0])
val valueAnimator = mock(ValueAnimator::class.java)
val textInterpolator = mock(TextInterpolator::class.java)
- val paint = mock(Paint::class.java)
+ val paint = arrayListOf(mock(Paint::class.java))
`when`(textInterpolator.targetPaint).thenReturn(paint)
val textAnimator = TextAnimator(layout, {}).apply {
@@ -82,10 +82,10 @@ class TextAnimatorTest : SysuiTestCase() {
@Test
fun testAnimationNotStarted() {
- val layout = makeLayout("Hello, World", PAINT)
+ val layout = makeLayout("Hello, World", PAINT[0])
val valueAnimator = mock(ValueAnimator::class.java)
val textInterpolator = mock(TextInterpolator::class.java)
- val paint = mock(Paint::class.java)
+ val paint = arrayListOf(mock(Paint::class.java))
`when`(textInterpolator.targetPaint).thenReturn(paint)
val textAnimator = TextAnimator(layout, {}).apply {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt
index 65ffcfc47380..002ba364e9d4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt
@@ -18,6 +18,7 @@ package com.android.keyguard
import android.graphics.Bitmap
import android.graphics.Canvas
+import android.graphics.Paint
import android.testing.AndroidTestingRunner
import android.text.Layout
import android.text.StaticLayout
@@ -37,13 +38,13 @@ private val PAINT = TextPaint().apply {
textSize = 32f
}
-private val START_PAINT = TextPaint(PAINT).apply {
+private val START_PAINT = arrayListOf<Paint>(TextPaint(PAINT).apply {
fontVariationSettings = "'wght' 400"
-}
+})
-private val END_PAINT = TextPaint(PAINT).apply {
+private val END_PAINT = arrayListOf<Paint>(TextPaint(PAINT).apply {
fontVariationSettings = "'wght' 700"
-}
+})
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -59,16 +60,16 @@ class TextInterpolatorTest : SysuiTestCase() {
val layout = makeLayout(TEXT, PAINT)
val interp = TextInterpolator(layout)
- interp.basePaint.set(START_PAINT)
+ TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
interp.onBasePaintModified()
- interp.targetPaint.set(END_PAINT)
+ TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
interp.onTargetPaintModified()
// Just after created TextInterpolator, it should have 0 progress.
assertThat(interp.progress).isEqualTo(0f)
val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
- val expected = makeLayout(TEXT, START_PAINT).toBitmap(BMP_WIDTH, BMP_HEIGHT)
+ val expected = makeLayout(TEXT, START_PAINT[0] as TextPaint).toBitmap(BMP_WIDTH, BMP_HEIGHT)
assertThat(expected.sameAs(actual)).isTrue()
}
@@ -78,15 +79,15 @@ class TextInterpolatorTest : SysuiTestCase() {
val layout = makeLayout(TEXT, PAINT)
val interp = TextInterpolator(layout)
- interp.basePaint.set(START_PAINT)
+ TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
interp.onBasePaintModified()
- interp.targetPaint.set(END_PAINT)
+ TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
interp.onTargetPaintModified()
interp.progress = 1f
val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
- val expected = makeLayout(TEXT, END_PAINT).toBitmap(BMP_WIDTH, BMP_HEIGHT)
+ val expected = makeLayout(TEXT, END_PAINT[0] as TextPaint).toBitmap(BMP_WIDTH, BMP_HEIGHT)
assertThat(expected.sameAs(actual)).isTrue()
}
@@ -96,10 +97,10 @@ class TextInterpolatorTest : SysuiTestCase() {
val layout = makeLayout(TEXT, PAINT)
val interp = TextInterpolator(layout)
- interp.basePaint.set(START_PAINT)
+ TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
interp.onBasePaintModified()
- interp.targetPaint.set(END_PAINT)
+ TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
interp.onTargetPaintModified()
// We cannot expect exact text layout of the middle position since we don't use text shaping
@@ -107,10 +108,10 @@ class TextInterpolatorTest : SysuiTestCase() {
// end state.
interp.progress = 0.5f
val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
- assertThat(actual.sameAs(makeLayout(TEXT, START_PAINT).toBitmap(BMP_WIDTH, BMP_HEIGHT)))
- .isFalse()
- assertThat(actual.sameAs(makeLayout(TEXT, END_PAINT).toBitmap(BMP_WIDTH, BMP_HEIGHT)))
- .isFalse()
+ assertThat(actual.sameAs(makeLayout(TEXT, START_PAINT[0] as TextPaint)
+ .toBitmap(BMP_WIDTH, BMP_HEIGHT))).isFalse()
+ assertThat(actual.sameAs(makeLayout(TEXT, END_PAINT[0] as TextPaint)
+ .toBitmap(BMP_WIDTH, BMP_HEIGHT))).isFalse()
}
@Test
@@ -118,10 +119,10 @@ class TextInterpolatorTest : SysuiTestCase() {
val layout = makeLayout(TEXT, PAINT)
val interp = TextInterpolator(layout)
- interp.basePaint.set(START_PAINT)
+ TextInterpolator.updatePaint(interp.basePaint, START_PAINT)
interp.onBasePaintModified()
- interp.targetPaint.set(END_PAINT)
+ TextInterpolator.updatePaint(interp.targetPaint, END_PAINT)
interp.onTargetPaintModified()
interp.progress = 0.5f