Merge "update ThreadNetworkControllerService to support border routing" into main
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 {
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index b8cf08e..fff9a30 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -1659,8 +1659,7 @@
             waitForIdle();
         }
 
-        public void startLegacyVpnPrivileged(VpnProfile profile,
-                @Nullable Network underlying, @NonNull LinkProperties egress) {
+        public void startLegacyVpnPrivileged(VpnProfile profile) {
             switch (profile.type) {
                 case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
                 case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
@@ -10252,7 +10251,7 @@
         b.expectBroadcast();
         // Simulate LockdownVpnTracker attempting to start the VPN since it received the
         // systemDefault callback.
-        mMockVpn.startLegacyVpnPrivileged(profile, mCellAgent.getNetwork(), cellLp);
+        mMockVpn.startLegacyVpnPrivileged(profile);
         if (expectSetVpnDefaultForUids) {
             // setVpnDefaultForUids() releases the original network request and creates a VPN
             // request so LOST callback is received.
@@ -10323,7 +10322,7 @@
         // callback with different network.
         final ExpectedBroadcast b6 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED);
         mMockVpn.stopVpnRunnerPrivileged();
-        mMockVpn.startLegacyVpnPrivileged(profile, mWiFiAgent.getNetwork(), wifiLp);
+        mMockVpn.startLegacyVpnPrivileged(profile);
         // VPN network is disconnected (to restart)
         callback.expect(LOST, mMockVpn);
         defaultCallback.expect(LOST, mMockVpn);
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 109c132..ea2228e 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -2039,16 +2039,7 @@
 
     private Vpn startLegacyVpn(final Vpn vpn, final VpnProfile vpnProfile) throws Exception {
         setMockedUsers(PRIMARY_USER);
-
-        // Dummy egress interface
-        final LinkProperties lp = new LinkProperties();
-        lp.setInterfaceName(EGRESS_IFACE);
-
-        final RouteInfo defaultRoute = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0),
-                        InetAddresses.parseNumericAddress("192.0.2.0"), EGRESS_IFACE);
-        lp.addRoute(defaultRoute);
-
-        vpn.startLegacyVpn(vpnProfile, EGRESS_NETWORK, lp);
+        vpn.startLegacyVpn(vpnProfile);
         return vpn;
     }