Make thread leak error message more readable

This change only outputs thread count differences instead of
listing all threads. Additionally, it ignores threads with 1
count, which effectively filter out threads created
by the test runner or other system components, such as
hwuiTask*, queued-work-looper, SurfaceSyncGroupTimer,
RenderThread, and Time-limited test.

Sample Output:
[1122/3060] android.net.connectivity.com.android.server.CSLocalAgentCreationTests#ThreadLeakMonitor: FAILED (1ms)

STACKTRACE:
java.lang.IllegalStateException: Unexpected thread changes: removed=[] added=[] updated=[TestAlarmManager=25,CSTestHandler=25]

Test: atest ConnectivityCoverageTests
      (with shouldThreadLeakFailTest set to true)
Bug: 310581973
Bug: 307693729
Change-Id: I085799ebbd69c29f833335684de208105302e012
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
index 1ba83ca..10accd4 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
@@ -17,9 +17,9 @@
 package com.android.testutils
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult
 import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
-import java.lang.IllegalStateException
 import java.lang.reflect.Modifier
 import org.junit.runner.Description
 import org.junit.runner.Runner
@@ -110,10 +110,19 @@
 
         notifier.fireTestStarted(leakMonitorDesc)
         val threadCountsAfterTest = getAllThreadNameCounts()
-        if (threadCountsBeforeTest != threadCountsAfterTest) {
+        // TODO : move CompareOrUpdateResult to its own util instead of LinkProperties.
+        val threadsDiff = CompareOrUpdateResult(
+                threadCountsBeforeTest.entries,
+                threadCountsAfterTest.entries
+        ) { it.key }
+        // Ignore removed threads, which typically are generated by previous tests.
+        // Because this is in the threadsDiff.updated member, for sure there is a
+        // corresponding key in threadCountsBeforeTest.
+        val increasedThreads = threadsDiff.updated
+                .filter { threadCountsBeforeTest[it.key]!! < it.value }
+        if (threadsDiff.added.isNotEmpty() || increasedThreads.isNotEmpty()) {
             notifier.fireTestFailure(Failure(leakMonitorDesc,
-                    IllegalStateException("Expected threads: $threadCountsBeforeTest " +
-                            "but got: $threadCountsAfterTest")))
+                    IllegalStateException("Unexpected thread changes: $threadsDiff")))
         }
         notifier.fireTestFinished(leakMonitorDesc)
     }
@@ -121,9 +130,13 @@
     private fun getAllThreadNameCounts(): Map<String, Int> {
         // Get the counts of threads in the group per name.
         // Filter system thread groups.
+        // Also ignore threads with 1 count, this effectively filtered out threads created by the
+        // test runner or other system components. e.g. hwuiTask*, queued-work-looper,
+        // SurfaceSyncGroupTimer, RenderThread, Time-limited test, etc.
         return Thread.getAllStackTraces().keys
                 .filter { it.threadGroup?.name != "system" }
                 .groupingBy { it.name }.eachCount()
+                .filter { it.value != 1 }
     }
 
     override fun getDescription(): Description {