1 package leakcanary.internal
2 
3 import android.app.Application
4 import android.os.Handler
5 import android.os.HandlerThread
6 import com.squareup.leakcanary.core.R
7 import leakcanary.AppWatcher
8 import leakcanary.LeakCanary
9 import leakcanary.internal.HeapDumpControl.ICanHazHeap.Nope
10 import leakcanary.internal.HeapDumpControl.ICanHazHeap.NotifyingNope
11 import leakcanary.internal.HeapDumpControl.ICanHazHeap.SilentNope
12 import leakcanary.internal.HeapDumpControl.ICanHazHeap.Yup
13 
14 internal object HeapDumpControl {
15 
16   sealed class ICanHazHeap {
17     object Yup : ICanHazHeap()
18     abstract class Nope(val reason: () -> String) : ICanHazHeap()
19     class SilentNope(reason: () -> String) : Nope(reason)
20 
21     /**
22      * Allows manual dumping via a notification
23      */
24     class NotifyingNope(reason: () -> String) : Nope(reason)
25   }
26 
27   @Volatile
28   private lateinit var latest: ICanHazHeap
29 
30   private val app: Application
31     get() = InternalLeakCanary.application
32 
<lambda>null33   private val testClassName by lazy {
34     InternalLeakCanary.application.getString(R.string.leak_canary_test_class_name)
35   }
36 
<lambda>null37   private val hasTestClass by lazy {
38     try {
39       Class.forName(testClassName)
40       true
41     } catch (e: Exception) {
42       false
43     }
44   }
45 
<lambda>null46   private val backgroundUpdateHandler by lazy {
47     val handlerThread = HandlerThread("LeakCanary-Background-iCanHasHeap-Updater")
48     handlerThread.start()
49     Handler(handlerThread.looper)
50   }
51 
52   private const val leakAssertionsClassName = "leakcanary.LeakAssertions"
53 
<lambda>null54   private val hasLeakAssertionsClass by lazy {
55     try {
56       Class.forName(leakAssertionsClassName)
57       true
58     } catch (e: Exception) {
59       false
60     }
61   }
62 
updateICanHasHeapInBackgroundnull63   fun updateICanHasHeapInBackground() {
64     backgroundUpdateHandler.post {
65       iCanHasHeap()
66     }
67   }
68 
iCanHasHeapnull69   fun iCanHasHeap(): ICanHazHeap {
70     val config = LeakCanary.config
71     val dumpHeap = if (!AppWatcher.isInstalled) {
72       // Can't use a resource, we don't have an Application instance when not installed
73       SilentNope { "AppWatcher is not installed." }
74     } else if (!InternalLeakCanary.dumpEnabledInAboutScreen) {
75       NotifyingNope {
76         app.getString(R.string.leak_canary_heap_dump_disabled_from_ui)
77       }
78     } else if (!config.dumpHeap) {
79       SilentNope { app.getString(R.string.leak_canary_heap_dump_disabled_by_app) }
80     } else if (hasTestClass) {
81       SilentNope {
82         app.getString(R.string.leak_canary_heap_dump_disabled_running_tests, testClassName)
83       }
84     } else if (hasLeakAssertionsClass) {
85       SilentNope {
86         app.getString(
87           R.string.leak_canary_heap_dump_disabled_running_tests,
88           leakAssertionsClassName
89         )
90       }
91     } else if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) {
92       backgroundUpdateHandler.postDelayed({
93         iCanHasHeap()
94       }, 20_000L)
95       NotifyingNope { app.getString(R.string.leak_canary_notification_retained_debugger_attached) }
96     } else Yup
97 
98     synchronized(this) {
99       if (::latest.isInitialized && dumpHeap is Yup && latest is Nope) {
100         InternalLeakCanary.scheduleRetainedObjectCheck()
101       }
102       latest = dumpHeap
103     }
104 
105     return dumpHeap
106   }
107 }
108