Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6249a86e authored by Hawkwood Glazier's avatar Hawkwood Glazier
Browse files

Adjust Fidget animation parameters

Bug: 374306512
Test: Checked animation
Flag: com.android.systemui.shared.clock_reactive_variants
Change-Id: I73bd8c13f4fd4c5698193bee1212346d3216b036
parent 974aea57
Loading
Loading
Loading
Loading
+109 −188
Original line number Diff line number Diff line
@@ -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,
    ) {
        try {
            if (animate) {
                animator.cancel()
                textInterpolator.rebase()
            }

            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
    }
            textInterpolator.onTargetPaintModified()

            if (animate) {
                animator.startDelay = delay
                animator.duration = if (duration == -1L) DEFAULT_ANIMATION_DURATION else duration
                interpolator?.let { animator.interpolator = it }
    /** 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,
    ) {
        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()
                                animator.removeListener(this)
                            }

                            override fun onAnimationCancel(animation: Animator) {
                                animator.removeListener(this)
                        }
                    }
                )
            }
                animator.start()
        }

        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 {
                // No animation is requested, thus set base and target state to the same state.
            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()
            }

            addListener(
                object : AnimatorListenerAdapter() {
                    override fun onAnimationEnd(animator: Animator) = textInterpolator.rebase()

                    override fun onAnimationCancel(animator: Animator) = textInterpolator.rebase()
                }
            )
        }
    }

    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
    }
}
+13 −14
Original line number Diff line number Diff line
@@ -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
                }
+2 −1
Original line number Diff line number Diff line
@@ -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,
)

+107 −79

File changed.

Preview size limit exceeded, changes collapsed.

+27 −28
Original line number Diff line number Diff line
@@ -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,
                eq(TextAnimator.Style(fVar = "'wght' 300", color = 200)),
                eq(
                    TextAnimator.Animation(
                        animate = false,
                        duration = 833L,
                        interpolator = Interpolators.EMPHASIZED_DECELERATE,
                delay = 0L,
                onAnimationEnd = null
                        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,
                eq(TextAnimator.Style(fVar = "'wght' 300", color = 200)),
                eq(
                    TextAnimator.Animation(
                        animate = true,
                        duration = 833L,
                        interpolator = Interpolators.EMPHASIZED_DECELERATE,
                delay = 0L,
                onAnimationEnd = null
                    )
                ),
            )
        verifyNoMoreInteractions(mockTextAnimator)
    }
Loading