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

Commit b0da3984 authored by Hawkwood Glazier's avatar Hawkwood Glazier
Browse files

Update TextAnimator to retry with updated layout when failed

The source of the underlying issue is that the text layout is a shared
object between the TextView which creates it and the TextAnimator which
consumes it and caches certain values internally. When the TextView
mutates the layout, the TextAnimator must be notified via updateLayout.

This change prevents that exception from crashing the application by
updating the layout after the fact, and retyring the setTextStyle call.
If it happens twice in a row, we let the exception escape and still
crash. Ideally the callsites would always call updateLayout when the
layout is mutated, but this has proved difficult to detect robustly.

Flag: N/A
Bug: 305221458
Test: Manual & Presubmits
Change-Id: I3f030674636710c0dadfd133c2aa72530ab1be10
parent 5f812109
Loading
Loading
Loading
Loading
+75 −50
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.graphics.fonts.FontVariationAxis
import android.text.Layout
import android.util.LruCache
import kotlin.math.roundToInt
import android.util.Log

private const val DEFAULT_ANIMATION_DURATION: Long = 300
private const val TYPEFACE_CACHE_MAX_ENTRIES = 5
@@ -140,7 +141,6 @@ class TextAnimator(
    }

    sealed class PositionedGlyph {

        /** Mutable X coordinate of the glyph position relative from drawing offset. */
        var x: Float = 0f

@@ -269,8 +269,23 @@ class TextAnimator(
        duration: Long = -1L,
        interpolator: TimeInterpolator? = null,
        delay: Long = 0,
        onAnimationEnd: Runnable? = null
        onAnimationEnd: Runnable? = null,
    ) = setTextStyleInternal(fvar, textSize, color, strokeWidth, animate, duration,
        interpolator, delay, onAnimationEnd, updateLayoutOnFailure = true)

    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()
@@ -279,11 +294,9 @@ class TextAnimator(
            if (textSize >= 0) {
                textInterpolator.targetPaint.textSize = textSize
            }

            if (!fvar.isNullOrBlank()) {
                textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
            }

            if (color != null) {
                textInterpolator.targetPaint.color = color
            }
@@ -302,8 +315,7 @@ class TextAnimator(
                    }
                interpolator?.let { animator.interpolator = it }
                if (onAnimationEnd != null) {
                val listener =
                    object : AnimatorListenerAdapter() {
                    val listener = object : AnimatorListenerAdapter() {
                        override fun onAnimationEnd(animation: Animator) {
                            onAnimationEnd.run()
                            animator.removeListener(this)
@@ -321,6 +333,17 @@ class TextAnimator(
                textInterpolator.rebase()
                invalidateCallback()
            }
        } catch (ex: IllegalArgumentException) {
            if (updateLayoutOnFailure) {
                Log.e(TAG, "setTextStyleInternal: Exception caught but retrying. This is usually" +
                    " due to the layout having changed unexpectedly without being notified.", ex)
                updateLayout(textInterpolator.layout)
                setTextStyleInternal(fvar, textSize, color, strokeWidth, animate, duration,
                    interpolator, delay, onAnimationEnd, updateLayoutOnFailure = false)
            } else {
                throw ex
            }
        }
    }

    /**
@@ -355,15 +378,13 @@ class TextAnimator(
        interpolator: TimeInterpolator? = null,
        delay: Long = 0,
        onAnimationEnd: Runnable? = null
    ) {
        val fvar = fontVariationUtils.updateFontVariation(
    ) = setTextStyleInternal(
            fvar = fontVariationUtils.updateFontVariation(
                weight = weight,
                width = width,
                opticalSize = opticalSize,
                roundness = roundness,
        )
        setTextStyle(
            fvar = fvar,
            ),
            textSize = textSize,
            color = color,
            strokeWidth = strokeWidth,
@@ -372,6 +393,10 @@ class TextAnimator(
            interpolator = interpolator,
            delay = delay,
            onAnimationEnd = onAnimationEnd,
            updateLayoutOnFailure = true,
        )

    companion object {
        private val TAG = TextAnimator::class.simpleName!!
    }
}