diff options
author | 2020-11-17 21:03:18 +0000 | |
---|---|---|
committer | 2020-11-17 21:03:18 +0000 | |
commit | 2aa31cc0702afa54d84aa6180ca6ce91e8cb6eda (patch) | |
tree | 94a9e9c1966d811b801e8642a36a89aff6fc94cc | |
parent | 2edad48f94044c7e9b5984da88a4379e145860a7 (diff) | |
parent | 192b7c9eaabd746ebdedb306a5a93d8740716d22 (diff) |
Merge "Add support for LS Clock to use > 1 Paint object"
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 |