diff options
7 files changed, 289 insertions, 322 deletions
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt index b9f9bc7e2daa..5b073e49192a 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt @@ -27,9 +27,8 @@ import android.graphics.fonts.FontVariationAxis import android.text.Layout import android.util.Log import android.util.LruCache - -private const val DEFAULT_ANIMATION_DURATION: Long = 300 -private const val TYPEFACE_CACHE_MAX_ENTRIES = 5 +import androidx.annotation.VisibleForTesting +import com.android.app.animation.Interpolators typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit @@ -76,6 +75,10 @@ class TypefaceVariantCacheImpl(var baseTypeface: Typeface, override val animatio cache.put(fvar, it) } } + + companion object { + private const val TYPEFACE_CACHE_MAX_ENTRIES = 5 + } } /** @@ -108,25 +111,12 @@ class TextAnimator( private val typefaceCache: TypefaceVariantCache, private val invalidateCallback: () -> Unit = {}, ) { - // Following two members are for mutable for testing purposes. - public var textInterpolator = TextInterpolator(layout, typefaceCache) - public var animator = - ValueAnimator.ofFloat(1f).apply { - duration = DEFAULT_ANIMATION_DURATION - addUpdateListener { - textInterpolator.progress = it.animatedValue as Float - textInterpolator.linearProgress = - it.currentPlayTime.toFloat() / it.duration.toFloat() - invalidateCallback() - } - addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) = textInterpolator.rebase() + @VisibleForTesting var textInterpolator = TextInterpolator(layout, typefaceCache) + @VisibleForTesting var createAnimator: () -> ValueAnimator = { ValueAnimator.ofFloat(1f) } - override fun onAnimationCancel(animation: Animator) = textInterpolator.rebase() - } - ) - } + var animator: ValueAnimator? = null + + val fontVariationUtils = FontVariationUtils() sealed class PositionedGlyph { /** Mutable X coordinate of the glyph position relative from drawing offset. */ @@ -165,8 +155,6 @@ class TextAnimator( protected set } - private val fontVariationUtils = FontVariationUtils() - fun updateLayout(layout: Layout, textSize: Float = -1f) { textInterpolator.layout = layout @@ -178,9 +166,8 @@ class TextAnimator( } } - fun isRunning(): Boolean { - return animator.isRunning - } + val isRunning: Boolean + get() = animator?.isRunning ?: false /** * GlyphFilter applied just before drawing to canvas for tweaking positions and text size. @@ -237,110 +224,110 @@ class TextAnimator( fun draw(c: Canvas) = textInterpolator.draw(c) - /** - * 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. - * By passing -1 to duration, the default text animation, 1000ms, is used. - * By passing false to animate, the text will be updated without animation. - * ``` - * - * @param fvar an optional text fontVariationSettings. - * @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 strokeWidth an optional paint stroke width - * @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 - * false. - * @param interpolator an optional time interpolator. If null is passed, last set interpolator - * will be used. This is ignored if animate is false. - */ - fun setTextStyle( - fvar: String? = "", - textSize: Float = -1f, - color: Int? = null, - strokeWidth: Float = -1f, - animate: Boolean = true, - duration: Long = -1L, - interpolator: TimeInterpolator? = null, - delay: Long = 0, - onAnimationEnd: Runnable? = null, + /** Style spec to use when rendering the font */ + data class Style( + val fVar: String? = null, + val textSize: Float? = null, + val color: Int? = null, + val strokeWidth: Float? = null, ) { - setTextStyleInternal( - fvar, - textSize, - color, - strokeWidth, - animate, - duration, - interpolator, - delay, - onAnimationEnd, - updateLayoutOnFailure = true, - ) + fun withUpdatedFVar( + fontVariationUtils: FontVariationUtils, + weight: Int = -1, + width: Int = -1, + opticalSize: Int = -1, + roundness: Int = -1, + ): Style { + return this.copy( + fVar = + fontVariationUtils.updateFontVariation( + weight = weight, + width = width, + opticalSize = opticalSize, + roundness = roundness, + ) + ) + } } - private fun setTextStyleInternal( - fvar: String?, - textSize: Float, - color: Int?, - strokeWidth: Float, - animate: Boolean, - duration: Long, - interpolator: TimeInterpolator?, - delay: Long, - onAnimationEnd: Runnable?, - updateLayoutOnFailure: Boolean, + /** Animation Spec for use when style changes should be animated */ + data class Animation( + val animate: Boolean = true, + val startDelay: Long = 0, + val duration: Long = DEFAULT_ANIMATION_DURATION, + val interpolator: TimeInterpolator = Interpolators.LINEAR, + val onAnimationEnd: Runnable? = null, ) { - try { - if (animate) { - animator.cancel() - textInterpolator.rebase() + fun configureAnimator(animator: Animator) { + animator.startDelay = startDelay + animator.duration = duration + animator.interpolator = interpolator + if (onAnimationEnd != null) { + animator.addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + onAnimationEnd.run() + } + } + ) } + } - if (textSize >= 0) { - textInterpolator.targetPaint.textSize = textSize - } - if (!fvar.isNullOrBlank()) { - textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(fvar) - } - if (color != null) { - textInterpolator.targetPaint.color = color - } - if (strokeWidth >= 0F) { - textInterpolator.targetPaint.strokeWidth = strokeWidth + companion object { + val DISABLED = Animation(animate = false) + } + } + + /** Sets the text style, optionally with animation */ + fun setTextStyle(style: Style, animation: Animation = Animation.DISABLED) { + animator?.cancel() + setTextStyleInternal(style, rebase = animation.animate) + + if (animation.animate) { + animator = buildAnimator(animation).apply { start() } + } else { + textInterpolator.progress = 1f + textInterpolator.rebase() + invalidateCallback() + } + } + + /** Builds a ValueAnimator from the specified animation parameters */ + private fun buildAnimator(animation: Animation): ValueAnimator { + return createAnimator().apply { + duration = DEFAULT_ANIMATION_DURATION + animation.configureAnimator(this) + + addUpdateListener { + textInterpolator.progress = it.animatedValue as Float + textInterpolator.linearProgress = it.currentPlayTime / it.duration.toFloat() + invalidateCallback() } - textInterpolator.onTargetPaintModified() - if (animate) { - animator.startDelay = delay - animator.duration = if (duration == -1L) DEFAULT_ANIMATION_DURATION else duration - interpolator?.let { animator.interpolator = it } - if (onAnimationEnd != null) { - animator.addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - onAnimationEnd.run() - animator.removeListener(this) - } - - override fun onAnimationCancel(animation: Animator) { - animator.removeListener(this) - } - } - ) + addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animator: Animator) = textInterpolator.rebase() + + override fun onAnimationCancel(animator: Animator) = textInterpolator.rebase() } - animator.start() - } else { - // No animation is requested, thus set base and target state to the same state. - textInterpolator.progress = 1f - textInterpolator.rebase() - invalidateCallback() + ) + } + } + + private fun setTextStyleInternal( + style: Style, + rebase: Boolean, + updateLayoutOnFailure: Boolean = true, + ) { + try { + if (rebase) textInterpolator.rebase() + style.color?.let { textInterpolator.targetPaint.color = it } + style.textSize?.let { textInterpolator.targetPaint.textSize = it } + style.strokeWidth?.let { textInterpolator.targetPaint.strokeWidth = it } + style.fVar?.let { + textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(it) } + textInterpolator.onTargetPaintModified() } catch (ex: IllegalArgumentException) { if (updateLayoutOnFailure) { Log.e( @@ -351,81 +338,15 @@ class TextAnimator( ) updateLayout(textInterpolator.layout) - setTextStyleInternal( - fvar, - textSize, - color, - strokeWidth, - animate, - duration, - interpolator, - delay, - onAnimationEnd, - updateLayoutOnFailure = false, - ) + setTextStyleInternal(style, rebase, updateLayoutOnFailure = false) } else { throw ex } } } - /** - * Set text style with animation. Similar as - * - * ``` - * fun setTextStyle( - * fvar: String? = "", - * textSize: Float = -1f, - * color: Int? = null, - * strokeWidth: Float = -1f, - * animate: Boolean = true, - * duration: Long = -1L, - * interpolator: TimeInterpolator? = null, - * delay: Long = 0, - * onAnimationEnd: Runnable? = null - * ) - * ``` - * - * @param weight an optional style value for `wght` in fontVariationSettings. - * @param width an optional style value for `wdth` in fontVariationSettings. - * @param opticalSize an optional style value for `opsz` in fontVariationSettings. - * @param roundness an optional style value for `ROND` in fontVariationSettings. - */ - fun setTextStyle( - weight: Int = -1, - width: Int = -1, - opticalSize: Int = -1, - roundness: Int = -1, - textSize: Float = -1f, - color: Int? = null, - strokeWidth: Float = -1f, - animate: Boolean = true, - duration: Long = -1L, - interpolator: TimeInterpolator? = null, - delay: Long = 0, - onAnimationEnd: Runnable? = null, - ) { - setTextStyleInternal( - fvar = - fontVariationUtils.updateFontVariation( - weight = weight, - width = width, - opticalSize = opticalSize, - roundness = roundness, - ), - textSize = textSize, - color = color, - strokeWidth = strokeWidth, - animate = animate, - duration = duration, - interpolator = interpolator, - delay = delay, - onAnimationEnd = onAnimationEnd, - updateLayoutOnFailure = true, - ) - } - companion object { private val TAG = TextAnimator::class.simpleName!! + const val DEFAULT_ANIMATION_DURATION = 300L } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index b76656d78cc4..4bf0ceb51784 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -366,7 +366,7 @@ constructor( fun animateCharge(isDozing: () -> Boolean) { // Skip charge animation if dozing animation is already playing. - if (textAnimator == null || textAnimator!!.isRunning()) { + if (textAnimator == null || textAnimator!!.isRunning) { return } @@ -444,29 +444,28 @@ constructor( delay: Long, onAnimationEnd: Runnable?, ) { - textAnimator?.let { - it.setTextStyle( - weight = weight, - color = color, + val style = TextAnimator.Style(color = color) + val animation = + TextAnimator.Animation( animate = animate && isAnimationEnabled, duration = duration, - interpolator = interpolator, - delay = delay, + interpolator = interpolator ?: Interpolators.LINEAR, + startDelay = delay, onAnimationEnd = onAnimationEnd, ) + textAnimator?.let { + it.setTextStyle( + style.withUpdatedFVar(it.fontVariationUtils, weight = weight), + animation, + ) it.glyphFilter = glyphFilter } ?: run { // when the text animator is set, update its start values onTextAnimatorInitialized = { textAnimator -> textAnimator.setTextStyle( - weight = weight, - color = color, - animate = false, - duration = duration, - interpolator = interpolator, - delay = delay, - onAnimationEnd = onAnimationEnd, + style.withUpdatedFVar(textAnimator.fontVariationUtils, weight = weight), + animation.copy(animate = false), ) textAnimator.glyphFilter = glyphFilter } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt index a5adfa2a1ac6..0b7ea1a335ef 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt @@ -21,6 +21,7 @@ import android.view.ViewGroup import android.view.animation.Interpolator import android.widget.RelativeLayout import androidx.annotation.VisibleForTesting +import com.android.systemui.animation.TextAnimator import com.android.systemui.customization.R import com.android.systemui.log.core.Logger import com.android.systemui.plugins.clocks.AlarmData @@ -65,7 +66,7 @@ data class DigitalAlignment( data class FontTextStyle( val lineHeight: Float? = null, val fontSizeScale: Float? = null, - val transitionDuration: Long = -1L, + val transitionDuration: Long = TextAnimator.DEFAULT_ANIMATION_DURATION, val transitionInterpolator: Interpolator? = null, ) diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt index 92fa6b5be1ed..8317aa39ef2b 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt @@ -33,7 +33,9 @@ import android.util.MathUtils import android.util.TypedValue import android.view.View.MeasureSpec.EXACTLY import android.view.animation.Interpolator +import android.view.animation.PathInterpolator import android.widget.TextView +import com.android.app.animation.Interpolators import com.android.internal.annotations.VisibleForTesting import com.android.systemui.animation.GSFAxes import com.android.systemui.animation.TextAnimator @@ -84,17 +86,20 @@ open class SimpleDigitalClockTextView( else -> listOf(FLEX_AOD_SMALL_WEIGHT_AXIS, FLEX_AOD_WIDTH_AXIS) } - private var lsFontVariation = - if (!isLegacyFlex) listOf(LS_WEIGHT_AXIS, WIDTH_AXIS, ROUND_AXIS, SLANT_AXIS).toFVar() - else listOf(FLEX_LS_WEIGHT_AXIS, FLEX_LS_WIDTH_AXIS, FLEX_ROUND_AXIS, SLANT_AXIS).toFVar() + private var lsFontVariation: String + private var aodFontVariation: String + private var fidgetFontVariation: String - private var aodFontVariation = run { + init { val roundAxis = if (!isLegacyFlex) ROUND_AXIS else FLEX_ROUND_AXIS - (fixedAodAxes + listOf(roundAxis, SLANT_AXIS)).toFVar() - } + val lsFontAxes = + if (!isLegacyFlex) listOf(LS_WEIGHT_AXIS, WIDTH_AXIS, ROUND_AXIS, SLANT_AXIS) + else listOf(FLEX_LS_WEIGHT_AXIS, FLEX_LS_WIDTH_AXIS, FLEX_ROUND_AXIS, SLANT_AXIS) - // TODO(b/374306512): Fidget endpoint to spec - private var fidgetFontVariation = aodFontVariation + lsFontVariation = lsFontAxes.toFVar() + aodFontVariation = (fixedAodAxes + listOf(roundAxis, SLANT_AXIS)).toFVar() + fidgetFontVariation = buildFidgetVariation(lsFontAxes).toFVar() + } private val parser = DimensionParser(clockCtx.context) var maxSingleDigitHeight = -1 @@ -121,7 +126,7 @@ open class SimpleDigitalClockTextView( protected val logger = ClockLogger(this, clockCtx.messageBuffer, this::class.simpleName!!) get() = field ?: ClockLogger.INIT_LOGGER - private var aodDozingInterpolator: Interpolator? = null + private var aodDozingInterpolator: Interpolator = Interpolators.LINEAR @VisibleForTesting lateinit var textAnimator: TextAnimator @@ -149,7 +154,7 @@ open class SimpleDigitalClockTextView( lockscreenColor = color lockScreenPaint.color = lockscreenColor if (dozeFraction < 1f) { - textAnimator.setTextStyle(color = lockscreenColor, animate = false) + textAnimator.setTextStyle(TextAnimator.Style(color = lockscreenColor)) } invalidate() } @@ -157,10 +162,8 @@ open class SimpleDigitalClockTextView( fun updateAxes(lsAxes: List<ClockFontAxisSetting>) { lsFontVariation = lsAxes.toFVar() aodFontVariation = lsAxes.replace(fixedAodAxes).toFVar() - logger.i({ "updateAxes(LS = $str1, AOD = $str2)" }) { - str1 = lsFontVariation - str2 = aodFontVariation - } + fidgetFontVariation = buildFidgetVariation(lsAxes).toFVar() + logger.updateAxes(lsFontVariation, aodFontVariation) lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(lsFontVariation) typeface = lockScreenPaint.typeface @@ -168,13 +171,28 @@ open class SimpleDigitalClockTextView( lockScreenPaint.getTextBounds(text, 0, text.length, textBounds) targetTextBounds.set(textBounds) - textAnimator.setTextStyle(fvar = lsFontVariation, animate = false) + textAnimator.setTextStyle(TextAnimator.Style(fVar = lsFontVariation)) measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED) recomputeMaxSingleDigitSizes() requestLayout() invalidate() } + fun buildFidgetVariation(axes: List<ClockFontAxisSetting>): List<ClockFontAxisSetting> { + val result = mutableListOf<ClockFontAxisSetting>() + for (axis in axes) { + result.add( + FIDGET_DISTS.get(axis.key)?.let { (dist, midpoint) -> + ClockFontAxisSetting( + axis.key, + axis.value + dist * if (axis.value > midpoint) -1 else 1, + ) + } ?: axis + ) + } + return result + } + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { logger.onMeasure() super.onMeasure(widthMeasureSpec, heightMeasureSpec) @@ -245,40 +263,54 @@ open class SimpleDigitalClockTextView( fun animateDoze(isDozing: Boolean, isAnimated: Boolean) { if (!this::textAnimator.isInitialized) return + logger.animateDoze() textAnimator.setTextStyle( - animate = isAnimated && isAnimationEnabled, - color = if (isDozing) AOD_COLOR else lockscreenColor, - textSize = if (isDozing) aodFontSizePx else lockScreenPaint.textSize, - fvar = if (isDozing) aodFontVariation else lsFontVariation, - duration = aodStyle.transitionDuration, - interpolator = aodDozingInterpolator, + TextAnimator.Style( + fVar = if (isDozing) aodFontVariation else lsFontVariation, + color = if (isDozing) AOD_COLOR else lockscreenColor, + textSize = if (isDozing) aodFontSizePx else lockScreenPaint.textSize, + ), + TextAnimator.Animation( + animate = isAnimated && isAnimationEnabled, + duration = aodStyle.transitionDuration, + interpolator = aodDozingInterpolator, + ), ) updateTextBoundsForTextAnimator() } fun animateCharge() { - if (!this::textAnimator.isInitialized || textAnimator.isRunning()) { + if (!this::textAnimator.isInitialized || textAnimator.isRunning) { // Skip charge animation if dozing animation is already playing. return } - logger.d("animateCharge()") + logger.animateCharge() + + val lsStyle = TextAnimator.Style(fVar = lsFontVariation) + val aodStyle = TextAnimator.Style(fVar = aodFontVariation) + textAnimator.setTextStyle( - fvar = if (dozeFraction == 0F) aodFontVariation else lsFontVariation, - animate = isAnimationEnabled, - onAnimationEnd = - Runnable { + if (dozeFraction == 0f) aodStyle else lsStyle, + TextAnimator.Animation( + animate = isAnimationEnabled, + duration = CHARGE_ANIMATION_DURATION, + onAnimationEnd = { textAnimator.setTextStyle( - fvar = if (dozeFraction == 0F) lsFontVariation else aodFontVariation, - animate = isAnimationEnabled, + if (dozeFraction == 0f) lsStyle else aodStyle, + TextAnimator.Animation( + animate = isAnimationEnabled, + duration = CHARGE_ANIMATION_DURATION, + ), ) updateTextBoundsForTextAnimator() }, + ), ) updateTextBoundsForTextAnimator() } fun animateFidget(x: Float, y: Float) { - if (!this::textAnimator.isInitialized || textAnimator.isRunning()) { + if (!this::textAnimator.isInitialized || textAnimator.isRunning) { // Skip fidget animation if other animation is already playing. return } @@ -286,19 +318,25 @@ open class SimpleDigitalClockTextView( logger.animateFidget(x, y) clockCtx.vibrator?.vibrate(FIDGET_HAPTICS) - // TODO(b/374306512): Duplicated charge animation as placeholder. Implement final version - // when we have a complete spec. May require additional code to animate individual digits. + // TODO(b/374306512): Delay each glyph's animation based on x/y position textAnimator.setTextStyle( - fvar = fidgetFontVariation, - animate = isAnimationEnabled, - onAnimationEnd = - Runnable { + TextAnimator.Style(fVar = fidgetFontVariation), + TextAnimator.Animation( + animate = isAnimationEnabled, + duration = FIDGET_ANIMATION_DURATION, + interpolator = FIDGET_INTERPOLATOR, + onAnimationEnd = { textAnimator.setTextStyle( - fvar = if (dozeFraction == 0F) lsFontVariation else aodFontVariation, - animate = isAnimationEnabled, + TextAnimator.Style(fVar = lsFontVariation), + TextAnimator.Animation( + animate = isAnimationEnabled, + duration = FIDGET_ANIMATION_DURATION, + interpolator = FIDGET_INTERPOLATOR, + ), ) updateTextBoundsForTextAnimator() }, + ), ) updateTextBoundsForTextAnimator() } @@ -329,42 +367,20 @@ open class SimpleDigitalClockTextView( } private fun getInterpolatedTextBounds(): Rect { - val interpolatedTextBounds = Rect() - if (textAnimator.animator.animatedFraction != 1.0f && textAnimator.animator.isRunning) { - interpolatedTextBounds.left = - MathUtils.lerp( - prevTextBounds.left, - targetTextBounds.left, - textAnimator.animator.animatedValue as Float, - ) - .toInt() - - interpolatedTextBounds.right = - MathUtils.lerp( - prevTextBounds.right, - targetTextBounds.right, - textAnimator.animator.animatedValue as Float, - ) - .toInt() - - interpolatedTextBounds.top = - MathUtils.lerp( - prevTextBounds.top, - targetTextBounds.top, - textAnimator.animator.animatedValue as Float, - ) - .toInt() - - interpolatedTextBounds.bottom = - MathUtils.lerp( - prevTextBounds.bottom, - targetTextBounds.bottom, - textAnimator.animator.animatedValue as Float, - ) - .toInt() - } else { - interpolatedTextBounds.set(targetTextBounds) + val progress = textAnimator.animator?.let { it.animatedValue as Float } ?: 1f + if (!textAnimator.isRunning || progress >= 1f) { + return Rect(targetTextBounds) } + + val interpolatedTextBounds = Rect() + interpolatedTextBounds.left = + MathUtils.lerp(prevTextBounds.left, targetTextBounds.left, progress).toInt() + interpolatedTextBounds.right = + MathUtils.lerp(prevTextBounds.right, targetTextBounds.right, progress).toInt() + interpolatedTextBounds.top = + MathUtils.lerp(prevTextBounds.top, targetTextBounds.top, progress).toInt() + interpolatedTextBounds.bottom = + MathUtils.lerp(prevTextBounds.bottom, targetTextBounds.bottom, progress).toInt() return interpolatedTextBounds } @@ -471,7 +487,7 @@ open class SimpleDigitalClockTextView( textStyle.lineHeight?.let { lineHeight = it.toInt() } this.aodStyle = aodStyle ?: textStyle.copy() - this.aodStyle.transitionInterpolator?.let { aodDozingInterpolator = it } + aodDozingInterpolator = this.aodStyle.transitionInterpolator ?: Interpolators.LINEAR lockScreenPaint.strokeWidth = textBorderWidth measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED) setInterpolatorPaint() @@ -500,7 +516,7 @@ open class SimpleDigitalClockTextView( recomputeMaxSingleDigitSizes() if (this::textAnimator.isInitialized) { - textAnimator.setTextStyle(textSize = lockScreenPaint.textSize, animate = false) + textAnimator.setTextStyle(TextAnimator.Style(textSize = lockScreenPaint.textSize)) } } @@ -525,10 +541,11 @@ open class SimpleDigitalClockTextView( textAnimator.textInterpolator.targetPaint.set(lockScreenPaint) textAnimator.textInterpolator.onTargetPaintModified() textAnimator.setTextStyle( - fvar = lsFontVariation, - textSize = lockScreenPaint.textSize, - color = lockscreenColor, - animate = false, + TextAnimator.Style( + fVar = lsFontVariation, + textSize = lockScreenPaint.textSize, + color = lockscreenColor, + ) ) } } @@ -572,6 +589,17 @@ open class SimpleDigitalClockTextView( .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 1.0f, 43) .compose() + val CHARGE_ANIMATION_DURATION = 500L + val FIDGET_ANIMATION_DURATION = 250L + val FIDGET_INTERPOLATOR = PathInterpolator(0.26873f, 0f, 0.45042f, 1f) + val FIDGET_DISTS = + mapOf( + GSFAxes.WEIGHT to Pair(200f, 500f), + GSFAxes.WIDTH to Pair(30f, 75f), + GSFAxes.ROUND to Pair(0f, 50f), + GSFAxes.SLANT to Pair(0f, -5f), + ) + val AOD_COLOR = Color.WHITE val LS_WEIGHT_AXIS = ClockFontAxisSetting(GSFAxes.WEIGHT, 400f) val AOD_WEIGHT_AXIS = ClockFontAxisSetting(GSFAxes.WEIGHT, 200f) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt index e076418f2630..79e78c9532c6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt @@ -20,9 +20,10 @@ import android.view.LayoutInflater import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.app.animation.Interpolators -import com.android.systemui.customization.R import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.FontVariationUtils import com.android.systemui.animation.TextAnimator +import com.android.systemui.customization.R import com.android.systemui.util.mockito.any import org.junit.Before import org.junit.Rule @@ -32,7 +33,9 @@ import org.mockito.Mock import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.eq @RunWith(AndroidJUnit4::class) @SmallTest @@ -46,6 +49,7 @@ class AnimatableClockViewTest : SysuiTestCase() { @Before fun setUp() { val layoutInflater = LayoutInflater.from(context) + whenever(mockTextAnimator.fontVariationUtils).thenReturn(FontVariationUtils()) clockView = layoutInflater.inflate(R.layout.clock_default_small, null) as AnimatableClockView clockView.textAnimatorFactory = { _, _ -> mockTextAnimator } @@ -57,18 +61,19 @@ class AnimatableClockViewTest : SysuiTestCase() { clockView.animateAppearOnLockscreen() clockView.measure(50, 50) + verify(mockTextAnimator).fontVariationUtils verify(mockTextAnimator).glyphFilter = any() verify(mockTextAnimator) .setTextStyle( - weight = 300, - textSize = -1.0f, - color = 200, - strokeWidth = -1F, - animate = false, - duration = 833L, - interpolator = Interpolators.EMPHASIZED_DECELERATE, - delay = 0L, - onAnimationEnd = null + eq(TextAnimator.Style(fVar = "'wght' 300", color = 200)), + eq( + TextAnimator.Animation( + animate = false, + duration = 833L, + interpolator = Interpolators.EMPHASIZED_DECELERATE, + onAnimationEnd = null, + ) + ), ) verifyNoMoreInteractions(mockTextAnimator) } @@ -79,30 +84,24 @@ class AnimatableClockViewTest : SysuiTestCase() { clockView.measure(50, 50) clockView.animateAppearOnLockscreen() + verify(mockTextAnimator, times(2)).fontVariationUtils verify(mockTextAnimator, times(2)).glyphFilter = any() verify(mockTextAnimator) .setTextStyle( - weight = 100, - textSize = -1.0f, - color = 200, - strokeWidth = -1F, - animate = false, - duration = 0L, - interpolator = null, - delay = 0L, - onAnimationEnd = null + eq(TextAnimator.Style(fVar = "'wght' 100", color = 200)), + eq(TextAnimator.Animation(animate = false, duration = 0)), ) + verify(mockTextAnimator) .setTextStyle( - weight = 300, - textSize = -1.0f, - color = 200, - strokeWidth = -1F, - animate = true, - duration = 833L, - interpolator = Interpolators.EMPHASIZED_DECELERATE, - delay = 0L, - onAnimationEnd = null + eq(TextAnimator.Style(fVar = "'wght' 300", color = 200)), + eq( + TextAnimator.Animation( + animate = true, + duration = 833L, + interpolator = Interpolators.EMPHASIZED_DECELERATE, + ) + ), ) verifyNoMoreInteractions(mockTextAnimator) } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt index 9a837446a802..3ed321e48cd3 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt @@ -83,6 +83,13 @@ class ClockLogger(private val view: View?, buffer: MessageBuffer, tag: String) : } } + fun updateAxes(lsFVar: String, aodFVar: String) { + i({ "updateAxes(LS = $str1, AOD = $str2)" }) { + str1 = lsFVar + str2 = aodFVar + } + } + fun addView(child: View) { d({ "addView($str1 @$int1)" }) { str1 = child::class.simpleName!! @@ -90,6 +97,14 @@ class ClockLogger(private val view: View?, buffer: MessageBuffer, tag: String) : } } + fun animateDoze() { + d("animateDoze()") + } + + fun animateCharge() { + d("animateCharge()") + } + fun animateFidget(x: Float, y: Float) { d({ "animateFidget($str1, $str2)" }) { str1 = x.toString() diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt index dcf38800bb01..14a81b3f8bfb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt @@ -30,7 +30,6 @@ import kotlin.math.ceil import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor -import org.mockito.Mockito.eq import org.mockito.Mockito.inOrder import org.mockito.Mockito.mock import org.mockito.Mockito.never @@ -60,10 +59,11 @@ class TextAnimatorTest : SysuiTestCase() { val textAnimator = TextAnimator(layout, TypefaceVariantCacheImpl(typeface, 20)).apply { this.textInterpolator = textInterpolator + this.createAnimator = { valueAnimator } this.animator = valueAnimator } - textAnimator.setTextStyle(weight = 400, animate = true) + textAnimator.setTextStyle(TextAnimator.Style("'wght' 400"), TextAnimator.Animation()) // If animation is requested, the base state should be rebased and the target state should // be updated. @@ -90,10 +90,11 @@ class TextAnimatorTest : SysuiTestCase() { val textAnimator = TextAnimator(layout, TypefaceVariantCacheImpl(typeface, 20)).apply { this.textInterpolator = textInterpolator + this.createAnimator = { valueAnimator } this.animator = valueAnimator } - textAnimator.setTextStyle(weight = 400, animate = false) + textAnimator.setTextStyle(TextAnimator.Style("'wght' 400")) // If animation is not requested, the progress should be 1 which is end of animation and the // base state is rebased to target state by calling rebase. @@ -118,23 +119,24 @@ class TextAnimatorTest : SysuiTestCase() { val textAnimator = TextAnimator(layout, TypefaceVariantCacheImpl(typeface, 20)).apply { this.textInterpolator = textInterpolator + this.createAnimator = { valueAnimator } this.animator = valueAnimator } textAnimator.setTextStyle( - weight = 400, - animate = true, - onAnimationEnd = animationEndCallback, + TextAnimator.Style("'wght' 400"), + TextAnimator.Animation(animate = true, onAnimationEnd = animationEndCallback), ) // Verify animationEnd callback has been added. val captor = ArgumentCaptor.forClass(AnimatorListenerAdapter::class.java) - verify(valueAnimator).addListener(captor.capture()) - captor.value.onAnimationEnd(valueAnimator) + verify(valueAnimator, times(2)).addListener(captor.capture()) + for (callback in captor.allValues) { + callback.onAnimationEnd(valueAnimator) + } // Verify animationEnd callback has been invoked and removed. verify(animationEndCallback).run() - verify(valueAnimator).removeListener(eq(captor.value)) } @Test @@ -148,18 +150,20 @@ class TextAnimatorTest : SysuiTestCase() { val textAnimator = TextAnimator(layout, TypefaceVariantCacheImpl(typeface, 20)).apply { this.textInterpolator = textInterpolator + this.createAnimator = { valueAnimator } this.animator = valueAnimator } - textAnimator.setTextStyle(weight = 400, animate = true) + val animation = TextAnimator.Animation(animate = true) + textAnimator.setTextStyle(TextAnimator.Style("'wght' 400"), animation) val prevTypeface = paint.typeface - textAnimator.setTextStyle(weight = 700, animate = true) + textAnimator.setTextStyle(TextAnimator.Style("'wght' 700"), animation) assertThat(paint.typeface).isNotSameInstanceAs(prevTypeface) - textAnimator.setTextStyle(weight = 400, animate = true) + textAnimator.setTextStyle(TextAnimator.Style("'wght' 400"), animation) assertThat(paint.typeface).isSameInstanceAs(prevTypeface) } |