1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.shared.clocks 18 19 import android.graphics.Typeface 20 import com.android.systemui.animation.FontCacheImpl 21 import com.android.systemui.animation.TypefaceVariantCache 22 import com.android.systemui.log.core.Logger 23 import com.android.systemui.log.core.MessageBuffer 24 import java.lang.ref.ReferenceQueue 25 import java.lang.ref.WeakReference 26 27 class TypefaceCache( 28 messageBuffer: MessageBuffer, 29 val animationFrameCount: Int, 30 val typefaceFactory: (String) -> Typeface, 31 ) { 32 private val logger = Logger(messageBuffer, this::class.simpleName!!) 33 34 private data class CacheKey(val res: String, val fvar: String?) 35 36 private inner class WeakTypefaceRef(val key: CacheKey, typeface: Typeface) : 37 WeakReference<Typeface>(typeface, queue) 38 39 private var totalHits = 0 40 41 private var totalMisses = 0 42 43 private var totalEvictions = 0 44 45 // We use a map of WeakRefs here instead of an LruCache. This prevents needing to resize the 46 // cache depending on the number of distinct fonts used by a clock, as different clocks have 47 // different numbers of simultaneously loaded and configured fonts. Because our clocks tend to 48 // initialize a number of parallel views and animators, our usages of Typefaces overlap. As a 49 // result, once a typeface is no longer being used, it is unlikely to be recreated immediately. 50 private val cache = mutableMapOf<CacheKey, WeakTypefaceRef>() 51 private val queue = ReferenceQueue<Typeface>() 52 private val fontCache = FontCacheImpl(animationFrameCount) 53 getTypefacenull54 fun getTypeface(res: String): Typeface { 55 checkQueue() 56 val key = CacheKey(res, null) 57 cache.get(key)?.get()?.let { 58 logHit(key) 59 return it 60 } 61 62 logMiss(key) 63 val result = typefaceFactory(res) 64 cache.put(key, WeakTypefaceRef(key, result)) 65 return result 66 } 67 getVariantCachenull68 fun getVariantCache(res: String): TypefaceVariantCache { 69 val baseTypeface = getTypeface(res) 70 return object : TypefaceVariantCache { 71 override val fontCache = this@TypefaceCache.fontCache 72 override val animationFrameCount = this@TypefaceCache.animationFrameCount 73 74 override fun getTypefaceForVariant(fvar: String?): Typeface? { 75 checkQueue() 76 val key = CacheKey(res, fvar) 77 cache.get(key)?.get()?.let { 78 logHit(key) 79 return it 80 } 81 82 logMiss(key) 83 return TypefaceVariantCache.createVariantTypeface(baseTypeface, fvar).also { 84 cache.put(key, WeakTypefaceRef(key, it)) 85 } 86 } 87 } 88 } 89 logHitnull90 private fun logHit(key: CacheKey) { 91 totalHits++ 92 if (DEBUG_HITS) 93 logger.i({ "HIT: $str1; Total: $int1" }) { 94 str1 = key.toString() 95 int1 = totalHits 96 } 97 } 98 logMissnull99 private fun logMiss(key: CacheKey) { 100 totalMisses++ 101 logger.w({ "MISS: $str1; Total: $int1" }) { 102 str1 = key.toString() 103 int1 = totalMisses 104 } 105 } 106 logEvictionnull107 private fun logEviction(key: CacheKey) { 108 totalEvictions++ 109 logger.i({ "EVICTED: $str1; Total: $int1" }) { 110 str1 = key.toString() 111 int1 = totalEvictions 112 } 113 } 114 checkQueuenull115 private fun checkQueue() = 116 generateSequence { queue.poll() } 117 .filterIsInstance<WeakTypefaceRef>() <lambda>null118 .forEach { 119 logEviction(it.key) 120 cache.remove(it.key) 121 } 122 123 companion object { 124 private val DEBUG_HITS = false 125 } 126 } 127