Loading packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt +11 −5 Original line number Original line Diff line number Diff line Loading @@ -18,8 +18,10 @@ package com.android.systemui.animation import android.graphics.fonts.Font import android.graphics.fonts.Font import android.graphics.fonts.FontVariationAxis import android.graphics.fonts.FontVariationAxis import android.util.LruCache import android.util.MathUtils import android.util.MathUtils import android.util.MathUtils.abs import android.util.MathUtils.abs import androidx.annotation.VisibleForTesting import java.lang.Float.max import java.lang.Float.max import java.lang.Float.min import java.lang.Float.min Loading @@ -34,6 +36,10 @@ private const val FONT_ITALIC_MIN = 0f private const val FONT_ITALIC_ANIMATION_STEP = 0.1f private const val FONT_ITALIC_ANIMATION_STEP = 0.1f private const val FONT_ITALIC_DEFAULT_VALUE = 0f private const val FONT_ITALIC_DEFAULT_VALUE = 0f // Benchmarked via Perfetto, difference between 10 and 50 entries is about 0.3ms in // frame draw time on a Pixel 6. @VisibleForTesting const val FONT_CACHE_MAX_ENTRIES = 10 /** Provide interpolation of two fonts by adjusting font variation settings. */ /** Provide interpolation of two fonts by adjusting font variation settings. */ class FontInterpolator { class FontInterpolator { Loading Loading @@ -81,8 +87,8 @@ class FontInterpolator { // Font interpolator has two level caches: one for input and one for font with different // Font interpolator has two level caches: one for input and one for font with different // variation settings. No synchronization is needed since FontInterpolator is not designed to be // variation settings. No synchronization is needed since FontInterpolator is not designed to be // thread-safe and can be used only on UI thread. // thread-safe and can be used only on UI thread. private val interpCache = hashMapOf<InterpKey, Font>() private val interpCache = LruCache<InterpKey, Font>(FONT_CACHE_MAX_ENTRIES) private val verFontCache = hashMapOf<VarFontKey, Font>() private val verFontCache = LruCache<VarFontKey, Font>(FONT_CACHE_MAX_ENTRIES) // Mutable keys for recycling. // Mutable keys for recycling. private val tmpInterpKey = InterpKey(null, null, 0f) private val tmpInterpKey = InterpKey(null, null, 0f) Loading Loading @@ -152,7 +158,7 @@ class FontInterpolator { tmpVarFontKey.set(start, newAxes) tmpVarFontKey.set(start, newAxes) val axesCachedFont = verFontCache[tmpVarFontKey] val axesCachedFont = verFontCache[tmpVarFontKey] if (axesCachedFont != null) { if (axesCachedFont != null) { interpCache[InterpKey(start, end, progress)] = axesCachedFont interpCache.put(InterpKey(start, end, progress), axesCachedFont) return axesCachedFont return axesCachedFont } } Loading @@ -160,8 +166,8 @@ class FontInterpolator { // Font.Builder#build won't throw IOException since creating fonts from existing fonts will // Font.Builder#build won't throw IOException since creating fonts from existing fonts will // not do any IO work. // not do any IO work. val newFont = Font.Builder(start).setFontVariationSettings(newAxes.toTypedArray()).build() val newFont = Font.Builder(start).setFontVariationSettings(newAxes.toTypedArray()).build() interpCache[InterpKey(start, end, progress)] = newFont interpCache.put(InterpKey(start, end, progress), newFont) verFontCache[VarFontKey(start, newAxes)] = newFont verFontCache.put(VarFontKey(start, newAxes), newFont) return newFont return newFont } } Loading packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt +7 −5 Original line number Original line Diff line number Diff line Loading @@ -24,8 +24,10 @@ import android.graphics.Canvas import android.graphics.Typeface import android.graphics.Typeface import android.graphics.fonts.Font import android.graphics.fonts.Font import android.text.Layout import android.text.Layout import android.util.LruCache private const val DEFAULT_ANIMATION_DURATION: Long = 300 private const val DEFAULT_ANIMATION_DURATION: Long = 300 private const val TYPEFACE_CACHE_MAX_ENTRIES = 5 typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit /** /** Loading Loading @@ -114,7 +116,7 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) { private val fontVariationUtils = FontVariationUtils() private val fontVariationUtils = FontVariationUtils() private val typefaceCache = HashMap<String, Typeface?>() private val typefaceCache = LruCache<String, Typeface>(TYPEFACE_CACHE_MAX_ENTRIES) fun updateLayout(layout: Layout) { fun updateLayout(layout: Layout) { textInterpolator.layout = layout textInterpolator.layout = layout Loading Loading @@ -218,11 +220,11 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) { } } if (!fvar.isNullOrBlank()) { if (!fvar.isNullOrBlank()) { textInterpolator.targetPaint.typeface = textInterpolator.targetPaint.typeface = typefaceCache.get(fvar) ?: run { typefaceCache.getOrElse(fvar) { textInterpolator.targetPaint.fontVariationSettings = fvar textInterpolator.targetPaint.fontVariationSettings = fvar textInterpolator.targetPaint.typeface?.also { typefaceCache.put(fvar, textInterpolator.targetPaint.typeface) typefaceCache.put(fvar, textInterpolator.targetPaint.typeface) textInterpolator.targetPaint.typeface } } } } } Loading packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt +25 −0 Original line number Original line Diff line number Diff line Loading @@ -106,4 +106,29 @@ class FontInterpolatorTest : SysuiTestCase() { val reversedFont = interp.lerp(endFont, startFont, 0.5f) val reversedFont = interp.lerp(endFont, startFont, 0.5f) assertThat(resultFont).isSameInstanceAs(reversedFont) assertThat(resultFont).isSameInstanceAs(reversedFont) } } @Test fun testCacheMaxSize() { val interp = FontInterpolator() val startFont = Font.Builder(sFont) .setFontVariationSettings("'wght' 100") .build() val endFont = Font.Builder(sFont) .setFontVariationSettings("'wght' 1") .build() val resultFont = interp.lerp(startFont, endFont, 0.5f) for (i in 0..FONT_CACHE_MAX_ENTRIES + 1) { val f1 = Font.Builder(sFont) .setFontVariationSettings("'wght' ${i * 100}") .build() val f2 = Font.Builder(sFont) .setFontVariationSettings("'wght' $i") .build() interp.lerp(f1, f2, 0.5f) } val cachedFont = interp.lerp(startFont, endFont, 0.5f) assertThat(resultFont).isNotSameInstanceAs(cachedFont) } } } Loading
packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt +11 −5 Original line number Original line Diff line number Diff line Loading @@ -18,8 +18,10 @@ package com.android.systemui.animation import android.graphics.fonts.Font import android.graphics.fonts.Font import android.graphics.fonts.FontVariationAxis import android.graphics.fonts.FontVariationAxis import android.util.LruCache import android.util.MathUtils import android.util.MathUtils import android.util.MathUtils.abs import android.util.MathUtils.abs import androidx.annotation.VisibleForTesting import java.lang.Float.max import java.lang.Float.max import java.lang.Float.min import java.lang.Float.min Loading @@ -34,6 +36,10 @@ private const val FONT_ITALIC_MIN = 0f private const val FONT_ITALIC_ANIMATION_STEP = 0.1f private const val FONT_ITALIC_ANIMATION_STEP = 0.1f private const val FONT_ITALIC_DEFAULT_VALUE = 0f private const val FONT_ITALIC_DEFAULT_VALUE = 0f // Benchmarked via Perfetto, difference between 10 and 50 entries is about 0.3ms in // frame draw time on a Pixel 6. @VisibleForTesting const val FONT_CACHE_MAX_ENTRIES = 10 /** Provide interpolation of two fonts by adjusting font variation settings. */ /** Provide interpolation of two fonts by adjusting font variation settings. */ class FontInterpolator { class FontInterpolator { Loading Loading @@ -81,8 +87,8 @@ class FontInterpolator { // Font interpolator has two level caches: one for input and one for font with different // Font interpolator has two level caches: one for input and one for font with different // variation settings. No synchronization is needed since FontInterpolator is not designed to be // variation settings. No synchronization is needed since FontInterpolator is not designed to be // thread-safe and can be used only on UI thread. // thread-safe and can be used only on UI thread. private val interpCache = hashMapOf<InterpKey, Font>() private val interpCache = LruCache<InterpKey, Font>(FONT_CACHE_MAX_ENTRIES) private val verFontCache = hashMapOf<VarFontKey, Font>() private val verFontCache = LruCache<VarFontKey, Font>(FONT_CACHE_MAX_ENTRIES) // Mutable keys for recycling. // Mutable keys for recycling. private val tmpInterpKey = InterpKey(null, null, 0f) private val tmpInterpKey = InterpKey(null, null, 0f) Loading Loading @@ -152,7 +158,7 @@ class FontInterpolator { tmpVarFontKey.set(start, newAxes) tmpVarFontKey.set(start, newAxes) val axesCachedFont = verFontCache[tmpVarFontKey] val axesCachedFont = verFontCache[tmpVarFontKey] if (axesCachedFont != null) { if (axesCachedFont != null) { interpCache[InterpKey(start, end, progress)] = axesCachedFont interpCache.put(InterpKey(start, end, progress), axesCachedFont) return axesCachedFont return axesCachedFont } } Loading @@ -160,8 +166,8 @@ class FontInterpolator { // Font.Builder#build won't throw IOException since creating fonts from existing fonts will // Font.Builder#build won't throw IOException since creating fonts from existing fonts will // not do any IO work. // not do any IO work. val newFont = Font.Builder(start).setFontVariationSettings(newAxes.toTypedArray()).build() val newFont = Font.Builder(start).setFontVariationSettings(newAxes.toTypedArray()).build() interpCache[InterpKey(start, end, progress)] = newFont interpCache.put(InterpKey(start, end, progress), newFont) verFontCache[VarFontKey(start, newAxes)] = newFont verFontCache.put(VarFontKey(start, newAxes), newFont) return newFont return newFont } } Loading
packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt +7 −5 Original line number Original line Diff line number Diff line Loading @@ -24,8 +24,10 @@ import android.graphics.Canvas import android.graphics.Typeface import android.graphics.Typeface import android.graphics.fonts.Font import android.graphics.fonts.Font import android.text.Layout import android.text.Layout import android.util.LruCache private const val DEFAULT_ANIMATION_DURATION: Long = 300 private const val DEFAULT_ANIMATION_DURATION: Long = 300 private const val TYPEFACE_CACHE_MAX_ENTRIES = 5 typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit /** /** Loading Loading @@ -114,7 +116,7 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) { private val fontVariationUtils = FontVariationUtils() private val fontVariationUtils = FontVariationUtils() private val typefaceCache = HashMap<String, Typeface?>() private val typefaceCache = LruCache<String, Typeface>(TYPEFACE_CACHE_MAX_ENTRIES) fun updateLayout(layout: Layout) { fun updateLayout(layout: Layout) { textInterpolator.layout = layout textInterpolator.layout = layout Loading Loading @@ -218,11 +220,11 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) { } } if (!fvar.isNullOrBlank()) { if (!fvar.isNullOrBlank()) { textInterpolator.targetPaint.typeface = textInterpolator.targetPaint.typeface = typefaceCache.get(fvar) ?: run { typefaceCache.getOrElse(fvar) { textInterpolator.targetPaint.fontVariationSettings = fvar textInterpolator.targetPaint.fontVariationSettings = fvar textInterpolator.targetPaint.typeface?.also { typefaceCache.put(fvar, textInterpolator.targetPaint.typeface) typefaceCache.put(fvar, textInterpolator.targetPaint.typeface) textInterpolator.targetPaint.typeface } } } } } Loading
packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt +25 −0 Original line number Original line Diff line number Diff line Loading @@ -106,4 +106,29 @@ class FontInterpolatorTest : SysuiTestCase() { val reversedFont = interp.lerp(endFont, startFont, 0.5f) val reversedFont = interp.lerp(endFont, startFont, 0.5f) assertThat(resultFont).isSameInstanceAs(reversedFont) assertThat(resultFont).isSameInstanceAs(reversedFont) } } @Test fun testCacheMaxSize() { val interp = FontInterpolator() val startFont = Font.Builder(sFont) .setFontVariationSettings("'wght' 100") .build() val endFont = Font.Builder(sFont) .setFontVariationSettings("'wght' 1") .build() val resultFont = interp.lerp(startFont, endFont, 0.5f) for (i in 0..FONT_CACHE_MAX_ENTRIES + 1) { val f1 = Font.Builder(sFont) .setFontVariationSettings("'wght' ${i * 100}") .build() val f2 = Font.Builder(sFont) .setFontVariationSettings("'wght' $i") .build() interp.lerp(f1, f2, 0.5f) } val cachedFont = interp.lerp(startFont, endFont, 0.5f) assertThat(resultFont).isNotSameInstanceAs(cachedFont) } } }