[automerge] Restrict maximum size of FontInterpolator font caches 2p: 39dbe90004
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/22757815 Bug: 275486055 Change-Id: I1cf0bfffd88cf766f5b6ece99fa3b502c6c7705b Merged-In: Id6e2b127f44f5e553ff7ac4ba640fd4f6f4698b0
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
@@ -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 {
|
||||||
|
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
/**
|
/**
|
||||||
@@ -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
|
||||||
@@ -218,12 +220,12 @@ 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
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (color != null) {
|
if (color != null) {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user