xref: /aosp_15_r20/external/leakcanary2/shark-android/src/main/java/shark/AndroidResourceIdNames.kt (revision d9e8da70d8c9df9a41d7848ae506fb3115cae6e6)

<lambda>null1 package shark
2 
3 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.IntArrayDump
4 
5 class AndroidResourceIdNames private constructor(
6   private val resourceIds: IntArray,
7   private val names: Array<String>
8 ) {
9 
10   operator fun get(id: Int): String? {
11     val indexOfId = resourceIds.binarySearch(id)
12     return if (indexOfId >= 0) {
13       names[indexOfId]
14     } else {
15       null
16     }
17   }
18 
19   companion object {
20 
21     internal const val FIRST_APP_RESOURCE_ID = 0x7F010000
22     internal const val RESOURCE_ID_TYPE_ITERATOR = 0x00010000
23 
24     @Volatile
25     @JvmStatic
26     private var holderField: AndroidResourceIdNames? = null
27 
28     /**
29      * @param getResourceTypeName a function that delegates to Android
30      * Resources.getResourceTypeName but returns null when the name isn't found instead of
31      * throwing an exception.
32      *
33      * @param getResourceEntryName a function that delegates to Android
34      * Resources.getResourceEntryName but returns null when the name isn't found instead of
35      * throwing an exception.
36      */
37     @Synchronized fun saveToMemory(
38       getResourceTypeName: (Int) -> String?,
39       getResourceEntryName: (Int) -> String?
40     ) {
41       if (holderField != null) {
42         return
43       }
44 
45       // This is based on https://jebware.com/blog/?p=600 which itself is based on
46       // https://stackoverflow.com/a/6646113/703646
47 
48       val idToNamePairs = mutableListOf<Pair<Int, String>>()
49       findIdTypeResourceIdStart(getResourceTypeName)?.let { idTypeResourceIdStart ->
50         var resourceId = idTypeResourceIdStart
51         while (true) {
52           val entry = getResourceEntryName(resourceId) ?: break
53           idToNamePairs += resourceId to entry
54           resourceId++
55         }
56       }
57       val resourceIds = idToNamePairs.map { it.first }
58         .toIntArray()
59       val names = idToNamePairs.map { it.second }
60         .toTypedArray()
61       holderField = AndroidResourceIdNames(resourceIds, names)
62     }
63 
64     private fun findIdTypeResourceIdStart(getResourceTypeName: (Int) -> String?): Int? {
65       var resourceTypeId = FIRST_APP_RESOURCE_ID
66       while (true) {
67         when (getResourceTypeName(resourceTypeId)) {
68           null -> return null
69           "id" -> return resourceTypeId
70           else -> resourceTypeId += RESOURCE_ID_TYPE_ITERATOR
71         }
72       }
73     }
74 
75     fun readFromHeap(graph: HeapGraph): AndroidResourceIdNames? {
76       return graph.context.getOrPut(AndroidResourceIdNames::class.java.name) {
77         val className = AndroidResourceIdNames::class.java.name
78         val holderClass = graph.findClassByName(className)
79         holderClass?.let {
80           val holderField = holderClass["holderField"]!!
81           holderField.valueAsInstance?.let { instance ->
82             val resourceIdsField = instance[className, "resourceIds"]!!
83             val resourceIdsArray = resourceIdsField.valueAsPrimitiveArray!!
84             val resourceIds =
85               (resourceIdsArray.readRecord() as IntArrayDump).array
86             val names = instance[className, "names"]!!.valueAsObjectArray!!.readElements()
87               .map { it.readAsJavaString()!! }
88               .toList()
89               .toTypedArray()
90             AndroidResourceIdNames(resourceIds, names)
91           }
92         }
93       }
94     }
95 
96     internal fun resetForTests() {
97       holderField = null
98     }
99   }
100 }