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