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

Commit cb49af29 authored by Hawkwood Glazier's avatar Hawkwood Glazier Committed by Android (Google) Code Review
Browse files

Merge "Adjust Fidget animation parameters" into main

parents 12e6669c 6249a86e
Loading
Loading
Loading
Loading
+109 −188
Original line number Original line Diff line number Diff line
@@ -27,9 +27,8 @@ import android.graphics.fonts.FontVariationAxis
import android.text.Layout
import android.text.Layout
import android.util.Log
import android.util.Log
import android.util.LruCache
import android.util.LruCache

import androidx.annotation.VisibleForTesting
private const val DEFAULT_ANIMATION_DURATION: Long = 300
import com.android.app.animation.Interpolators
private const val TYPEFACE_CACHE_MAX_ENTRIES = 5


typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit
typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit


@@ -76,6 +75,10 @@ class TypefaceVariantCacheImpl(var baseTypeface: Typeface, override val animatio
            cache.put(fvar, it)
            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 typefaceCache: TypefaceVariantCache,
    private val invalidateCallback: () -> Unit = {},
    private val invalidateCallback: () -> Unit = {},
) {
) {
    // Following two members are for mutable for testing purposes.
    @VisibleForTesting var textInterpolator = TextInterpolator(layout, typefaceCache)
    public var textInterpolator = TextInterpolator(layout, typefaceCache)
    @VisibleForTesting var createAnimator: () -> ValueAnimator = { ValueAnimator.ofFloat(1f) }
    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()


                    override fun onAnimationCancel(animation: Animator) = textInterpolator.rebase()
    var animator: ValueAnimator? = null
                }

            )
    val fontVariationUtils = FontVariationUtils()
        }


    sealed class PositionedGlyph {
    sealed class PositionedGlyph {
        /** Mutable X coordinate of the glyph position relative from drawing offset. */
        /** Mutable X coordinate of the glyph position relative from drawing offset. */
@@ -165,8 +155,6 @@ class TextAnimator(
            protected set
            protected set
    }
    }


    private val fontVariationUtils = FontVariationUtils()

    fun updateLayout(layout: Layout, textSize: Float = -1f) {
    fun updateLayout(layout: Layout, textSize: Float = -1f) {
        textInterpolator.layout = layout
        textInterpolator.layout = layout


@@ -178,9 +166,8 @@ class TextAnimator(
        }
        }
    }
    }


    fun isRunning(): Boolean {
    val isRunning: Boolean
        return animator.isRunning
        get() = animator?.isRunning ?: false
    }


    /**
    /**
     * GlyphFilter applied just before drawing to canvas for tweaking positions and text size.
     * 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)
    fun draw(c: Canvas) = textInterpolator.draw(c)


    /**
    /** Style spec to use when rendering the font */
     * Set text style with animation.
    data class Style(
     *
        val fVar: String? = null,
     * ```
        val textSize: Float? = null,
     * By passing -1 to weight, the view preserve the current weight.
        val color: Int? = null,
     * By passing -1 to textSize, the view preserve the current text size.
        val strokeWidth: Float? = null,
     * 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,
    ) {
    ) {
        setTextStyleInternal(
        fun withUpdatedFVar(
            fvar,
            fontVariationUtils: FontVariationUtils,
            textSize,
            weight: Int = -1,
            color,
            width: Int = -1,
            strokeWidth,
            opticalSize: Int = -1,
            animate,
            roundness: Int = -1,
            duration,
        ): Style {
            interpolator,
            return this.copy(
            delay,
                fVar =
            onAnimationEnd,
                    fontVariationUtils.updateFontVariation(
            updateLayoutOnFailure = true,
                        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) {
    /** Animation Spec for use when style changes should be animated */
                animator.startDelay = delay
    data class Animation(
                animator.duration = if (duration == -1L) DEFAULT_ANIMATION_DURATION else duration
        val animate: Boolean = true,
                interpolator?.let { animator.interpolator = it }
        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) {
            if (onAnimationEnd != null) {
                animator.addListener(
                animator.addListener(
                    object : AnimatorListenerAdapter() {
                    object : AnimatorListenerAdapter() {
                        override fun onAnimationEnd(animation: Animator) {
                        override fun onAnimationEnd(animation: Animator) {
                            onAnimationEnd.run()
                            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 {
        } else {
                // No animation is requested, thus set base and target state to the same state.
            textInterpolator.progress = 1f
            textInterpolator.progress = 1f
            textInterpolator.rebase()
            textInterpolator.rebase()
            invalidateCallback()
            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) {
        } catch (ex: IllegalArgumentException) {
            if (updateLayoutOnFailure) {
            if (updateLayoutOnFailure) {
                Log.e(
                Log.e(
@@ -351,81 +338,15 @@ class TextAnimator(
                )
                )


                updateLayout(textInterpolator.layout)
                updateLayout(textInterpolator.layout)
                setTextStyleInternal(
                setTextStyleInternal(style, rebase, updateLayoutOnFailure = false)
                    fvar,
                    textSize,
                    color,
                    strokeWidth,
                    animate,
                    duration,
                    interpolator,
                    delay,
                    onAnimationEnd,
                    updateLayoutOnFailure = false,
                )
            } else {
            } else {
                throw ex
                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 {
    companion object {
        private val TAG = TextAnimator::class.simpleName!!
        private val TAG = TextAnimator::class.simpleName!!
        const val DEFAULT_ANIMATION_DURATION = 300L
    }
    }
}
}
+13 −14
Original line number Original line Diff line number Diff line
@@ -366,7 +366,7 @@ constructor(


    fun animateCharge(isDozing: () -> Boolean) {
    fun animateCharge(isDozing: () -> Boolean) {
        // Skip charge animation if dozing animation is already playing.
        // Skip charge animation if dozing animation is already playing.
        if (textAnimator == null || textAnimator!!.isRunning()) {
        if (textAnimator == null || textAnimator!!.isRunning) {
            return
            return
        }
        }


@@ -444,29 +444,28 @@ constructor(
        delay: Long,
        delay: Long,
        onAnimationEnd: Runnable?,
        onAnimationEnd: Runnable?,
    ) {
    ) {
        textAnimator?.let {
        val style = TextAnimator.Style(color = color)
            it.setTextStyle(
        val animation =
                weight = weight,
            TextAnimator.Animation(
                color = color,
                animate = animate && isAnimationEnabled,
                animate = animate && isAnimationEnabled,
                duration = duration,
                duration = duration,
                interpolator = interpolator,
                interpolator = interpolator ?: Interpolators.LINEAR,
                delay = delay,
                startDelay = delay,
                onAnimationEnd = onAnimationEnd,
                onAnimationEnd = onAnimationEnd,
            )
            )
        textAnimator?.let {
            it.setTextStyle(
                style.withUpdatedFVar(it.fontVariationUtils, weight = weight),
                animation,
            )
            it.glyphFilter = glyphFilter
            it.glyphFilter = glyphFilter
        }
        }
            ?: run {
            ?: run {
                // when the text animator is set, update its start values
                // when the text animator is set, update its start values
                onTextAnimatorInitialized = { textAnimator ->
                onTextAnimatorInitialized = { textAnimator ->
                    textAnimator.setTextStyle(
                    textAnimator.setTextStyle(
                        weight = weight,
                        style.withUpdatedFVar(textAnimator.fontVariationUtils, weight = weight),
                        color = color,
                        animation.copy(animate = false),
                        animate = false,
                        duration = duration,
                        interpolator = interpolator,
                        delay = delay,
                        onAnimationEnd = onAnimationEnd,
                    )
                    )
                    textAnimator.glyphFilter = glyphFilter
                    textAnimator.glyphFilter = glyphFilter
                }
                }
+2 −1
Original line number Original line Diff line number Diff line
@@ -21,6 +21,7 @@ import android.view.ViewGroup
import android.view.animation.Interpolator
import android.view.animation.Interpolator
import android.widget.RelativeLayout
import android.widget.RelativeLayout
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting
import com.android.systemui.animation.TextAnimator
import com.android.systemui.customization.R
import com.android.systemui.customization.R
import com.android.systemui.log.core.Logger
import com.android.systemui.log.core.Logger
import com.android.systemui.plugins.clocks.AlarmData
import com.android.systemui.plugins.clocks.AlarmData
@@ -65,7 +66,7 @@ data class DigitalAlignment(
data class FontTextStyle(
data class FontTextStyle(
    val lineHeight: Float? = null,
    val lineHeight: Float? = null,
    val fontSizeScale: Float? = null,
    val fontSizeScale: Float? = null,
    val transitionDuration: Long = -1L,
    val transitionDuration: Long = TextAnimator.DEFAULT_ANIMATION_DURATION,
    val transitionInterpolator: Interpolator? = null,
    val transitionInterpolator: Interpolator? = null,
)
)


+107 −79

File changed.

Preview size limit exceeded, changes collapsed.

+27 −28
Original line number Original line Diff line number Diff line
@@ -20,9 +20,10 @@ import android.view.LayoutInflater
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.app.animation.Interpolators
import com.android.app.animation.Interpolators
import com.android.systemui.customization.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.FontVariationUtils
import com.android.systemui.animation.TextAnimator
import com.android.systemui.animation.TextAnimator
import com.android.systemui.customization.R
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.any
import org.junit.Before
import org.junit.Before
import org.junit.Rule
import org.junit.Rule
@@ -32,7 +33,9 @@ import org.mockito.Mock
import org.mockito.Mockito.times
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoJUnit
import org.mockito.kotlin.eq


@RunWith(AndroidJUnit4::class)
@RunWith(AndroidJUnit4::class)
@SmallTest
@SmallTest
@@ -46,6 +49,7 @@ class AnimatableClockViewTest : SysuiTestCase() {
    @Before
    @Before
    fun setUp() {
    fun setUp() {
        val layoutInflater = LayoutInflater.from(context)
        val layoutInflater = LayoutInflater.from(context)
        whenever(mockTextAnimator.fontVariationUtils).thenReturn(FontVariationUtils())
        clockView =
        clockView =
            layoutInflater.inflate(R.layout.clock_default_small, null) as AnimatableClockView
            layoutInflater.inflate(R.layout.clock_default_small, null) as AnimatableClockView
        clockView.textAnimatorFactory = { _, _ -> mockTextAnimator }
        clockView.textAnimatorFactory = { _, _ -> mockTextAnimator }
@@ -57,18 +61,19 @@ class AnimatableClockViewTest : SysuiTestCase() {
        clockView.animateAppearOnLockscreen()
        clockView.animateAppearOnLockscreen()
        clockView.measure(50, 50)
        clockView.measure(50, 50)


        verify(mockTextAnimator).fontVariationUtils
        verify(mockTextAnimator).glyphFilter = any()
        verify(mockTextAnimator).glyphFilter = any()
        verify(mockTextAnimator)
        verify(mockTextAnimator)
            .setTextStyle(
            .setTextStyle(
                weight = 300,
                eq(TextAnimator.Style(fVar = "'wght' 300", color = 200)),
                textSize = -1.0f,
                eq(
                color = 200,
                    TextAnimator.Animation(
                strokeWidth = -1F,
                        animate = false,
                        animate = false,
                        duration = 833L,
                        duration = 833L,
                        interpolator = Interpolators.EMPHASIZED_DECELERATE,
                        interpolator = Interpolators.EMPHASIZED_DECELERATE,
                delay = 0L,
                        onAnimationEnd = null,
                onAnimationEnd = null
                    )
                ),
            )
            )
        verifyNoMoreInteractions(mockTextAnimator)
        verifyNoMoreInteractions(mockTextAnimator)
    }
    }
@@ -79,30 +84,24 @@ class AnimatableClockViewTest : SysuiTestCase() {
        clockView.measure(50, 50)
        clockView.measure(50, 50)
        clockView.animateAppearOnLockscreen()
        clockView.animateAppearOnLockscreen()


        verify(mockTextAnimator, times(2)).fontVariationUtils
        verify(mockTextAnimator, times(2)).glyphFilter = any()
        verify(mockTextAnimator, times(2)).glyphFilter = any()
        verify(mockTextAnimator)
        verify(mockTextAnimator)
            .setTextStyle(
            .setTextStyle(
                weight = 100,
                eq(TextAnimator.Style(fVar = "'wght' 100", color = 200)),
                textSize = -1.0f,
                eq(TextAnimator.Animation(animate = false, duration = 0)),
                color = 200,
                strokeWidth = -1F,
                animate = false,
                duration = 0L,
                interpolator = null,
                delay = 0L,
                onAnimationEnd = null
            )
            )

        verify(mockTextAnimator)
        verify(mockTextAnimator)
            .setTextStyle(
            .setTextStyle(
                weight = 300,
                eq(TextAnimator.Style(fVar = "'wght' 300", color = 200)),
                textSize = -1.0f,
                eq(
                color = 200,
                    TextAnimator.Animation(
                strokeWidth = -1F,
                        animate = true,
                        animate = true,
                        duration = 833L,
                        duration = 833L,
                        interpolator = Interpolators.EMPHASIZED_DECELERATE,
                        interpolator = Interpolators.EMPHASIZED_DECELERATE,
                delay = 0L,
                    )
                onAnimationEnd = null
                ),
            )
            )
        verifyNoMoreInteractions(mockTextAnimator)
        verifyNoMoreInteractions(mockTextAnimator)
    }
    }
Loading