• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

<lambda>null1 package leakcanary.internal.activity.screen
2 
3 import android.text.Html
4 import android.text.SpannableStringBuilder
5 import android.util.Patterns
6 import android.view.View
7 import android.view.ViewGroup
8 import android.widget.AdapterView
9 import android.widget.AdapterView.OnItemSelectedListener
10 import android.widget.ListView
11 import android.widget.Spinner
12 import android.widget.TextView
13 import com.squareup.leakcanary.core.R
14 import leakcanary.internal.DisplayLeakAdapter
15 import leakcanary.internal.SquigglySpan
16 import leakcanary.internal.activity.db.HeapAnalysisTable
17 import leakcanary.internal.activity.db.LeakTable
18 import leakcanary.internal.activity.db.LeakTable.LeakProjection
19 import leakcanary.internal.activity.db.LeakTable.LeakTraceProjection
20 import leakcanary.internal.activity.db.executeOnDb
21 import leakcanary.internal.activity.share
22 import leakcanary.internal.activity.shareHeapDump
23 import leakcanary.internal.activity.shareToStackOverflow
24 import leakcanary.internal.activity.ui.SimpleListAdapter
25 import leakcanary.internal.activity.ui.TimeFormatter
26 import leakcanary.internal.activity.ui.UiUtils.replaceUrlSpanWithAction
27 import leakcanary.internal.navigation.Screen
28 import leakcanary.internal.navigation.activity
29 import leakcanary.internal.navigation.goTo
30 import leakcanary.internal.navigation.inflate
31 import shark.HeapAnalysisSuccess
32 import shark.LeakTrace
33 import shark.LibraryLeak
34 import shark.SharkLog
35 
36 internal class LeakScreen(
37   private val leakSignature: String,
38   private val selectedHeapAnalysisId: Long? = null
39 ) : Screen() {
40   override fun createView(container: ViewGroup) =
41     container.inflate(R.layout.leak_canary_leak_screen)
42       .apply {
43         activity.title = resources.getString(R.string.leak_canary_loading_title)
44         executeOnDb {
45           val leak = LeakTable.retrieveLeakBySignature(db, leakSignature)
46 
47           if (leak == null) {
48             updateUi {
49               activity.title = resources.getString(R.string.leak_canary_leak_not_found)
50             }
51           } else {
52             val selectedLeakIndex =
53               if (selectedHeapAnalysisId == null) 0 else leak.leakTraces.indexOfFirst { it.heapAnalysisId == selectedHeapAnalysisId }
54 
55             if (selectedLeakIndex != -1) {
56               val heapAnalysisId = leak.leakTraces[selectedLeakIndex].heapAnalysisId
57               val selectedHeapAnalysis =
58                 HeapAnalysisTable.retrieve<HeapAnalysisSuccess>(db, heapAnalysisId)!!
59 
60               updateUi {
61                 onLeaksRetrieved(leak, selectedLeakIndex, selectedHeapAnalysis)
62               }
63             } else {
64               // This can happen if a delete was enqueued and is slow and the user tapped on a leak
65               // row before the deletion is perform and the UI update that leaves the screen
66               // executes.
67               updateUi {
68                 activity.title = "Selected heap analysis deleted"
69               }
70             }
71             LeakTable.markAsRead(db, leakSignature)
72           }
73         }
74       }
75 
76   private fun View.onLeaksRetrieved(
77     leak: LeakProjection,
78     selectedLeakTraceIndex: Int,
79     selectedHeapAnalysis: HeapAnalysisSuccess
80   ) {
81     val isLibraryLeak = leak.isLibraryLeak
82     val isNew = leak.isNew
83     val newChipView = findViewById<TextView>(R.id.leak_canary_chip_new)
84     val libraryLeakChipView = findViewById<TextView>(R.id.leak_canary_chip_library_leak)
85     newChipView.visibility = if (isNew) View.VISIBLE else View.GONE
86     libraryLeakChipView.visibility = if (isLibraryLeak) View.VISIBLE else View.GONE
87 
88     activity.title = String.format(
89       resources.getQuantityText(
90         R.plurals.leak_canary_group_screen_title, leak.leakTraces.size
91       )
92         .toString(), leak.leakTraces.size, leak.shortDescription
93     )
94 
95     val singleLeakTraceRow = findViewById<View>(R.id.leak_canary_single_leak_trace_row)
96     val spinner = findViewById<Spinner>(R.id.leak_canary_spinner)
97 
98     if (leak.leakTraces.size == 1) {
99       spinner.visibility = View.GONE
100 
101       val leakTrace = leak.leakTraces.first()
102 
103       bindSimpleRow(singleLeakTraceRow, leakTrace)
104       onLeakTraceSelected(selectedHeapAnalysis, leakTrace.heapAnalysisId, leakTrace.leakTraceIndex)
105     } else {
106       singleLeakTraceRow.visibility = View.GONE
107 
108       spinner.adapter =
109         SimpleListAdapter(R.layout.leak_canary_simple_row, leak.leakTraces) { view, position ->
110           bindSimpleRow(view, leak.leakTraces[position])
111         }
112 
113       var lastSelectedLeakTraceIndex = selectedLeakTraceIndex
114       var lastSelectedHeapAnalysis = selectedHeapAnalysis
115 
116       spinner.onItemSelectedListener = object : OnItemSelectedListener {
117         override fun onNothingSelected(parent: AdapterView<*>?) {
118         }
119 
120         override fun onItemSelected(
121           parent: AdapterView<*>?,
122           view: View?,
123           position: Int,
124           id: Long
125         ) {
126           val selectedLeakTrace = leak.leakTraces[position]
127 
128           val selectedHeapAnalysisId = selectedLeakTrace.heapAnalysisId
129           val lastSelectedHeapAnalysisId =
130             leak.leakTraces[lastSelectedLeakTraceIndex].heapAnalysisId
131 
132           if (selectedHeapAnalysisId != lastSelectedHeapAnalysisId) {
133             executeOnDb {
134               val newSelectedHeapAnalysis =
135                 HeapAnalysisTable.retrieve<HeapAnalysisSuccess>(db, selectedHeapAnalysisId)!!
136               updateUi {
137                 lastSelectedLeakTraceIndex = position
138                 lastSelectedHeapAnalysis = newSelectedHeapAnalysis
139                 onLeakTraceSelected(
140                   newSelectedHeapAnalysis, selectedHeapAnalysisId,
141                   selectedLeakTrace.leakTraceIndex
142                 )
143               }
144             }
145           } else {
146             lastSelectedLeakTraceIndex = position
147             onLeakTraceSelected(
148               lastSelectedHeapAnalysis, selectedHeapAnalysisId, selectedLeakTrace.leakTraceIndex
149             )
150           }
151         }
152       }
153       spinner.setSelection(selectedLeakTraceIndex)
154     }
155   }
156 
157   private fun bindSimpleRow(
158     view: View,
159     leakTrace: LeakTraceProjection
160   ) {
161     val titleView = view.findViewById<TextView>(R.id.leak_canary_row_text)
162     val timeView = view.findViewById<TextView>(R.id.leak_canary_row_small_text)
163 
164     titleView.text =
165       view.resources.getString(R.string.leak_canary_class_has_leaked, leakTrace.classSimpleName)
166     timeView.text = TimeFormatter.formatTimestamp(view.context, leakTrace.createdAtTimeMillis)
167   }
168 
169   private fun parseLinks(str: String): String {
170     val words = str.split(" ")
171     var parsedString = ""
172     for (word in words) {
173       parsedString += if (Patterns.WEB_URL.matcher(word)
174           .matches()
175       ) {
176         "<a href=\"${word}\">${word}</a>"
177       } else {
178         word
179       }
180       if (words.indexOf(word) != words.size - 1) parsedString += " "
181     }
182     return parsedString
183   }
184 
185   private fun View.onLeakTraceSelected(
186     analysis: HeapAnalysisSuccess,
187     heapAnalysisId: Long,
188     leakTraceIndex: Int
189   ) {
190     val selectedLeak = analysis.allLeaks.first { it.signature == leakSignature }
191     val leakTrace = selectedLeak.leakTraces[leakTraceIndex]
192 
193     val listView = findViewById<ListView>(R.id.leak_canary_list)
194     listView.alpha = 0f
195     listView.animate()
196       .alpha(1f)
197 
198     val titleText = """
199       Open <a href="open_analysis">Heap Dump</a><br><br>
200       Share leak trace <a href="share">as text</a> or on <a href="share_stack_overflow">Stack Overflow</a><br><br>
201       Print leak trace <a href="print">to Logcat</a> (tag: LeakCanary)<br><br>
202       Share <a href="share_hprof">Heap Dump file</a><br><br>
203       References <b><u>underlined</u></b> are the likely causes of the leak.
204       Learn more at <a href="https://squ.re/leaks">https://squ.re/leaks</a>
205     """.trimIndent() + if (selectedLeak is LibraryLeak) "<br><br>" +
206       "A <font color='#FFCC32'>Library Leak</font> is a leak caused by a known bug in 3rd party code that you do not have control over. " +
207       "(<a href=\"https://square.github.io/leakcanary/fundamentals-how-leakcanary-works/#4-categorizing-leaks\">Learn More</a>)<br><br>" +
208       "<b>Leak pattern</b>: ${selectedLeak.pattern}<br><br>" +
209       "<b>Description</b>: ${parseLinks(selectedLeak.description)}" else ""
210 
211     val title = Html.fromHtml(titleText) as SpannableStringBuilder
212     SquigglySpan.replaceUnderlineSpans(title, context)
213 
214     replaceUrlSpanWithAction(title) { urlSpan ->
215       when (urlSpan) {
216         "share" -> {
217           {
218             share(LeakTraceWrapper.wrap(leakToString(leakTrace, analysis), 80))
219           }
220         }
221         "share_stack_overflow" -> {
222           {
223             shareToStackOverflow(LeakTraceWrapper.wrap(leakToString(leakTrace, analysis), 80))
224           }
225         }
226         "print" -> {
227           {
228             SharkLog.d {
229               "\u200B\n" + LeakTraceWrapper.wrap(
230                 leakToString(leakTrace, analysis), 120
231               )
232             }
233           }
234         }
235         "open_analysis" -> {
236           {
237             goTo(HeapDumpScreen(heapAnalysisId))
238           }
239         }
240         "share_hprof" -> {
241           {
242             shareHeapDump(analysis.heapDumpFile)
243           }
244         }
245         else -> null
246       }
247     }
248 
249     val adapter = DisplayLeakAdapter(context, leakTrace, title)
250     listView.adapter = adapter
251   }
252 
253   private fun leakToString(
254     leakTrace: LeakTrace,
255     analysis: HeapAnalysisSuccess
256   ) = """$leakTrace
257 
258 METADATA
259 
260 ${
261     if (analysis.metadata.isNotEmpty()) {
262       analysis.metadata
263         .map { "${it.key}: ${it.value}" }
264         .joinToString("\n")
265     } else {
266       ""
267     }
268   }
269 Analysis duration: ${analysis.analysisDurationMillis} ms"""
270 }
271