Repository: square/leakcanary
Issue: AndroidReferenceMatchers.BLOCKING_QUEUE has false positives

Found an example of false positive on Stack Overflow: https://stackoverflow.com/questions/62518100/not-able-to-find-cause-in-leak-canary-trace

```
┬
├─ android.os.MessageQueue
│    Leaking: NO (MessageQueue#mQuitting is false)
│    GC Root: Input or output parameters in native code
│    ↓ MessageQueue.mMessages
│                   ~~~~~~~~~
├─ android.os.Message
│    Leaking: UNKNOWN
│    ↓ Message.next
│              ~~~~
├─ android.os.Message
│    Leaking: UNKNOWN
│    ↓ Message.callback
│              ~~~~~~~~
├─ android.view.ViewRootImpl$1
│    Leaking: UNKNOWN
│    Anonymous class implementing java.lang.Runnable
│    ↓ ViewRootImpl$1.this$0
│                     ~~~~~~
├─ android.view.ViewRootImpl
│    Leaking: YES (ViewRootImpl#mView is null)
│    ↓ ViewRootImpl.mActivityConfigCallback
├─ android.app.-$$Lambda$ActivityThread$ActivityClientRecord$HOrG1qglSjSUHSjKBn2rXtX0gGg
│    Leaking: YES (ViewRootImpl↑ is leaking)
│    ↓ -$$Lambda$ActivityThread$ActivityClientRecord$HOrG1qglSjSUHSjKBn2rXtX0gGg.f$0
├─ android.app.ActivityThread$ActivityClientRecord
│    Leaking: YES (-$$Lambda$ActivityThread$ActivityClientRecord$HOrG1qglSjSUHSjKBn2rXtX0gGg↑ is leaking)
│    ↓ ActivityThread$ActivityClientRecord.activity
╰→ <>.MediaControlActivity
​     Leaking: YES (ActivityThread$ActivityClientRecord↑ is leaking and Activity#mDestroyed is true and ObjectWatcher was watching this)

pattern=instance field android.os.Message#next
```

Here's an example of true positive: https://stackoverflow.com/questions/60904229/datepickerfragment-memory-leak

```
┬
├─ android.os.HandlerThread
│    Leaking: UNKNOWN
│    Thread name: 'queued-work-looper'
│    GC Root: Java local variable
│    ↓ thread HandlerThread.<Java Local>
│                           ~~~~~~~~~~~~
├─ android.os.Message
│    Leaking: UNKNOWN
│    ↓ Message.obj
│              ~~~
├─ android.app.DatePickerDialog
│    Leaking: YES (Dialog#mDecor is null)
│    ↓ DatePickerDialog.mDateSetListener
├─ god.panchang.DatePickerFragment$onCreateDialog$1
│    Leaking: YES (DatePickerDialog↑ is leaking)
│    Anonymous class implementing android.app.DatePickerDialog$OnDateSetListener
│    ↓ DatePickerFragment$onCreateDialog$1.this$0
╰→ god.panchang.DatePickerFragment
    ​     Leaking: YES (DatePickerFragment$onCreateDialog$1↑ is leaking and Fragment#mFragmentManager is null and ObjectWatcher was watching this)

pattern=instance field android.os.Message#obj
```

BLOCKING_QUEUE is defined as:

```
  BLOCKING_QUEUE {
    override fun add(
      references: MutableList<ReferenceMatcher>
    ) {
      val description = "..."
      references += instanceFieldLeak("android.os.Message", "obj", description)
      references += instanceFieldLeak("android.os.Message", "next", description)
      references += instanceFieldLeak("android.os.Message", "target", description)
    }
  },
```

Changed files:
shark/src/main/java/shark/KeyedWeakReferenceFinder.kt, shark-android/src/main/java/shark/AndroidObjectInspectors.kt, shark-android/src/main/java/shark/AndroidReferenceMatchers.kt

diff --git a/shark-android/src/main/java/shark/AndroidObjectInspectors.kt b/shark-android/src/main/java/shark/AndroidObjectInspectors.kt
index 6a56273c3..5e5a0c750 100644
--- a/shark-android/src/main/java/shark/AndroidObjectInspectors.kt
+++ b/shark-android/src/main/java/shark/AndroidObjectInspectors.kt
@@ -19,6 +19,7 @@ import shark.AndroidObjectInspectors.Companion.appDefaults
 import shark.FilteringLeakingObjectFinder.LeakingObjectFilter
 import shark.HeapObject.HeapInstance
 import java.util.EnumSet
+import kotlin.math.absoluteValue
 
 /**
  * A set of default [ObjectInspector]s that knows about common AOSP and library
@@ -361,6 +362,19 @@ enum class AndroidObjectInspectors : ObjectInspector {
         } else {
           notLeakingReasons += mQuitting describedWithValue "false"
         }
+
+        val queueHead = instance["android.os.MessageQueue", "mMessages"]!!.valueAsInstance
+        if (queueHead != null) {
+          val targetHandler = queueHead["android.os.Message", "target"]!!.valueAsInstance
+          if (targetHandler != null) {
+            val looper = targetHandler["android.os.Handler", "mLooper"]!!.valueAsInstance
+            if (looper != null) {
+              val thread = looper["android.os.Looper", "mThread"]!!.valueAsInstance!!
+              val threadName = thread[Thread::class, "name"]!!.value.readAsJavaString()
+              labels += "HandlerThread: \\"$threadName\\""
+            }
+          }
+        }
       }
     }
   },
@@ -486,6 +500,31 @@ enum class AndroidObjectInspectors : ObjectInspector {
     }
   },
 
+  MESSAGE {
+    override fun inspect(reporter: ObjectReporter) {
+      reporter.whenInstanceOf("android.os.Message") { instance ->
+        labels += "Message.what = ${instance["android.os.Message", "what"]!!.value.asInt}"
+
+        val heapDumpUptimeMillis = KeyedWeakReferenceFinder.heapDumpUptimeMillis(instance.graph)
+        val whenUptimeMillis = instance["android.os.Message", "when"]!!.value.asLong!!
+
+        labels += if (heapDumpUptimeMillis != null) {
+          val diffMs = whenUptimeMillis - heapDumpUptimeMillis
+          if (diffMs > 0) {
+            "Message.when = $whenUptimeMillis ($diffMs ms after heap dump)"
+          } else {
+            "Message.when = $whenUptimeMillis (${diffMs.absoluteValue} ms before heap dump)"
+          }
+        } else {
+          "Message.when = $whenUptimeMillis"
+        }
+
+        labels += "Message.obj = ${instance["android.os.Message", "obj"]!!.value.asObject}"
+        labels += "Message.callback = ${instance["android.os.Message", "callback"]!!.value.asObject}"
+      }
+    }
+  },
+
   TOAST {
     override val leakingObjectFilter = { heapObject: HeapObject ->
       if (heapObject is HeapInstance && heapObject instanceOf "android.widget.Toast") {
diff --git a/shark-android/src/main/java/shark/AndroidReferenceMatchers.kt b/shark-android/src/main/java/shark/AndroidReferenceMatchers.kt
index 4ad102a61..84c30f052 100644
--- a/shark-android/src/main/java/shark/AndroidReferenceMatchers.kt
+++ b/shark-android/src/main/java/shark/AndroidReferenceMatchers.kt
@@ -185,8 +185,6 @@ enum class AndroidReferenceMatchers {
           + " has been shown to happen in both Dalvik and ART.")
 
       references += instanceFieldLeak("android.os.Message", "obj", description)
-      references += instanceFieldLeak("android.os.Message", "next", description)
-      references += instanceFieldLeak("android.os.Message", "target", description)
     }
   },
 
diff --git a/shark/src/main/java/shark/KeyedWeakReferenceFinder.kt b/shark/src/main/java/shark/KeyedWeakReferenceFinder.kt
index 17c1d4e71..63719d4ae 100644
--- a/shark/src/main/java/shark/KeyedWeakReferenceFinder.kt
+++ b/shark/src/main/java/shark/KeyedWeakReferenceFinder.kt
@@ -13,27 +13,34 @@ object KeyedWeakReferenceFinder : LeakingObjectFinder {
     findKeyedWeakReferences(graph).map { it.referent.value }
         .toSet()
 
-  internal fun findKeyedWeakReferences(graph: HeapGraph): List<KeyedWeakReferenceMirror> {
-    return graph.context.getOrPut(KEYED_WEAK_REFERENCE.name) {
+  fun heapDumpUptimeMillis(graph: HeapGraph): Long? {
+    return graph.context.getOrPut("heapDumpUptimeMillis") {
       val keyedWeakReferenceClass = graph.findClassByName("leakcanary.KeyedWeakReference")
-
       val heapDumpUptimeMillis = if (keyedWeakReferenceClass == null) {
         null
       } else {
         keyedWeakReferenceClass["heapDumpUptimeMillis"]?.value?.asLong
       }
-
       if (heapDumpUptimeMillis == null) {
         SharkLog.d {
           "leakcanary.KeyedWeakReference.heapDumpUptimeMillis field not found, " +
               "this must be a heap dump from an older version of LeakCanary."
         }
       }
+      heapDumpUptimeMillis
+    }
+  }
+
+  internal fun findKeyedWeakReferences(graph: HeapGraph): List<KeyedWeakReferenceMirror> {
+    return graph.context.getOrPut(KEYED_WEAK_REFERENCE.name) {
+      val keyedWeakReferenceClass = graph.findClassByName("leakcanary.KeyedWeakReference")
 
       val keyedWeakReferenceClassId = keyedWeakReferenceClass?.objectId ?: 0
       val legacyKeyedWeakReferenceClassId =
         graph.findClassByName("com.squareup.leakcanary.KeyedWeakReference")?.objectId ?: 0
 
+      val heapDumpUptimeMillis = heapDumpUptimeMillis(graph)
+
       val addedToContext: List<KeyedWeakReferenceMirror> = graph.instances
           .filter { instance ->
             instance.instanceClassId == keyedWeakReferenceClassId || instance.instanceClassId == legacyKeyedWeakReferenceClassId