<lambda>null1 package leakcanary.internal.activity.db
2 
3 import android.content.ContentValues
4 import android.database.sqlite.SQLiteDatabase
5 import android.os.AsyncTask
6 import leakcanary.internal.LeakDirectoryProvider
7 import leakcanary.internal.Serializables
8 import leakcanary.internal.toByteArray
9 import leakcanary.internal.friendly.checkNotMainThread
10 import leakcanary.internal.friendly.mainHandler
11 import org.intellij.lang.annotations.Language
12 import shark.HeapAnalysis
13 import shark.HeapAnalysisFailure
14 import shark.HeapAnalysisSuccess
15 import shark.SharkLog
16 import java.io.File
17 import java.util.concurrent.CopyOnWriteArrayList
18 
19 internal object HeapAnalysisTable {
20 
21   /**
22    * CopyOnWriteArrayList because registered listeners can remove themselves from this list while
23    * iterating and invoking them, which would trigger a ConcurrentModificationException (see #2019).
24    */
25   private val updateListeners = CopyOnWriteArrayList<() -> Unit>()
26 
27   @Language("RoomSql")
28   const val create = """CREATE TABLE heap_analysis
29         (
30         id INTEGER PRIMARY KEY AUTOINCREMENT,
31         created_at_time_millis INTEGER,
32         dump_duration_millis INTEGER DEFAULT -1,
33         leak_count INTEGER DEFAULT 0,
34         exception_summary TEXT DEFAULT NULL,
35         object BLOB
36         )"""
37 
38   @Language("RoomSql")
39   const val drop = "DROP TABLE IF EXISTS heap_analysis"
40 
41   fun onUpdate(block: () -> Unit): () -> Unit {
42     updateListeners.add(block)
43     return {
44       updateListeners.remove(block)
45     }
46   }
47 
48   fun insert(
49     db: SQLiteDatabase,
50     heapAnalysis: HeapAnalysis
51   ): Long {
52     val values = ContentValues()
53     values.put("created_at_time_millis", heapAnalysis.createdAtTimeMillis)
54     values.put("dump_duration_millis", heapAnalysis.dumpDurationMillis)
55     values.put("object", heapAnalysis.toByteArray())
56     when (heapAnalysis) {
57       is HeapAnalysisSuccess -> {
58         val leakCount = heapAnalysis.applicationLeaks.size + heapAnalysis.libraryLeaks.size
59         values.put("leak_count", leakCount)
60       }
61       is HeapAnalysisFailure -> {
62         val cause = heapAnalysis.exception.cause!!
63         val exceptionSummary = "${cause.javaClass.simpleName} ${cause.message}"
64         values.put("exception_summary", exceptionSummary)
65       }
66     }
67 
68     return db.inTransaction {
69       val heapAnalysisId = db.insertOrThrow("heap_analysis", null, values)
70       if (heapAnalysis is HeapAnalysisSuccess) {
71         heapAnalysis.allLeaks
72           .forEach { leakingInstance ->
73             LeakTable.insert(
74               db, heapAnalysisId, leakingInstance
75             )
76           }
77       }
78       heapAnalysisId
79     }.apply { notifyUpdateOnMainThread() }
80   }
81 
82   private fun notifyUpdateOnMainThread() {
83     checkNotMainThread()
84     mainHandler.post {
85       updateListeners.forEach { it() }
86     }
87   }
88 
89   inline fun <reified T : HeapAnalysis> retrieve(
90     db: SQLiteDatabase,
91     id: Long
92   ): T? {
93     return db.rawQuery(
94       """
95               SELECT
96               object
97               FROM heap_analysis
98               WHERE id=$id
99               """, null
100     )
101       .use { cursor ->
102         if (cursor.moveToNext()) {
103           val analysis = Serializables.fromByteArray<T>(cursor.getBlob(0))
104           if (analysis == null) {
105             delete(db, id, null)
106           }
107           analysis
108         } else {
109           null
110         }
111       }
112   }
113 
114   fun retrieveAll(db: SQLiteDatabase): List<Projection> {
115     return db.rawQuery(
116       """
117           SELECT
118           id
119           , created_at_time_millis
120           , leak_count
121           , exception_summary
122           FROM heap_analysis
123           ORDER BY created_at_time_millis DESC
124           """, null
125     )
126       .use { cursor ->
127         val all = mutableListOf<Projection>()
128         while (cursor.moveToNext()) {
129           val summary = Projection(
130             id = cursor.getLong(0),
131             createdAtTimeMillis = cursor.getLong(1),
132             leakCount = cursor.getInt(2),
133             exceptionSummary = cursor.getString(3)
134           )
135           all.add(summary)
136         }
137         all
138       }
139   }
140 
141   fun delete(
142     db: SQLiteDatabase,
143     heapAnalysisId: Long,
144     heapDumpFile: File?
145   ) {
146     if (heapDumpFile != null) {
147       AsyncTask.SERIAL_EXECUTOR.execute {
148         val path = heapDumpFile.absolutePath
149         val heapDumpDeleted = heapDumpFile.delete()
150         if (heapDumpDeleted) {
151           LeakDirectoryProvider.filesDeletedRemoveLeak += path
152         } else {
153           SharkLog.d { "Could not delete heap dump file ${heapDumpFile.path}" }
154         }
155       }
156     }
157 
158     db.inTransaction {
159       db.delete("heap_analysis", "id=$heapAnalysisId", null)
160       LeakTable.deleteByHeapAnalysisId(db, heapAnalysisId)
161     }
162     notifyUpdateOnMainThread()
163   }
164 
165   fun deleteAll(db: SQLiteDatabase) {
166     db.inTransaction {
167       rawQuery(
168         """
169               SELECT
170               id,
171               object
172               FROM heap_analysis
173               """, null
174       )
175         .use { cursor ->
176           val all = mutableListOf<Pair<Long, HeapAnalysis>>()
177           while (cursor.moveToNext()) {
178             val id = cursor.getLong(0)
179             val analysis = Serializables.fromByteArray<HeapAnalysis>(cursor.getBlob(1))
180             if (analysis != null) {
181               all += id to analysis
182             }
183           }
184           all.forEach { (id, _) ->
185             db.delete("heap_analysis", "id=$id", null)
186             LeakTable.deleteByHeapAnalysisId(db, id)
187           }
188           AsyncTask.SERIAL_EXECUTOR.execute {
189             all.forEach { (_, analysis) ->
190               analysis.heapDumpFile.delete()
191             }
192           }
193         }
194     }
195     notifyUpdateOnMainThread()
196   }
197 
198   class Projection(
199     val id: Long,
200     val createdAtTimeMillis: Long,
201     val leakCount: Int,
202     val exceptionSummary: String?
203   )
204 }