1 package leakcanary
2 
3 import android.app.Instrumentation
4 import android.os.SystemClock
5 import androidx.test.platform.app.InstrumentationRegistry
6 import leakcanary.GcTrigger.Default
7 import leakcanary.HeapAnalysisDecision.NoHeapAnalysis
8 import leakcanary.HeapAnalysisDecision.AnalyzeHeap
9 
10 class AndroidDetectLeaksInterceptor(
11   private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
12   private val objectWatcher: ObjectWatcher = AppWatcher.objectWatcher,
13   private val retainedDelayMillis: Long = AppWatcher.retainedDelayMillis
14 ) : DetectLeaksInterceptor {
15 
16   @Suppress("ReturnCount")
waitUntilReadyForHeapAnalysisnull17   override fun waitUntilReadyForHeapAnalysis(): HeapAnalysisDecision {
18     val leakDetectionTime = SystemClock.uptimeMillis()
19 
20     if (!objectWatcher.hasWatchedObjects) {
21       return NoHeapAnalysis("No watched objects.")
22     }
23 
24     instrumentation.waitForIdleSync()
25     if (!objectWatcher.hasWatchedObjects) {
26       return NoHeapAnalysis("No watched objects after waiting for idle sync.")
27     }
28 
29     Default.runGc()
30     if (!objectWatcher.hasWatchedObjects) {
31       return NoHeapAnalysis("No watched objects after triggering an explicit GC.")
32     }
33 
34     // Waiting for any delayed UI post (e.g. scroll) to clear. This shouldn't be needed, but
35     // Android simply has way too many delayed posts that aren't canceled when views are detached.
36     SystemClock.sleep(2000)
37 
38     if (!objectWatcher.hasWatchedObjects) {
39       return NoHeapAnalysis("No watched objects after delayed UI post is cleared.")
40     }
41 
42     // Aaand we wait some more.
43     // 4 seconds (2+2) is greater than the 3 seconds delay for
44     // FINISH_TOKEN in android.widget.Filter
45     SystemClock.sleep(2000)
46 
47     val endOfWatchDelay = retainedDelayMillis - (SystemClock.uptimeMillis() - leakDetectionTime)
48     if (endOfWatchDelay > 0) {
49       SystemClock.sleep(endOfWatchDelay)
50     }
51 
52     Default.runGc()
53 
54     if (!objectWatcher.hasRetainedObjects) {
55       return NoHeapAnalysis("No retained objects after waiting for retained delay.")
56     }
57     return AnalyzeHeap
58   }
59 }
60