<lambda>null1 package leakcanary.internal.activity.screen
2 
3 import android.app.AlertDialog
4 import android.view.View
5 import android.view.View.OnAttachStateChangeListener
6 import android.view.View.VISIBLE
7 import android.view.ViewGroup
8 import android.widget.EditText
9 import android.widget.ListView
10 import android.widget.TextView
11 import android.widget.Toast
12 import com.squareup.leakcanary.core.R
13 import leakcanary.internal.activity.db.Io
14 import leakcanary.internal.activity.db.executeOnIo
15 import leakcanary.internal.activity.ui.SimpleListAdapter
16 import leakcanary.internal.navigation.Screen
17 import leakcanary.internal.navigation.activity
18 import leakcanary.internal.navigation.inflate
19 import shark.HeapField
20 import shark.HeapObject.HeapClass
21 import shark.HeapObject.HeapInstance
22 import shark.HeapObject.HeapObjectArray
23 import shark.HeapObject.HeapPrimitiveArray
24 import shark.HeapValue
25 import shark.HprofHeapGraph.Companion.openHeapGraph
26 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.BooleanArrayDump
27 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ByteArrayDump
28 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.CharArrayDump
29 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.DoubleArrayDump
30 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.FloatArrayDump
31 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.IntArrayDump
32 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.LongArrayDump
33 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ShortArrayDump
34 import shark.ValueHolder.BooleanHolder
35 import shark.ValueHolder.ByteHolder
36 import shark.ValueHolder.CharHolder
37 import shark.ValueHolder.DoubleHolder
38 import shark.ValueHolder.FloatHolder
39 import shark.ValueHolder.IntHolder
40 import shark.ValueHolder.LongHolder
41 import shark.ValueHolder.ReferenceHolder
42 import shark.ValueHolder.ShortHolder
43 import java.io.Closeable
44 import java.io.File
45 
46 internal class HprofExplorerScreen(
47   private val heapDumpFile: File
48 ) : Screen() {
49   override fun createView(container: ViewGroup) =
50     container.inflate(R.layout.leak_canary_hprof_explorer).apply {
51       container.activity.title = resources.getString(R.string.leak_canary_loading_title)
52 
53       var closeable: Closeable? = null
54 
55       addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
56         override fun onViewAttachedToWindow(view: View) {
57         }
58 
59         override fun onViewDetachedFromWindow(view: View) {
60           Io.execute {
61             closeable?.close()
62           }
63         }
64       })
65 
66       executeOnIo {
67         val graph = heapDumpFile.openHeapGraph()
68         closeable = graph
69         updateUi {
70           container.activity.title =
71             resources.getString(R.string.leak_canary_explore_heap_dump)
72           val titleView = findViewById<TextView>(R.id.leak_canary_explorer_title)
73           val searchView = findViewById<View>(R.id.leak_canary_search_button)
74           val listView = findViewById<ListView>(R.id.leak_canary_explorer_list)
75           titleView.visibility = VISIBLE
76           searchView.visibility = VISIBLE
77           listView.visibility = VISIBLE
78           searchView.setOnClickListener {
79             val input = EditText(context)
80             AlertDialog.Builder(context)
81               .setIcon(android.R.drawable.ic_dialog_alert)
82               .setTitle("Type a fully qualified class name")
83               .setView(input)
84               .setPositiveButton(android.R.string.ok) { _, _ ->
85                 executeOnIo {
86                   val partialClassName = input.text.toString()
87                   val matchingClasses = graph.classes
88                     .filter { partialClassName in it.name }
89                     .toList()
90 
91                   if (matchingClasses.isEmpty()) {
92                     updateUi {
93                       Toast.makeText(
94                         context, "No class matching [$partialClassName]", Toast.LENGTH_LONG
95                       )
96                         .show()
97                     }
98                   } else {
99                     updateUi {
100                       titleView.text =
101                         "${matchingClasses.size} classes matching [$partialClassName]"
102                       listView.adapter = SimpleListAdapter(
103                         R.layout.leak_canary_simple_row, matchingClasses
104                       ) { view, position ->
105                         val itemTitleView = view.findViewById<TextView>(R.id.leak_canary_row_text)
106                         itemTitleView.text = matchingClasses[position].name
107                       }
108                       listView.setOnItemClickListener { _, _, position, _ ->
109                         val selectedClass = matchingClasses[position]
110                         showClass(titleView, listView, selectedClass)
111                       }
112                     }
113                   }
114                 }
115               }
116               .setNegativeButton(android.R.string.cancel, null)
117               .show()
118           }
119         }
120       }
121     }
122 
123   private fun View.showClass(
124     titleView: TextView,
125     listView: ListView,
126     selectedClass: HeapClass
127   ) {
128     executeOnIo {
129       val className = selectedClass.name
130       val instances = selectedClass.directInstances.toList()
131       val staticFields = selectedClass.readStaticFields()
132         .fieldsAsString()
133       updateUi {
134         titleView.text =
135           "Class $className (${instances.size} instances)"
136         listView.adapter = SimpleListAdapter(
137           R.layout.leak_canary_simple_row, staticFields + instances
138         ) { view, position ->
139           val itemTitleView =
140             view.findViewById<TextView>(R.id.leak_canary_row_text)
141           if (position < staticFields.size) {
142             itemTitleView.text = staticFields[position].second
143           } else {
144             itemTitleView.text = "@${instances[position - staticFields.size].objectId}"
145           }
146         }
147         listView.setOnItemClickListener { _, _, position, _ ->
148           if (position < staticFields.size) {
149             val staticField = staticFields[position].first
150             onHeapValueClicked(titleView, listView, staticField.value)
151           } else {
152             val instance = instances[position - staticFields.size]
153             showInstance(titleView, listView, instance)
154           }
155         }
156       }
157     }
158   }
159 
160   private fun View.showInstance(
161     titleView: TextView,
162     listView: ListView,
163     instance: HeapInstance
164   ) {
165     executeOnIo {
166       val fields = instance.readFields()
167         .fieldsAsString()
168       val className = instance.instanceClassName
169       updateUi {
170         titleView.text = "Instance @${instance.objectId} of class $className"
171         listView.adapter = SimpleListAdapter(
172           R.layout.leak_canary_simple_row, fields
173         ) { view, position ->
174           val itemTitleView =
175             view.findViewById<TextView>(R.id.leak_canary_row_text)
176           itemTitleView.text = fields[position].second
177         }
178         listView.setOnItemClickListener { _, _, position, _ ->
179           val field = fields[position].first
180           onHeapValueClicked(titleView, listView, field.value)
181         }
182       }
183     }
184   }
185 
186   private fun View.showObjectArray(
187     titleView: TextView,
188     listView: ListView,
189     instance: HeapObjectArray
190   ) {
191     executeOnIo {
192       val elements = instance.readElements()
193         .mapIndexed { index: Int, element: HeapValue ->
194           element to "[$index] = ${element.heapValueAsString()}"
195         }
196         .toList()
197       val arrayClassName = instance.arrayClassName
198       val className = arrayClassName.substring(0, arrayClassName.length - 2)
199       updateUi {
200         titleView.text = "Array $className[${elements.size}]"
201         listView.adapter = SimpleListAdapter(
202           R.layout.leak_canary_simple_row, elements
203         ) { view, position ->
204           val itemTitleView =
205             view.findViewById<TextView>(R.id.leak_canary_row_text)
206           itemTitleView.text = elements[position].second
207         }
208         listView.setOnItemClickListener { _, _, position, _ ->
209           val element = elements[position].first
210           onHeapValueClicked(titleView, listView, element)
211         }
212       }
213     }
214   }
215 
216   private fun View.showPrimitiveArray(
217     titleView: TextView,
218     listView: ListView,
219     instance: HeapPrimitiveArray
220   ) {
221     executeOnIo {
222       val (type, values) = when (val record = instance.readRecord()) {
223         is BooleanArrayDump -> "boolean" to record.array.map { it.toString() }
224         is CharArrayDump -> "char" to record.array.map { "'$it'" }
225         is FloatArrayDump -> "float" to record.array.map { it.toString() }
226         is DoubleArrayDump -> "double" to record.array.map { it.toString() }
227         is ByteArrayDump -> "byte" to record.array.map { it.toString() }
228         is ShortArrayDump -> "short" to record.array.map { it.toString() }
229         is IntArrayDump -> "int" to record.array.map { it.toString() }
230         is LongArrayDump -> "long" to record.array.map { it.toString() }
231       }
232       updateUi {
233         titleView.text = "Array $type[${values.size}]"
234         listView.adapter = SimpleListAdapter(
235           R.layout.leak_canary_simple_row, values
236         ) { view, position ->
237           val itemTitleView =
238             view.findViewById<TextView>(R.id.leak_canary_row_text)
239           itemTitleView.text = "$type ${values[position]}"
240         }
241         listView.setOnItemClickListener { _, _, _, _ ->
242         }
243       }
244     }
245   }
246 
247   private fun View.onHeapValueClicked(
248     titleView: TextView,
249     listView: ListView,
250     heapValue: HeapValue
251   ) {
252     if (heapValue.isNonNullReference) {
253       when (val objectRecord = heapValue.asObject!!) {
254         is HeapInstance -> {
255           showInstance(titleView, listView, objectRecord)
256         }
257         is HeapClass -> {
258           showClass(titleView, listView, objectRecord)
259         }
260         is HeapObjectArray -> {
261           showObjectArray(titleView, listView, objectRecord)
262         }
263         is HeapPrimitiveArray -> {
264           showPrimitiveArray(titleView, listView, objectRecord)
265         }
266       }
267     }
268   }
269 
270   private fun Sequence<HeapField>.fieldsAsString(): List<Pair<HeapField, String>> {
271     return map { field ->
272       field to "${field.declaringClass.simpleName}.${field.name} = ${field.value.heapValueAsString()}"
273     }
274       .toList()
275   }
276 
277   private fun HeapValue.heapValueAsString(): String {
278     return when (val heapValue = holder) {
279       is ReferenceHolder -> {
280         if (isNullReference) {
281           "null"
282         } else {
283           when (val objectRecord = asObject!!) {
284             is HeapInstance -> {
285               if (objectRecord instanceOf "java.lang.String") {
286                 "${objectRecord.instanceClassName}@${heapValue.value} \"${objectRecord.readAsJavaString()!!}\""
287               } else {
288                 "${objectRecord.instanceClassName}@${heapValue.value}"
289               }
290             }
291             is HeapClass -> {
292               "Class ${objectRecord.name}"
293             }
294             is HeapObjectArray -> {
295               objectRecord.arrayClassName
296             }
297             is HeapPrimitiveArray -> objectRecord.arrayClassName
298           }
299         }
300       }
301       is BooleanHolder -> "boolean ${heapValue.value}"
302       is CharHolder -> "char ${heapValue.value}"
303       is FloatHolder -> "float ${heapValue.value}"
304       is DoubleHolder -> "double ${heapValue.value}"
305       is ByteHolder -> "byte ${heapValue.value}"
306       is ShortHolder -> "short ${heapValue.value}"
307       is IntHolder -> "int ${heapValue.value}"
308       is LongHolder -> "long ${heapValue.value}"
309     }
310   }
311 }
312