Merge "Update to /external/robolectric rather than older /external/robolectric-shadows"
diff --git a/Cronet/tests/cts/src/android/net/http/cts/ConnectionMigrationOptionsTest.kt b/Cronet/tests/cts/src/android/net/http/cts/ConnectionMigrationOptionsTest.kt
index 77cb30e..219db61 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/ConnectionMigrationOptionsTest.kt
+++ b/Cronet/tests/cts/src/android/net/http/cts/ConnectionMigrationOptionsTest.kt
@@ -32,37 +32,37 @@
         val options =
                 ConnectionMigrationOptions.Builder().build()
 
-        assertEquals(MIGRATION_OPTION_UNSPECIFIED, options.allowNonDefaultNetworkUsageEnabled)
-        assertEquals(MIGRATION_OPTION_UNSPECIFIED, options.defaultNetworkMigrationEnabled)
-        assertEquals(MIGRATION_OPTION_UNSPECIFIED, options.pathDegradationMigrationEnabled)
+        assertEquals(MIGRATION_OPTION_UNSPECIFIED, options.allowNonDefaultNetworkUsage)
+        assertEquals(MIGRATION_OPTION_UNSPECIFIED, options.defaultNetworkMigration)
+        assertEquals(MIGRATION_OPTION_UNSPECIFIED, options.pathDegradationMigration)
     }
 
     @Test
     fun testConnectionMigrationOptions_enableDefaultNetworkMigration_returnSetValue() {
         val options =
             ConnectionMigrationOptions.Builder()
-                    .setDefaultNetworkMigrationEnabled(MIGRATION_OPTION_ENABLED)
+                    .setDefaultNetworkMigration(MIGRATION_OPTION_ENABLED)
                     .build()
 
-        assertEquals(MIGRATION_OPTION_ENABLED, options.defaultNetworkMigrationEnabled)
+        assertEquals(MIGRATION_OPTION_ENABLED, options.defaultNetworkMigration)
     }
 
     @Test
     fun testConnectionMigrationOptions_enablePathDegradationMigration_returnSetValue() {
         val options =
             ConnectionMigrationOptions.Builder()
-                    .setPathDegradationMigrationEnabled(MIGRATION_OPTION_ENABLED)
+                    .setPathDegradationMigration(MIGRATION_OPTION_ENABLED)
                     .build()
 
-        assertEquals(MIGRATION_OPTION_ENABLED, options.pathDegradationMigrationEnabled)
+        assertEquals(MIGRATION_OPTION_ENABLED, options.pathDegradationMigration)
     }
 
     @Test
     fun testConnectionMigrationOptions_allowNonDefaultNetworkUsage_returnSetValue() {
         val options =
                 ConnectionMigrationOptions.Builder()
-                        .setAllowNonDefaultNetworkUsageEnabled(MIGRATION_OPTION_ENABLED).build()
+                        .setAllowNonDefaultNetworkUsage(MIGRATION_OPTION_ENABLED).build()
 
-        assertEquals(MIGRATION_OPTION_ENABLED, options.allowNonDefaultNetworkUsageEnabled)
+        assertEquals(MIGRATION_OPTION_ENABLED, options.allowNonDefaultNetworkUsage)
     }
 }
diff --git a/Cronet/tests/cts/src/android/net/http/cts/DnsOptionsTest.kt b/Cronet/tests/cts/src/android/net/http/cts/DnsOptionsTest.kt
index 8af544e..6f4a979 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/DnsOptionsTest.kt
+++ b/Cronet/tests/cts/src/android/net/http/cts/DnsOptionsTest.kt
@@ -34,22 +34,22 @@
     fun testDnsOptions_defaultValues() {
         val options = DnsOptions.Builder().build()
 
-        assertEquals(DNS_OPTION_UNSPECIFIED, options.persistHostCacheEnabled)
+        assertEquals(DNS_OPTION_UNSPECIFIED, options.persistHostCache)
         assertNull(options.persistHostCachePeriod)
-        assertEquals(DNS_OPTION_UNSPECIFIED, options.staleDnsEnabled)
+        assertEquals(DNS_OPTION_UNSPECIFIED, options.staleDns)
         assertNull(options.staleDnsOptions)
-        assertEquals(DNS_OPTION_UNSPECIFIED, options.useHttpStackDnsResolverEnabled)
+        assertEquals(DNS_OPTION_UNSPECIFIED, options.useHttpStackDnsResolver)
         assertEquals(DNS_OPTION_UNSPECIFIED,
-                options.preestablishConnectionsToStaleDnsResultsEnabled)
+                options.preestablishConnectionsToStaleDnsResults)
     }
 
     @Test
     fun testDnsOptions_persistHostCache_returnSetValue() {
         val options = DnsOptions.Builder()
-                .setPersistHostCacheEnabled(DNS_OPTION_ENABLED)
+                .setPersistHostCache(DNS_OPTION_ENABLED)
                 .build()
 
-        assertEquals(DNS_OPTION_ENABLED, options.persistHostCacheEnabled)
+        assertEquals(DNS_OPTION_ENABLED, options.persistHostCache)
     }
 
     @Test
@@ -63,43 +63,43 @@
     @Test
     fun testDnsOptions_enableStaleDns_returnSetValue() {
         val options = DnsOptions.Builder()
-                .setStaleDnsEnabled(DNS_OPTION_ENABLED)
+                .setStaleDns(DNS_OPTION_ENABLED)
                 .build()
 
-        assertEquals(DNS_OPTION_ENABLED, options.staleDnsEnabled)
+        assertEquals(DNS_OPTION_ENABLED, options.staleDns)
     }
 
     @Test
     fun testDnsOptions_useHttpStackDnsResolver_returnsSetValue() {
         val options = DnsOptions.Builder()
-                .setUseHttpStackDnsResolverEnabled(DNS_OPTION_ENABLED)
+                .setUseHttpStackDnsResolver(DNS_OPTION_ENABLED)
                 .build()
 
-        assertEquals(DNS_OPTION_ENABLED, options.useHttpStackDnsResolverEnabled)
+        assertEquals(DNS_OPTION_ENABLED, options.useHttpStackDnsResolver)
     }
 
     @Test
     fun testDnsOptions_preestablishConnectionsToStaleDnsResults_returnsSetValue() {
         val options = DnsOptions.Builder()
-                .setPreestablishConnectionsToStaleDnsResultsEnabled(DNS_OPTION_ENABLED)
+                .setPreestablishConnectionsToStaleDnsResults(DNS_OPTION_ENABLED)
                 .build()
 
         assertEquals(DNS_OPTION_ENABLED,
-                options.preestablishConnectionsToStaleDnsResultsEnabled)
+                options.preestablishConnectionsToStaleDnsResults)
     }
 
     @Test
     fun testDnsOptions_setStaleDnsOptions_returnsSetValues() {
         val staleOptions = DnsOptions.StaleDnsOptions.Builder()
-                .setAllowCrossNetworkUsageEnabled(DNS_OPTION_ENABLED)
+                .setAllowCrossNetworkUsage(DNS_OPTION_ENABLED)
                 .setFreshLookupTimeout(Duration.ofMillis(1234))
                 .build()
         val options = DnsOptions.Builder()
-                .setStaleDnsEnabled(DNS_OPTION_ENABLED)
+                .setStaleDns(DNS_OPTION_ENABLED)
                 .setStaleDnsOptions(staleOptions)
                 .build()
 
-        assertEquals(DNS_OPTION_ENABLED, options.staleDnsEnabled)
+        assertEquals(DNS_OPTION_ENABLED, options.staleDns)
         assertEquals(staleOptions, options.staleDnsOptions)
     }
 
@@ -107,18 +107,18 @@
     fun testStaleDnsOptions_defaultValues() {
         val options = DnsOptions.StaleDnsOptions.Builder().build()
 
-        assertEquals(DNS_OPTION_UNSPECIFIED, options.allowCrossNetworkUsageEnabled)
+        assertEquals(DNS_OPTION_UNSPECIFIED, options.allowCrossNetworkUsage)
         assertNull(options.freshLookupTimeout)
         assertNull(options.maxExpiredDelay)
-        assertEquals(DNS_OPTION_UNSPECIFIED, options.useStaleOnNameNotResolvedEnabled)
+        assertEquals(DNS_OPTION_UNSPECIFIED, options.useStaleOnNameNotResolved)
     }
 
     @Test
     fun testStaleDnsOptions_allowCrossNetworkUsage_returnsSetValue() {
         val options = DnsOptions.StaleDnsOptions.Builder()
-                .setAllowCrossNetworkUsageEnabled(DNS_OPTION_ENABLED).build()
+                .setAllowCrossNetworkUsage(DNS_OPTION_ENABLED).build()
 
-        assertEquals(DNS_OPTION_ENABLED, options.allowCrossNetworkUsageEnabled)
+        assertEquals(DNS_OPTION_ENABLED, options.allowCrossNetworkUsage)
     }
 
     @Test
@@ -133,10 +133,10 @@
     @Test
     fun testStaleDnsOptions_useStaleOnNameNotResolved_returnsSetValue() {
         val options = DnsOptions.StaleDnsOptions.Builder()
-                .setUseStaleOnNameNotResolvedEnabled(DNS_OPTION_ENABLED)
+                .setUseStaleOnNameNotResolved(DNS_OPTION_ENABLED)
                 .build()
 
-        assertEquals(DNS_OPTION_ENABLED, options.useStaleOnNameNotResolvedEnabled)
+        assertEquals(DNS_OPTION_ENABLED, options.useStaleOnNameNotResolved)
     }
 
     @Test
diff --git a/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java b/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
index 0be3ea1..c561ee7 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
+++ b/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
@@ -105,8 +105,9 @@
 
     @Test
     public void testHttpEngine_EnableHttpCache() {
-        // We need a server which sets cache-control != no-cache.
-        String url = "https://www.example.com";
+        String url = mTestServer.getCacheableTestDownloadUrl(
+                /* downloadId */ "cacheable-download",
+                /* numBytes */ 10);
         mEngine =
                 mEngineBuilder
                         .setStoragePath(mContext.getApplicationInfo().dataDir)
@@ -118,9 +119,7 @@
                 mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback);
         mRequest = builder.build();
         mRequest.start();
-        // This tests uses a non-hermetic server. Instead of asserting, assume the next callback.
-        // This way, if the request were to fail, the test would just be skipped instead of failing.
-        mCallback.assumeCallback(ResponseStep.ON_SUCCEEDED);
+        mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
         UrlResponseInfo info = mCallback.mResponseInfo;
         assumeOKStatusCode(info);
         assertFalse(info.wasCached());
@@ -129,7 +128,7 @@
         builder = mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback);
         mRequest = builder.build();
         mRequest.start();
-        mCallback.assumeCallback(ResponseStep.ON_SUCCEEDED);
+        mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
         info = mCallback.mResponseInfo;
         assertOKStatusCode(info);
         assertTrue(info.wasCached());
@@ -153,11 +152,12 @@
 
     @Test
     public void testHttpEngine_EnablePublicKeyPinningBypassForLocalTrustAnchors() {
+        String url = mTestServer.getSuccessUrl();
         // For known hosts, requests should succeed whether we're bypassing the local trust anchor
         // or not.
         mEngine = mEngineBuilder.setEnablePublicKeyPinningBypassForLocalTrustAnchors(false).build();
         UrlRequest.Builder builder =
-                mEngine.newUrlRequestBuilder(URL, mCallback.getExecutor(), mCallback);
+                mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback);
         mRequest = builder.build();
         mRequest.start();
         mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
@@ -165,7 +165,7 @@
         mEngine.shutdown();
         mEngine = mEngineBuilder.setEnablePublicKeyPinningBypassForLocalTrustAnchors(true).build();
         mCallback = new TestUrlRequestCallback();
-        builder = mEngine.newUrlRequestBuilder(URL, mCallback.getExecutor(), mCallback);
+        builder = mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback);
         mRequest = builder.build();
         mRequest.start();
         mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
@@ -211,6 +211,7 @@
     @Test
     public void testHttpEngine_GetDefaultUserAgent() throws Exception {
         assertThat(mEngineBuilder.getDefaultUserAgent(), containsString("AndroidHttpClient"));
+        assertThat(mEngineBuilder.getDefaultUserAgent()).contains(HttpEngine.getVersionString());
     }
 
     @Test
@@ -333,4 +334,9 @@
         UrlResponseInfo info = mCallback.mResponseInfo;
         assertOKStatusCode(info);
     }
-}
\ No newline at end of file
+
+    @Test
+    public void getVersionString_notEmpty() {
+        assertThat(HttpEngine.getVersionString()).isNotEmpty();
+    }
+}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/QuicExceptionTest.kt b/Cronet/tests/cts/src/android/net/http/cts/QuicExceptionTest.kt
new file mode 100644
index 0000000..4b7aa14
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/QuicExceptionTest.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.http.cts
+
+import android.net.http.QuicException
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class QuicExceptionTest {
+
+    @Test
+    fun testQuicException_returnsInputParameters() {
+        val message = "failed"
+        val cause = Throwable("thrown")
+        val quicException =
+            object : QuicException(message, cause) {
+                override fun getErrorCode() = 0
+                override fun isImmediatelyRetryable() = false
+            }
+
+        assertEquals(message, quicException.message)
+        assertEquals(cause, quicException.cause)
+    }
+
+    // TODO: add test for QuicException triggered from HttpEngine
+}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/QuicOptionsTest.kt b/Cronet/tests/cts/src/android/net/http/cts/QuicOptionsTest.kt
index a05aecd..5f1979f 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/QuicOptionsTest.kt
+++ b/Cronet/tests/cts/src/android/net/http/cts/QuicOptionsTest.kt
@@ -19,6 +19,9 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth.assertThat
 import java.time.Duration
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -30,7 +33,10 @@
         assertThat(quicOptions.allowedQuicHosts).isEmpty()
         assertThat(quicOptions.handshakeUserAgent).isNull()
         assertThat(quicOptions.idleConnectionTimeout).isNull()
-        assertThat(quicOptions.inMemoryServerConfigsCacheSize).isNull()
+        assertFalse(quicOptions.hasInMemoryServerConfigsCacheSize())
+        assertFailsWith(IllegalStateException::class) {
+            quicOptions.inMemoryServerConfigsCacheSize
+        }
     }
 
     @Test
@@ -61,6 +67,7 @@
         val quicOptions = QuicOptions.Builder()
                 .setInMemoryServerConfigsCacheSize(42)
                 .build()
+        assertTrue(quicOptions.hasInMemoryServerConfigsCacheSize())
         assertThat(quicOptions.inMemoryServerConfigsCacheSize)
                 .isEqualTo(42)
     }
diff --git a/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java b/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java
index e24121f..e4949d3 100644
--- a/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java
+++ b/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java
@@ -29,6 +29,7 @@
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
+import android.net.http.HeaderBlock;
 import android.net.http.HttpEngine;
 import android.net.http.HttpException;
 import android.net.http.InlineExecutionProhibitedException;
@@ -43,6 +44,7 @@
 import android.net.http.cts.util.TestUrlRequestCallback;
 import android.net.http.cts.util.TestUrlRequestCallback.ResponseStep;
 import android.net.http.cts.util.UploadDataProviders;
+import android.webkit.cts.CtsTestServer;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -59,11 +61,16 @@
 import java.net.URLEncoder;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 @RunWith(AndroidJUnit4.class)
 public class UrlRequestTest {
@@ -330,6 +337,50 @@
         assertThat(mCallback.mResponseAsString).isEqualTo(body);
     }
 
+    @Test
+    public void testUrlRequest_customHeaders() throws Exception {
+        UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getEchoHeadersUrl());
+
+        List<Map.Entry<String, String>> expectedHeaders = Arrays.asList(
+                Map.entry("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="),
+                Map.entry("Max-Forwards", "10"),
+                Map.entry("X-Client-Data", "random custom header content"));
+
+        for (Map.Entry<String, String> header : expectedHeaders) {
+            builder.addHeader(header.getKey(), header.getValue());
+        }
+
+        builder.build().start();
+        mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
+
+        assertOKStatusCode(mCallback.mResponseInfo);
+
+        List<Map.Entry<String, String>> echoedHeaders =
+                extractEchoedHeaders(mCallback.mResponseInfo.getHeaders());
+
+        // The implementation might decide to add more headers like accepted encodings it handles
+        // internally so the server is likely to see more headers than explicitly set
+        // by the developer.
+        assertThat(echoedHeaders)
+                .containsAtLeastElementsIn(expectedHeaders);
+    }
+
+    private static List<Map.Entry<String, String>> extractEchoedHeaders(HeaderBlock headers) {
+        return headers.getAsList()
+                .stream()
+                .flatMap(input -> {
+                    if (input.getKey().startsWith(CtsTestServer.ECHOED_RESPONSE_HEADER_PREFIX)) {
+                        String strippedKey =
+                                input.getKey().substring(
+                                        CtsTestServer.ECHOED_RESPONSE_HEADER_PREFIX.length());
+                        return Stream.of(Map.entry(strippedKey, input.getValue()));
+                    } else {
+                        return Stream.empty();
+                    }
+                })
+                .collect(Collectors.toList());
+    }
+
     private static class StubUrlRequestCallback implements UrlRequest.Callback {
 
         @Override
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index 4080029..4f95bdd 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -127,7 +127,7 @@
     out: ["framework_tethering_jarjar_rules.txt"],
     cmd: "$(location jarjar-rules-generator) " +
         "$(location :framework-tethering-pre-jarjar{.jar}) " +
-        "--apistubs $(location :framework-tethering.stubs.module_lib{.jar}) " + 
+        "--apistubs $(location :framework-tethering.stubs.module_lib{.jar}) " +
         "--prefix android.net.http.internal " +
         "--excludes $(location jarjar-excludes.txt) " +
         "--output $(out)",
@@ -151,6 +151,7 @@
 
 filegroup {
     name: "framework-tethering-srcs",
+    defaults: ["framework-sources-module-defaults"],
     srcs: [
         "src/**/*.aidl",
         "src/**/*.java",
diff --git a/Tethering/common/TetheringLib/cronet_enabled/api/current.txt b/Tethering/common/TetheringLib/cronet_enabled/api/current.txt
index 66a0295..333ea1c 100644
--- a/Tethering/common/TetheringLib/cronet_enabled/api/current.txt
+++ b/Tethering/common/TetheringLib/cronet_enabled/api/current.txt
@@ -51,9 +51,9 @@
   }
 
   public class ConnectionMigrationOptions {
-    method public int getAllowNonDefaultNetworkUsageEnabled();
-    method public int getDefaultNetworkMigrationEnabled();
-    method public int getPathDegradationMigrationEnabled();
+    method public int getAllowNonDefaultNetworkUsage();
+    method public int getDefaultNetworkMigration();
+    method public int getPathDegradationMigration();
     field public static final int MIGRATION_OPTION_DISABLED = 2; // 0x2
     field public static final int MIGRATION_OPTION_ENABLED = 1; // 0x1
     field public static final int MIGRATION_OPTION_UNSPECIFIED = 0; // 0x0
@@ -62,18 +62,18 @@
   public static final class ConnectionMigrationOptions.Builder {
     ctor public ConnectionMigrationOptions.Builder();
     method @NonNull public android.net.http.ConnectionMigrationOptions build();
-    method @NonNull public android.net.http.ConnectionMigrationOptions.Builder setAllowNonDefaultNetworkUsageEnabled(int);
-    method @NonNull public android.net.http.ConnectionMigrationOptions.Builder setDefaultNetworkMigrationEnabled(int);
-    method @NonNull public android.net.http.ConnectionMigrationOptions.Builder setPathDegradationMigrationEnabled(int);
+    method @NonNull public android.net.http.ConnectionMigrationOptions.Builder setAllowNonDefaultNetworkUsage(int);
+    method @NonNull public android.net.http.ConnectionMigrationOptions.Builder setDefaultNetworkMigration(int);
+    method @NonNull public android.net.http.ConnectionMigrationOptions.Builder setPathDegradationMigration(int);
   }
 
   public final class DnsOptions {
-    method public int getPersistHostCacheEnabled();
+    method public int getPersistHostCache();
     method @Nullable public java.time.Duration getPersistHostCachePeriod();
-    method public int getPreestablishConnectionsToStaleDnsResultsEnabled();
-    method public int getStaleDnsEnabled();
+    method public int getPreestablishConnectionsToStaleDnsResults();
+    method public int getStaleDns();
     method @Nullable public android.net.http.DnsOptions.StaleDnsOptions getStaleDnsOptions();
-    method public int getUseHttpStackDnsResolverEnabled();
+    method public int getUseHttpStackDnsResolver();
     field public static final int DNS_OPTION_DISABLED = 2; // 0x2
     field public static final int DNS_OPTION_ENABLED = 1; // 0x1
     field public static final int DNS_OPTION_UNSPECIFIED = 0; // 0x0
@@ -82,28 +82,28 @@
   public static final class DnsOptions.Builder {
     ctor public DnsOptions.Builder();
     method @NonNull public android.net.http.DnsOptions build();
-    method @NonNull public android.net.http.DnsOptions.Builder setPersistHostCacheEnabled(int);
+    method @NonNull public android.net.http.DnsOptions.Builder setPersistHostCache(int);
     method @NonNull public android.net.http.DnsOptions.Builder setPersistHostCachePeriod(@NonNull java.time.Duration);
-    method @NonNull public android.net.http.DnsOptions.Builder setPreestablishConnectionsToStaleDnsResultsEnabled(int);
-    method @NonNull public android.net.http.DnsOptions.Builder setStaleDnsEnabled(int);
+    method @NonNull public android.net.http.DnsOptions.Builder setPreestablishConnectionsToStaleDnsResults(int);
+    method @NonNull public android.net.http.DnsOptions.Builder setStaleDns(int);
     method @NonNull public android.net.http.DnsOptions.Builder setStaleDnsOptions(@NonNull android.net.http.DnsOptions.StaleDnsOptions);
-    method @NonNull public android.net.http.DnsOptions.Builder setUseHttpStackDnsResolverEnabled(int);
+    method @NonNull public android.net.http.DnsOptions.Builder setUseHttpStackDnsResolver(int);
   }
 
   public static class DnsOptions.StaleDnsOptions {
-    method public int getAllowCrossNetworkUsageEnabled();
+    method public int getAllowCrossNetworkUsage();
     method @Nullable public java.time.Duration getFreshLookupTimeout();
     method @Nullable public java.time.Duration getMaxExpiredDelay();
-    method public int getUseStaleOnNameNotResolvedEnabled();
+    method public int getUseStaleOnNameNotResolved();
   }
 
   public static final class DnsOptions.StaleDnsOptions.Builder {
     ctor public DnsOptions.StaleDnsOptions.Builder();
     method @NonNull public android.net.http.DnsOptions.StaleDnsOptions build();
-    method @NonNull public android.net.http.DnsOptions.StaleDnsOptions.Builder setAllowCrossNetworkUsageEnabled(int);
+    method @NonNull public android.net.http.DnsOptions.StaleDnsOptions.Builder setAllowCrossNetworkUsage(int);
     method @NonNull public android.net.http.DnsOptions.StaleDnsOptions.Builder setFreshLookupTimeout(@NonNull java.time.Duration);
     method @NonNull public android.net.http.DnsOptions.StaleDnsOptions.Builder setMaxExpiredDelay(@NonNull java.time.Duration);
-    method @NonNull public android.net.http.DnsOptions.StaleDnsOptions.Builder setUseStaleOnNameNotResolvedEnabled(int);
+    method @NonNull public android.net.http.DnsOptions.StaleDnsOptions.Builder setUseStaleOnNameNotResolved(int);
   }
 
   public abstract class HeaderBlock {
@@ -177,7 +177,8 @@
     method @NonNull public java.util.Set<java.lang.String> getAllowedQuicHosts();
     method @Nullable public String getHandshakeUserAgent();
     method @Nullable public java.time.Duration getIdleConnectionTimeout();
-    method @Nullable public Integer getInMemoryServerConfigsCacheSize();
+    method public int getInMemoryServerConfigsCacheSize();
+    method public boolean hasInMemoryServerConfigsCacheSize();
   }
 
   public static final class QuicOptions.Builder {
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
index 75c819b..ac3d713 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
@@ -156,6 +156,7 @@
     @After
     fun tearDown() {
         fakeTetheringThread.quitSafely()
+        fakeTetheringThread.join()
     }
 
     private fun verifyActivityPendingIntent(intent: Intent, flags: Int) {
diff --git a/framework-t/Sources.bp b/framework-t/Sources.bp
index 391a562..b8eb1f6 100644
--- a/framework-t/Sources.bp
+++ b/framework-t/Sources.bp
@@ -16,15 +16,13 @@
 
 filegroup {
     name: "framework-connectivity-tiramisu-updatable-sources",
+    defaults: ["framework-sources-module-defaults"],
     srcs: [
         "src/**/*.java",
         "src/**/*.aidl",
     ],
     path: "src",
-    visibility: [
-        "//frameworks/base",
-        "//packages/modules/Connectivity:__subpackages__",
-    ],
+    visibility: ["//packages/modules/Connectivity:__subpackages__"],
 }
 
 cc_library_shared {
diff --git a/framework-t/src/android/net/nsd/INsdManager.aidl b/framework-t/src/android/net/nsd/INsdManager.aidl
index 89e9cdb..9d14b1a 100644
--- a/framework-t/src/android/net/nsd/INsdManager.aidl
+++ b/framework-t/src/android/net/nsd/INsdManager.aidl
@@ -26,5 +26,5 @@
  * {@hide}
  */
 interface INsdManager {
-    INsdServiceConnector connect(INsdManagerCallback cb);
+    INsdServiceConnector connect(INsdManagerCallback cb, boolean useJavaBackend);
 }
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index 36808cf..96f2f80 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -16,6 +16,7 @@
 
 package android.net.nsd;
 
+import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_PLATFORM_MDNS_BACKEND;
 import static android.net.connectivity.ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER;
 
 import android.annotation.IntDef;
@@ -510,7 +511,8 @@
         mHandler = new ServiceHandler(t.getLooper());
 
         try {
-            mService = service.connect(new NsdCallbackImpl(mHandler));
+            mService = service.connect(new NsdCallbackImpl(mHandler), CompatChanges.isChangeEnabled(
+                    ENABLE_PLATFORM_MDNS_BACKEND));
         } catch (RemoteException e) {
             throw new RuntimeException("Failed to connect to NsdService");
         }
diff --git a/framework/Android.bp b/framework/Android.bp
index 3950dba..2d729c5 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -45,14 +45,12 @@
 // TODO: use a java_library in the bootclasspath instead
 filegroup {
     name: "framework-connectivity-sources",
+    defaults: ["framework-sources-module-defaults"],
     srcs: [
         ":framework-connectivity-internal-sources",
         ":framework-connectivity-aidl-export-sources",
     ],
-    visibility: [
-        "//frameworks/base",
-        "//packages/modules/Connectivity:__subpackages__",
-    ],
+    visibility: ["//packages/modules/Connectivity:__subpackages__"],
 }
 
 java_defaults {
diff --git a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
index 2cfda9e..dfe5867 100644
--- a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
+++ b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
@@ -73,6 +73,17 @@
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public static final long ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION = 266524688;
 
+    /**
+     * Apps targeting < Android 14 use a legacy NSD backend.
+     *
+     * The legacy apps use a legacy native daemon as NsdManager backend, but other apps use a
+     * platform-integrated mDNS implementation as backend.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    public static final long ENABLE_PLATFORM_MDNS_BACKEND = 270306772L;
     private ConnectivityCompatChanges() {
     }
 }
diff --git a/nearby/framework/Android.bp b/nearby/framework/Android.bp
index e223b54..f6e0995 100644
--- a/nearby/framework/Android.bp
+++ b/nearby/framework/Android.bp
@@ -32,10 +32,10 @@
 
 filegroup {
     name: "framework-nearby-sources",
+    defaults: ["framework-sources-module-defaults"],
     srcs: [
         ":framework-nearby-java-sources",
     ],
-    visibility: ["//frameworks/base"],
 }
 
 // Build of only framework-nearby (not as part of connectivity) for
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index c92e9a9..cbe6691 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -53,7 +53,6 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.Pair;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -397,13 +396,12 @@
                 final int clientId = msg.arg2;
                 switch (msg.what) {
                     case NsdManager.REGISTER_CLIENT:
-                        final Pair<NsdServiceConnector, INsdManagerCallback> arg =
-                                (Pair<NsdServiceConnector, INsdManagerCallback>) msg.obj;
-                        final INsdManagerCallback cb = arg.second;
+                        final ConnectorArgs arg = (ConnectorArgs) msg.obj;
+                        final INsdManagerCallback cb = arg.callback;
                         try {
-                            cb.asBinder().linkToDeath(arg.first, 0);
-                            cInfo = new ClientInfo(cb);
-                            mClients.put(arg.first, cInfo);
+                            cb.asBinder().linkToDeath(arg.connector, 0);
+                            cInfo = new ClientInfo(cb, arg.useJavaBackend);
+                            mClients.put(arg.connector, cInfo);
                         } catch (RemoteException e) {
                             Log.w(TAG, "Client " + clientId + " has already died");
                         }
@@ -608,7 +606,8 @@
                         final NsdServiceInfo info = args.serviceInfo;
                         id = getUniqueId();
                         final String serviceType = constructServiceType(info.getServiceType());
-                        if (mDeps.isMdnsDiscoveryManagerEnabled(mContext)
+                        if (clientInfo.mUseJavaBackend
+                                || mDeps.isMdnsDiscoveryManagerEnabled(mContext)
                                 || useDiscoveryManagerForType(serviceType)) {
                             if (serviceType == null) {
                                 clientInfo.onDiscoverServicesFailed(clientId,
@@ -702,7 +701,8 @@
                         final NsdServiceInfo serviceInfo = args.serviceInfo;
                         final String serviceType = serviceInfo.getServiceType();
                         final String registerServiceType = constructServiceType(serviceType);
-                        if (mDeps.isMdnsAdvertiserEnabled(mContext)
+                        if (clientInfo.mUseJavaBackend
+                                || mDeps.isMdnsAdvertiserEnabled(mContext)
                                 || useAdvertiserForType(registerServiceType)) {
                             if (registerServiceType == null) {
                                 Log.e(TAG, "Invalid service type: " + serviceType);
@@ -782,7 +782,8 @@
                         final NsdServiceInfo info = args.serviceInfo;
                         id = getUniqueId();
                         final String serviceType = constructServiceType(info.getServiceType());
-                        if (mDeps.isMdnsDiscoveryManagerEnabled(mContext)
+                        if (clientInfo.mUseJavaBackend
+                                ||  mDeps.isMdnsDiscoveryManagerEnabled(mContext)
                                 || useDiscoveryManagerForType(serviceType)) {
                             if (serviceType == null) {
                                 clientInfo.onResolveServiceFailed(clientId,
@@ -1532,12 +1533,27 @@
         }
     }
 
+    private static class ConnectorArgs {
+        @NonNull public final NsdServiceConnector connector;
+        @NonNull public final INsdManagerCallback callback;
+        public final boolean useJavaBackend;
+
+        ConnectorArgs(@NonNull NsdServiceConnector connector, @NonNull INsdManagerCallback callback,
+                boolean useJavaBackend) {
+            this.connector = connector;
+            this.callback = callback;
+            this.useJavaBackend = useJavaBackend;
+        }
+    }
+
     @Override
-    public INsdServiceConnector connect(INsdManagerCallback cb) {
+    public INsdServiceConnector connect(INsdManagerCallback cb, boolean useJavaBackend) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, "NsdService");
+        if (DBG) Log.d(TAG, "New client connect. useJavaBackend=" + useJavaBackend);
         final INsdServiceConnector connector = new NsdServiceConnector();
         mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
-                NsdManager.REGISTER_CLIENT, new Pair<>(connector, cb)));
+                NsdManager.REGISTER_CLIENT,
+                new ConnectorArgs((NsdServiceConnector) connector, cb, useJavaBackend)));
         return connector;
     }
 
@@ -1793,9 +1809,12 @@
 
         // The target SDK of this client < Build.VERSION_CODES.S
         private boolean mIsPreSClient = false;
+        // The flag of using java backend if the client's target SDK >= U
+        private final boolean mUseJavaBackend;
 
-        private ClientInfo(INsdManagerCallback cb) {
+        private ClientInfo(INsdManagerCallback cb, boolean useJavaBackend) {
             mCb = cb;
+            mUseJavaBackend = useJavaBackend;
             if (DBG) Log.d(TAG, "New client");
         }
 
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java b/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
index be2555b..e0100f6 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
@@ -81,6 +81,21 @@
         return a == null || a.getTtl() == b.getTtl();
     }
 
+    private <T extends MdnsRecord> boolean addOrReplaceRecord(@NonNull T record,
+            @NonNull List<T> recordsList) {
+        final int existing = recordsList.indexOf(record);
+        if (existing >= 0) {
+            if (recordsAreSame(record, recordsList.get(existing))) {
+                return false;
+            }
+            final MdnsRecord existedRecord = recordsList.remove(existing);
+            records.remove(existedRecord);
+        }
+        recordsList.add(record);
+        records.add(record);
+        return true;
+    }
+
     /**
      * Adds a pointer record.
      *
@@ -92,17 +107,7 @@
             throw new IllegalArgumentException(
                     "Pointer records for different service names cannot be added");
         }
-        final int existing = pointerRecords.indexOf(pointerRecord);
-        if (existing >= 0) {
-            if (recordsAreSame(pointerRecord, pointerRecords.get(existing))) {
-                return false;
-            }
-            final MdnsRecord record = pointerRecords.remove(existing);
-            records.remove(record);
-        }
-        pointerRecords.add(pointerRecord);
-        records.add(pointerRecord);
-        return true;
+        return addOrReplaceRecord(pointerRecord, pointerRecords);
     }
 
     /** Gets the pointer records. */
@@ -207,17 +212,7 @@
     /** Add the IPv4 address record. */
     public synchronized boolean addInet4AddressRecord(
             @NonNull MdnsInetAddressRecord newInet4AddressRecord) {
-        final int existing = inet4AddressRecords.indexOf(newInet4AddressRecord);
-        if (existing >= 0) {
-            if (recordsAreSame(newInet4AddressRecord, inet4AddressRecords.get(existing))) {
-                return false;
-            }
-            final MdnsRecord record = inet4AddressRecords.remove(existing);
-            records.remove(record);
-        }
-        inet4AddressRecords.add(newInet4AddressRecord);
-        records.add(newInet4AddressRecord);
-        return true;
+        return addOrReplaceRecord(newInet4AddressRecord, inet4AddressRecords);
     }
 
     /** Gets the IPv4 address records. */
@@ -248,17 +243,7 @@
     /** Sets the IPv6 address records. */
     public synchronized boolean addInet6AddressRecord(
             @NonNull MdnsInetAddressRecord newInet6AddressRecord) {
-        final int existing = inet6AddressRecords.indexOf(newInet6AddressRecord);
-        if (existing >= 0) {
-            if (recordsAreSame(newInet6AddressRecord, inet6AddressRecords.get(existing))) {
-                return false;
-            }
-            final MdnsRecord record = inet6AddressRecords.remove(existing);
-            records.remove(record);
-        }
-        inet6AddressRecords.add(newInet6AddressRecord);
-        records.add(newInet6AddressRecord);
-        return true;
+        return addOrReplaceRecord(newInet6AddressRecord, inet6AddressRecords);
     }
 
     /**
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
index 0151202..129db7e 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
@@ -220,9 +220,10 @@
                         // This bit, the cache-flush bit, tells neighboring hosts
                         // that this is not a shared record type.  Instead of merging this new
                         // record additively into the cache in addition to any previous records with
-                        // the same name, rrtype, and rrclass, all old records with that name,
-                        // rrtype, and rrclass that were received more than one second ago are
-                        // declared invalid, and marked to expire from the cache in one second.
+                        // the same name, rrtype, and rrclass.
+                        // TODO: All old records with that name, rrtype, and rrclass that were
+                        //       received more than one second ago are declared invalid, and marked
+                        //       to expire from the cache in one second.
                         if (inetRecord.getCacheFlush()) {
                             response.clearInet4AddressRecords();
                             response.clearInet6AddressRecords();
@@ -236,9 +237,10 @@
                         // This bit, the cache-flush bit, tells neighboring hosts
                         // that this is not a shared record type.  Instead of merging this new
                         // record additively into the cache in addition to any previous records with
-                        // the same name, rrtype, and rrclass, all old records with that name,
-                        // rrtype, and rrclass that were received more than one second ago are
-                        // declared invalid, and marked to expire from the cache in one second.
+                        // the same name, rrtype, and rrclass.
+                        // TODO: All old records with that name, rrtype, and rrclass that were
+                        //       received more than one second ago are declared invalid, and marked
+                        //       to expire from the cache in one second.
                         if (inetRecord.getCacheFlush()) {
                             response.clearInet4AddressRecords();
                             response.clearInet6AddressRecords();
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index e969cd6..2af30dd 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -10734,6 +10734,18 @@
                         callback));
     }
 
+    private boolean hasUnderlyingTestNetworks(NetworkCapabilities nc) {
+        final List<Network> underlyingNetworks = nc.getUnderlyingNetworks();
+        if (underlyingNetworks == null) return false;
+
+        for (Network network : underlyingNetworks) {
+            if (getNetworkCapabilitiesInternal(network).hasTransport(TRANSPORT_TEST)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @Override
     public void simulateDataStall(int detectionMethod, long timestampMillis,
             @NonNull Network network, @NonNull PersistableBundle extras) {
@@ -10744,14 +10756,18 @@
                 android.Manifest.permission.MANAGE_TEST_NETWORKS,
                 android.Manifest.permission.NETWORK_STACK);
         final NetworkCapabilities nc = getNetworkCapabilitiesInternal(network);
-        if (!nc.hasTransport(TRANSPORT_TEST)) {
-            throw new SecurityException("Data Stall simulation is only possible for test networks");
+        if (!nc.hasTransport(TRANSPORT_TEST) && !hasUnderlyingTestNetworks(nc)) {
+            throw new SecurityException(
+                    "Data Stall simulation is only possible for test networks or networks built on"
+                            + " top of test networks");
         }
 
         final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
-        if (nai == null || nai.creatorUid != mDeps.getCallingUid()) {
-            throw new SecurityException("Data Stall simulation is only possible for network "
-                + "creators");
+        if (nai == null
+                || (nai.creatorUid != mDeps.getCallingUid()
+                        && nai.creatorUid != Process.SYSTEM_UID)) {
+            throw new SecurityException(
+                    "Data Stall simulation is only possible for network " + "creators");
         }
 
         // Instead of passing the data stall directly to the ConnectivityDiagnostics handler, treat
diff --git a/tests/common/java/android/net/NetworkProviderTest.kt b/tests/common/java/android/net/NetworkProviderTest.kt
index c0e7f61..fcbb0dd 100644
--- a/tests/common/java/android/net/NetworkProviderTest.kt
+++ b/tests/common/java/android/net/NetworkProviderTest.kt
@@ -79,6 +79,7 @@
     @After
     fun tearDown() {
         mHandlerThread.quitSafely()
+        mHandlerThread.join()
         instrumentation.getUiAutomation().dropShellPermissionIdentity()
     }
 
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java
index 449454e..fe522a0 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java
@@ -32,6 +32,7 @@
 import com.android.networkstack.apishim.VpnServiceBuilderShimImpl;
 import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
 import com.android.networkstack.apishim.common.VpnServiceBuilderShim;
+import com.android.testutils.PacketReflector;
 
 import java.io.IOException;
 import java.net.InetAddress;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/PacketReflector.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/PacketReflector.java
deleted file mode 100644
index 124c2c3..0000000
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/PacketReflector.java
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.net.hostside;
-
-import static android.system.OsConstants.ICMP6_ECHO_REPLY;
-import static android.system.OsConstants.ICMP6_ECHO_REQUEST;
-import static android.system.OsConstants.ICMP_ECHO;
-import static android.system.OsConstants.ICMP_ECHOREPLY;
-
-import android.system.ErrnoException;
-import android.system.Os;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-
-public class PacketReflector extends Thread {
-
-    private static int IPV4_HEADER_LENGTH = 20;
-    private static int IPV6_HEADER_LENGTH = 40;
-
-    private static int IPV4_ADDR_OFFSET = 12;
-    private static int IPV6_ADDR_OFFSET = 8;
-    private static int IPV4_ADDR_LENGTH = 4;
-    private static int IPV6_ADDR_LENGTH = 16;
-
-    private static int IPV4_PROTO_OFFSET = 9;
-    private static int IPV6_PROTO_OFFSET = 6;
-
-    private static final byte IPPROTO_ICMP = 1;
-    private static final byte IPPROTO_TCP = 6;
-    private static final byte IPPROTO_UDP = 17;
-    private static final byte IPPROTO_ICMPV6 = 58;
-
-    private static int ICMP_HEADER_LENGTH = 8;
-    private static int TCP_HEADER_LENGTH = 20;
-    private static int UDP_HEADER_LENGTH = 8;
-
-    private static final byte ICMP_ECHO = 8;
-    private static final byte ICMP_ECHOREPLY = 0;
-
-    private static String TAG = "PacketReflector";
-
-    private FileDescriptor mFd;
-    private byte[] mBuf;
-
-    public PacketReflector(FileDescriptor fd, int mtu) {
-        super("PacketReflector");
-        mFd = fd;
-        mBuf = new byte[mtu];
-    }
-
-    private static void swapBytes(byte[] buf, int pos1, int pos2, int len) {
-        for (int i = 0; i < len; i++) {
-            byte b = buf[pos1 + i];
-            buf[pos1 + i] = buf[pos2 + i];
-            buf[pos2 + i] = b;
-        }
-    }
-
-    private static void swapAddresses(byte[] buf, int version) {
-        int addrPos, addrLen;
-        switch(version) {
-            case 4:
-                addrPos = IPV4_ADDR_OFFSET;
-                addrLen = IPV4_ADDR_LENGTH;
-                break;
-            case 6:
-                addrPos = IPV6_ADDR_OFFSET;
-                addrLen = IPV6_ADDR_LENGTH;
-                break;
-            default:
-                throw new IllegalArgumentException();
-        }
-        swapBytes(buf, addrPos, addrPos + addrLen, addrLen);
-    }
-
-    // Reflect TCP packets: swap the source and destination addresses, but don't change the ports.
-    // This is used by the test to "connect to itself" through the VPN.
-    private void processTcpPacket(byte[] buf, int version, int len, int hdrLen) {
-        if (len < hdrLen + TCP_HEADER_LENGTH) {
-            return;
-        }
-
-        // Swap src and dst IP addresses.
-        swapAddresses(buf, version);
-
-        // Send the packet back.
-        writePacket(buf, len);
-    }
-
-    // Echo UDP packets: swap source and destination addresses, and source and destination ports.
-    // This is used by the test to check that the bytes it sends are echoed back.
-    private void processUdpPacket(byte[] buf, int version, int len, int hdrLen) {
-        if (len < hdrLen + UDP_HEADER_LENGTH) {
-            return;
-        }
-
-        // Swap src and dst IP addresses.
-        swapAddresses(buf, version);
-
-        // Swap dst and src ports.
-        int portOffset = hdrLen;
-        swapBytes(buf, portOffset, portOffset + 2, 2);
-
-        // Send the packet back.
-        writePacket(buf, len);
-    }
-
-    private void processIcmpPacket(byte[] buf, int version, int len, int hdrLen) {
-        if (len < hdrLen + ICMP_HEADER_LENGTH) {
-            return;
-        }
-
-        byte type = buf[hdrLen];
-        if (!(version == 4 && type == ICMP_ECHO) &&
-            !(version == 6 && type == (byte) ICMP6_ECHO_REQUEST)) {
-            return;
-        }
-
-        // Save the ping packet we received.
-        byte[] request = buf.clone();
-
-        // Swap src and dst IP addresses, and send the packet back.
-        // This effectively pings the device to see if it replies.
-        swapAddresses(buf, version);
-        writePacket(buf, len);
-
-        // The device should have replied, and buf should now contain a ping response.
-        int received = readPacket(buf);
-        if (received != len) {
-            Log.i(TAG, "Reflecting ping did not result in ping response: " +
-                       "read=" + received + " expected=" + len);
-            return;
-        }
-
-        byte replyType = buf[hdrLen];
-        if ((type == ICMP_ECHO && replyType != ICMP_ECHOREPLY)
-                || (type == (byte) ICMP6_ECHO_REQUEST && replyType != (byte) ICMP6_ECHO_REPLY)) {
-            Log.i(TAG, "Received unexpected ICMP reply: original " + type
-                    + ", reply " + replyType);
-            return;
-        }
-
-        // Compare the response we got with the original packet.
-        // The only thing that should have changed are addresses, type and checksum.
-        // Overwrite them with the received bytes and see if the packet is otherwise identical.
-        request[hdrLen] = buf[hdrLen];          // Type
-        request[hdrLen + 2] = buf[hdrLen + 2];  // Checksum byte 1.
-        request[hdrLen + 3] = buf[hdrLen + 3];  // Checksum byte 2.
-
-        // Since Linux kernel 4.2, net.ipv6.auto_flowlabels is set by default, and therefore
-        // the request and reply may have different IPv6 flow label: ignore that as well.
-        if (version == 6) {
-            request[1] = (byte)(request[1] & 0xf0 | buf[1] & 0x0f);
-            request[2] = buf[2];
-            request[3] = buf[3];
-        }
-
-        for (int i = 0; i < len; i++) {
-            if (buf[i] != request[i]) {
-                Log.i(TAG, "Received non-matching packet when expecting ping response.");
-                return;
-            }
-        }
-
-        // Now swap the addresses again and reflect the packet. This sends a ping reply.
-        swapAddresses(buf, version);
-        writePacket(buf, len);
-    }
-
-    private void writePacket(byte[] buf, int len) {
-        try {
-            Os.write(mFd, buf, 0, len);
-        } catch (ErrnoException|IOException e) {
-            Log.e(TAG, "Error writing packet: " + e.getMessage());
-        }
-    }
-
-    private int readPacket(byte[] buf) {
-        int len;
-        try {
-            len = Os.read(mFd, buf, 0, buf.length);
-        } catch (ErrnoException|IOException e) {
-            Log.e(TAG, "Error reading packet: " + e.getMessage());
-            len = -1;
-        }
-        return len;
-    }
-
-    // Reads one packet from our mFd, and possibly writes the packet back.
-    private void processPacket() {
-        int len = readPacket(mBuf);
-        if (len < 1) {
-            return;
-        }
-
-        int version = mBuf[0] >> 4;
-        int addrPos, protoPos, hdrLen, addrLen;
-        if (version == 4) {
-            hdrLen = IPV4_HEADER_LENGTH;
-            protoPos = IPV4_PROTO_OFFSET;
-            addrPos = IPV4_ADDR_OFFSET;
-            addrLen = IPV4_ADDR_LENGTH;
-        } else if (version == 6) {
-            hdrLen = IPV6_HEADER_LENGTH;
-            protoPos = IPV6_PROTO_OFFSET;
-            addrPos = IPV6_ADDR_OFFSET;
-            addrLen = IPV6_ADDR_LENGTH;
-        } else {
-            return;
-        }
-
-        if (len < hdrLen) {
-            return;
-        }
-
-        byte proto = mBuf[protoPos];
-        switch (proto) {
-            case IPPROTO_ICMP:
-            case IPPROTO_ICMPV6:
-                processIcmpPacket(mBuf, version, len, hdrLen);
-                break;
-            case IPPROTO_TCP:
-                processTcpPacket(mBuf, version, len, hdrLen);
-                break;
-            case IPPROTO_UDP:
-                processUdpPacket(mBuf, version, len, hdrLen);
-                break;
-        }
-    }
-
-    public void run() {
-        Log.i(TAG, "PacketReflector starting fd=" + mFd + " valid=" + mFd.valid());
-        while (!interrupted() && mFd.valid()) {
-            processPacket();
-        }
-        Log.i(TAG, "PacketReflector exiting fd=" + mFd + " valid=" + mFd.valid());
-    }
-}
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index f578ff3..b535a8f 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -191,6 +191,7 @@
         callbacksToCleanUp.forEach { mCM.unregisterNetworkCallback(it) }
         qosTestSocket?.close()
         mHandlerThread.quitSafely()
+        mHandlerThread.join()
         instrumentation.getUiAutomation().dropShellPermissionIdentity()
     }
 
diff --git a/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt b/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt
index eb41d71..fcfecad 100644
--- a/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt
@@ -90,6 +90,7 @@
         mCm.unregisterNetworkCallback(agentCleanUpCb)
 
         mHandlerThread.quitSafely()
+        mHandlerThread.join()
         callbacksToCleanUp.forEach { mCm.unregisterNetworkCallback(it) }
     }
 
diff --git a/tests/cts/net/src/android/net/cts/NetworkValidationTest.kt b/tests/cts/net/src/android/net/cts/NetworkValidationTest.kt
index 8e98dba..621af23 100644
--- a/tests/cts/net/src/android/net/cts/NetworkValidationTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkValidationTest.kt
@@ -146,6 +146,7 @@
         httpServer.stop()
         handlerThread.threadHandler.post { reader.stop() }
         handlerThread.quitSafely()
+        handlerThread.join()
 
         iface.fileDescriptor.close()
     }
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 6fd2321..db4f937 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -432,6 +432,7 @@
         }
         handlerThread.waitForIdle(TIMEOUT_MS)
         handlerThread.quitSafely()
+        handlerThread.join()
     }
 
     @Test
diff --git a/tests/unit/java/android/net/nsd/NsdManagerTest.java b/tests/unit/java/android/net/nsd/NsdManagerTest.java
index da65b62..0965193 100644
--- a/tests/unit/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/unit/java/android/net/nsd/NsdManagerTest.java
@@ -21,6 +21,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -73,11 +74,11 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        doReturn(mServiceConn).when(mService).connect(any());
+        doReturn(mServiceConn).when(mService).connect(any(), anyBoolean());
         mManager = new NsdManager(mContext, mService);
         final ArgumentCaptor<INsdManagerCallback> cbCaptor = ArgumentCaptor.forClass(
                 INsdManagerCallback.class);
-        verify(mService).connect(cbCaptor.capture());
+        verify(mService).connect(cbCaptor.capture(), anyBoolean());
         mCallback = cbCaptor.getValue();
     }
 
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index c1c6a8a..6c89c38 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -2219,7 +2219,9 @@
         ConnectivityResources.setResourcesContextForTest(null);
 
         mCsHandlerThread.quitSafely();
+        mCsHandlerThread.join();
         mAlarmManagerThread.quitSafely();
+        mAlarmManagerThread.join();
     }
 
     private void mockDefaultPackages() throws Exception {
@@ -10126,6 +10128,7 @@
         b2.expectBroadcast();
 
         VMSHandlerThread.quitSafely();
+        VMSHandlerThread.join();
     }
 
     @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
@@ -16974,6 +16977,7 @@
         } finally {
             cellFactory.terminate();
             handlerThread.quitSafely();
+            handlerThread.join();
         }
     }
 
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 0b48e08..2ed989e 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -17,6 +17,8 @@
 package com.android.server;
 
 import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_PLATFORM_MDNS_BACKEND;
+import static android.net.connectivity.ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER;
 import static android.net.nsd.NsdManager.FAILURE_BAD_PARAMETERS;
 import static android.net.nsd.NsdManager.FAILURE_INTERNAL_ERROR;
 import static android.net.nsd.NsdManager.FAILURE_OPERATION_NOT_RUNNING;
@@ -54,7 +56,6 @@
 import android.content.Context;
 import android.net.INetd;
 import android.net.Network;
-import android.net.connectivity.ConnectivityCompatChanges;
 import android.net.mdns.aidl.DiscoveryInfo;
 import android.net.mdns.aidl.GetAddressInfo;
 import android.net.mdns.aidl.IMDnsEventListener;
@@ -190,7 +191,9 @@
     }
 
     @Test
-    @DisableCompatChanges(ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @DisableCompatChanges({
+            RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER,
+            ENABLE_PLATFORM_MDNS_BACKEND})
     public void testPreSClients() throws Exception {
         // Pre S client connected, the daemon should be started.
         connectClient(mService);
@@ -217,7 +220,8 @@
     }
 
     @Test
-    @EnableCompatChanges(ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @EnableCompatChanges(RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testNoDaemonStartedWhenClientsConnect() throws Exception {
         // Creating an NsdManager will not cause daemon startup.
         connectClient(mService);
@@ -251,7 +255,8 @@
     }
 
     @Test
-    @EnableCompatChanges(ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @EnableCompatChanges(RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testClientRequestsAreGCedAtDisconnection() throws Exception {
         final NsdManager client = connectClient(mService);
         final INsdManagerCallback cb1 = getCallback();
@@ -294,7 +299,8 @@
     }
 
     @Test
-    @EnableCompatChanges(ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @EnableCompatChanges(RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testCleanupDelayNoRequestActive() throws Exception {
         final NsdManager client = connectClient(mService);
 
@@ -330,6 +336,7 @@
     }
 
     @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testDiscoverOnTetheringDownstream() throws Exception {
         final NsdManager client = connectClient(mService);
         final int interfaceIdx = 123;
@@ -420,6 +427,7 @@
     }
 
     @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testDiscoverOnBlackholeNetwork() throws Exception {
         final NsdManager client = connectClient(mService);
         final DiscoveryListener discListener = mock(DiscoveryListener.class);
@@ -449,6 +457,7 @@
     }
 
     @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testServiceRegistrationSuccessfulAndFailed() throws Exception {
         final NsdManager client = connectClient(mService);
         final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
@@ -495,6 +504,7 @@
     }
 
     @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testServiceDiscoveryFailed() throws Exception {
         final NsdManager client = connectClient(mService);
         final DiscoveryListener discListener = mock(DiscoveryListener.class);
@@ -521,6 +531,7 @@
     }
 
     @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testServiceResolutionFailed() throws Exception {
         final NsdManager client = connectClient(mService);
         final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
@@ -551,6 +562,7 @@
     }
 
     @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testGettingAddressFailed() throws Exception {
         final NsdManager client = connectClient(mService);
         final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
@@ -597,6 +609,7 @@
     }
 
     @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testNoCrashWhenProcessResolutionAfterBinderDied() throws Exception {
         final NsdManager client = connectClient(mService);
         final INsdManagerCallback cb = getCallback();
@@ -616,6 +629,7 @@
     }
 
     @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testStopServiceResolution() {
         final NsdManager client = connectClient(mService);
         final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
@@ -638,6 +652,7 @@
     }
 
     @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testStopResolutionFailed() {
         final NsdManager client = connectClient(mService);
         final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
@@ -662,6 +677,7 @@
     }
 
     @Test @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testStopResolutionDuringGettingAddress() throws RemoteException {
         final NsdManager client = connectClient(mService);
         final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
@@ -823,6 +839,7 @@
     }
 
     @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testMdnsDiscoveryManagerFeature() {
         // Create NsdService w/o feature enabled.
         final NsdManager client = connectClient(mService);
@@ -1012,6 +1029,7 @@
     }
 
     @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testMdnsAdvertiserFeatureFlagging() {
         // Create NsdService w/o feature enabled.
         final NsdManager client = connectClient(mService);
@@ -1047,6 +1065,7 @@
     }
 
     @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testTypeSpecificFeatureFlagging() {
         doReturn("_type1._tcp:flag1,_type2._tcp:flag2").when(mDeps).getTypeAllowlistFlags();
         doReturn(true).when(mDeps).isFeatureEnabled(any(),
@@ -1234,6 +1253,37 @@
         assertEquals("_TEST._sub._999._tcp", constructServiceType(serviceType4));
     }
 
+    @Test
+    @EnableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
+    public void testEnablePlatformMdnsBackend() {
+        final NsdManager client = connectClient(mService);
+        final NsdServiceInfo regInfo = new NsdServiceInfo("a".repeat(70), SERVICE_TYPE);
+        final Network network = new Network(999);
+        regInfo.setHostAddresses(List.of(parseNumericAddress("192.0.2.123")));
+        regInfo.setPort(12345);
+        regInfo.setAttribute("testattr", "testvalue");
+        regInfo.setNetwork(network);
+
+        // Verify the registration uses MdnsAdvertiser
+        final RegistrationListener regListener = mock(RegistrationListener.class);
+        client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
+        waitForIdle();
+        verify(mSocketProvider).startMonitoringSockets();
+        verify(mAdvertiser).addService(anyInt(), any());
+
+        // Verify the discovery uses MdnsDiscoveryManager
+        final DiscoveryListener discListener = mock(DiscoveryListener.class);
+        client.discoverServices(SERVICE_TYPE, PROTOCOL, network, r -> r.run(), discListener);
+        waitForIdle();
+        verify(mDiscoveryManager).registerListener(anyString(), any(), any());
+
+        // Verify the discovery uses MdnsDiscoveryManager
+        final ResolveListener resolveListener = mock(ResolveListener.class);
+        client.resolveService(regInfo, r -> r.run(), resolveListener);
+        waitForIdle();
+        verify(mDiscoveryManager, times(2)).registerListener(anyString(), any(), any());
+    }
+
     private void waitForIdle() {
         HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
     }
@@ -1241,7 +1291,8 @@
     NsdService makeService() {
         final NsdService service = new NsdService(mContext, mHandler, CLEANUP_DELAY_MS, mDeps) {
             @Override
-            public INsdServiceConnector connect(INsdManagerCallback baseCb) {
+            public INsdServiceConnector connect(INsdManagerCallback baseCb,
+                    boolean runNewMdnsBackend) {
                 // Wrap the callback in a transparent mock, to mock asBinder returning a
                 // LinkToDeathRecorder. This will allow recording the binder death recipient
                 // registered on the callback. Use a transparent mock and not a spy as the actual
@@ -1250,7 +1301,7 @@
                         AdditionalAnswers.delegatesTo(baseCb));
                 doReturn(new LinkToDeathRecorder()).when(cb).asBinder();
                 mCreatedCallbacks.add(cb);
-                return super.connect(cb);
+                return super.connect(cb, runNewMdnsBackend);
             }
         };
         return service;
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
index 375c150..a917361 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
@@ -97,6 +97,7 @@
     @After
     fun tearDown() {
         thread.quitSafely()
+        thread.join()
     }
 
     @Test
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
index 6c3f729..7c6cb3e 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
@@ -63,6 +63,7 @@
     @After
     fun tearDown() {
         thread.quitSafely()
+        thread.join()
     }
 
     private class TestAnnouncementInfo(
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
index 2d8d8f3..0ca0835 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
@@ -136,6 +136,7 @@
     @After
     fun tearDown() {
         thread.quitSafely()
+        thread.join()
     }
 
     @Test
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
index a2dbbc6..2b5423b 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
@@ -68,6 +68,7 @@
     @After
     fun tearDown() {
         thread.quitSafely()
+        thread.join()
     }
 
     private class TestProbeInfo(probeRecords: List<MdnsRecord>, private val delayMs: Long = 1L) :
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
index 5665091..44e0d08 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
@@ -79,6 +79,7 @@
     @After
     fun tearDown() {
         thread.quitSafely()
+        thread.join()
     }
 
     @Test
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
index a80c078..b0a1f18 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
@@ -156,16 +156,20 @@
             + "010001000000780004C0A8018A0000000000000000000000000000"
             + "000000");
 
-    // MDNS record for name "testhost1" with an IPv4 address of 10.1.2.3
+    // MDNS record for name "testhost1" with an IPv4 address of 10.1.2.3. Also set cache flush bit
+    // for the records changed.
     private static final byte[] DATAIN_IPV4_1 = HexDump.hexStringToByteArray(
             "0974657374686f73743100000180010000007800040a010203");
-    // MDNS record for name "testhost1" with an IPv4 address of 10.1.2.4
+    // MDNS record for name "testhost1" with an IPv4 address of 10.1.2.4. Also set cache flush bit
+    // for the records changed.
     private static final byte[] DATAIN_IPV4_2 = HexDump.hexStringToByteArray(
             "0974657374686f73743100000180010000007800040a010204");
-    // MDNS record w/name "testhost1" & IPv6 address of aabb:ccdd:1122:3344:a0b0:c0d0:1020:3040
+    // MDNS record w/name "testhost1" & IPv6 address of aabb:ccdd:1122:3344:a0b0:c0d0:1020:3040.
+    // Also set cache flush bit for the records changed.
     private static final byte[] DATAIN_IPV6_1 = HexDump.hexStringToByteArray(
             "0974657374686f73743100001c8001000000780010aabbccdd11223344a0b0c0d010203040");
-    // MDNS record w/name "testhost1" & IPv6 address of aabb:ccdd:1122:3344:a0b0:c0d0:1020:3030
+    // MDNS record w/name "testhost1" & IPv6 address of aabb:ccdd:1122:3344:a0b0:c0d0:1020:3030.
+    // Also set cache flush bit for the records changed.
     private static final byte[] DATAIN_IPV6_2 = HexDump.hexStringToByteArray(
             "0974657374686f73743100001c8001000000780010aabbccdd11223344a0b0c0d010203030");
     // MDNS record w/name "test" & PTR to foo.bar.quxx
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
index 3e2ea35..5d58f5d 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -449,8 +449,8 @@
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
                 "service-instance-1",
                 SERVICE_TYPE_LABELS,
-                /* ipv4Address= */ List.of(),
-                /* ipv6Address= */ List.of(),
+                /* ipv4Addresses= */ List.of(),
+                /* ipv6Addresses= */ List.of(),
                 /* port= */ 0,
                 /* subTypes= */ List.of(),
                 Collections.emptyMap(),
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
index 5e7f0ff..e6aba22 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
@@ -86,8 +86,9 @@
     }
 
     @After
-    public void cleanUp() {
+    public void cleanUp() throws InterruptedException {
         mHandlerThread.quitSafely();
+        mHandlerThread.join();
     }
 
     private void initMockResources() {
diff --git a/tests/unit/java/com/android/server/net/IpConfigStoreTest.java b/tests/unit/java/com/android/server/net/IpConfigStoreTest.java
index 4adc999..dcf0f75 100644
--- a/tests/unit/java/com/android/server/net/IpConfigStoreTest.java
+++ b/tests/unit/java/com/android/server/net/IpConfigStoreTest.java
@@ -36,7 +36,6 @@
 
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
-import com.android.testutils.HandlerUtils;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -58,7 +57,7 @@
 @RunWith(DevSdkIgnoreRunner.class)
 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
 public class IpConfigStoreTest {
-    private static final int TIMEOUT_MS = 2_000;
+    private static final int TIMEOUT_MS = 5_000;
     private static final int KEY_CONFIG = 17;
     private static final String IFACE_1 = "eth0";
     private static final String IFACE_2 = "eth1";
@@ -139,6 +138,8 @@
                     }
                     @Override
                     public void quitHandlerThread(HandlerThread handlerThread) {
+                        // Don't join in here, quitHandlerThread runs on the
+                        // handler thread itself.
                         testHandlerThread.quitSafely();
                     }
         };
@@ -155,7 +156,7 @@
         final DelayedDiskWrite writer = new DelayedDiskWrite(dependencies);
         final IpConfigStore store = new IpConfigStore(writer);
         store.writeIpConfigurations(configFile.getPath(), expectedNetworks);
-        HandlerUtils.waitForIdle(testHandlerThread, TIMEOUT_MS);
+        testHandlerThread.join();
 
         // Read IP config from the file path.
         final ArrayMap<String, IpConfiguration> actualNetworks =
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 8046263..04163fd 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -539,6 +539,7 @@
         mService = null;
 
         mHandlerThread.quitSafely();
+        mHandlerThread.join();
     }
 
     private void initWifiStats(NetworkStateSnapshot snapshot) throws Exception {