<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