<lambda>null1 package leakcanary.internal.activity.screen
2
3 import android.R.drawable
4 import android.R.string
5 import android.app.ActivityManager
6 import android.app.AlertDialog.Builder
7 import android.text.Html
8 import android.text.SpannableStringBuilder
9 import android.text.method.LinkMovementMethod
10 import android.view.View
11 import android.view.View.GONE
12 import android.view.View.VISIBLE
13 import android.view.ViewGroup
14 import android.widget.BaseAdapter
15 import android.widget.ListView
16 import android.widget.TextView
17 import com.squareup.leakcanary.core.R
18 import leakcanary.internal.activity.db.HeapAnalysisTable
19 import leakcanary.internal.activity.db.LeakTable
20 import leakcanary.internal.activity.db.executeOnDb
21 import leakcanary.internal.activity.share
22 import leakcanary.internal.activity.shareHeapDump
23 import leakcanary.internal.activity.ui.TimeFormatter
24 import leakcanary.internal.activity.ui.UiUtils
25 import leakcanary.internal.navigation.Screen
26 import leakcanary.internal.navigation.activity
27 import leakcanary.internal.navigation.goBack
28 import leakcanary.internal.navigation.goTo
29 import leakcanary.internal.navigation.inflate
30 import leakcanary.internal.navigation.onCreateOptionsMenu
31 import shark.HeapAnalysis
32 import shark.HeapAnalysisSuccess
33 import shark.LibraryLeak
34 import shark.SharkLog
35
36 internal class HeapDumpScreen(
37 private val analysisId: Long
38 ) : Screen() {
39
40 override fun createView(container: ViewGroup) =
41 container.inflate(R.layout.leak_canary_list).apply {
42 activity.title = resources.getString(R.string.leak_canary_loading_title)
43
44 executeOnDb {
45 val heapAnalysis = HeapAnalysisTable.retrieve<HeapAnalysisSuccess>(db, analysisId)
46 if (heapAnalysis == null) {
47 updateUi {
48 activity.title = resources.getString(R.string.leak_canary_analysis_deleted_title)
49 }
50 } else {
51 val signatures = heapAnalysis.allLeaks.map { it.signature }
52 .toSet()
53 val leakReadStatus = LeakTable.retrieveLeakReadStatuses(db, signatures)
54 val heapDumpFileExist = heapAnalysis.heapDumpFile.exists()
55 updateUi { onSuccessRetrieved(heapAnalysis, leakReadStatus, heapDumpFileExist) }
56 }
57 }
58 }
59
60 private fun View.onSuccessRetrieved(
61 heapAnalysis: HeapAnalysisSuccess,
62 leakReadStatus: Map<String, Boolean>,
63 heapDumpFileExist: Boolean
64 ) {
65
66 activity.title = TimeFormatter.formatTimestamp(context, heapAnalysis.createdAtTimeMillis)
67
68 onCreateOptionsMenu { menu ->
69 if (!ActivityManager.isUserAMonkey()) {
70 menu.add(R.string.leak_canary_delete)
71 .setOnMenuItemClickListener {
72 executeOnDb {
73 HeapAnalysisTable.delete(db, analysisId, heapAnalysis.heapDumpFile)
74 updateUi {
75 goBack()
76 }
77 }
78 true
79 }
80 }
81 if (heapDumpFileExist) {
82 menu.add(R.string.leak_canary_options_menu_render_heap_dump)
83 .setOnMenuItemClickListener {
84 goTo(RenderHeapDumpScreen(heapAnalysis.heapDumpFile))
85 true
86 }
87 }
88 }
89
90 val listView = findViewById<ListView>(R.id.leak_canary_list)
91
92 val leaks = heapAnalysis.allLeaks.sortedByDescending { it.leakTraces.size }
93 .toList()
94
95 listView.adapter = object : BaseAdapter() {
96 override fun getView(
97 position: Int,
98 convertView: View?,
99 parent: ViewGroup
100 ) = when (getItemViewType(position)) {
101 METADATA -> {
102 bindMetadataRow(convertView, parent, heapDumpFileExist, heapAnalysis)
103 }
104 LEAK_TITLE -> {
105 val view = convertView ?: parent.inflate(R.layout.leak_canary_heap_dump_leak_title)
106 val leaksTextView = view.findViewById<TextView>(R.id.leak_canary_heap_dump_leaks)
107 leaksTextView.text = resources.getQuantityString(
108 R.plurals.leak_canary_distinct_leaks,
109 leaks.size, leaks.size
110 )
111 view
112 }
113 LEAK_ROW -> {
114 val view = convertView ?: parent.inflate(R.layout.leak_canary_leak_row)
115 val countView = view.findViewById<TextView>(R.id.leak_canary_count_text)
116 val descriptionView = view.findViewById<TextView>(R.id.leak_canary_leak_text)
117 val timeView = view.findViewById<TextView>(R.id.leak_canary_time_text)
118 val newChipView = view.findViewById<TextView>(R.id.leak_canary_chip_new)
119 val libraryLeakChipView = view.findViewById<TextView>(R.id.leak_canary_chip_library_leak)
120
121 val leak = leaks[position - 2]
122
123 val isNew = !leakReadStatus.getValue(leak.signature)
124
125 countView.isEnabled = isNew
126 countView.text = leak.leakTraces.size.toString()
127 newChipView.visibility = if (isNew) VISIBLE else GONE
128 libraryLeakChipView.visibility = if (leak is LibraryLeak) VISIBLE else GONE
129 descriptionView.text = leak.shortDescription
130
131 val formattedDate =
132 TimeFormatter.formatTimestamp(view.context, heapAnalysis.createdAtTimeMillis)
133 timeView.text = formattedDate
134 view
135 }
136 else -> {
137 throw IllegalStateException("Unexpected type ${getItemViewType(position)}")
138 }
139 }
140
141 override fun getItem(position: Int) = this
142
143 override fun getItemId(position: Int) = position.toLong()
144
145 override fun getCount() = 2 + leaks.size
146
147 override fun getItemViewType(position: Int) = when (position) {
148 0 -> METADATA
149 1 -> LEAK_TITLE
150 else -> LEAK_ROW
151 }
152
153 override fun getViewTypeCount() = 3
154
155 override fun isEnabled(position: Int) = getItemViewType(position) == LEAK_ROW
156 }
157
158 listView.setOnItemClickListener { _, _, position, _ ->
159 if (position > LEAK_TITLE) {
160 goTo(LeakScreen(leaks[position - 2].signature, analysisId))
161 }
162 }
163 }
164
165 private fun View.bindMetadataRow(
166 convertView: View?,
167 parent: ViewGroup,
168 heapDumpFileExist: Boolean,
169 heapAnalysis: HeapAnalysisSuccess
170 ): View {
171 val view = convertView ?: parent.inflate(R.layout.leak_canary_leak_header)
172 val textView = view.findViewById<TextView>(R.id.leak_canary_header_text)
173 textView.movementMethod = LinkMovementMethod.getInstance()
174
175 val explore =
176 if (heapDumpFileExist) """Explore <a href="explore_hprof">Heap Dump</a><br><br>""" else ""
177 val shareAnalysis = """Share <a href="share">Heap Dump analysis</a><br><br>"""
178 val printAnalysis = """Print analysis <a href="print">to Logcat</a> (tag: LeakCanary)<br><br>"""
179 val shareFile =
180 if (heapDumpFileExist) """Share <a href="share_hprof">Heap Dump file</a><br><br>""" else ""
181
182 val seeMetadata = "See <a href=\"metadata\">Metadata</a>"
183
184 val dumpDurationMillis =
185 if (heapAnalysis.dumpDurationMillis != HeapAnalysis.DUMP_DURATION_UNKNOWN) {
186 "${heapAnalysis.dumpDurationMillis} ms"
187 } else {
188 "Unknown"
189 }
190 val metadata = (heapAnalysis.metadata + mapOf(
191 "Analysis duration" to "${heapAnalysis.analysisDurationMillis} ms",
192 "Heap dump file path" to heapAnalysis.heapDumpFile.absolutePath,
193 "Heap dump timestamp" to "${heapAnalysis.createdAtTimeMillis}",
194 "Heap dump duration" to dumpDurationMillis
195 ))
196 .map { "<b>${it.key}:</b> ${it.value}" }
197 .joinToString("<br>")
198 val titleText = explore + shareAnalysis + printAnalysis + shareFile + seeMetadata
199 val title = Html.fromHtml(titleText) as SpannableStringBuilder
200
201 UiUtils.replaceUrlSpanWithAction(title) { urlSpan ->
202 when (urlSpan) {
203 "explore_hprof" -> {
204 {
205 goTo(HprofExplorerScreen(heapAnalysis.heapDumpFile))
206 }
207 }
208 "share" -> {
209 {
210 share(LeakTraceWrapper.wrap(heapAnalysis.toString(), 80))
211 }
212 }
213 "print" -> {
214 {
215 SharkLog.d { "\u200B\n" + LeakTraceWrapper.wrap(heapAnalysis.toString(), 120) }
216 }
217 }
218 "share_hprof" -> {
219 {
220 shareHeapDump(heapAnalysis.heapDumpFile)
221 }
222 }
223 "metadata" -> {
224 {
225 Builder(context)
226 .setIcon(drawable.ic_dialog_info)
227 .setTitle("Metadata")
228 .setMessage(Html.fromHtml(metadata))
229 .setPositiveButton(string.ok, null)
230 .show()
231 }
232 }
233 else -> null
234 }
235 }
236
237 textView.text = title
238 return view
239 }
240
241 companion object {
242 const val METADATA = 0
243 const val LEAK_TITLE = 1
244 const val LEAK_ROW = 2
245 }
246 }
247